Knockoutjs. Как вычислить данные, измененные внутри наблюдаемого массива

Пожалуйста, посмотрите мой текст. Я пытаюсь использовать наблюдаемый массив нокаутов и foreach для вычисления данных массива. Пример 1 работает нормально: общая сумма вычисляется, если вы изменяете данные в полях. Но пример 2 не работает.

<html>
<head>                                                                         
    <title></title>                                                            
    <script type='text/javascript' src='/js/jquery-1.8.2.min.js'></script>     
    <script type='text/javascript' src='/js/knockout-2.1.0.debug.js'></script> 
</head>                                                                        
<body>
<p>Example 1</p>
<div>
    <p>
    <input data-bind="value: fnum1" />
    <input data-bind="value: fnum2" />
    <span data-bind="text: ftotsum"></span>
    </p>
</div>    
<p>Example 2</p>
<div>
    <p>
    <!-- ko foreach: fields -->
    <input data-bind="value: $data" />
    <!-- /ko -->
    <span data-bind="text: ltotsum"></span>
    </p>
</div>
</body>
<script>
    function vm(){
        //Calc Example 1
        var self = this;
        self.fnum1 = ko.observable(1);
        self.fnum2 = ko.observable(2);
        self.ftotsum = ko.computed(function(){
            return parseFloat(self.fnum1()) + parseFloat(self.fnum2());
        });
        //Calc Example 2
        self.fields = ko.observableArray([1, 2]);
        self.ltotsum = ko.computed(function(){
            var total = 0;
            ko.utils.arrayForEach(self.fields(), function(item) {
                total += parseFloat(item);
            })
            return total;
        }); 
    };

    ko.applyBindings(new vm());
</script>
</html>

Ответ 1

РЕДАКТИРОВАТЬ:. Счастье работает, Раффаэле прав, говоря, что вам нужно обернуть наблюдаемое внутри объекта, но вы можете сделать это внутри самого создания массива, и мне нравится использовать ko.utils чтобы развернуть мои наблюдаемые, он делает то же самое для наблюдаемых, но он не будет разбиваться, если к нему не будет наблюдаться. Подробнее см. fiddle.

Наблюдаемый массив не делает переданные значения наблюдаемыми, это распространенная ошибка. Наблюдаемый массив просто замечает изменения в массиве, а не значения. Если вы хотите, чтобы ваши значения внутри вашего массива были видимыми, вы должны сделать их так.

function vm(){
    //Calc Example 1
    var self = this;
    self.fnum1 = ko.observable(1);
    self.fnum2 = ko.observable(2);
    self.ftotsum = ko.computed(function(){
        return parseFloat(self.fnum1()) + parseFloat(self.fnum2());
    });
    //Calc Example 2
    self.fields = ko.observableArray([{"num":ko.observable(1)},{"num":ko.observable(2)}]);
    self.ltotsum = ko.computed(function(){
        var total = 0;
        ko.utils.arrayForEach(self.fields(), function(item) {
            total += parseFloat(ko.utils.unwrapObservable(item.num));
        });
        return total;
    }); 
};
ko.applyBindings(new vm());

Теперь нужно работать с приведенным выше примером.

Ответ 2

documentation говорит:

Ключевые моменты: observableArray отслеживает, какие объекты находятся в массиве, а не состояние этих объектов

Просто размещение объекта в наблюдаемом массиве не делает все что свойства объектов сами по себе наблюдаемы. Конечно вы можете сделайте эти свойства наблюдаемыми, если хотите, но это независимый выбор. Наблюдаемый массив просто отслеживает, какие объекты он хранит и уведомляет слушателей, когда объекты добавлены или удалены.

Второй пример не работает, потому что значение полей ввода не привязано к значениям в массиве. Эти значения в массиве используются только один раз в привязке foreach, но когда вы вводите поля ввода, ничего не запускает KO.

Вот рабочая скрипка с реализованным решением. Я использовал вспомогательный ObsNumber

function vm(){
    var self = this;

    var ObsNumber = function(i) {
        this.value = ko.observable(i);
    }

    self.fields = ko.observableArray([new ObsNumber(1) ,
                                      new ObsNumber(2)]);

    self.sum = ko.computed(function(){
        var total = 0;
        ko.utils.arrayForEach(self.fields(), function(item) {
            total += parseFloat(item.value());
        });
        return total;
    });
};

ko.applyBindings(new vm());

и следующую разметку

<div>
    <p>
    <!-- ko foreach: fields -->
    <input data-bind="value: $data.value" />
    <!-- /ko -->
    <span data-bind="text: sum"></span>
    </p>
</div>​