【问题标题】:KnockoutJS binding when source is null/undefined源为空/未定义时的 KnockoutJS 绑定
【发布时间】:2012-07-08 09:44:23
【问题描述】:

是否有更短/更简洁的方法来进行空/未定义测试?

<select data-bind="options: SelectedBusinessLine() ? SelectedBusinessLine().Clusters() : [],
                               optionsText: 'Title',
                               value: SelectedCluster,
                               optionsCaption: 'Select Cluster..'">
            </select>

代替

data-bind="options: SelectedBusinessLine() ? SelectedBusinessLine().Clusters() : [],

我愿意

data-bind="options: SelectedBusinessLine().Clusters(),

给予或接受()

或者至少是一个更简单的空运算符检查'??'选定的业务线 ?? []

或绑定参数来自动检查 null 或静默失败。

如果可能的话,有什么想法吗?

【问题讨论】:

    标签: binding knockout.js


    【解决方案1】:

    This page 提供了几种解决方案。相关部分是这个:

    防止空对象

    如果你有一个包含对象的 observable,并且你想绑定到该对象的属性,那么你需要小心它是否有可能是 null 或 undefined。你可以这样写你的绑定:

    <span data-bind="text: selectedItem() ? selectedItem().name() : 'unknown'"></span>
    

    有很多方法可以处理这个问题。首选方法是简单地使用模板绑定:

    var viewModel = {
      items: ko.observableArray(),
      selectedItem: ko.observable()
    };
    
    <ul data-bind="template: { name: 'editorTmpl', data: selectedItem }"></ul>
    <script id="editorTmpl" type="text/html">
      <li>
        <input data-bind="value: name" />
      </li>
    </script>
    

    使用这个方法,如果selectedItem 为空,那么它就不会渲染任何东西。因此,您不会像在原始绑定中那样看到未知。但是,它确实具有简化绑定的额外好处,因为您现在可以直接指定属性名称,而不是 selectedItem().name。这是最简单的解决方案。

    只是为了探索一些选项,这里有一些替代方案:

    您可以像我们之前一样使用计算的 observable。

    viewModel.selectedItemName = ko.computed(function() {
      var selected = this.selected();
      return selected ? selected.name() : 'unknown';
    }, viewModel);
    

    但是,这又给我们的视图模型增加了一些我们可能不想要的膨胀,我们可能不得不对许多属性重复此操作。

    您可以使用自定义绑定,例如:

    <div data-bind="safeText: { value: selectedItem, property: 'name', default: 'unknown' }"></div>
    
    ko.bindingHandlers.safeText = {
      update: function(element, valueAccessor, allBindingsAccessor) {
        var options = ko.utils.unwrapObservable(valueAccessor()),
        value = ko.utils.unwrapObservable(options.value),
        property = ko.utils.unwrapObservable(options.property),
        fallback = ko.utils.unwrapObservable(options.default) || "",
        text;
    
        text = value ? (options.property ? value[property] : value) : fallback;
    
        ko.bindingHandlers.text.update(element, function() { return text; });
      }
    };
    

    这比原版好吗?我会说可能不会。它确实避免了我们绑定中的 JavaScript,但它仍然非常冗长。

    另一个选择是创建一个增强的 observable,它提供了一种访问属性的安全方法,同时仍然允许实际值为 null。可能看起来像:

    ko.safeObservable = function(initialValue) {
      var result = ko.observable(initialValue);
      result.safe = ko.dependentObservable(function() {
        return result() || {};
      });
    
      return result;
    };
    

    所以,这只是一个 observable,它还公开了一个名为 safe 的计算 observable,它总是返回一个空对象,但实际的 observable 可以继续存储 null。

    现在,您可以像这样绑定到它:

    <div data-bind="text: selectedItem.safe().name"></div>
    

    当它为null时你不会看到未知值,但是当selectedItem为null时它至少不会导致错误。

    我确实认为在这种情况下首选的选项是使用模板绑定,尤其是当您有许多此类属性要绑定时。

    【讨论】:

    • 模板的唯一问题是有时我想将选择框显示为空的选项而不是隐藏它。我认为框架应该为我们提供一种方法,让我们不使用 if null else 代码来膨胀源代码..
    • 这里的模板示例实际上并没有工作 - 如果模板的“数据”部分为空,那么我会遇到绑定错误。我可以通过为模板绑定应用“if”属性来解决这个问题:
    • @DrewMiller,我认为处理空对象处理的是 jquery 模板引擎。当使用本机淘汰模板引擎时,if 属性可以解决问题。
    • 仅供参考 ko.dependentObservable is deprecated ko.computed 现在。
    • 最佳答案见下方“with”
    【解决方案2】:

    另一个答案引用的excellent page 中未提及的一种方法是使用with

    <div data-bind="with: selecteditem">
        <form>
            <fieldset>
                <div>
                    <label>first name</label>
                    <input data-bind="value: firstname"></input>
                </div>
                <div>
                    <label>lasst name</label>
                    <input data-bind="value: lastname"></input>
                </div>
            </fieldset>
            <div>
                <a href="#" data-bind="click: $root.savechanges">Save</a>
            </div>
        </form>
    </div>
    

    如果selecteditem 为空,则整个 UI 将消失。

    【讨论】:

    • (这是我们想要的)
    • 最简单,最优雅。谢谢!
    • 这应该是公认的答案。当前接受的答案太复杂,无法使这样一个简单的要求起作用。这是with的核心功能之一。
    • 如果您需要访问上述“范围”内的内容,请使用$parent
    • @Simon_Weaver 我打算避免使用 ifnot 复制 UI:VM is null ' 绑定,而且我没有明确定义的 VM(因为它有很多属性),但只是 mapping.fromJS 是够了。
    【解决方案3】:

    “With”有效(可能其他人也有效......)

    但是使用“With”,即使里面有条件,角色 UI 也会消失/出现......例如......我需要设置一个状态(真/假)按钮,但前提是状态不是空...

    <!-- ko ifnot: Status() == null -->
    <span data-bind="if: Status">
        <a class="rk-button rk-red-action" data-bind="click: changeStatus"><i class="rk-ico rk-ico-save"></i>Desativar</a>
    </span>
    <span data-bind="ifnot: Status">
        <a class="rk-button rk-black-action" data-bind="click: changeStatus"><i class="rk-ico rk-ico-save"></i>Ativar</a>
    </span>
    <!-- /ko -->
    

    这行得通。在这种情况下。

    但有时就像 Simon_Weaver 所说的,只是“With”的作品!

    【讨论】:

    • 我从来不知道ifnot :-)
    • 如果你的Status observable 是一个计算出的 observable 并且你也启用了 ko.options.deferUpdates = true;` 时要小心,因为你仍然可以得到空错误 - stackoverflow.com/questions/43341484
    【解决方案4】:

    我更喜欢这种方法

    创建自定义绑定

    ko.bindingHandlers.safeText = {
        update: function (element, valueAccessor, allBindingsAccessor) {
            try {
                var tryGetValue = valueAccessor()();
                ko.bindingHandlers.text.update(element, valueAccessor());
            } catch (e) {
                ko.bindingHandlers.text.update(element, function () { return ""; });
            }
        }
    };
    

    用法

    data-bind="safeText: function() { return my().nested.object.property; }
    

    您需要添加的唯一额外内容是使用 'function() { return ... }' 包装原始值

    但是,这将停止值调用下的所有错误。您可以通过仅查找“未定义”异常来改进自定义绑定。您可能还想通过添加默认文本选项来改进此绑定。

    【讨论】:

      【解决方案5】:

      这些解决方案中的大多数在我设置属性的特定情况下都不起作用:

      <div role="combobox" data-bind="attr: {
        'aria-activedescendant': activedescendant() ? activedescendant().id : null
      }">
        ...
      </div>
      

      templatewith 绑定在这里不起作用,因为如果我没有活跃的后代,那么我的 div 将为空。对于我的解决方案,我创建了一个可观察的方法:

      ko.observable.fn.get = function (propertyName, defaultValue) {
          var
          self = this(),
          propertyValue;
      
          if (self != null) {
              propertyValue = ko.unwrap(self[propertyName]);
          }
      
          return propertyValue || defaultValue;
      }
      

      这允许我将绑定更改为:

      <div role="combobox" data-bind="attr: {
        'aria-activedescendant': activedescendant.get('id')}">
        ...
      </div>
      

      【讨论】:

        【解决方案6】:

        上述大多数解决方案对我来说都不是开箱即用的,因为我只想将其应用于 foreach 中的单个元素,所以我稍微修改了接受答案的方法:

        <span data-bind="text: ((typeof info).localeCompare('undefined')) ? info : ''"></span>
        

        【讨论】:

          猜你喜欢
          • 2016-03-31
          • 2014-07-06
          • 2015-07-27
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多