6

I would like to create a 'conditional click' binding in KnockoutJS. Basically this is a standard click binding like you would use it in Knockout, but a condition needs to be met in order for the attached function to be executed. In my case, the best option is to create a custom bindinghandler in which you then call the standard click binding if allowed.

ko.bindingHandlers.safeClick = {
    'init': function(element, valueAccessor, allBindingsAccessor, context) {
        $(element).click(function() {
            if(mycondition == true) {
                // call the standard click binding here -> this is the part I don't know
            }
        });
    }
}

I want to replace all of my standard click bindings with this custom binding. Therefore it is important to call the click binding in the right way, so all of the parameters provided in HTML are passed through to the function. For example:

<a href="#" data-bind="click: basevm.gotopage.bind($data, '#homepage')">Home</a>
<a href="#" data-bind="click: itemvm.activateItem">Activate</a>

These need to be replaced by

<a href="#" data-bind="safeClick: basevm.gotopage.bind($data, '#homepage')">Home</a>
<a href="#" data-bind="safeClick: itemvm.activateItem">Activate</a>

I would very much appreciate it if you could help me with the missing part in the custom binding.

Bram W.
  • 1,587
  • 4
  • 16
  • 39

2 Answers2

13

The correct way to delegate the click biding here is the following:

  • you take your original function (e.g using valueAccessor())
  • create a new valueaccessor function where you return a new function containing your condition which then calls your original function. And pass this new valueaccessor to the click binding.

So your safeClick will look like this:

ko.bindingHandlers.safeClick = {
    'init': function (element, valueAccessor, allBindingsAccessor, 
                      viewModel, bindingContext) {
        var originalFunction = valueAccessor();
        var newValueAccesssor = function() {
            return function () {
                if (mycondition == true)
                    //pass through the arguments
                    originalFunction.apply(viewModel, arguments);
            }
        }
        ko.bindingHandlers.click.init(element, newValueAccesssor, 
            allBindingsAccessor, viewModel, bindingContext);
    }
}

Demo JSFiddle.

This is will work for the first click, and you don't have to manually subscribe on the click event or trigger it with jQuery.

nemesv
  • 138,284
  • 16
  • 416
  • 359
  • This method assumes that you pass your condition as a Knockout observable in the HTML code. I do not wish to do this. Instead, I want to manually check the condition in JavaScript code each time the element is clicked. Do you have any advice on how to do that? – Bram W. Aug 09 '13 at 09:21
  • You can write anything what to you want instead of the `if (ko.utils.unwrapObservable(allBindingsAccessor().condition))`. It will gets evaluated each time the link is clicked. I just needed the condition observable for my demo jsfiddle. See this modified fiddle: http://jsfiddle.net/w7nKB/2/ – nemesv Aug 09 '13 at 09:22
  • I created this fiddle (http://jsfiddle.net/Ed8Fc/) to illustrate the problem between the normal click binding and this custom binding. I need to have access to the DOM element that triggered the click event. You see that with the normal click binding this is ok, but with the safeClick binding there is an error reported in the browser console. – Bram W. Aug 09 '13 at 09:38
  • @BramW. I see now the problem. In my origianl solution I've forget to pass through the arguements when calling the original function: `originalFunction.apply(viewModel, arguments);` see in this updated fiddle: http://jsfiddle.net/Ed8Fc/1/ – nemesv Aug 09 '13 at 09:50
  • Awesome. Something to keep in mind if you need to hook an event handler on element with `$(element).mouseup(....` or similar make sure to put the handler **outside of** `newValueAccesssor = function() {` otherwise the behavior is very unexpected. – Chris Marisic Dec 03 '14 at 21:20
  • 1
    @nemesv this solution is awesome, and I can't even count the number of times I've used it since seeing this. Tx man! – pim Mar 24 '17 at 21:01
1

You can call it as follow:

ko.bindingHandlers.click.init(element, valueAccessor, allBindingsAccessor, context);

EDIT: You can mannualy call click event first time:

ko.bindingHandlers.safeClick = {
    'init': function(element, valueAccessor, allBindingsAccessor, context) {
        $(element).click(function() {
            if(mycondition == true) {
                ko.bindingHandlers.click.init(element, valueAccessor, allBindingsAccessor, context);
            }
        });
        if(mycondition == true) {
             $(element).trigger('click');
        }
    }
}
Artem Vyshniakov
  • 16,355
  • 3
  • 43
  • 47
  • I already came across this one while searching the web. Unfortunately I found that this only works from the second time you click the anchor element. My solution needs to work from the first time the user clicks. – Bram W. Aug 09 '13 at 08:11