Implementing an event listener within a knockoutjs custom directive

I have extensive experience as a knockout user, but I am currently struggling to achieve a specific scenario. For the past few days, I have been trying to create a system within a knockout component that allows observables to translate themselves into different languages.

In order to accomplish this, I have developed a custom binding that is applied to elements in the following manner:

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

This custom binding is then linked to a property in my knockout component using a standard observable:

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

Although I am using TypeScript for this project, I can work with either TS or JS interchangeably. With the custom binding, I can still update the observable in the same way as a normal text binding by using 'translatedStringFour("foo").

We store the translations in HTML5 localStorage with the translationToken from the binding serving as the key. When the page loads, another component is responsible for retrieving the translated strings based on the user's chosen language.

These translated strings are then stored in localStorage using the translationToken. When the custom bind is invoked, we search localStorage for the corresponding value to replace the untranslated string. The code for our custom binding is as follows:

  ko.bindingHandlers.translatedText = {

        init: (element: HTMLElement, valueAccessor: Function, allBindings: KnockoutAllBindingsAccessor, viewModel: any, bindingContext: KnockoutBindingContext) => {
            var value = valueAccessor();
            var associatedObservable = value.observable;
            var translationToken = value.translationToken;
        },

        update: (element: HTMLElement, valueAccessor: Function, allBindings: KnockoutAllBindingsAccessor, viewModel: any, bindingContext: KnockoutBindingContext) => {
            var value = valueAccessor();
            var associatedObservable = value.observable;
            var translationToken = value.translationToken;
            var translatedText = sessionStorage[translationToken];
            if (undefined === translatedText) {
                translatedText = "No Translation ID";
            }
            associatedObservable(translatedText);
            ko.utils.setTextContent(element, associatedObservable());
        }

    }

While this system works well once all translations are loaded into localStorage, there may be instances where the translations are not yet available during the initial page load. In such cases, I have various methods in place to notify the components when the translations are ready, such as window.postMessage(), someElement.dispatchEvent(), or ko.postbox.publish().

The challenge arises in integrating the event/message handler within the binding handler so that the element can receive notifications and retry translations. This requirement is crucial for certain components that may need these translations before they are fully loaded. Despite multiple attempts, including using postmessage API, custom events, and JQuery, I have not been successful in implementing an event listener within the binding.

If you have any insights on how to add an event handler inside a custom binding without relying on external dependencies other than Knockout core, your input would be greatly appreciated.

Shawty

Update (About an hour later)

After reviewing Regis's answer, I realized that my issue was related to targeting the element within the binding. By attaching event handlers to the Window object rather than the specific element, I was able to resolve the problem. This change allowed me to successfully implement the necessary functionality for handling events within the custom binding.

Answer №1

[UPDATE: providing a more detailed response]

I'm having trouble understanding the essence of your question, as I fail to see how sessionStorage loading can occur asynchronously.

One possible scenario is that sessionStorage gets populated through asynchronous functions such as an ajax call to a translation API.

If you already have all the necessary code in your question, it's unclear what exactly is causing the issue for you:

var sessionStorageMock = { // essential for mocking in code snippets: initially empty
};

