Значение KnockoutjsHasMutated не работает правильно

Надеюсь, это будет быстрый для нокаутирующего гуру....

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

Один - перевод текста, другой - перевод атрибута placeholder для элементов ввода HTML5.

Обе привязки идентичны, кроме последнего оператора, где один обновляет текст в элементе, а другой обновляет значение атрибута.

Текст один работает отлично, но заполнитель один нет, и я застрял в ответе на вопрос, почему.

Обязательный код выглядит следующим образом:

Привязка переведенного текста

ko.bindingHandlers.translatedText = {

    init: (element: HTMLElement, valueAccessor: Function, allBindings: KnockoutAllBindingsAccessor, viewModel: any, bindingContext: KnockoutBindingContext) => {

        // Get our custom binding values
        var value = valueAccessor();
        var associatedObservable = value.observable;
        var translationToken = value.translationToken;

        // Set up an event handler that will respond to events telling it when our translations have finished loading
        // the custom binding will instantly update when a key matching it's translation ID is loaded into the
        // local session store
        window.addEventListener("TranslationsLoaded", (e) => {
            //associatedObservable(" "); // Force an update on our observable, so that the update routine below is triggered
            associatedObservable.valueHasMutated();
        }, false);

    },

    update: (element: HTMLElement, valueAccessor: Function, allBindings: KnockoutAllBindingsAccessor, viewModel: any, bindingContext: KnockoutBindingContext) => {

        // Get our custom binding values
        var value = valueAccessor();
        var associatedObservable = value.observable;
        var translationToken = value.translationToken;

        // Ask local storage if we have a token by that name
        var translatedText = utilityLib.getTranslatedString(translationToken);

        // Check if our translated text is defined, if it's not then substitute it for a fixed string that will
        // be seen in the UI (Whatever you put into the 'associatedObservable' at this point WILL appear in the element
        if (undefined === translatedText || translatedText === "" || translatedText === null) {
            if (sessionStorage["translations"] === undefined) {
                // No translations have loaded yet, so we blank the text
                translatedText = "";
            } else {
                // Translations have loaded, and the token is still not found
                translatedText = "No Translation ID";
            }
        }
        associatedObservable(translatedText);
        ko.utils.setTextContent(element, associatedObservable());
    }

} // End of translatedText binding

Переведенная привязка заполнителя

ko.bindingHandlers.translatedPlaceholder = {

    // This one works pretty much the same way as the translated text binding, except for the final part where
    // the translated text is inserted into the element.

    init: (element: HTMLElement, valueAccessor: Function, allBindings: KnockoutAllBindingsAccessor, viewModel: any, bindingContext: KnockoutBindingContext) => {
        var value = valueAccessor();
        var associatedObservable = value.observable;
        var translationToken = value.translationToken;
        window.addEventListener("TranslationsLoaded", (e) => {
            debugger;
            associatedObservable.valueHasMutated();
        }, false);
    },

    update: (element: HTMLElement, valueAccessor: Function, allBindings: KnockoutAllBindingsAccessor, viewModel: any, bindingContext: KnockoutBindingContext) => {
        var value = valueAccessor();
        var associatedObservable = value.observable;
        var translationToken = value.translationToken;
        var translatedText = utilityLib.getTranslatedString(translationToken);
        debugger;
        if (undefined === translatedText || translatedText === "" || translatedText === null) {
            if (sessionStorage["translations"] === undefined) {
                translatedText = "";
            } else {
                translatedText = "No Translation ID";
            }
        }
        associatedObservable(translatedText);
        element.setAttribute("placeholder", translatedText);
    }

} // End of translatedPlaceholder binding

Идея проста: если привязка выполняется и переводы уже присутствуют в sessionStorage, то мы берем переведенную строку и подключаем ее к наблюдаемой, связанной с элементом.

Если переводы загружены, но перевод не найден, "Нет идентификатора перевода" вставляется в видимую привязку к элементу.

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

Тем не мение.....

Неважно, как сильно я стараюсь, переведенная привязка заполнителя просто не запустит свое обновление.

Я ясно вижу в отладчике, что событие получено на обеих привязках, и вызывается функция мутирования.

На переведенную текстовую привязку я получаю следующую последовательность...

