【问题标题】:Synchronizing a button class with an option control using knockoutjs使用 knockoutjs 将按钮类与选项控件同步
【发布时间】:2014-08-20 04:11:45
【问题描述】:

通常我可以想办法让 Knockout-js 做我想做的事。然而,在这种情况下,我有点挣扎,所以我在 SO 上向社区提出这个问题。

简介

我正在使用 typescript、bootstrap、knockoutjs 和 nodejs 后端编写一个 HTML5 网络应用程序。

在我的 UI 中,所有这些都通过 knockoutJS 进行控制,我有一组按钮,形成一个引导 3 按钮组的可选选项。

这个合理的组,给我 4 个水平按钮,但允许按钮选择的行为与一组选项按钮保持一致。

一致性很重要,因为一次只能选择一个按钮,所以当一个按钮被点击时,其余的取消选择。

这是 BS3 中的默认组件,如下图所示:

正如您在图像中看到的那样,选择了“丢弃”按钮,为了实现这一点,必须将“活动”类添加到构成控件的内部单选元素周围的标签元素的现有类列表中.以下 HTML 用于创建此图像:

<div class="btn-group btn-group-justified" data-toggle="buttons">
  <label class="btn btn-primary">
    <input type="radio" name="options" id="option1" checked >Rejected
  </label>
  <label class="btn btn-primary active">
    <input type="radio" name="options" id="option2">Discarded
  </label>
  <label class="btn btn-primary">
    <input type="radio" name="options" id="option3">Held
  </label>
  <label class="btn btn-primary">
    <input type="radio" name="options" id="option3">Header Added
  </label>
</div>

这一切都很好,除了一个小缺陷,我正在使用淘汰赛 JS 来管理 UI。

我要解决的问题

每个选项的选中状态都与应用于 HTML 的视图模型内的属性相关联,因此例如被拒绝按钮上的内部选项添加了一个普通的 knockout-js 选中绑定,如下所示:

<input type="radio" name="options" id="option1" checked data-bind="checked: reject">Rejected

每个选项,每个选项都有自己的支持字段:

reject
discard
hold
addheader

这些支持字段中的每一个都是一个标准的布尔值,持有真/假,我不知道如何在封闭标签上添加/删除“活动”类,以反映这些状态中的哪一个已选中。

更准确地说,我想不出最好的优雅方式。

我尝试过的方法

我所知道的工作是为每个返回的标签添加一个简单的计算 observable

"btn btn-primary active"

当该选项设置为 true 时,并且

"btn btn-primary"

如果不是。

我知道这一点,因为在我的视图模型中,我有一个简单的函数:

SenderDialogViewModel.prototype.isRejectSelected = function () {
        if (this.reject == true) {
            return "btn btn-primary active";
        }
        return "btn btn-primary";
    };

但是,这种方法意味着 4 个函数,每个标记一个要测试,因此很难在以后添加新标记。

我想做的事情如下:

<label class="btn btn-primary" data-bind="class: isSelected(reject)">

举个例子。

我几乎要对上述内容稍作修改:

SenderDialogViewModel.prototype.isSelected = function (selectionVariable) {
        if (selectionVariable == true) {
            return "active";
        }
        return "";
    };

其中选择变量可以是视图模型中可用的任何标志,传入。

这里的问题是,这仅在第一次绘制 UI 时更新,随后对标志的更改未能更新 UI 以反映给定状态。

为了尝试解决这个问题,我将函数更改为计算的 observable,然后在绘制 UI 时收到 JS 错误,说明计算的 observable 必须添加一个“写入”处理程序,因为我正在传入一个参数。

如果我需要添加一个写处理程序,那很好,但我不想这样做。

总结

总而言之,有一些方法可以与其他选项同步更改类列表,但其中大多数都很混乱,我想做的是创建一种在添加新按钮时易于扩展的方法(这是重要,因为一些按钮集是动态生成的),而不是添加一个处理程序来单独检查和报告那里的每个变量的状态,在一个函数调用中可以简单地添加到视图模型中并一次又一次地重复使用.

