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.
One is to translate text, the other is to translate the 'placeholder' attribute on HTML5 input elements.
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.
If the translations have loaded, but the translation is not found "No Translation ID" is plugged into the observable bound to the element.
If the translations have NOT yet loaded, plug an empty string into the observable, then wait for the event 'TranslationsLoaded' to fire. 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.
On the translated text binding, I get the following sequence...
'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'
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.
Cheers Shawty