'init' -> 'update' -> 'event' -> 'mutate' -> 'update'

Это именно то, что я ожидаю, и это происходит на каждом элементе + наблюдаемой привязке к этой привязке.

На переведенный заполнитель я получаю

'init' -> 'update' -> 'event' -> 'mutate'

но окончательное обновление никогда не происходит.

В результате, переведенная строка для заполнителя никогда не просматривается правильно, текстовая строка с идентичным кодом работает отлично!!

Для тех, кто спросит, я использую привязки вот так:

<input type="text" class="form-control" data-bind="value: userName, translatedPlaceholder: { observable: namePlaceHolderText, translationToken: 'loginBoxNamePlaceholderText'}">

<span class="help-block" data-bind="translatedText: {observable: nameErrorText, translationToken: 'loginBoxUserNameEmptyValidationText'}"></span>

а внутри модели представления "наблюдаемые" параметры - это просто обычные переменные ko.observable, содержащие строки.

Ура Shawty

2 ответа

Я полагаю, что у вас есть проблемы с переполнением событий... попробуйте поставить 'return true' после вашего вызова valueHasMutated следующим образом

init: (element: HTMLElement, valueAccessor: Function, allBindings: KnockoutAllBindingsAccessor, viewModel: any, bindingContext: KnockoutBindingContext) => {
    var value = valueAccessor();
    var associatedObservable = value.observable;
    var translationToken = value.translationToken;
    window.addEventListener("TranslationsLoaded", (e) => {
        associatedObservable.valueHasMutated();
        return true; // allow event to bubble
    }, false);
},

При этом, я думаю, что все эти ручные соревнования противоречат тому, что может сделать для вас нокаут. Вы должны наблюдать за своими данными, связываться с ними и позволять нокауту делать все внутренние события для вас... это то, что он делает.

Пример, основанный на скрипке пользователя 3297291:

ko.bindingHandlers.translatedPlaceholder = {
  init: function(element, valueAccessor) {
    var va = valueAccessor();
    var obs = ko.utils.unwrapObservable(va.obs);
    var placeholderStr = obs[va.key];
    console.log(placeholderStr);
    element.setAttribute("placeholder", placeholderStr);
  },
  update: function(element, valueAccessor) {
    var va = valueAccessor();
    var obs = ko.utils.unwrapObservable(va.obs);
        var placeholderStr = obs[va.key];
    console.log(placeholderStr);
    element.setAttribute("placeholder", placeholderStr);
  }
};

var vm = function() {
    var self = this;
    self.dictionary = ko.observable({
    "placeholder": "Initial State"
  });

  self.switchTranslations = function() {
    // Set the 'new' dictionary data:
    self.dictionary({
      "placeholder": "My Translated Placeholder"
    });
  };
}
ko.applyBindings(new vm());

Скрипка: https://jsfiddle.net/brettwgreen/5pmmd0va/

Запустить обработчик обновления

Из нокаут-документации:

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

Ключ в том, что вы должны получить доступ к наблюдаемому в пределах update функция для получения обновлений. В вашем translatedText привязка у вас так:

ko.utils.setTextContent(element, associatedObservable());

Но нет такого доступа associatedObservable в update функция translatedPlaceholder, Вам нужно будет добавить это так:

associatedObservable(translatedText);
associatedObservable();  // get notified of updates to associatedObservable
element.setAttribute("placeholder", translatedText);

Лучший подход

Для вашего случая, действительно нет необходимости update обработчик, потому что вам не нужно обновлять представление на основе изменений в viewmodel. Вместо этого ваши обновления приходят только из событий, которые можно настроить в init обработчик.

ko.bindingHandlers.translatedPlaceholder = {
    init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
        function loadTranslation() {
            var translationToken = valueAccessor(),
                translatedText = utilityLib.getTranslatedString(translationToken);
            element.setAttribute("placeholder", translatedText || "No Translation ID");

            window.removeEventListener("TranslationsLoaded", loadTranslation);
        }
        if (sessionStorage["translations"] === undefined)        
            window.addEventListener("TranslationsLoaded", loadTranslation, false);
        } else {
            loadTranslation();
        }
    }
}

Использование:

data-bind="value: userName, translatedPlaceholder: 'loginBoxNamePlaceholderText'"
Другие вопросы по тегам