I I have created a text counter to tell the user how many characters of they have typed and how many they have remaining available. This should show when the text area has focus and disappear then the text area loses focus.
I have created a binding handler that uses an extender to extend the observable object that is being passed into it. The problem is that it works only after entering text, navigating off of the text area, and then navigating back to the text area.
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
</head>
<body>
<div class="question" >
<label for="successes" data-textkey="successes">This is a question</label>
<textarea data-bind="textCounter: successes, hasFocus: successes.hasFocus, maxLength:200, event: { keyup:successes.updateRemaining }"></textarea>
<div class="lengthmessage edit" data-bind="visible:successes.hasFocus()">
<div >
<em>Length:</em> <span data-bind="text:successes.currentLength"></span>
<em>Remaining:</em> <span data-bind="text:successes.remainingLength"></span>
</div>
</div>
</div>
<script src="../Scripts/knockout-2.3.0.debug.js" type="text/javascript"></script>
<script type="text/javascript">
(function (ko) {
ko.extenders.textCounter = function (target, options) {
options = options || {};
options.maxLength = options.maxLength ? parseInt(options.maxLength) : 2000;
target.maxLength = ko.observable(options.maxLength);
target.currentLength = ko.observable(target().length);
target.remainingLength = ko.observable(target.maxLength() - target.currentLength());
target.hasFocus = ko.observable(false);
target.hasFocus.subscribe(function () {
target.currentLength(target().length);
target.remainingLength(target.maxLength() - target.currentLength());
});
target.updateRemaining = function (data, event) {
if (event.target == undefined && event.srcElement.value == "") {
target.currentLength(0);
}
else {
var e = $(event.target || event.srcElement);
target.currentLength(e.val().length);
if (target.currentLength() > target.maxLength()) {
e.val(e.val().substr(0, target.maxLength()));
target.currentLength(target.maxLength());
}
}
target.remainingLength(target.maxLength() - target.currentLength());
};
return target;
};
ko.bindingHandlers.textCounter = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var val = ko.utils.unwrapObservable(valueAccessor());
var observable = valueAccessor();
observable.extend({ textCounter: allBindingsAccessor() });
ko.applyBindingsToNode(element, {
value: valueAccessor()
});
},
update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var val = ko.utils.unwrapObservable(valueAccessor());
var observable = valueAccessor();
ko.bindingHandlers.css.update(element, function () { return { hasFocus: observable.hasFocus }; });
}
};
var viewModel = function () {
this.successes = ko.observable("");
//this.successes.hasFocus = ko.observable();
}
ko.applyBindings(new viewModel());
} (ko));
</script>
</body>
</html>
If I uncomment:
//this.successes.hasFocus = ko.observable();
The page will behave the way that I want it to, from the very beginning, but it defeats the whole purpose of using the extender since my view model now has one of the objects from the extender in it.
I have got to believe that there is something relatively simple that I am missing here.
Thanks for your help..
The issue is that
hasFocus
has not been defined when the binding string here is parsed:So, when the binding string is parsed
successes.hasFocus
is undefined.One option would be to apply the
hasFocus
binding inside of yourtextCounter
binding after yourhasFocus
property is available.Also, in Knockout 3.0 (released today), the parsing of the binding string happens when the value is accessed in the binding itself. So, your code actually works property in KO 3.0 already.