【问题讨论】:

  • 我会创建一个自定义绑定,因为这会将 ViewModel 与 DOM 分离
  • 是的,我已经想到了,不过上次我做了一个自定义绑定,它变得非常混乱,非常快。但是,我似乎刚刚得出了答案,因此已将其正式发布到问题上。

标签: javascript html twitter-bootstrap user-interface knockout.js


【解决方案1】:

好的...因为它总是会发生,所以我一发布这个,就真正想出如何让它发挥作用。

解决方案一直盯着我的脸,以淘汰赛 js css 绑定的形式。

引用 knockout-js 文档:

css 绑定向关联的 DOM 元素添加或删除一个或多个命名的 CSS 类。这很有用,例如,如果某个值变为负数,则用红色突出显示它。

这就是说,“我可以根据视图模型中变量的值将单个类应用或删除到已经存在的类集合”

所以,我的问题的答案很简单:

<div class="btn-group btn-group-justified" data-toggle="buttons">
  <label class="btn btn-primary" data-bind="css: { active: reject }">
    <input type="radio" name="options" id="option1" checked >Rejected
  </label>
  <label class="btn btn-primary" data-bind="css: { active: discard }">
    <input type="radio" name="options" id="option2">Discarded
  </label>
  <label class="btn btn-primary" data-bind="css: { active: hold }">
    <input type="radio" name="options" id="option3">Held
  </label>
  <label class="btn btn-primary" data-bind="css: { active: addheader }">
    <input type="radio" name="options" id="option3">Header Added
  </label>
</div>

除此之外,如果我现在只是将适当的选项/选中的绑定添加到选项控件本身,那么当单击按钮时,所有内容都应该根据需要正确更新。

关于制定自己的答案的小说明

我认为,有时,只需仔细考虑您的问题,同时“实际上”尝试向他人描述它,就会触发大脑朝不同的方向思考。

这当然有帮助,当便士掉下来时我正在输入这个,但我继续进行然后适当地回答/更新,因为我想到这个问题也会绊倒其他人,希望这将作为对可能像我一样的旅行者的教育,他们只是在遭受周日晚上的脑残。

2014 年 6 月 30 日星期一更新

事实证明,这比我最初预期的要复杂一些。当然,我解决了关于为上面的按钮同步 CSS 的问题的主要答案。但是......同步选项按钮也被证明是一个相当大的挑战,这个更新是为了提供一个完整的端到端解决方案。

首先,您需要像这样标记您的 HTML:

<p><strong>Rule type:</strong></p>
<div class="btn-group btn-group-justified" data-toggle="buttons">
  <label class="btn btn-primary" data-bind="css: { active: reject }, click: function(){ changeType('reject'); }">
    <input type="radio" name="ruletype" value="reject" data-bind="checked: selectedOptionString" >Rejected
  </label>
  <label class="btn btn-primary" data-bind="css: { active: discard }, click: function(){ changeType('discard'); }">
    <input type="radio" name="ruletype" value="discard" value="true" data-bind="checked: selectedOptionString">Discarded
  </label>
  <label class="btn btn-primary" data-bind="css: { active: hold }, click: function(){ changeType('hold'); }">
    <input type="radio" name="ruletype" value="hold" data-bind="checked: selectedOptionString">Held
  </label>
  <label class="btn btn-primary" data-bind="css: { active: addheader }, click: function(){ changeType('addheader'); }">
    <input type="radio" name="ruletype" value="addheader" data-bind="checked: selectedOptionString">Header Added
  </label>
</div>

