最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Knockout JS Calling a ViewModel function inside a foreach binding - Stack Overflow

programmeradmin1浏览0评论

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: /

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:

  • There's a way to call the showName function from the View, without passing the name or $data (similar behaviour than with the Remove link)
  • If not, there's something wrong do it that way? I have an interesting discussion with a workmate that thinks that is not right do it that way because you are sending data from the View ($root.showName(name)), and then this is not a "pure" MVVM pattern. He's proposing create a custom KO binding to achieve the functionality. In my opinion is killing flies with a tank, but I'm very curious to know if there's a different way or you also think I'm not doing a pure MVVM pattern with my code.

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/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:

  • There's a way to call the showName function from the View, without passing the name or $data (similar behaviour than with the Remove link)
  • If not, there's something wrong do it that way? I have an interesting discussion with a workmate that thinks that is not right do it that way because you are sending data from the View ($root.showName(name)), and then this is not a "pure" MVVM pattern. He's proposing create a custom KO binding to achieve the functionality. In my opinion is killing flies with a tank, but I'm very curious to know if there's a different way or you also think I'm not doing a pure MVVM pattern with my code.
Share Improve this question edited Feb 25, 2014 at 22:02 AAlferez 1,5022 gold badges23 silver badges50 bronze badges asked Feb 25, 2014 at 21:33 Luis Manez - MS MVPLuis Manez - MS MVP 4024 silver badges14 bronze badges 2
  • out of interest why would you hide the name span but leave the button visible. If you want to hide both then there is a different solution that what Luis describes below – Robert Slaney Commented Feb 26, 2014 at 0:10
  • @RobertSlaney the remove button is there only for helping to understand what I was asking. THere' no remove button in my project, and the showName is for other purposes (but exactly the same simplicity). thanks! – Luis Manez - MS MVP Commented Feb 26, 2014 at 8:15
Add a ment  | 

1 Answer 1

Reset to default 6

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 munication 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 bines 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.puted(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.

发布评论

评论列表(0)

  1. 暂无评论