Ian
Ian

Reputation: 34489

Knockout custom binding causing double events?

I've just created a custom knockout binding that seems to be behaving a bit weird and I'm wondering if anyone can help. I've put together a JSFiddle to illustrate:

JSFiddle

What I've noticed is that I seem to have to click the toggle switch twice each time to make it switch.

enter image description here

I've isolated this down to the following line in the custom binding (line 27 in the JSFiddle):

valueAccessor()(active);

If you comment this line out, then all of a sudden you need click just the once for the slider to change, obviously the bindings however break in this case.

Can anyone explain what the cause might be, or how I might be able to fix it?

Upvotes: 1

Views: 182

Answers (2)

hereswhatidid
hereswhatidid

Reputation: 734

The easiest way would be to detach the actual click functionality from the toggle control and have Knockout handle it. That way the observable value will always match the toggle control state. Here is the updated JavaScript that uses this method. I've added a timeout to show that the observable will update the state of the toggle control when the value changes outside of the click event attached to it.

$('.toggle').each(function () {

    var self = $(this);
    var onText = self.attr("data-on") || "ON";
    var offText = self.attr("data-off") || "OFF";

    // Go through each item individually so we can get the data-id out
 var toggle = $(this).toggles({ text: { on: onText, off: offText }, drag: false, click: false });
});
// Define the knockout custom toggle binding see
// http://knockoutjs.com/documentation/custom-bindings.html
ko.bindingHandlers.toggle = {
    init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
        // This will be called when the binding is first applied to an element
        // Set up any initial state, event handlers, etc. here

        // Configure an event to update the observable when the slider is clicked
        ko.utils.registerEventHandler(element, "click", function () {
            var observable = valueAccessor();
            observable( ! observable() );
        });
    },
    update: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
        // This will be called once when the binding is first applied to an element,
        // and again whenever the associated observable changes value.
        // Update the DOM element based on the supplied values here.
        // Grab the value for the item then update the toggle
        var value = ko.unwrap(valueAccessor());
        if ( true === value ) {
            $(element).trigger("toggleOn");
        }  else {
            $(element).trigger("toggleOff");
        }   
    }
};

var viewModel = {};
var settings = {};
settings.test = { value: "test", override: true };
viewModel.settings = ko.mapping.fromJS(settings);

ko.applyBindings(viewModel);

setTimeout( function() {
    viewModel.settings.test.override( false );
}, 5000 );

And an updated, working fiddle here: http://jsfiddle.net/6L5Tq/1/

Upvotes: 1

Maxim Nikonov
Maxim Nikonov

Reputation: 674

The everything will works fine if you comment

 update: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
    console.log("update");
    var value = ko.unwrap(valueAccessor());
    /*if (value) 
        $(element).trigger("toggleOn");
    else
        $(element).trigger("toggleOff");*/
}

JSFIDDLE

It will be one side binding, to prevent that you should check that observable value not equal to toggle-plugin state, in update, before firing trigger. But jquery-toggles has very poor functionality and I'm not able to figure state property.

Upvotes: 0

Related Questions