October 26, 2015, 8:51 am
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:
- This prevents the UI from going over the maxlength, but not code. I preferred a solution that did both.
- 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:
- Have knockout handle the max length and not have maxlength in the html.
- 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.
January 29, 2014, 10:50 am
<!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>