Posts
202
Comments
1112
Trackbacks
51
KnockoutJS Custom Binding for Invoking an Action with Enter Key

KnockoutJS is an awesome MVVM JavaScript data binding framework for building rich user experiences. As sites get more interactive and more presentation logic gets pushed to the client-side, it’s important to have rich frameworks which enable these scenarios. The next version of MVC is going to include the Knockout NuGet package in the box. Now is a perfect time to start learning Knockout.

As I’ve been learning Knockout, I’ve been pleased to discover that, although it comes with a rich set of features built-in, one of the best aspects of Knockout is the extensibility. When there is a behavior you need that doesn’t match the out of the box capabilities of Knockout it’s easy to customize. Typically this takes the form of building your own custom binding. Let’s take an example.

A typical Knockout scenario is when you have a text box which is used to add an item to a list. From the jsFiddle below you the see the JavaScript and HTML for this are very straight-forward. If you click the “Result” tab, you will see that any item you type in the text box will be added to the list when you click the “Add” button:

 

Direct link to the jsFiddle.

 

This is all well and good but it would be more convenient for the user if they did not have to pick up their mouse to click the Add button for each item – but instead were able to just hit the Enter key to add the item. This can be done with relative ease by using the event binding (in conjunction with the keypress event) that comes with Knockout as shown in the next jsFiddle:

 

Direct link to the jsFiddle.

 

As you can see in the JavaScript above, the addOnEnter function checks the charCode of 13 (i.e., the Enter key) and invokes the addItem() function for this scenario.

We now have the behavior we want. However, the problem here is that my addOnEnter function() is not a re-usable solution at all because the call to the specific addItem function is directly embedded inside. If we have another text box that should add to another collection, we can’t use the above solution without some copy/paste ugliness replacing the 1-line of code to call the appropriate “add” function. Fortunately, the re-usability problem can be solved easily be created your own custom binding. Basically what we’re after is the ability to have our own binding called “executeOnEnter” which automatically checks the Enter key press and provides the ability to specify the function that should be called. In other words, we’d like to declaratively be able to specify this:

   1:  <input type="text" data-bind="value: itemToAdd, valueUpdate: 'afterkeydown', executeOnEnter: addItem" />

In order to do this, we have to write our own custom binding for executeOnEnter which is shown in the following jsFiddle:

 

Direct link to the jsFiddle.

 

The key thing to notice is that we are passing in the reference to the function that we want to be executed. This happens automatically via the “allBindingsAccessor” variable that is available for all custom bindings. However, instead of invoking the function directly, I use the JavaScript call() function and pass in the viewModel (conveniently, the viewModel is another parameter that is automatically available for all custom bindings). In JavaScript, the call() method assigns the this pointer for the specific method invocation. If we didn’t do this, this addItem() method wouldn’t work properly because the this pointer would be in the context of the custom binding rather than the context of the view model. It would force us to have to write our addItem() method like this:

   1:  addItem: function() {
   2:      viewModel.items.push({ name: viewModel.itemToAdd() });
   3:      viewModel.itemToAdd("");
   4:  } 

rather than like this (much preferred):

   1:  addItem: function() {
   2:      this.items.push({ name: this.itemToAdd() });
   3:      this.itemToAdd("");
   4:  } 

 

This is just the tip of the iceberg for the types of things you can do with custom bindings with Knockout!

posted on Tuesday, October 11, 2011 10:26 PM Print
Comments
Gravatar
# re: KnockoutJS Custom Binding for Invoking an Action with Enter Key
IWantExamples
10/12/2011 9:53 AM
Welcome to JS. Your example doesn't work the same in IE (works) and FF (doesn't work).

In fact in IE9 all of the examples respond to the enter key!
Gravatar
# re: KnockoutJS Custom Binding for Invoking an Action with Enter Key
Steve
10/12/2011 11:04 AM
@IWantExamples - the only reason you're seeing the first example "work" in IE is because that button is right next to the text box. If you run this jsfiddle you'll see the example holds up where IE *won't* add the item on enter (expected behavior): http://jsfiddle.net/smichelotti/wVAnc/.

All examples I tested in Chrome. I admit I did not test with FF (yet).
Gravatar
# re: KnockoutJS Custom Binding for Invoking an Action with Enter Key
IWantExamples
10/12/2011 11:32 AM
I think the deal is that IE handles focus on a form like your example different than other browsers -- IE gives focus to the button even though you are typing in a text box.
Gravatar
# re: KnockoutJS Custom Binding for Invoking an Action with Enter Key
Ryan Cromwell
10/12/2011 4:54 PM
The data-bind element of Knockout reminds me of early versions of Caliburn binding expressions. Nasty. I think I'll prefer backbone views to Knockout until it looks at a better way such as more custom attributes.
Gravatar
# re: KnockoutJS Custom Binding for Invoking an Action with Enter Key
Steve Gentile
10/12/2011 6:24 PM
I agree Ryan, that is/will get quite messy.

People have helped fix the above though - ie.
http://userinexperience.com/?p=689

I'd be more inclined to go the route of backbone as well, and use something like this for declarative bindings:
Backbone has data-bind attributes as well:
http://lostechies.com/derickbailey/2011/07/29/knockout-style-data-bind-attributes-for-backbone/

Gravatar
# re: KnockoutJS Custom Binding for Invoking an Action with Enter Key
Ryan Cromwell
10/12/2011 8:33 PM
@Gentile Backbone has data-*** as well and I prefer that over data-bind, but even then we should be preferring template per item.

I would like to see one of the frameworks move towards a conventional binding approach like Caliburn[.Micro]. For example, why wouldn't I just id my button as 'addItem' and applyBindings would conventionally connect my button to the addItemClick method in my ViewModel.

