1

I have a data-bind="click: ..." event in Knockout.js and I'm trying to disable it whenever a disabled class is presence in the element.

<span class="disabled" data-bind="click: alerting">Two</span>

I'm trying this without success:

$('.disabled').click(function(e){
    e.preventDefault();
});

Reproduction online

I prefer to do it by using a classname as this way I can handle both the styling of the element and the interaction with it for multiple elements in the site.

Alvaro
  • 40,778
  • 30
  • 164
  • 336

5 Answers5

2

The issue here is that there's no guarantee as to which click handler is going to run first. In this case, the knockout click binding is executing before the jquery handler.

Given that you're working with knockout, the "proper" way to do this would be for the viewmodel to handle everything, and not rely on something external, such as jQuery, to prevent the click from happening.

In your case, the viewmodel might look like this:

var viewModel = {
    disabled: ko.observable(false),
    alerting: function(data, e) {
       if (!this.disabled())
           alert("two");
    }
};

You then just update the disabled observable to enable/disable the click handler. You could also make use of this observable to apply different styling to elements that should be disabled, eg adding the disabled style, rather than using the style to control the knockout behaviour.

James Thorpe
  • 31,411
  • 5
  • 72
  • 93
  • But If I want to disable 20 elements, that means I have to create 20 conditions and 20 observable. Plus I still need to add the class anyway to deal with the visual styles. – Alvaro Dec 17 '15 at 15:38
  • You're sort of fighting against knockout doing it this way though - if you want to add/remove classes to bound elements dynamically, that really ought to be being done through the viewmodel and `css` bindings etc. External manipulation of the DOM of data-bound elements is going to go badly at some point. – James Thorpe Dec 17 '15 at 15:39
  • I'm in fact using the `css` binding to add the `disabled` class. But having to deal with the logic it in different models and different functions makes it a pain compare with what a single line of jQuery could achieve. – Alvaro Dec 17 '15 at 15:42
  • @Alvaro if you want to control 20 elements together, you need one observable. You can use the same observable to control the style. – Roy J Dec 17 '15 at 15:46
  • I'm talking about different elements in the screen. Lets say you have multiple dropdowns and you want to disable 20 different elements in them. You would need an observable per each. In any case, I'm thinking maybe adding a function for the login I'm using on the `css` binding and maybe just use it within the same viewmodel for the `click` action by using a condition. – Alvaro Dec 17 '15 at 15:50
  • @Alvaro I agree this might be a bit cumbersome for what you're asking - without seeing a bit more context and detail it's hard to give a better answer. – James Thorpe Dec 17 '15 at 15:52
  • No, @Alvaro, you would need one observable. – Roy J Dec 17 '15 at 15:58
  • @RoyJ I guess I could use an observable with an object with all the possible combinations, but in any case, that's not the point here. – Alvaro Dec 17 '15 at 16:03
  • Here's the solution I came out with. I guess this makes more sense from the KO point of view, plus I get rid of some logic in the `data-bind` by creating the function for the `disabled` class: http://jsfiddle.net/mCxjz/90/ – Alvaro Dec 17 '15 at 16:04
  • @Alvaro Yeah that's more along the lines of how I'd be approaching it - viewmodel isn't going anywhere near the view now – James Thorpe Dec 17 '15 at 16:06
1

There's a few ways I could think of for you to deal with this requirement.

I've put a few options in this working example for you to look at: http://plnkr.co/edit/t1jG3JmsywxteRyNNKS4?p=preview.

I've also forked your JSFiddle here http://jsfiddle.net/8pa84cmu/1/

