digital-pollution
digital-pollution

Reputation: 1074

knockout js prevents existing html observable from updating

I'm just starting learning knockout.js and am a bit stuck on managing observables and updating them correctly. See the jsfiddle for the code.

 self.addItem = function(item, event){
    ptitle = $(event.currentTarget).prev('h3').text();

    self.items.push({
      productQty: self.productQty(),
      productClip: self.productClip(),
      productTitle: ptitle
    });
  }

http://jsfiddle.net/L61qhrc1/

What I have is a list of existing html elements. I want to create another list from that list with some input fields that can be set. It's mostly working but I cannot figure out from the examples around the net I've been looking at.

when one field updates all the fields in the list update but I only want the field I'm currently updating to be updated not the whole list.

Can any kind person point me in the right direction? Cheers.

Upvotes: 1

Views: 126

Answers (1)

Tomalak
Tomalak

Reputation: 338128

As I said in my comment, your user interface has to be a logical consequence of your data and viewmodel. Your viewmodel must never be concerned with the details of the view.

Also, your fiddle looks pretty over-engineered to me.

The following is what I have gathered from your sample, but in a less byzantine way.

  • Make separate self-contained viewmodels. Ideally make them so that they can bootstrap themselves from the data you pass to the constructor.
  • Working with templates keeps the HTML of the view clean and improves modularity and reusability.
  • For more complex data, consider the the mapping plugin to bootstrap your models.
  • Consult Unique ids in knockout.js templates for a way to create working <input> / <label> pairs.

function ListItem(data, parent) {
    var self = this;
    data = data || {};

    self.productQty = ko.observable(data.productQty);
    self.productClip = ko.observable(!!data.productClip);
}

function Product(data) {
    var self = this;
    data = data || {};

    self.title = ko.observable(data.title);
    self.id = ko.observable(data.id);
    self.items = ko.observableArray();
    self.newItem = new ListItem();

    self.addItem = function () {
        self.items.push(new ListItem(ko.toJS(self.newItem), self));
    };
    self.removeItem = function (item) {
        self.items.remove(item);
    };
}

function ProductList(data) {
    var self = this;
    data = data || {};

    self.products = ko.observableArray(ko.utils.arrayMap(data.products, function (p) {
        return new Product(p);
    }));
}

var vm = new ProductList({
    products: [
        {title: "ProductName 1", id: "ProductId 1"},
        {title: "ProductName 2", id: "ProductId 2"}
    ]
});

ko.applyBindings(vm);
ul {
    list-style-type: none;
    padding: 5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>

<div class="container">
    <div class="m-col-6">
        <ul data-bind="foreach: products">
            <li data-bind="template: 'productTemplate'"></li>
        </ul>
    </div>
</div>

<hr />
<pre data-bind="text: ko.toJSON($data, null, 2)"></pre>

<script type="text/html" id="productTemplate">
    <h1 data-bind="text: title"></h1>
    <p data-bind="text: id"></p>
    <div data-bind="with: newItem">
        <input type="checkbox" data-bind="checked: productClip" />
        <label>has pump clips</label>
        <input type="number" data-bind="value: productQty" />
        <button data-bind="click: $parent.addItem">add to list</button>
    </div>
    <ul data-bind="foreach: items">
        <li>
            <!-- ko template: 'itemsTemplate' --><!-- /ko -->
            <button data-bind="click: $parent.removeItem">Remove</button>
        </li>
    </ul>
</script>

<script type="text/html" id="itemsTemplate">
    <b data-bind="text: $parent.title"></b>
    <span data-bind="text: productClip() ? 'has' : 'does not have'"></span> pump clips
    (<span data-bind="text: productQty"></span>)
</script>

Upvotes: 2

Related Questions