Let's consider a view model using knockout like that:
var data = [{ id: 1, name: "John Doe" }, { id: 2, name: ""}, { id: 3, name: "Peter Parker"}];
var viewModel = {
items: ko.observableArray(data)
};
viewModel.showName = function (name) {
console.log(this);
return name && name.length > 0;
};
viewModel.removePerson = function () {
console.log(this);
};
ko.applyBindings(viewModel);
With this View:
<ul data-bind="foreach: items">
<li><span data-bind="text: id"></span>
<span data-bind="visible: $root.showName(name)">Yes! show the name</span>
<a href="#" data-bind="click: $root.removePerson">Remove</a>
</li>
</ul>
You can see it in action here: http://jsfiddle.net/SmW35/8/
In this case, when someone clicks the "Remove" link, and KO calls the showName function, the object "this" inside the function, it's an object with the current item, for example, if I click "remove" in the item 2, "this" is {id: 2, name: ""} However, when KO is binding the "visible" and calls the showName function, the "this" object doesn't contains the current item, and you have to pass "name" to the function (or you could use the $data).
So, I have 2 questions:
In a sense your colleague has a point. I wouldn't personally create a custom binding to handle this though (on a continued subjective note, custom bindings are more intended if there's a special way of communication between a view and view model; see this post for a great explanation of when to use them).
On a side note, if we do explore the option of a custom binding, I guess you can do something like a textIfNotEmpty
binding handler that combines text
and visible
in one. On the other hand, if the showName
functionality stays as simple as it is you could also go for:
<span data-bind="visible: !!name, text: name"></span>
In any case, I'd prefer the following...
The underlying problem is IMO that the View Model is violating the Single Responsibility principle: the showName
functionality should be the responsibility of a View Model representing an item.
var Item = function(data) {
var self = this;
self.id = data.id;
self.name = ko.observable(data.name);
// or plain "self.name = data.name;" if you don't need 2way binding
self.showName = ko.computed(function() {
return self.name() && self.name().length > 0;
});
}
Now you can easily bind like this:
<ul data-bind="foreach: items">
<li><span data-bind="text: id"></span>
<span data-bind="visible: showName">Yes! show the name</span>
<a href="#" data-bind="click: $root.removePerson">Remove</a>
</li>
</ul>
Which also allows you to rewrite the removePerson
to this:
viewModel.removePerson = function (person) {
console.log(person);
};
This does require you to do a wee bit of extra work in constructing the observable array, but it's worth it as it clearly seperates all the concerns. It could be done along these lines:
var viewModel = {
items: ko.observableArray(data.map(function(item) { return new Item(item); }))
};
See this fiddle for a demo of the above.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With