var counter = 0;
var attemptTranslation = function() { 
  setInterval(function() { // let's assume it makes AJAX calls, and the results are cached in sessionStorage
    var token = "token"; // this should be a collection
    sessionStorageMock[token] = "after translation " + (counter++); // notification to 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" />

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Sorting `divs` based on the number of user clicks in JavaScript: A simple guide

I've implemented a script that tracks the number of clicks on each link and arranges them based on this count... Currently, everything is functioning correctly except when the <a> tags are nested inside a div. In such cases, the script ignores ...

Converting a JSON object into a JavaScript array

I have a php controller that runs a database query and returns the result to an Ajax function. Below is the php code. public function trackUnreadMsgs(){ $data['userData'] = $this->session->userdata('userData'); $ ...

Validating dates with JavaScript from the start date to the end date

I need to validate the from and to date fields using the date format d/m/Y H:i. This is what my code looks like: var startDate = new Date($('#fromdate').val()); var endDate = new Date($('#todate').val()); if (endDate.getTi ...

Issues with Toggling Visibility in HTML, CSS, and Javascript

Currently, I am working on a website and following a tutorial called "Creating Slideshow using HTML, CSS, and Javascript" by W3Schools. In the project, I want to hide the thumbnail images located at the bottom and the navigation arrows initially and have t ...

What is the best way to design a new class that will serve as the parent class for both of my existing classes, allowing them

I am facing a challenge with my programming classes. I have two classes, "Player" and "Enemy", each with similar methods and properties. I want them to inherit from a parent class that I'll create called "Game Object". How can I approach creating thi ...

Creating Instances of Parameterized Types

Consider the following scenario: class Datum {} An error message (error TS2304: Cannot find name 'T') is encountered when attempting the following: class Data<T extends Datum> { datum: T constructor() { this.datum = new ...

Guide to choosing a default setting in an ng2 framework by utilizing a child component

Currently, I am in the process of developing an application where I have created an input component that can adapt to various types such as text input, select, text area, and more. To achieve this flexibility, I set up a model to provide all necessary inf ...

Issue with HighCharts Series Data Points Not Being Added

I am currently facing a major challenge in dynamically changing data for highcharts based on date. To provide context for my project, it involves logging system data with timestamps. I have implemented a date and time picker to specify the start and end da ...

Please wait for the window to finish formatting before attempting to print

I'm currently using the javascript code below to pass an element ID to a method. This method then formats that particular ID in a separate window and prints it. However, I've encountered an issue where the print dialogue box opens up before the w ...

What is the reason for the text not being written continuously in the textfield?

Looking to create a page for collecting user information. This is a Codesandbox.io page where the issue arises. https://codesandbox.io/s/material-demo-z1x3q?fontsize=14 When I try to input "d" continuously in the 성별* textfield, I can only enter "d" ...

The magic of $.ajax lies in its ability to load an unexpected URL, diverging from my original

Every time I send a request using an absolute URL, Ajax is posting the wrong URL. For instance, when I request "http://localhost/app/home/session", it mistakenly calls "http://localhost/app/home/home/session" var baseURL = function(link) { var url = & ...

Responsive menu not collapsing properly and appearing funky in Drupal

I've managed to create a responsive navigation bar, but I'm encountering two issues. Firstly, when I resize the screen on my test pages, not all of the links hide as expected. Secondly, after inserting the code into Drupal, the nested links appea ...

The page you're looking for is nowhere to be seen on the Angular Routing for Mobile View - it seems to have vanished into thin

After creating an angular app, I encountered an issue while using ng build --prod to build it for production and hosting. On the mobile view, every routing except the homepage displayed a 404 Page Not Found error. I am currently unable to determine the roo ...

Dealing with a surprise JSON error in Express.js using Javascript

Dealing with Unexpected JSON in my express js application using try and catch. I attempted to achieve this as follows: try{ let body = JSON.parse(req.body); }catch(e){ res.json({ error:e }) } However, the Unexpected JSON error is not caught in ...

Is there a method to place two items in a flex container while only centering one of them?

I managed to achieve the desired effect using absolute positioning, but I know this is not the most elegant solution and it lacks re-usability. Is there a way for me to group these two elements in a flex container and center only the text? Also, I'm f ...

Tips for incorporating VueJS 2 global components within single file components

As I attempt to utilize a globally registered component (using Vue.component) within a single file component, I consistently encounter the following warning: vue.common.js:2611[Vue warn]: Unknown custom element: <my-component> - did you register the ...

Replicating entities in TypeScript

I am currently developing an Angular 2 application using TypeScript. In a User Management component, I have implemented a table that displays all the users in my system. When a user is clicked on within the table, a form appears with their complete set of ...

What is the best way to add a constant value to all objects within an array without having to iterate through each one

Is there a more concise way to add a fixed value to each object in an array without using a loop in JavaScript? Programming Language used: JavaScript Example Array: "cars": [ { "name":"Ford", "models":"Fiesta" }, { "name":"BMW", "models":"X1" }, ...

Showing the information obtained from an API request once the user submits the form without the need to reload the page

I am currently working on a form that will take search query requests from users upon submission and then display the results by making an API call. My goal is to show these results without the page having to refresh, using AJAX. The backend connection to ...

Unable to access member function of Typescript class

I recently started using typescript and encountered an issue while working on a problem. I initially created the following class: export class ModuleInfoContainer extends Array<ModuleInfo> { constructor() { super(); } search(id: number) { ...