What is the preferred way to maintain state between init and update for custom knockout bindings?

I am currently saving state using jQuery data for the dom element.

ko.bindingHandlers.customValue = { init: function init(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) { var state = { isEditing: false }; $(element).focus(function focus() { state.isEditing = true; }).blur(function blur() { state.isEditing = false; }).data("customBinding", state); }, update: function update(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) { // ignore if updating if (!$(element).data("customBinding").isEditing) { // handle update if they are not updating } } };โ€‹ 

Is there a better place to store state for bindings that dom does not require? Can bindingContext be used to store state for each binding instance?

+54
Apr 15 2018-12-12T00:
source share
5 answers

bindingContext is a feature, but only for transferring data from init to update when the binding is first started. The next time update fires, it will be gone.

In fact, there are two options for maintaining this type of state:

1- On the item as you stated. You can use jQuery $.data , or KO includes an API for this, as well as ko.utils.domData.get(element, key) and ko.utils.domData.set(element, key, value) .

2- Put this type of information in your view model, if necessary. The flag for isEditing not necessarily inappropriate in the view model. I personally would like to put this type of "metadata" as sub-observables from the observable type:

 var name = ko.observable("Bob"); name.isEditing = ko.observable(false); 

You could bind to name and name.isEditing .

This has some advantages:

  • keeps the review model pretty clean since you are not introducing new top-level properties.
  • saves sub-observable attached to parent observable (no need for nameIsEditing , etc.)
  • when it turns into JSON with something like ko.toJSON , the sub-observable isEditing will simply be deleted when the parent is unpacked. This way you will not send unnecessary values โ€‹โ€‹back to the server.
  • in this case, it may also have the advantage of being available for other calculations in your view model or for binding to multiple elements of your user interface.
+46
Apr 17 '12 at 1:17
source share

Attaching data to an element is fine, and Knockout uses this method to bind a control flow (if, with, etc.), for example.

Another method is to use the init function and use the computed observable to process updates. I use this method in a repeat binding. Here are the important parts:

 ko.bindingHandlers['repeat'] = { 'init': function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) { ... // set up persistent data var lastRepeatCount = 0; ... ko.computed(function() { var repeatCount = ko.utils.unwrapObservable(valueAccessor()); ... // Remove nodes from end if array is shorter for (; lastRepeatCount > repeatCount; lastRepeatCount--) { ... } ... // Add nodes to end if array is longer (also initially populates nodes) for (; lastRepeatCount < repeatCount; lastRepeatCount++) { ... } }, null, {'disposeWhenNodeIsRemoved': placeholder}); ... } }; 
+7
May 15 '12 at 1:56
source share

I often use this template:

 define(['knockout'], function(ko) { var interInstanceVariable = null; function Tree(element) { var privateInstanceVariable = null; function privateInstanceMethod() {} this.publicInstanceMethod = function() {} } ko.bindingHandlers.cannDendrogram = { init: function(element, valueAccessor) { $(element).data('tree', new Tree(element)); }, update: function(element, valueAccessor) { var tree = $(element).data('tree'); tree.publicMethod(); } }; }); 
+1
Oct 24 '15 at 0:00
source share

I understand that this question is old, but I came across it, looking for an approach, so I thought that I would come up with a more modern method ko.

You can simply add properties to bindingContext. $ data directly. I chose the annoying variable name "___IsEditing" to avoid potential collisions, but you get the idea ...

 ko.bindingHandlers.customValue = { init: function init(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) { bindingContext.$data.___IsEditing = false; $(element).focus(function focus() { bindingContext.$data.___IsEditing = true; }).blur(function blur() { bindingContext.$data.___IsEditing = false; }).data("customBinding", state); }, update: function update(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) { // ignore if updating if (bindingContext.$data.___IsEditing) { // handle update if they are not updating } } 

};

0
Dec 29 '16 at 15:38
source share

I use a function to create a common area for init and update, defining common data inside the function.

-2
Dec 29 '16 at 23:52
source share



All Articles