Привязка не смогла обнаружить изменения observableArray при назначении нового массива observableArray в Knockout JS - PullRequest
0 голосов
/ 29 декабря 2011

У меня есть привязка шаблона к observableArray

<ul data-bind="template: { name: 'defaultTemplate', foreach: itemsArray}"></ul>
...

И мне нужно каждый раз обновлять данные, нажимая ссылку

<a data-bind="click: LoadData"><span>Refresh</span></a>

Первая версия моей viewModel:

function viewModel (){  
    this.itmesArray = ko.observableArray([]);
    self = this;
    this.LoadData(){
        if('undefined' != typeof MusicArray )
            self.itmesArray.removeAll();
        LoadDataFromServerAsync(self.itmesArray);
    }   
    //ini
    LoadData();
    ...
}

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

Затем вторая версия viewModel:

function viewModel (){  
    this.itmesArray = ko.observableArray([]);
    self = this;
    this.LoadData(){
            if('undefined' != typeof MusicArray )
                self.itmesArray.removeAll();
            var tempArray = new Array();
            LoadDataFromServerAsync(tempArray);
            self.itmesArray(tempArray);
    }   
    //ini
    LoadData();
    ...
}

И новая проблема заключается в том, что пользовательский интерфейс не может воспринимать изменения массива, кажется self.itmesArray (tempArray) создаст новый объект observableArray, но привязка шаблона все еще отслеживает старый объект observableArray, я не уверен в этом.Поэтому я хочу знать, как уведомить привязку шаблона / пользовательского интерфейса о том, что мой массив изменился или есть ли другой способ обойти мою проблему?

Добавлено: Код на jsFiddle http://jsfiddle.net/zzcJC/10/

Ответы [ 4 ]

1 голос
/ 30 декабря 2011

вы можете сделать так, как предлагает Джин. для выполнения всего на стороне клиента вы можете создать вспомогательный объект, который инкапсулирует логику: он запускает запросы, получает ответы, имеет метод «abort», чтобы остановить запрос (если пользователь нажимает «обновить», все еще ожидая данных ) и обновляет viewmodel только при получении всех данных.

Я создал jsFiddle: http://jsfiddle.net/saurus/dE6S9/

При этом используется setTimeout () для имитации вызовов ajax, хранит идентификаторы тайм-аутов в массиве (вы должны хранить xhr, возвращаемый вызовами ajax), и вызываете "cancelTimeout ()" для прерывания вызовов (вы будете использовать "xhr .abort или аналогичный).

0 голосов
/ 29 декабря 2011

так что, если я правильно понимаю, вы хотите нажать «обновить», это вызовет некоторые вызовы ajax, и данные, возвращенные из этих вызовов, полностью заменят старые данные.

Один из способов - убедиться, что каждый звонок работает независимо для других. Например, что-то вроде того, что я быстро предложил в комментарии: делайте каждый вызов отдельным, а когда один вызов возвращает новые данные, удалите старые данные из массива и добавьте новые. Это предполагает, что вы можете различать элементы, возвращаемые различными вызовами.

другой способ - вообще избежать нескольких вызовов, вы можете заблокировать взаимодействие с пользовательским интерфейсом до конца (например, с помощью плагина jquery blockui или чего-то подобного), или если вы хотите позволить пользователю продолжать использовать интерфейс во время обновления таким образом, вы можете просто избежать запуска нового запроса, пока некоторые еще находятся в полете.

оба способа должны знать, когда начинается серия запросов, но это легко: это происходит при запуске метода «LoadData». Зная, когда все завершенные запросы немного сложнее, возможно, есть лучший способ, если нет, вы можете создать счетчик, увеличивая его на 1 для каждого запускаемого вами запроса ajax, а затем для каждого завершенного запроса «выдвигайте» новые данные. на observableArray и уменьшите счетчик (в jquery я бы использовал «полный» обратный вызов, потому что он гарантированно вызывается даже в случае ошибки). в самом начале «LoadData» (даже до очистки массива) добавьте тест:

if (counter > 0) return;

Если вы используете метод «blockui», вы должны проверять счетчик в каждом «завершенном» обратном вызове после его уменьшения, если он равен нулю, пора разблокировать UI (опция «blockui» нуждается в некоторой работе, чтобы избежать выполнения UI). в viewModel).

Если я все еще что-то упускаю ...

0 голосов
/ 29 декабря 2011

Возможно, один из способов справиться с перекрывающимися запросами - это сохранить счетчик действий пользователя на клиенте и передать это значение на сервер, предполагая, что у вас есть контроль над тем, что сервер отправляет обратно.Затем вы можете игнорировать ответы с устаревшим значением действия пользователя.Если у вас нет доступа к данным сервера, вы все равно можете использовать этот подход, вставив значение счетчика действий в обработчик успеха.Вот набросок этого кода.(Предупреждение: не проверено)

function viewModel() {
    var self = this;
    var actionCounter = 0;
    var items = ko.observableArray([]);


    // This is triggered by a user request
    this.handleUserAction = function() {
        self.clearData();
        self.loadData(0, ++actionCounter); // assume 0=root
    }

    this.clearData = function() {
        this.items.removeAll();
    }

    // return some handle of the next request, or null
    this.moreRequestsRequired = function(data) {
         ...
         return ...;
    }

    this.processData = function(data, reqNo) {
        // this is where you would append it to the array
        ...

        // this is where the recursive call could be made
        var handle = self.moreRequestsRequired(data);
        if (handle)
            loadData(handle, reqNo);
    }

    this.loadData = function(handle, n) {
        var reqNumber = n;
        $.ajax('server/request', {
            data: {dir: handle},
            success: function(data) {
                if (reqNumber == actionCounter)
                   processData(data);
            }
        });
    }
};
0 голосов
/ 29 декабря 2011

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

self.itmesArray(tempArray);

выполняется задолго до того, как

LoadDataFromServerAsync(tempArray);

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

Чтобы исправить это, вы должны выполнить привязку после того, как сервер вернул новые данные.Например, используя jquery для выполнения вызова ajax, вы должны обновить viewmodel в обратном вызове «success».Вот как я это делаю:

    $.ajax({
      type: "POST",
      url: "ws.asmx/GetData",
      data: "{}",
      contentType: "application/json; charset=utf-8",
      dataType: "json",
      processData: false,
      success: function(msg) {
           viewModel.itmesArray(msg);
      }
    });

, если это вам не поможет, я думаю, что нам нужно взглянуть на реализацию LoadDataFromServerAsync.

...