在knockoutjs自定义绑定

时间:2016-05-20 08:07:54

标签: javascript knockout.js typescript

我是一位相当经验丰富的淘汰赛用户,因此我了解了很多引人注目的内容,但我现在已经在争取几天试图弄清楚如何实现特定场景。

我必须创建一个系统,允许在给定的淘汰组件中的可观察对象能够将自己翻译成不同的语言。

为方便起见,我创建了一个自定义绑定,它以下列方式应用于给定元素。

<p data-bind="translatedText: {observable: translatedStringFour, translationToken: 'testUiTransFour'}"></p>

这又附加到我的挖空组件中的属性,带有一个简单的标准可观察

private translatedStringFour: KnockoutObservable<string> = ko.observable<string>("I'm an untranslated string four....");

(是的,我正在使用该项目的打字稿,但我可以使用TS / JS .....)

通过我的自定义绑定,我仍然可以做到&lt; translateStringFour(&#34; foo&#34;)&#39;它仍将以与普通文本绑定完全相同的方式更新。

如果将翻译存储在HTML5 localStorage键/值存储区中,并且在我们的应用程序启动时就开始存在,还有另一个组件负责,以获取翻译ID和列表根据用户选择的语言从我们的应用程序请求翻译的字符串。

然后使用translationToken(在绑定中看到)作为键将这些字符串存储在localStorage中。

这意味着当页面加载,并且我们的自定义绑定触发时,我们可以从绑定中获取translationToken,并询问localStorage以请求替换未转换字符串的值,我们的自定义绑定的代码如下: / p>

  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;

        },

        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 = sessionStorage[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 (We should really not change this but this is for dev purposes so we can see whats missing)
            if (undefined === translatedText) {
                translatedText = "No Translation ID";
            }
            associatedObservable(translatedText);
            ko.utils.setTextContent(element, associatedObservable());
        }

    }

现在,到目前为止,这项工作非常出色,只要翻译的完整缓存已加载到localStorage中,observable将根据需要自行翻译正确的字符串。

无论其......

因为这个翻译加载器可能需要几秒钟,并且它加载的初始页面也需要翻译一些元素,所以第一次加载页面时,翻译很可能是UI要求尚未加载到localStorage,或者可能正在加载。

处理这个并不是什么大问题,我使用承诺执行加载,因此加载发生,我的then子句触发,我做了类似的事情

window.postMessage(...);

someElement.dispatchEvent(...);

甚至(我最喜欢的)

ko.postbox.publish(...)

这里的要点是我不乏提出某些描述的事件/消息以通知页面和/或其翻译已完成加载的组件的方法,并且您可以自由重试请求它们如果你愿意的话。

在这里....解决我的问题。

我需要接收此消息的事件/消息处理程序存在于绑定处理程序内部,这样我的行为就像绑定&#34;使用我们的自定义绑定,将添加此元素接收此事件/消息的能力,并能够重试。

这对于应用程序中的其他页面来说不是问题,因为当用户登录时,翻译所有的爵士乐都已加载并安全地存储在本地存储中。

我非常乐意使用邮箱(绝对令人敬畏的工作就像Ryan一样 - 如果你阅读它......它是一个非常有用的插件,并且应该被构建进入核心IMHO)但是,我打算将这个绑定包装在一个独立的类中,然后我会根据需要通过需要它的那些组件加载requireJs。但我无法保证在加载绑定之前甚至在加载绑定的同一时刻加载邮箱。

我试图让一个事件监听器在绑定中工作的其他方法刚刚被忽略,没有任何错误或任何错误,他们只是不会开火。

我尝试过使用postmessage api,我尝试过使用自定义事件,我甚至试图滥用JQuery,但都无济于事。

我已经搜索了KO源代码,特别是事件绑定,并且最接近我在init处理程序中附加事件的方法如下:

        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 on session storage, by doing this
            // the custom binding will instantly update when a key matching it's translation ID is loaded into the
            // local session store

            //ko.utils.registerEventHandler(element, 'storage', (event) => {
            //    console.log("Storage event");
            //    console.log(event);
            //});

            ko.utils.registerEventHandler(element, 'customEvent', (event) => {
                console.log("HTML5 custom event recieved in the binding handler.");
                console.log(event);
            });
        },

这一切都没有用,所以Knockout社区的人们......

如何在自定义绑定中添加一个事件处理程序,然后我可以从该绑定外部触发,但不依赖于除Knockout核心和我的绑定之外的其他任何东西。

辣妹

更新(大约一小时后)

我想添加这部分,因为它不是100%清楚为什么瑞吉斯的答案解决了我的问题。

实际上,我使用完全相同的方法,但是(这是关键部分)我的目标是&#34;元素&#34;作为约束的一部分而来。

这是我的想法是正确的方法,因为我希望事件特别坚持使用绑定所应用的元素,因为据说我想重新尝试它的翻译元素一旦它知道了它得到了反对。

然而,在查看瑞吉斯的代码并将其与我的代码进行比较之后,我注意到他正在将他的事件处理程序附加到&#34; Window&#34;对象,而不是&#34;元素&#34;。

对此进行跟进,我也改变了我的代码以使用窗口对象,并且我尝试的所有内容都开始起作用。

更重要的是,元素特定定位也有效,所以我在实际元素上获得了需要重新尝试翻译的实际绑定中的实际事件。

1 个答案:

答案 0 :(得分:1)

[编辑:试图更好地回答这个问题]

我没有真正理解问题的全部内容,因为我不知道sessionStorage加载是如何异步的。

因此我认为sessionStorage是从som异步函数填充的,比如对转换API的ajax调用。

但是我不知道你在这里阻止了什么,因为你已经拥有了问题中的所有代码:

&#13;
&#13;
var sessionStorageMock = { // mandatory to mock in code snippets: initially empty
};

var counter = 0;
var attemptTranslation = function() { 
  setInterval(function() { // let's say it performs some AJAX calls which result is cached in the sessionStorage
    var token = "token"; // that should be a collection
    sessionStorageMock[token] = "after translation " + (counter++); // we're done, notifying event handlers
    window.dispatchEvent(new Event("translation-" + token));
  }, 500);
};



ko.bindingHandlers.translated = {
  init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
    var val = valueAccessor();
    var token = val.token;
    console.log("init");
    window.addEventListener("translation-" + token, function() {
      if (token && sessionStorageMock[token]) {
        val.observable(sessionStorageMock[token]);
      }
    });
  }
};


var vm = function() {
  this.aftertranslation = ko.observable("before translation");
};


ko.applyBindings(new vm());
attemptTranslation();
&#13;
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>


<div data-bind="translated: { observable: aftertranslation, token: 'token' }, text: aftertranslation" />
&#13;
&#13;
&#13;