Adding an event handler in a custom binding

60 views
Skip to first unread message

Peter Shaw

unread,
May 19, 2016, 11:54:34 AM5/19/16
to KnockoutJS
Hi All,

I'm a fairly experienced knockout user, so I understand quite a bit of the under the hood stuff, I have however been battling now for a few days trying to figure out how to achieve a given scenario.

I have to create a system that allows observable's within a given knockout component to be able to translate themselves to different languages.

to facilitate this, I've created a custom binding, which is applied to a given element in the following way.

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

This is in turn attached to a property in my knockout component with a simple standard observable

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

(YES, I am using typescript for the project, but TS/JS either I can work with.....)

With my custom binding I can still do 'translatedStringFour("foo")' and it will still update in exactly the same way as the normal text binding.

Where storing the translations in the HTML5 localStorage key/value store, and right at the beginning when our app is launched, there is another component that's responsible, for taking a list of translation ID's and requesting the translated strings from our app, based on the users chosen language.

These strings are then stored in localStorage using the translationToken (seen in the binding) as the key.

This means that when the page loads, and our custom bind fires, we can grab the translationToken off the binding, and interrogate localStorage to ask for the value to replace the untranslated string with, the code for our custom binding follows:

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

        }

Now, thus far this works brilliantly, as long as the full cache of translations has been loaded into localStorage, the observables will self translate with the correct strings as needed.

HOWEVER......

Because this translation loader may take more than a few seconds, and the initial page that it's loading on also needs to have some elements translated, the first time the page is loaded it is very possible that the translations the UI is asking for have not yet been loaded into into localStorage, or may be in the process of still loading.

Handling this is not a big deal, I'm performing the load using a promise, so the load takes place, my then clause fires, and I do something like

window.postMessage(...);

or

someElement.dispatchEvent(...);

or even (my favorite)

ko.postbox.publish(...)

The point here is I have no shortage of ways to raise an event/message of some description to notify the page and/or it's components that the translations have finished loading, and you are free to retry requesting them if you so wish.

HERE IN.... Lies my problem.

I need the event/message handler that receives this message to live inside the binding handler, so that the very act of me "binding" using our custom binding, will add the ability for this element to receive this event/message, and be able to retry.

This is not a problem for other pages in the application, because by the time the user has logged in, and all that jazz the translations will have loaded and be safely stored in local storage.

I'm more than happy to use post box (Absolutely awesome job by the way Ryan -- if your reading this.... it's an amazingly useful plugin, and should be built into the core IMHO) but, I intend to wrap this binding in a stand alone class which I'll then just load with requireJs as needed, by those components that need it.  I cannot however guarantee that postbox will be loaded before or even at the same instant the binding is loaded.

Every other approach i've tried to get an event listener working in the binding have just gotten ignored, no errors or anything, they just don't fire.

I've tried using the postmessage api, I've tried using a custom event, I've even tried abusing JQuery, and all to no avail.

I've scoured the KO source code, specifically the event binding, and the closest I've come to attaching an event in the init handler is as follows:

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


None of this has worked, so folks of the Knockout community.....

How do I add an event handler inside of a custom binding, that I can then trigger from outside that binding, but without depending on anything other than Knockout core and my binding being loaded.

Shawty


(PS: I posted this here, because on SO, it'll either get ignored because it's too complicated or I'll get an army of nit-wits picking fault with everything they can and arguing over my approach to translation...:-D )


Peter Shaw

unread,
May 20, 2016, 6:08:50 AM5/20/16
to KnockoutJS
Hi all.... just a quick update.  I did end up posting this on SO after all....   and lo and behold, I did get a sensible answer, which can be seen here


Cheers
Shawty
 
Reply all
Reply to author
Forward
0 new messages