简体   繁体   English

Knockoutjs valueHasMutated无法正常工作

[英]Knockoutjs valueHasMutated not working correctly

Hopefully this will be a quick one for a knockout guru.... 希望这对于淘汰赛大师来说是个快速的选择。

I'm writing a couple of custom bindings to help me translate a UI using a custom translation engine in the project I'm working on. 我正在编写几个自定义绑定,以帮助我在正在处理的项目中使用自定义翻译引擎来翻译UI。

One is to translate text, the other is to translate the 'placeholder' attribute on HTML5 input elements. 一种是翻译文本,另一种是翻译HTML5输入元素上的“占位符”属性。

Both bindings are identical apart from the last statement, where one updates the text in the element and the other updates an attribute value. 除最后一条语句外,这两个绑定是相同的,在最后一条语句中,一个绑定更新元素中的文本,另一个绑定更新属性值。

The text one works perfectly, but the place holder one does not, and I'm stuck for an answer as to why. 文本一个完美地工作,但是占位符一个不工作,我被迫寻求一个答案。

The binding code is as follows: 绑定代码如下:

Translated Text Binding 翻译文字绑定

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

Translated Placeholder 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

The idea is a simple one, if the binding runs and the translations are already present in sessionStorage, then we pick up the translated string and plug it in to the observable associated with the element. 这个想法很简单,如果绑定运行并且转换已经存在于sessionStorage中,那么我们将选择转换后的字符串并将其插入与元素关联的observable中。

If the translations have loaded, but the translation is not found "No Translation ID" is plugged into the observable bound to the element. 如果已加载翻译,但未找到翻译,则将“无翻译ID”插入到元素的可观察范围内。

If the translations have NOT yet loaded, plug an empty string into the observable, then wait for the event 'TranslationsLoaded' to fire. 如果尚未加载翻译,则将一个空字符串插入可观察对象,然后等待事件“ TranslationsLoaded”触发。 When this event is raised, the observable bound to the element is mutated, causing an update to happen, which in turn re-checks the translations, which it then finds have loaded and so acts accordingly. 引发此事件时,元素的可观察绑定发生突变,从而导致更新发生,更新又重新检查翻译,然后发现翻译已加载并因此采取相应措施。

However..... 然而.....

It doesn't matter how hard I try, the translated placeholder binding just will not fire it's update. 不管我多么努力,翻译后的占位符绑定都不会触发它的更新。

I can clearly see in the debugger that the event is recieved on both bindings, and the mutate function IS called. 我可以在调试器中清楚地看到两个绑定都接收到该事件,并且调用了mutate函数。

On the translated text binding, I get the following sequence... 在翻译的文本绑定上,我得到以下序列...

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

Which is exactly what I expect, and it occurs on every element+observable bound to that binding. 这正是我所期望的,它发生在该绑定的每个元素+可观察到的绑定上。

On the translated placeholder i get 在翻译后的占位符上,我得到

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

but the final update never occurs. 但是最终更新永远不会发生。

As a result, the translated string for the placeholder is never looked up correctly, the text one with identical code works perfectly!! 结果,占位符的翻译后的字符串永远不会正确查找,具有相同代码的文本可以完美地工作!

For those who'll ask, i'm using the bindings like this: 对于那些会问的人,我正在使用这样的绑定:

<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>

and inside the view model, the 'observable' parameters are just normal ko.observable variables holding strings. 在视图模型内部,“ observable”参数只是保存字符串的普通ko.observable变量。

Cheers Shawty 干杯

I believe you have event bubbling issues... try putting a 'return true' after your call to valueHasMutated like this: 我相信您有事件冒泡的问题...在调用valueHasMutated之后,尝试将“ return true”设置为:

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);
},

That being said, I am thinking all this manual eventing is against-the-grain for what knockout can do for you. 话虽这么说,我认为所有这些手动事件都与淘汰赛可以为您带来的好处相悖。 You should be observing your data, binding to it, and letting knockout do all the internal eventing for you... that's what it does. 您应该观察数据,将其绑定,并让敲除为您完成所有内部事件……这就是它的作用。

An example building on user3297291's fiddle: 以user3297291的小提琴为基础的示例:

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());

Fiddle: https://jsfiddle.net/brettwgreen/5pmmd0va/ 小提琴: https : //jsfiddle.net/brettwgreen/5pmmd0va/

Trigger the update handler 触发更新处理程序

From the Knockout documentation : 淘汰赛文档中

Knockout will call the update callback initially when the binding is applied to an element and track any dependencies (observables/computeds) that you access. 当绑定应用于元素时,Knockout会首先调用update回调,并跟踪您访问的所有依赖项(可观察对象/计算对象)。 When any of these dependencies change, the update callback will be called once again. 当这些依赖项中的任何一个发生更改时,将再次调用update回调。

The key is that you have to access the observable within the update function to get updates. 关键是您必须访问update函数中的observable才能获取更新。 In your translatedText binding you do so: 在您的translatedText绑定中,您可以这样做:

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

But there is no such access of associatedObservable in the update function of translatedPlaceholder . 但是,在translatedPlaceholderupdate函数中没有对associatedObservable访问。 You'll need to add it like this: 您需要像这样添加它:

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

A better approach 更好的方法

For your case, there really isn't a need for an update handler because you don't need to update the view based on changes to the viewmodel. 对于您的情况,确实不需要update处理程序,因为您不需要基于对视图模型的更改来更新视图。 Instead your updates just come from events, which can be set up in the init handler. 相反,您的更新仅来自事件,可以在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();
        }
    }
}

Usage: 用法:

data-bind="value: userName, translatedPlaceholder: 'loginBoxNamePlaceholderText'"

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM