0

1. The goal

Find an attribute inside a list using JavaScript, KnockoutJS or jQuery.

2. The scenario

I have a store application working with KnockoutJS to dynamize its UI.

3. The problem

Each product available to add to shopping cart of my store has an add button, but it is only available if the product isn't on shopping cart already.

I need to iterate with the shopping cart to discover if each product of my available products list is already on it.

4. A few code ago...

Each product of my available products to add to list is using this fragment to toggle between the buttons:

<!-- ko if: Summary.hasItem($element) -->
    <button class="btn btn-small action remove">
        <i class="icon-minus"></i>
    </button>
<!-- /ko -->
<!-- ko ifnot: Summary.hasItem($element) -->
    <button class="btn btn-small action add">
        <i class="icon-plus"></i>
    </button>
<!-- /ko -->

As you can see, I'm using the hasItem() function to check if the product is already on the shopping cart or not — but I have to implement it, and I need your help to do this.

5. What I've already tried

As you can see below, I tried to make a loop to check product by product inside my shopping cart until...

self.hasItem = function (element) {
    var $productId = $(element).closest("li").data("productid"),
        products = self.products();

    if (products.length > 0) {
        for (var product in products) {
            if (products[product].id() == $productId) {
                return true;
            } else {
                return false;
            }
        }
    }
};

... something went wrong! Continues in the next chapter.

6. Something goes wrong

My brain can not compute the logical, but as I have this loop for each button, it seems that when one runs, the other does not run, or if there is an item in the list, the other does not add.

7. Playground

Play with this!

8. I need to ask...

My loop/logic is right?

Guilherme Oderdenge
  • 4,935
  • 6
  • 61
  • 96
  • `for (var product in products)` does not do what you would think: it does not iterate over the array!. See: http://stackoverflow.com/questions/500504/why-is-using-for-in-with-array-iteration-such-a-bad-idea – nemesv Jul 05 '13 at 14:43

3 Answers3

1

The code inside the for loop only executes for the first product.

You should have something like this:

for (var product in products) {
        if (products[product].id() == $productId) {
            return true;
        } 
}
return false;

Beside the loop, where you should probably use arrayFirst from ko.utils anyway, there seems to be a problem with the add function.

The ng-click binding provides the DOM event as the second parameter, so you probably want to change the signature of the method add to

function(data,event) { 
    var element = event.target;
}

See http://codepen.io/anon/pen/rcwil

self.add = function(model, event) {
    console.log($(event.target));

    var $productId = $(event.target).closest("li").data("productid"),
        $productName = $(event.target).closest("h1");

    self.products.push(new Product($productId, $productName));
};
Iulian Margarintescu
  • 2,656
  • 21
  • 22
1

You need to look at this a little differently, instead of calling a "hasItem", you should have something like "isSelected" within your Product object.

Then, you can loop around your Products and say (pseudo-code) if is not "isSelected" then show the add button. When this add button is clicked, then the "isSelected" property of your Product object will be set to true.

Make Knockout JS work with your self-contained objects where possible.

Such as:

<!-- ko foreach: Products -->
   <!-- ko if: IsSelected --> // Automatically inherited from parent
    <button class="btn btn-small action remove">
        <i class="icon-minus"></i>
    </button>
    <!-- /ko -->
<-- /ko -->
Chris Dixon
  • 9,147
  • 5
  • 36
  • 68
  • Hello, @ChrisDixon. Thanks about your idea. Can you give me an example of how to I loop around the Products? I'm confusing at this part. – Guilherme Oderdenge Jul 05 '13 at 18:18
  • Updated my answer to give you an example :) – Chris Dixon Jul 05 '13 at 19:04
  • Thanks, Chris. But you told me the following: "Make Knockout JS work with your self-contained objects where possible." — How can I do it? I ask this because `Products` isn't child of `SummaryViewModel`. (I feel we are almost there!) – Guilherme Oderdenge Jul 05 '13 at 19:08
  • In your ViewModel, you should have a list of Products. These should be self-contained (IsSelected in Product) and so your list of Products should be an observableArray (so you can do a foreach on them, also). This is the beauty and power of Knockout. – Chris Dixon Jul 05 '13 at 19:12
  • If the list of Products is my observableArray, then `self.products = ko.observableArray();` is what we are talking about. So, the foreach have to work like this: ``, right? Dude, thank you very much for your help! – Guilherme Oderdenge Jul 05 '13 at 19:17
  • That's exactly right, yup :) but, call it ko.observableArray([]) so it has a value when it's initialised. No problem at all - the more people using Knockout the better, it's an awesome piece of kit. – Chris Dixon Jul 05 '13 at 19:27
  • Oh, thank you again, but I found a problem =(. When there are two or more products in the *products list* (not shopping cart), all buttons are changed/affected. Any idea? – Guilherme Oderdenge Jul 05 '13 at 19:32
  • The problem specifically is in the "else" statement of KnockoutJS. I'm using the syntax that you have passed, but and if `isSelected` returns false? – Guilherme Oderdenge Jul 05 '13 at 19:42
  • Hmm, is the IsSelected an observable item within your Product object? – Chris Dixon Jul 06 '13 at 10:51
  • Yes, Chris, `isAdded` is an observable item within my Product object. See: `self.isAdded = ko.observable(isAdded);`. And about my code, [just click here](http://codepen.io/anon/pen/EtfCh). – Guilherme Oderdenge Jul 08 '13 at 12:39
1

In your case this will work :

self.hasItem = function (element) {
    var $productId = $(element).closest("li").data("productid"),
        products = self.products();

     return ko.utils.arrayFirst(products, function(p){
         return (p.id() == $productId);               
     }) != null;
}

ko.utils.arrayFirst returns the first item that match the given predicate; otherwise null.

Damien
  • 8,889
  • 3
  • 32
  • 40
  • The same problem that happens with the @lulian's solution — when I add something to my list and then I remove it without adding another, the other products can't be added. In other words, the other items are affected. – Guilherme Oderdenge Jul 05 '13 at 16:47
  • Does ko.utils.arrayFirst return a reference to the object, or a new copy of it? – bubbleking Dec 06 '16 at 17:48