Question

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..

Was it helpful?

Solution

The issue is that hasFocus has not been defined when the binding string here is parsed:

<textarea data-bind="textCounter: successes, hasFocus: successes.hasFocus, maxLength:200, event: { keyup:successes.updateRemaining }"></textarea>

So, when the binding string is parsed successes.hasFocus is undefined.

One option would be to apply the hasFocus binding inside of your textCounter binding after your hasFocus 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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top