Posts tagged ‘knockout’

Handling input maxlength using knockout

I set out to handle maxlength with the following goals:

  • Set maxlength on an input field. That took about 10 seconds.

This wasn’t sufficient however. It had two problems:

  1. This prevents the UI from going over the maxlength, but not code. I preferred a solution that did both.
  2. Once the maxlength is reached, there is no indication of why the next character typed is ignored. I wanted the textbox to flash red.

So I decided to do the following:

  1. Have knockout handle the max length and not have maxlength in the html.
  2. Have knockout change the css style for 3 seconds.

Knockout allows for something called extenders. I quickly saw extenders as the way to solve my problem. I followed the documentation and modified the example for my needs. I used Plunker to develop this solution and have a working model here:

http://plnkr.co/edit/9SZzcIPUSwWjBttHDQ64

My extender is as follows:

ko.extenders.maxLength = function (target, maxLength) {
    var result = ko.computed({
        read: target,
        write: function (val) {
            if (maxLength > 0) {
                if (val.length > maxLength) {
                    var limitedVal = val.substring(0, maxLength);
                    if (target() === limitedVal) {
                        target.notifySubscribers();
                    }
                    else {
                        target(limitedVal);
                    }
                    result.css("errorborder");
                    setTimeout(function () { result.css(""); }, 500);
                }
                else { target(val); }
            }
            else { target(val); }
        }
    }).extend({ notify: 'always' });
    result.css = ko.observable();
    result(target());
    return result;
};

Now, as you see, I set the css to errorborder, then I remove it 500 milliseconds (1/2 second) later. In order to make this work, I need an errorborder css style. Here is my first quick stab at that.

.errorborder {
  outline: none;
  border: 2px solid red;
}

.errorborder:focus {
  outline: none;
  border: 2px solid red;
  border-color: red;
  box-shadow: 0 0 3px red;
}

The user will see the border of the text box go red for half a second as they try to type more. Having validation text pop-up saying that maxlength has been reached can be done with this as well, but is probably not necessary. This red border should be sufficient visual feedback.

HTML Search using MVVM with knockout.js

<!DOCTYPE html>
<html>
<head>
    <script type="text/javascript" src="http://code.jquery.com/jquery-latest.pack.js"></script>
    <script type="text/javascript" src="http://knockoutjs.com/downloads/knockout-3.0.0.js"></script>
     
    <script type="text/javascript" >
        $(function(){
         
            // custom binding handler so pressing enter in a textbox is the same
            // as clicking the button.
            ko.bindingHandlers.enterKey = {
                init: function(element, valueAccessor, allBindings, vm) {
                    ko.utils.registerEventHandler(element, "keyup", function(event) {
                        if (event.keyCode === 13) {
                            ko.utils.triggerEvent(element, "change");
                            valueAccessor().call(vm, vm);
                        }                        
                        return true;
                    });
                }         
            };
         
            var fakeSearch = function(args, callback) {
                var data = args;
                callback(data);
                return args;
            };

            var ButtonViewModel = function(text, searchMethod, canSearchMethod) {
                var _self = this;
                _self._canSearchMethod = canSearchMethod;
                _self.text = ko.observable(text);
                _self.onClick = searchMethod;
                _self.canClick = ko.computed(_self._canSearchMethod);                
            };

            var SearchParametersViewModel = {
                'SearchTerm': ko.observable(''),
                'SearchOption': ko.observable(''), // Not used in example
                'StartDate': ko.observable(''),    // Not used in example
                'EndDate': ko.observable('')       // Not used in example
            };

            var SearchViewModel = function(searchMethod, searchArgs) {
                // private properties
                var _self = this;
                _self._searchMethod = searchMethod;
                _self._searchArgs = searchArgs;
                _self._canSearch = function() {
                    return _self.searchParameters.SearchTerm() != null && _self.searchParameters.SearchTerm() != '';
                };

                // public properties
                _self.searchParameters = SearchParametersViewModel;

                _self.results = ko.observable('');
                
                _self.searchCallBack = function (data) {
                    _self.results(JSON.stringify(data));
                };

                _self.searchButton = new ButtonViewModel("Search", 
                function () {
                    _self._searchMethod(_self._searchArgs, _self.searchCallBack);
                }, _self._canSearch);
                
            };
           
          ko.applyBindings(new SearchViewModel(fakeSearch, {a: "1", b: "2"}));
        });
    </script>
</head>
<body>
    <input data-bind="value: searchParameters.SearchTerm, valueUpdate: 'afterkeydown', enterKey: searchButton.onClick" />
    <button type="button" id="btnSearch" data-bind="text: searchButton.text, enable: searchButton.canClick, click: searchButton.onClick"></button>
    <p data-bind="text: results"></p>
</body>
</html>