I think Backbone has this partially correct with the events section of the view. I would say that this binding between the view and the operations of the model belongs in a more traditional controller as apposed to their controller, now router, which is far from a traditional controller in the MVC sense.
Gravatar
# re: KnockoutJS Custom Binding for Invoking an Action with Enter Key
Ryan Niemeyer
10/14/2011 10:07 AM
Nice work! Very minor point that does not change the functionality: the function that you passed to your binding is available directly from valueAccessor(). Typically, you would use allBindingsAccessor to access other options passed in the same data-bind. For example, the "value" binding uses allBindingsAccessor to access "valueUpdate", which is not its own binding just an optional param.

Doesn't change anything though...minor point.

Custom bindings are an incredibly powerful tool. I hope that you share more of yours in the future.
Gravatar
# re: KnockoutJS Custom Binding for Invoking an Action with Enter Key
Steve
10/14/2011 12:00 PM
@Ryan - Thanks for the feedback. You know, I did notice that I could use either and it was a little unclear to me which one was the *correct* parameter to use given that they both worked. I guess the answer is: use valueAccessor() for the main one and use allBindingsAccessor for everything else (while ignoring that I can get first one from allBindingsAccessor). Thanks again!
Gravatar
# re: KnockoutJS Custom Binding for Invoking an Action with Enter Key
Noel Abrahams
10/14/2011 1:52 PM
@Steve, the reason this is not working in FF is because event.charcode is non-standard.

If you were to use if (event.which === 13) {...} instead then I believe it should work.
Gravatar
# re: KnockoutJS Custom Binding for Invoking an Action with Enter Key
Geoff
11/21/2011 5:28 AM
@Noel, this should be failsafe for e.keyCode versus e.which. Also, as per standard ko-handlers, you should pass in the event object (so you can figure out what DOM object was hit):

ko.bindingHandlers.executeOnEnter = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var allBindings = allBindingsAccessor();
$(element).keypress(function (event) {
var intKey = (window.Event) ? event.which : event.keyCode;
if (intKey === 13) {
allBindings.executeOnEnter.call(viewModel, event);
return false;
}
return true;
});
}
};
Gravatar
# Small Bug?
Guy
1/2/2012 7:56 PM
If you hard refresh this page (Ctrl+F5) and go to the Result tab of the first jsFiddle example you'll notice that the enter key will add items to the list. Then go to the second jsFiddle and do the same. Now return to the first jsFiddle and you will notice that the enter key no longer enters the item. i.e. expected behavior of the first jsFiddle is only seen after executing the second jsFiddle. (Tesed using Chrome.)

However: This is an awesome blog post - thank you!
Gravatar
# re: KnockoutJS Custom Binding for Invoking an Action with Enter Key
Steve Michelotti
1/17/2012 4:10 PM
@Guy - I'm not seeing the same results as you. In IE the first sample will add an item to the list only because the <button> in the next (i.e., default) form element (Chrome won't add it).
Gravatar
# re: KnockoutJS Custom Binding for Invoking an Action with Enter Key
Enis Pilavdzic
3/29/2012 2:53 PM
The value of the observable is not yet set correctly yet at the time this binding executes the function.

In many cases it is desirable to use this binding in this way:

<input type="text" data-bind="value: searchValue, executeOnEnter: serverSearch" />
<input type="button" value="Search" data-bind="click:serverSearch"/>

Below is the binding with my bugfix that addresses this issue:

ko.bindingHandlers.executeOnEnter = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var allBindings = allBindingsAccessor();
$(element).keypress(function (event) {
var keyCode = (event.which ? event.which : event.keyCode);
if (keyCode === 13) {
allBindings.value($(element).val()); //set the value of the observable early because often the function you call on the next line wants to reference it
allBindings.executeOnEnter.call(viewModel);
return false;
}
return true;
});
}
};

Yes, this sets the observable value early, but I am not aware of any negative side-effects of doing this (please chime in if you know of a disadvantage of doing this).

It is however MUCH simpler and more convenient to reuse the same functions you use in other places, which typically access observable values directly from the viewModel rather than the local scope.
Gravatar
# re: KnockoutJS Custom Binding for Invoking an Action with Enter Key
Steve Michelotti
3/29/2012 7:01 PM
@Enis - Why are you saying the value of the observable is not yet set? In the markup I'm using "valueUpdate: 'afterkeydown'" which sets it immediately anytime someone enters any character. I suppose your method of extracting the value directly from the element would enable someone to not to have to use valueUpdate but then the downside would be that it's ignoring dependent updates at that point.
Gravatar
# re: KnockoutJS Custom Binding for Invoking an Action with Enter Key
Ilian
9/10/2012 4:35 AM
Works super.
Thanks!
Gravatar
# re: KnockoutJS Custom Binding for Invoking an Action with Enter Key
serge klok
12/23/2012 11:10 PM
This will work only with ko version 1.3
With ko version #1.8 it's not going to work.
What is new alternative to function event.keyCode in a last ko?
Gravatar
# re: KnockoutJS Custom Binding for Invoking an Action with Enter Key
Adam Kochanowicz
3/22/2013 12:13 PM
If anyone else is having issues getting the keyCode read from the event parameter, edit this:

addOnEnter: function(event) {

to read this:

addOnEnter: function(model, event) {

Post Comment

Title *
Name *
Email
Comment *  
 

View Steve Michelotti's profile on LinkedIn

profile for Steve Michelotti at Stack Overflow, Q&A for professional and enthusiast programmers




Google My Blog

Tag Cloud