Insights and discoveries
from deep in the weeds
Outsharked

Wednesday, May 15, 2013

Adding "enter key" handling to forms using Knockout.js & jQuery

Been a long time since my last post here, busy year! And man does this blog layout look dated.. priorities...

Here's a quick and easy solution to a problem for which I didn't find a complete solution elsewhere. A nice input form UI will let the user press the ENTER key to submit the form. Browsers don't have a predictable and consistent handling of this action, especially if there's more than one form on a page. Furthermore, if you aren't actually using a submit type element, browsers won't do anything as they have no way of knowing the intent anyway.

This extender for knockout.js + jQuery adds this functionality. All you need to do is add a binding for enterkey to an element that wraps your form, and pressing enter on any input control that's a descendent of the element containing the binding will cause that controller method to be invoked.

Example knockout bindings:
    <div id="form1" data-bind="enterkey: submit">
       Name: <input type=text data-bind="value: fullname">
       ... more fields ...

       <a href="#" data-bind="click: submit">Save</a>
    </div>

    <script type="text/javascript">
        var model = {
            fullname: ko.observable(),
            // ... more fields
            submit: function() {
                // process the form
            }
        };
        ko.applyBindings(model);
    </script>
The code:
ko.bindingHandlers.enterkey = {
    init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
       var allBindings = allBindingsAccessor();

            $(element).on('keypress', 'input, textarea, select', function (e) {
                var keyCode = e.which || e.keyCode;
                if (keyCode !== 13) {
                    return true;
                }

                var target = e.target;
                target.blur();

                allBindings.enterkey.call(viewModel, viewModel, target, element);

                return false;
            });
        }
    }
};
See an example on JsFiddle

6 comments:

  1. Hey Jamie,

    You always give me good CsQuery advice, I figured I'll try to see how I can improve here :)

    "if($(target).parents().add(target).filter(element).length === 0)"

    Could be written as "if(!element.contains(target))"

    jQuery shims event.which, so you don't need "var keyCode = e.which || e.keyCode;..." and can write " if (w.which === 13) {"

    http://jsfiddle.net/Yzawy/

    ReplyDelete
  2. I thought the test was a bit wordy... but this "Element.contains" isn't a universal method is it? It happens to work in Chrome & IE, but not Firefox. I can't seem to find any documentation (even for browers in which it works) with 30 seconds of googling...

    Didn't know that about which, learn something new every day!

    ReplyDelete
  3. .. completely changed that to just use event bubbling in the first place, that original code made no sense and only worked despite itself :)

    ReplyDelete
  4. Why not use KO Submit binding http://knockoutjs.com/documentation/submit-binding.html

    ReplyDelete
  5. Submit binding doesn't give you any specific control over how the enter key behaves in a form, it just does something when the form is submitted. The purpose of this is to give you fine grained control over how your form behaves when you press enter. The behavior of browsers when you press "Enter" is not well defined, see:

    http://stackoverflow.com/questions/925334/how-is-the-default-submit-button-on-an-html-form-determined/925387#925387

    While things might work out most of the time if you have a single type=submit button in a form, if you have zero (because you just aren't using a button element for some reason) or more than one (because you have multiple possible actions on a form) then it gets ugly very quickly without some way to explicitly define a default action on enter.

    ReplyDelete
  6. Thanks this is a nice solution. The name of the binding is probably a bit generic because of the way it relates to particular form elements but that is a minor thing that anyone can decide to change.

    Your jsfiddle is more up-to-date now than what is shown above in your post.

    There was also 1 thing I had to fix when using this with TypeScript which was able to tell me there was a problem.

    particularly this bit:
    var target = e.target;
    target.blur();

    changed to:
    var target = e.target;
    $(target).blur();

    ReplyDelete