这里的关键点如下:

  • 1) CSS 规则必须指定“活动”类并绑定到您的独立选项标志,该标志显示所选选项的真/假。

  • 2) 您必须在按钮上有点击处理程序,BS3(正如我发现的那样)处理对 按钮 的点击而不是在选项控件上,因为敲除是如何工作的必须是内联函数,传入单个参数,不要试图将其直接绑定到选项使用的计算 observable,它不会起作用。

  • 3) 您必须将选项元素标记为如图所示,即它们必须全部具有相同的名称属性,值必须与您希望所选选项描绘的内容相匹配,并且必须与您的按钮处理程序的字符串相匹配正在发送

  • 4) 每个选项元素都不能绑定到一个简单的变量,您需要将它传递给一个计算的 observable,这不仅是因为我处理它们的方式,而且即使对于简单的单个布尔开关,它也使用 " true" 和 "false" 作为字符串,而不是您可能期望的布尔值。

一旦你标记了你的 HTML,你就需要构建一个视图模型来支持它,在我的例子中,我实际上是使用 typescript 完成的,然后编译为 JS,我在这里粘贴的代码是 JS TS 编译器生成的代码。

首先,您需要确保您的视图模型具有以下属性:

this.reject = ko.observable(false);
this.discard = ko.observable(false);
this.hold = ko.observable(false);
this.addheader = ko.observable(false);

(使用 self、this、me... 或者你用什么来定义你的淘汰赛模型)重要的是它们是简单的 ko observable boolean's

您还需要一个同时具有写入和读取功能的计算 observable:

this.selectedOptionString = ko.computed({
    read: function () {
        if (this.reject())
            return "reject";
        if (this.discard())
            return "discard";
        if (this.hold())
            return "hold";
        if (this.addheader())
            return "addheader";
    },
    write: function (value) {
        console.log("rejectstr:");
        console.log(value);
        if (value == "reject") {
            this.reject(true);
            this.discard(false);
            this.hold(false);
            this.addheader(false);
        }
        if (value == "discard") {
            this.reject(false);
            this.discard(true);
            this.hold(false);
            this.addheader(false);
        }
        if (value == "hold") {
            this.reject(false);
            this.discard(false);
            this.hold(true);
            this.addheader(false);
        }
        if (value == "addheader") {
            this.reject(false);
            this.discard(false);
            this.hold(false);
            this.addheader(true);
        }
    }
}, this);

这可能会更优雅地完成,但本质上,当一个组中的一个选项在淘汰赛下被激活时,淘汰赛会获取 'Value' 属性中的任何内容并将其作为字符串发送到您的视图模型中。

如果你把它绑定到一个简单的 observable 上,那么这个 observable 会被设置为那个属性的字符串值。然而,对我来说,因为我有一系列 4 个标志来设置控制 UI 上的各种状态,如果合适的话,一个链式的(一个开关或者可能是一个查找数组或哈希表也可以工作)

observable 的最终结果是一次只设置一个布尔值,并且因为按钮中的 CSS 绑定到此标志,然后活动类将应用于给定的按钮设置为真。

对于读取,您需要将您的标志状态转换回一个字符串,以便与它知道的值进行比较,因此读取执行相反的操作。

对于按钮单击处理程序,您必须像标记中所示那样内联执行此操作,这是因为敲除保留了一些自动参数,例如元素名称,事件信息等,我们在这里不需要,所以最简单方法是内联它。

但是,在 lining 中,这意味着你没有绑定到一个属性,所以你不能将它直接绑定到选项控件使用的计算 observable。

您需要做的是向您的视图模型添加一个小的存根函数,如下所示:

SenderDialogViewModel.prototype.changeType = function (newType) {
    this.selectedOptionString(newType);
};

这不需要以任何方式观察到,因为它只会在您的按钮被点击时以一种方式调用。

如果您想查看完整的解决方案,我将在我的 git-hub 页面上免费提供该应用程序,供人们使用,但尚未完成。

希望以上所有内容对某些人有用,我不得不承认,这比我预期的更具挑战性。

肖蒂

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-30
    • 2010-10-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多