4

If the model properties are ko.observable(), these can be accessed as below within the custom binding.

var observable = valueAccessor();

When using Knockout-ES5 plugin how to get hold of the observable within the custom binding? Check the code below and look for comment "How to get propertyName here?"

JS Fiddle when not using Knockout-ES plugin courtesy of Another Look at Custom Bindings for KnockoutJS

Updated fiddle with model changed to use Knockout-ES plugin

ko.bindingHandlers.datepicker = {
    init: function(element, valueAccessor, allBindingsAccessor, viewModel) {
        //initialize datepicker with some optional options
        var options = allBindingsAccessor().datepickerOptions || {};
        $(element).datepicker(options);

        //handle the field changing
        ko.utils.registerEventHandler(element, "change", function () {
            var observable = valueAccessor();
            if (!ko.isObservable(observable)) {                
                console.log("Not Observable");
                //How to get propertyName here?
                //ko.getObservable(viewModel, 'propertyName');
                return;
            }
            observable($(element).datepicker("getDate"));
        });

        //handle disposal (if KO removes by the template binding)
        ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
            $(element).datepicker("destroy");
        });

    },
    //update the control when the view model changes
    update: function(element, valueAccessor) {
        var value = ko.utils.unwrapObservable(valueAccessor()),
            current = $(element).datepicker("getDate");

        if (value - current !== 0) {
            $(element).datepicker("setDate", value);   
        }
    }
};

var viewModel = {
    myDate: new Date("11/01/2011"),
    setToCurrentDate: function() {
       this.myDate = new Date();   
    }
};

ko.track(viewModel);

ko.applyBindings(viewModel);
amit_g
  • 30,880
  • 8
  • 61
  • 118

5 Answers5

2

You could pass the actual observable to the binding:

data-bind="datepicker: ko.getObservable($data, 'myDate') ..."

http://jsfiddle.net/xb6vR/1/

But that's ugly. Fortunately, Knockout does provide a way (undocumented) to write to a property value from a binding:

//handle the field changing
ko.utils.registerEventHandler(element, "change", function () {
    var writable = valueAccessor();
    if (!ko.isObservable(writable)) {                
        var propWriters = allBindingsAccessor()._ko_property_writers;
        if (propWriters && propWriters.datepicker) {
            writable = propWriters.datepicker;
        } else {
            return;
        }
    }
    writable($(element).datepicker("getDate"));
});

http://jsfiddle.net/xb6vR/3/

Michael Best
  • 16,623
  • 1
  • 37
  • 70
  • Thanks. I used workaround of passing the setter function as part of the binding. [JS Fiddle](http://jsfiddle.net/xb6vR/5/). Which one would you recommend? – amit_g Jun 08 '13 at 23:00
  • You are a member of the github repo. Do you know if Knockout is planning to provide an official way to access the observable when using es5 plugin i.e. not having to resort to _ko_property_writers? – amit_g Jun 08 '13 at 23:21
  • The ES5 plugin isn't official so I'm not directly involved in its development. I did log this as an [issue in Github](https://github.com/SteveSanderson/knockout-es5/issues/2) already though. – Michael Best Jun 09 '13 at 07:55
  • The change probably would be in the core knockout to be able to provide the bindings official way to get the writers. Even if ES5 plugin added the property somePropertyObservable, it would not be accessible in the bindings. Thanks for the links. I chose to pass the property name instead of the setter function so as to not worry about using setter.call in case of viewmodels used in the examples. Posted the solution I am using. Let me know of you notice anything wrong with it. Thanks again for your help. – amit_g Jun 09 '13 at 19:54
1

I ended up using combination of passing a propertyname and falling back to _ko_property_writers. JS Fiddle.

HTML

<input data-bind="datepicker: myDate, datePropertyName: 'myDate',
       datepickerOptions: { minDate: new Date() }" />

Javascript

ko.bindingHandlers.datepicker = {
    init: function(element, valueAccessor, allBindingsAccessor, viewModel) {
        var _viewModel = viewModel;
        //initialize datepicker with some optional options
        var options = allBindingsAccessor().datepickerOptions || {};
        $(element).datepicker(options);

        //handle the field changing
        ko.utils.registerEventHandler(element, "change", function () {
            var observable = valueAccessor();
            if (!ko.isObservable(observable)) {                
                console.log("Not Observable");
                var datePropertyName = allBindingsAccessor().datePropertyName;

                if (datePropertyName) {
                    console.log("Using datePropertyName");
                    observable = ko.getObservable(_viewModel, datePropertyName);
                }
                else {
                    console.log("No datePropertyName, trying _ko_property_writers");
                    var propWriters = allBindingsAccessor()._ko_property_writers;

                    if (propWriters && propWriters.datepicker) {
                        observable = propWriters.datepicker;
                    } else {
                        console.log("No way to get observable");
                        return;
                    }
                }
            }
            observable($(element).datepicker("getDate"));
        });

        //handle disposal (if KO removes by the template binding)
        ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
            $(element).datepicker("destroy");
        });

    },
    //update the control when the view model changes
    update: function(element, valueAccessor) {
        var value = ko.utils.unwrapObservable(valueAccessor()),
            current = $(element).datepicker("getDate");

        if (value - current !== 0) {
            $(element).datepicker("setDate", value);   
        }
    }
};

