Question

I am trying to create a knockout template for my text count tracker so that I can repeat this for each text area on the page I am creating. I have created two fiddles that demonstrate different approaches I have been taking: 1. http://jsfiddle.net/ajdisalvo/9W84x/3/ and 2. http://jsfiddle.net/ajdisalvo/ex2uJ/

In both, I am have created a bindinghandler that is a wrapper for the template that I am attempting to bind to.

In the first approach I create a separate viewModel, attach it to the existing main viewModel and it seems to behave until it attempts to update the value 'successes' in the main viewModel. At this point, it sets the value to [Object object]. Although, this fiddle seems to be really close to working, I am concerned that I could be creating a recursive loop that might be an inherent flaw to this approach.

(function (ko) {
        ko.bindingHandlers.templateWrapper = {
            init: function (element, valueAccessor, allBindingsAccessor, viewModel, context) {
                var existingOptions = ko.utils.unwrapObservable(valueAccessor());
                viewModel[existingOptions.itemName] = new subModel(existingOptions.dataInput(), existingOptions.maxLength);
                viewModel[existingOptions.itemName].itemText.subscribe(function (value) {
                    existingOptions.dataInput(value);
                });

                newOptions = ko.bindingHandlers.templateWrapper.buildTemplateOptions(existingOptions, viewModel[existingOptions.itemName]);
                //return ko.bindingHandlers.template.init(element, function () { return newOptions; }, allBindingsAccessor, viewModel, context);
                return { controlsDescendantBindings: true };
            },
            'update': function (element, valueAccessor, allBindingsAccessor, viewModel, context) {
                newOptions = ko.bindingHandlers.templateWrapper.buildTemplateOptions(valueAccessor(), viewModel[valueAccessor().itemName]);
                ko.bindingHandlers.template.update(element, function () { return newOptions; }, allBindingsAccessor, viewModel, context);
            },
            //extend the existing options by adding a name
            buildTemplateOptions: function (options, vm) {
                return { data: vm, name: ko.bindingHandlers.templateWrapper.templateName };
            },
            templateName: "textTrackerTemplate"
        };

        var viewModel = {
            successes: ko.observable("Test input")
        };

        var subModel = function (value, maxLength) {
            var self = this;
            self.itemText = ko.observable(value);
            self.maxLength = ko.observable(maxLength);
            self.currentLength = ko.observable(self.itemText().length);
            self.remainingLength = ko.computed(function () { return self.maxLength() - self.currentLength() });
            self.hasFocus = ko.observable(false);

            self.updateRemaining = function (data, event) {
                var e = $(event.target || event.srcElement);
                self.currentLength(e.val().length);
                if (self.currentLength() > self.maxLength()) {
                    e.val(e.val().substr(0, self.maxLength()));
                    self.ItemText(e.val());
                    self.currentLength(self.itemText().length);
                }
            };

        };

        ko.applyBindings(viewModel);

    } (ko));

In my second approach, I am using an extender in my bindinghandler to add the necessary properties to populate my count tracker, but it seems that the objects created in the extender are not instantiated at the time the knockout renders the page.

    (function (ko) {
        ko.bindingHandlers.templateWrapper = {
            init: function (element, valueAccessor, allBindingsAccessor, viewModel, context) {
                var existingOptions = ko.utils.unwrapObservable(valueAccessor());
                newOptions = ko.bindingHandlers.templateWrapper.buildTemplateOptions(existingOptions, valueAccessor);
                return ko.bindingHandlers.template.init(element, function () { return newOptions; }, allBindingsAccessor, viewModel, context);          
            },
            'update': function (element, valueAccessor, allBindingsAccessor, viewModel, context) {
                var existingOptions = ko.utils.unwrapObservable(valueAccessor());
                newOptions = ko.bindingHandlers.templateWrapper.buildTemplateOptions(existingOptions, valueAccessor);
                ko.bindingHandlers.template.update(element, function () { return newOptions; }, allBindingsAccessor, viewModel, context);
            },
            buildTemplateOptions: function (options, valueAccessor) {
                valueAccessor().dataInput = valueAccessor().dataInput.extend({ textTracker: options.maxLength });
                return { data: valueAccessor, name: ko.bindingHandlers.templateWrapper.templateName };
            },
            templateName: "textTrackerTemplate"
        };

        var viewModel = {
            successes: ko.observable("Test input")
        };

        ko.extenders.textTracker = function (target, maxLength) {
            target.itemText = ko.computed({
                read: function () {
                    return target();
                },
                write: function (value) {
                    target(value);
                }
            });

            target.maxLength = ko.observable(maxLength);
            target.currentLength = ko.observable(target.itemText().length);
            target.remainingLength = ko.computed(function () { 
                                            return target.maxLength() - target.currentLength(); });
            target.hasFocus = ko.observable(false);
            target.updateRemaining = function (data, event) {
                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(e.val());
                    target.currentLength(target.itemText().length);
                }
            };
            return target;
        };

        ko.applyBindings(viewModel);

    } (ko));

Thanks in advance for any help/suggestions that you might provide..

Was it helpful?

Solution

I figured it out. I knew there were issues with my bindinghandler. I was actually making my binding more complicated that it needed to be. I just needed to extend the the value accessor only in the init method and then pass it on to the template init binding method. In the update statement, I just needed to use the existing value accessor, since it had already been extended. Here is the new binding handler:

        ko.bindingHandlers.textTracker = {
            init: function (element, valueAccessor, allBindings, viewModel, context) {
                var options = ko.utils.unwrapObservable(allBindings());
                var observable = valueAccessor();
                var newValueAccessor = observable.extend({ textTracker: options });
                return ko.bindingHandlers.template.init(element,
                        function () {
                            return { data: newValueAccessor,
                                name: ko.bindingHandlers.textTracker.templateName
                            };
                        },
                        allBindings, viewModel, context);
            },
            update: function (element, valueAccessor, allBindings, viewModel, context) {
                return ko.bindingHandlers.template.update(element,
                        function () {
                            return { data: valueAccessor,
                                name: ko.bindingHandlers.textTracker.templateName
                            };
                        },
                        allBindings, viewModel, context);
            },
            templateName: "textTrackerTemplate"
        };

Special thanks to RPNiemeyer's answer to 19732545 which helped me re-look at what I was doing which helped helped simplify the problem.

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