【问题标题】:Knockout JS using computed arrays outside of ViewModelKnockout JS 使用 ViewModel 之外的计算数组
【发布时间】:2015-11-23 17:12:40
【问题描述】:

我想在页面上显示项目列表,并能够通过使用所有位置的下拉列表来动态重新定位项目。从下拉列表中选择一个位置将更改项目的当前位置并重新移动列表中任何受影响元素的位置。

我确实有这个概念的工作版本,但并不理想。出于某种原因,当我引用我的 selectedItems 计算数组时(我通过设置 selectedItem 可观察项来过滤我的项目),返回项目中包含的位置是项目的原始位置值,而不是通过设置的位置值下拉菜单/我的重新定位功能。这有点奇怪,因为 'items' observableArray 确实包含更新后的值,并且 computedArray 确实返回了正确的项目,只是没有最新的值。

下面是一个工作的 JSfiddle。但是,它进行了大量的手动计算,并且没有利用如上所述的计算数组。该问题可能与从 ViewModel 外部设置 Knockout observables 有关。要查看问题,请取消注释“文档就绪”块中的 2 行,我尝试在其中查找项目的当前位置,并注释掉我手动查找当前项目的 for 循环。

https://jsfiddle.net/tq1m873m/5/

一般来说,我是 KnockoutJS 和 JS 的新手,请保持温和 :)

$(document).ready(function () {

    $("select[id^='selectName_']").change(function () {

        //Extract the item ID from the select html id attribute
        var curItemIDNum = $(this).attr('id').substring(15);

        var currentPos = 0;

        // myViewModel.selectedItem("item" + curItemIDNum);
        // currentPos = myViewModel.selectedItems()[0].position();  

        // START - really bad code, shield your eyes
        // I can't seem to get the current position via the 2 commented lines above and have to resort to converting the observable array to a regular array and pulling the value that way. Not pretty!
        var itemsJS = ko.toJS(self.items());
        for (var x = 0; x < itemsJS.length; x++) {
            if (("item" + curItemIDNum) == itemsJS[x].name) {
                currentPos = itemsJS[x].position;
                break;
            }
        }
        // END - really bad code

        reposition("item" + curItemIDNum, currentPos, $(this).val());
        refreshDropDowns();
    });

    refreshDropDowns();

});

【问题讨论】:

    标签: javascript jquery knockout.js


    【解决方案1】:

    您之前正在处理此问题,但我没有适合您的解决方案。今天,我愿意。您对 jQuery 触发器的使用不会很好。让我们用 Knockout 来完成这一切。

    我将items 设置为一个没有指定位置的对象数组。 orderedItems 是一个计算值,它依次通过 items 并为 position 创建一个 observable。

    订阅 position observable 调用 moveItemTo,它会重新排列项目,并且所有依赖项都由 Knockout 更新。

    $(function() {
      ko.applyBindings(viewModel());
    });
    
    function item(name) {
      return {
        name: name
      };
    }
    
    var viewModel = function() {
      var self = {};
      self.items = ko.observableArray([
        item('item1'),
        item('item2'),
        item('item4'),
        item('item5'),
        item('item3')
      ]);
    
      function moveItemTo(item, pos) {
        var oldPos = self.items.indexOf(item),
          newPos = pos - 1,
          items;
        if (oldPos < newPos) {
          items = self.items.slice(oldPos, newPos + 1);
          items.push(items.shift());
          self.items.splice.bind(self.items, oldPos, items.length).apply(self.items, items);
        } else {
          items = self.items.slice(newPos, oldPos + 1);
          items.unshift(items.pop());
          self.items.splice.bind(self.items, newPos, items.length).apply(self.items, items);
        }
    
    
      }
      self.orderedItems = ko.computed(function() {
        return ko.utils.arrayMap(self.items(), function(item, index) {
          var pos = ko.observable(index + 1);
          pos.subscribe(moveItemTo.bind(null, item));
          return {
            name: item.name,
            position: pos
          };
        });
      });
      return self;
    }; //end of viewmodel
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
    <div>Set the order of the item by selecting a new position from the dropdown:
      <ul data-bind="foreach: orderedItems">
        <li>
          <div>	<span data-bind="text: name"></span>	
            <select data-bind="options: $root.orderedItems, optionsValue:'position', value: position"></select>
          </div>
        </li>
      </ul>
    </div>ITEMS CONTENTS:
    <BR>
    <span data-bind="text: JSON.stringify(ko.toJS(items), null, 4)"></span>

    【讨论】:

    • 谢谢罗伊。我得拿出你的答案,打印出来,拿起我的尺子,逐行研究。 :) 。我确实有一个问题 - 您创建 self.positions 纯粹是为了提高效率(因为您正在展平阵列)还是它提供其他功能?换句话说,是否有可能(虽然不理想)在 foreach 循环中只使用orderedItems 而不是self.positions?
    • @PaulP 既然你问了,我就试一试。它工作正常。只需在选择绑定中使用 optionsValue 来指示要使用的 orderedItems 中的字段。
    • 谢谢。可以理解,您的解决方案在性能方面比我的要快得多,并且不需要超级计算机即可运行。
    【解决方案2】:

    我能想到的一种方法是使用计算值来保持位置的当前状态。这将允许您在以下项目上设置新位置时进行重新洗牌:

    ko.utils.arrayForEach(self.items(), function (x) {
            x.newPosition = ko.computed({
                read: function () {
                    return x.position();
                },
                write: function (val) {
                    //get the item in the prev position
                    var temp = ko.utils.arrayFirst(self.items(), function (y) {
                        return y.position() == val;
                    });
                    //swap positons here
                    if (temp) {
                        temp.position(x.position());
                        x.position(val);
                    }
                }
            });
        });
    

    在标记中它会是

    <select data-bind="options:positions,value:newPosition"></select>
    

    等等计算“写”...脚本交换位置值。我把你原来的绑定留给了orderedItems。你可以在这里找到一个工作样本https://jsfiddle.net/p1yhmvcr/2/ ...这里值得注意的一件事是排序并不是真正的物理。可观察的数组项仍在数组中的原始索引上,代码仅更改位置值。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-09-23
      • 1970-01-01
      • 2018-09-08
      • 2014-03-28
      • 1970-01-01
      • 1970-01-01
      • 2018-01-21
      • 2013-01-10
      相关资源
      最近更新 更多