Adding an event handler inside a knockoutjs custom binding

shawty

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

Update (About an hour later)

I wanted to add this part, beacuse it's not 100% clear why Regis's answer solves my problem.

Effectively, I was using exactly the same method, BUT (and this is the crucial part) I was targeting the "element" that came in as part of the binding.

This is my mind was the correct approach, as I wanted the event to stick specifically with the element the binding was applied too, as it was said element that I wanted to re-try it's translation once it knew it had the go-ahead.

However, after looking at Regis's code, and comparing it to mine, I noticed he was attaching his event handlers to the "Window" object, and not the "Element".

Following up on this, I too changed my code to use the window object, and everything I'd been attempting started to work.

More's the point, the element specific targeting works too, so I get the actual event, on the actual element, in the actual binding that needs to re-try it's translation.

Regis Portalez

[EDIT: trying to better answer the question]

I don't really get the whole point of the question, since I don't see how sessionStorage load can be asynchronous.

I supposed therefore sessionStorage is populated from som asynchronous functions like an ajax call to a translation API.

But I don't see what blocks you here, since you already have all the code in your question:

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

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related

From Dev

Binding a custom handler twice in knockoutjs

From Dev

Binding a custom handler twice in knockoutjs

From Dev

Dispose event for knockoutjs custom binding

From Dev

Changing observable array alters visibility of custom binding handler, knockoutjs

From Dev

Custom Href Data Binding KnockoutJs

From Dev

Adding Event Handler in VB.NET Custom Control

From Dev

Adding Event Handler in VB.NET Custom Control

From Dev

Adding an Event Handler for a List

From Dev

Why does adding a setTimeout inside a blur event handler fix the "masking" of another click handler?

From Dev

Is it necessary to unbind/off JQuery click event inside Knockout custom binding?

From Dev

Event handler inside event handler is multiplying

From Dev

Anchor data binding inside a td - knockoutjs

From Dev

libavg custom event handler

From Dev

libavg custom event handler

From Dev

jQuery .on() not binding event-handler

From Dev

Event handler binding memory leaks

From Dev

KnockoutJS custom binding with multiple color pickers

From Dev

KnockoutJS: How pass observable to custom binding?

From Dev

Custom virtual binding handler not working

From Dev

Custom virtual binding handler not working

From Dev

Adding class on hover event handler

From Dev

Binding and event handler — passing the event object

From Dev

KnockoutJS autoNumeric binding handler replace empty value with 0

From Dev

Return inside event completed handler

From Dev

Gaining access to the submit handler's event object in KnockoutJS

From Dev

Gaining access to the submit handler's event object in KnockoutJS

From Dev

Java custom event handler and listeners

From Dev

Custom event handler in Unfolding Maps

From Dev

Javascript custom event with user handler

Related Related

  1. 1

    Binding a custom handler twice in knockoutjs

  2. 2

    Binding a custom handler twice in knockoutjs

  3. 3

    Dispose event for knockoutjs custom binding

  4. 4

    Changing observable array alters visibility of custom binding handler, knockoutjs

  5. 5

    Custom Href Data Binding KnockoutJs

  6. 6

    Adding Event Handler in VB.NET Custom Control

  7. 7

    Adding Event Handler in VB.NET Custom Control

  8. 8

    Adding an Event Handler for a List

  9. 9

    Why does adding a setTimeout inside a blur event handler fix the "masking" of another click handler?

  10. 10

    Is it necessary to unbind/off JQuery click event inside Knockout custom binding?

  11. 11

    Event handler inside event handler is multiplying

  12. 12

    Anchor data binding inside a td - knockoutjs

  13. 13

    libavg custom event handler

  14. 14

    libavg custom event handler

  15. 15

    jQuery .on() not binding event-handler

  16. 16

    Event handler binding memory leaks

  17. 17

    KnockoutJS custom binding with multiple color pickers

  18. 18

    KnockoutJS: How pass observable to custom binding?

  19. 19

    Custom virtual binding handler not working

  20. 20

    Custom virtual binding handler not working

  21. 21

    Adding class on hover event handler

  22. 22

    Binding and event handler — passing the event object

  23. 23

    KnockoutJS autoNumeric binding handler replace empty value with 0

  24. 24

    Return inside event completed handler

  25. 25

    Gaining access to the submit handler's event object in KnockoutJS

  26. 26

    Gaining access to the submit handler's event object in KnockoutJS

  27. 27

    Java custom event handler and listeners

  28. 28

    Custom event handler in Unfolding Maps

  29. 29

    Javascript custom event with user handler

HotTag

Archive