【问题标题】:Unique ids in knockout.js templatesknockout.js 模板中的唯一 ID
【发布时间】:2012-03-03 05:46:45
【问题描述】:

假设我有这样的 knockout.js 模板:

<script type="text/html" id="mytemplate">
    <label for="inputId">Label for input</label>
    <input type="text" id="inputId" data-bind="value: inputValue"/>
</script>

如果我在页面上的多个位置呈现此模板,我最终会得到具有相同 id 的多个输入(以及具有相同 for 值的多个标签),这会产生不良后果。特别是,所有依赖于 id 的代码可能无法正常工作(在我的情况下,我使用 jquery.infieldlabel 插件,它会被具有相同 id 的多个输入混淆)。我现在解决此问题的方法是向绑定到模板的模型添加唯一 id 属性:

<script type="text/html" id="mytemplate">
    <label data-bind="attr: {for: id}>Label for input</label>
    <input type="text" data-bind="attr: {id: id}, value: inputValue"/>
</script>

这可行,但它不是很优雅,因为我的模型中必须有这个不用于其他任何东西的人工 id 属性。我想知道这里是否有更好的解决方案。

【问题讨论】:

    标签: knockout.js client-side-templating


    【解决方案1】:

    我无法回复所选答案,但我有一个增强版的代码,它支持每个输入值的多个唯一 ID。它在我的博客http://drewp.quickwitretort.com/2012/09/18/0 上并在这里重复:

    ko.bindingHandlers.uniqueId = {
        /*
          data-bind="uniqueId: $data" to stick a new id on $data and
          use it as the html id of the element. 
    
          data-which="foo" (optional) adds foo to the id, to separate
          it from other ids made from this same $data.
        */
        counter: 0,
        _ensureId: function (value, element) {
    
        if (value.id === undefined) {
            value.id = "elem" + (++ko.bindingHandlers.uniqueId.counter);
        }
    
        var id = value.id, which = element.getAttribute("data-which");
        if (which) {
            id += "-" + which;
        }
        return id;
        },
        init: function(element, valueAccessor) {
            var value = valueAccessor();
            element.id = ko.bindingHandlers.uniqueId._ensureId(value, element);
        },
    };
    
    ko.bindingHandlers.uniqueFor = {
        /*
          data-bind="uniqueFor: $data" works like uniqueId above, and
          adds a for="the-new-id" attr to this element.
    
          data-which="foo" (optional) works like it does with uniqueId.
        */
        init: function(element, valueAccessor) {
            element.setAttribute(
            "for", ko.bindingHandlers.uniqueId._ensureId(valueAccessor(), element));
        } 
    };
    

    现在您可以为一条具有自动 ID 的记录设置多个带标签的复选框:

    <li data-bind="foreach: channel">
      <input type="checkbox" data-which="mute" data-bind="uniqueId: $data, checked: mute"> 
         <label data-which="mute" data-bind="uniqueFor: $data">Mute</label>
    
      <input type="checkbox" data-which="solo" data-bind="uniqueId: $data, checked: solo"> 
         <label data-which="solo" data-bind="uniqueFor: $data">Solo</label>
    </li>
    

    【讨论】:

      【解决方案2】:

      另一种不依赖于字段绑定顺序的替代方法是让绑定在数据本身上设置一个id 属性,这需要是一个可观察的。

      ko.bindingHandlers.uniqueId = {
          init: function(element, valueAccessor) {
              var value = valueAccessor();
              value.id = value.id || ko.bindingHandlers.uniqueId.prefix + (++ko.bindingHandlers.uniqueId.counter);
      
              element.id = value.id;
          },
          counter: 0,
          prefix: "unique"
      };
      
      ko.bindingHandlers.uniqueFor = {
          init: function(element, valueAccessor) {
              var value = valueAccessor();
              value.id = value.id || ko.bindingHandlers.uniqueId.prefix + (++ko.bindingHandlers.uniqueId.counter);
      
              element.setAttribute("for", value.id);
          } 
      };
      

      你会像这样使用它:

      <ul data-bind="foreach: items">
          <li>
              <label data-bind="uniqueFor: name">Before</label>
              <input data-bind="uniqueId: name, value: name" />
              <label data-bind="uniqueFor: name">After</label>
          </li>
      </ul>
      

      示例:http://jsfiddle.net/rniemeyer/JjBhY/

      向 observable 函数添加属性的好处在于,当您将其转换为 JSON 发送回服务器时,它会自然而然地消失,因为 observable 将变成其未包装的值。

      【讨论】:

      • 我同意这一点。但是我能不能生气并就问题的一个稍微复杂的变体寻求建议 - 你有一对收音机,例如“是”和“否”作为标签,每个绑定回一个布尔可观察对象,例如“活跃”。目前所有 4 个元素 - 2 个收音机和 2 个标签 - 都获得相同的 ID。无线电对本身出现多次,因此我使用 Knockout 绑定为每对生成一个唯一的名称属性,因此我需要能够将当前对的特定名称作为 ID 的前缀。
      • 你也可以使用 data-bind="attr: { for: 'status_' + $index }" 和 data-bind="attr: { id: 'status_' + $index }"唯一 ID
      • 你也可以使用 data-bind="attr: { for: 'status_' + $index }" 和 data-bind="attr: { id: 'status_' + $index }"唯一的 ID。 $index to 指的是当前数组项的从零开始的索引。 $index 是一个 observable 并且每当项目的索引发生变化时都会更新(例如,如果项目被添加到数组中或从数组中删除)。
      • $index 在询问此问题时不存在,但如果您不需要 id 始终是唯一的(如果您删除最后一项并添加他们将拥有的新项目)相同的ID)。此外,在您的绑定中,如果您在如下表达式中使用$index,则需要将其作为函数调用:'status_' + $index()
      • 我在这里发现的一个令人不安的问题是,在元素添加到 DOM 之后 更改了 id。我想知道有多少库/软件可以接受这一点,因为一旦元素在 DOM 中,他们中的许多人会将 ID 视为不可变属性。比如,屏幕阅读器是否可以监听元素的ID变化并进行适应?这在 IE6+ 中也有效吗?目前我正在使用 Handlebars 在处理到 ko 之前填充 ID 和“for”标签,但不确定这是否过度。
      【解决方案3】:

      我过去做过这样的事情:

      ko.bindingHandlers.uniqueId = {
          init: function(element) {
              element.id = ko.bindingHandlers.uniqueId.prefix + (++ko.bindingHandlers.uniqueId.counter);
          },
          counter: 0,
          prefix: "unique"
      };
      
      ko.bindingHandlers.uniqueFor = {
          init: function(element, valueAccessor) {
              var after = ko.bindingHandlers.uniqueId.counter + (ko.utils.unwrapObservable(valueAccessor()) === "after" ? 0 : 1);
                element.setAttribute("for", ko.bindingHandlers.uniqueId.prefix + after);
          } 
      };
      

      你会像这样使用它们:

      <ul data-bind="foreach: items">
          <li>
              <label data-bind="uniqueFor: 'before'">Before</label>
              <input data-bind="uniqueId: true, value: name" />
              <label data-bind="uniqueFor: 'after'">After</label>
          </li>
      </ul>
      

      所以,它只是保持绑定本身的状态递增ko.bindingHandlers.uniqueId.counter。然后,uniqueFor 绑定只需要知道它是在字段之前还是之后就知道如何获取正确的 id。

      示例:http://jsfiddle.net/rniemeyer/8KJD3/

      如果您的标签不在其字段附近(在每个标签之前绑定多个输入,可能在表格的不同行中),那么您需要考虑不同的策略。

      【讨论】:

      • 我也在考虑这些方面的事情,它肯定会在我的情况下工作,但我不喜欢这个解决方案的是它取决于呈现标签的顺序。不过感谢您提供代码,这绝对是其中一种选择。
      • 如果订单是一个问题,那么这里是另一个选项:jsfiddle.net/rniemeyer/JjBhY。这很相似,但会在可能是可观察的东西上设置一个“id”属性。无论哪个绑定首先命中它都会更新 id。在 observable 函数上设置“id”属性的好处是,当您将其转换为 JSON 时它会消失,因为您只会留下 observable 的未包装值。
      • 谢谢,这正是我想要的!您介意将其发布为答案以便我接受吗?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-12-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-07-05
      • 2012-06-30
      相关资源
      最近更新 更多