var viewModel = {
    myDate: new Date("11/01/2011"),
    setToCurrentDate: function() {
       this.myDate = new Date();   
    }
};

ko.track(viewModel);

ko.applyBindings(viewModel);
amit_g
  • 30,880
  • 8
  • 61
  • 118
1

As an addition to the answer of Michael Best, if you are using knockoutjs 3.0 together with the es5 plugin, it is not possible to access the observable with allBindingsAccessor()._ko_property_writers; However I figured out it is possible to access it with the ko.getObservable function. So it should look like this:

ko.bindingHandlers.myCustomBinder = {
    init: function (element, valueAccessor, allBindingsAccessor, context) {
        var observable = ko.getObservable(context, 'observableName');
    }
}
Memet Olsen
  • 4,578
  • 5
  • 40
  • 50
  • How do you get the 'observableName'? It's not likely to be the same for all users of this binding. – CrimsonChris Feb 18 '15 at 20:25
  • @CrimsonChris - see this answer: http://stackoverflow.com/a/16994975/1053381. You don't do ko.getObservable in the binding handler, but instead do it in the HTML data-bind attribute. – Ian Yates Mar 15 '15 at 11:38
  • I ended up using the undocumented property setter. It is fairly clean to use in Knockout 3.3. We needed a solution that would work for non observable properties as well. – CrimsonChris Mar 15 '15 at 19:13
0

Well, you could use the preprocess to put the getObservable and use it as normal inside the binding handler.

preprocess: function (markup: string) {
        console.log("markup", markup);

        var lastDotIndex = markup.lastIndexOf('.');

        if (lastDotIndex > 0) {
            var obj = markup.substring(0, lastDotIndex);
            var property = markup.substring(lastDotIndex + 1);

            return 'ko.getObservable(' + obj + ', \'' + property + '\');';

        } else {
            return 'ko.getObservable($data, \'' + markup + '\')';
        }
    },
  • With this code, when using the knockout es, you will receive the observable in the init or update function, from the valueAccessor(); var observable = valueAccessor(); // use as you want! – Daniel Ferreira Monteiro Alves Mar 28 '14 at 23:41
0
function es5Preprocess(val){
    var alt = val.split('.');
    var tail = alt.pop();
    if(alt.length == 0) alt.push('$data');
    tail = 'ko.getObservable( ' + alt.join('.') + ', ' + '\'' + tail + '\'' + ' )';
    return tail + ' || ' + val;
};
window.ko.es5.customBinding = function(name, binding){
    binding.preprocess = es5Preprocess;
    window.ko.bindingHandlers[name] = binding;
};

And then

window.ko.es5.customBinding('contentEditable', {
    init: function(element, valueAccessor) {
        var val = valueAccessor();
        element.onblur = function() {
            val(element.textContent);
        };
    },
    update: function(element, valueAccessor) {
        element.textContent = valueAccessor()();
    }
});