<p>Option 1 - Applying the disabled class, leave button clickable but check for class in click handler and do nothing</p>
<button class="disabled" data-bind="click: RegisterClick">Click</button>
<hr/>
<p>Option 2 - An enabled button doing what the click handler asks</p>
<button class="enabled" data-bind="click: RegisterClick">Click</button>
<hr/>
<p>Option 3 - A binding using a boolean to apply the class name if required and disabling it if required</p>
<button data-bind="click: RegisterClick, css: { disabled }, disable: disabled">Click</button>
<hr/>
<p>Option 4 - A binding just dealing with enabling / disabling</p>
<button data-bind="click: RegisterClick, enable: IsEnabled">Click</button>
var ViewModel = function() {
  this.RegisterClick = function(model, event) {
    if (event.target.className === "disabled") {
      return;
    }

    console.log("Clicked");
  }

  this.disabled = true;
  this.IsEnabled = false;
}

window.onload = function() {
  ko.applyBindings(new ViewModel());
}
James Thorpe
  • 31,411
  • 5
  • 72
  • 93
yellowbrickcode
  • 651
  • 5
  • 15
  • Note that this only works if the `className` is _only_ `disabled`, and doesn't have any other classes applied to the element. I still don't like this approach of the viewmodel querying the view though :) – James Thorpe Dec 17 '15 at 16:04
  • Yes very true, good point :) Something like `if(event.target.className.indexOf("disabled") !== -1)` would handle the possibility of other classes being present. I also prefer the option of keeping it all in the Knockout whenever possible though. – yellowbrickcode Dec 17 '15 at 16:09
0

You should use disabled attr to disable the element. You can deal with visual styling using this:

a:disabled {
    color: red;
}

You can also, check the disable class inside knockout's function (not recommend, just an example). Like this http://jsfiddle.net/mCxjz/81/

Fabio
  • 11,892
  • 1
  • 25
  • 41
0

Here is how you would control styling of an element, and have a callback take that styling into account. If you had 20 elements that you wanted to style together, you would use the same observable for each of them. This example makes three clickable spans, and disables them after 2.5 seconds. You can see the styling change.

var viewModel = {
  isDisabled: ko.observable(false),
  alerting: function(data, event) {
    if ($(event.target).hasClass('disabled')) return;
    alert(event.target.innerHTML);
  }
};

ko.applyBindings(viewModel);

setTimeout(function () {
 viewModel.isDisabled(true);
}, 2500);
.clickable {
  border-radius: 5px;
  padding: 5px;
  background-color: lightgreen;
  cursor: pointer;
}
.disabled {
  background-color: lightgray;
  cursor: not-allowed;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<span class="clickable" data-bind="click: alerting, disabled: isDisabled, css: {disabled: isDisabled}">One</span>
<span class="clickable" data-bind="click: alerting, disabled: isDisabled, css: {disabled: isDisabled}">Two</span>
<span class="clickable" data-bind="click: alerting, disabled: isDisabled, css: {disabled: isDisabled}">Three</span>
Roy J
  • 42,522
  • 10
  • 78
  • 102
  • Each of them should get disabled based on different conditions... and based on different viewmodels – Alvaro Dec 17 '15 at 16:09
  • In which case you wouldn't be able to do it all with one jQuery statement, either. – Roy J Dec 17 '15 at 16:10
  • I would, if the disabled class is added using a `data-bind` based on different conditions. – Alvaro Dec 17 '15 at 16:12
  • @Alvaro If you want to have the callback look at the classes of the element on which it was called, you can actually do that. I've updated my example to show how. A real-world example would likely use a different approach, though. – Roy J Dec 17 '15 at 16:22
-2

I updated to your code to fit your requirements. What I understand from jQuery is that executing .click only binds more events to the elem. What the following code does is it overrides the exiting click handler.

$._data($(elem)[0], 'events').click[0].handler = function() {return;}

Should you want to get back your old function, probably save it in a var and reassign it later. Hope this helps.

http://jsfiddle.net/mCxjz/84/

jkris
  • 5,851
  • 1
  • 22
  • 30
  • This is now _really_ fighting against the knockout way of doing things. – James Thorpe Dec 17 '15 at 15:50
  • Haha. I can't disagree or agree on that point, I just happened to see OP using jQuery. And TBH I've never used KnockoutJS. Just here to share what I know. Also just to point out, I happen to like my solution since its the least intrusive. – jkris Dec 17 '15 at 15:55