Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Knockout-Kendo listview not initializing after valueHasMutated

I'm working on a project which requires me to use Kendo UI along with Knockout.js for a mobile application, as a way to bind this libraries I'm using the knockout-kendo library, the app consists of a simple product list with detailed views for each product and a shopping cart, however I'm having some issues updating the quantity of the items in my cart.

I'm using knockout-kendo bindings in my app as the following:

<div data-role="view" id="cart" data-title="Cart" data-layout="main-layout">
    <div data-bind="if: items().length == 0">No items currently in cart</div>
    <ul data-role="listview" data-style="inset" data-bind="kendoListView: { data: items, template: cartTemplate }"></ul>
</div>

where the template is:

<script type="text/x-kendo-template" id="cartListTemplate">
    <div class="km-listview-link cart-item-container" data-id="#= Id #">
      <div class="product-image">
      <img src="#= ImageUrl #">
    </div>
    <div class="product-description">
      <p>#= Name #</p>
      <p>#= formattedPrice #</p>
      <p>#= quantity #</p>
    </div>
    <a data-role="button" data-icon="delete" class="km-primary" data-bind="click: removeItem">Delete</a>
  </div>
</script>

and the ViewModel is:

CartViewModel : function () {
        var self = this;
        globalKo.cartItems = self.items = ko.observableArray(JSON.parse(localStorage.getItem('cart')) || []);
        self.cartTemplate = kendo.template($('#cartListTemplate').html());
        self.removeItem = function (vm, event) {
            var element = $(event.target).parents('div.cart-item-container');
            productId = element.data('id');
            var cartItem = globalKo.cartItems().filter(function (element) {
                return element.Id == productId;
            })[0];
            if (cartItem.quantity > 1) {
                cartItem.quantity --;
            } else {
                self.items.remove(cartItem);
            }
            app.saveCart(); 
            self.items.valueHasMutated(); 
        }
    }

It all apparently works as intended, except that when the array value mutates (the valueHasMutated function gets called or the array has an element added or removed) suddenly the buttons stop being buttons and turn into simple text, they also don't work as they stop calling the function they are bound to. As you can see from the code snippets the click binding is done by markup and doesn't work as intended.

It may be worth noting that I'm calling the valueHasMutated function because otherwise the view doesn't update the number of items in cart.

To illustrate the problem here are some images:

Before pressing the button

Before pressing the button

After pressing the button

After pressing the button

I don't quite understand why this is happening, I'm guessing it has something to do with Kendo UI and not so much with knockout.js.

I've also made a fiddle demonstrating the issue, you can find it here

like image 980
Max Rasguido Avatar asked Apr 13 '16 20:04

Max Rasguido


1 Answers

As this article points there isn't a complete support for Kendo and Knockout integration (http://www.telerik.com/blogs/knockout-js-and-kendo-ui---a-potent-duo) so in some scenarios some work around has to be done.

First try to use a Knockout template:

<script type="text/html" id="cartListTemplate">
  <div class="km-listview-link cart-item-container" data-id="text: Id">
    <div class="product-image">
      <img data-bind="attr:{src: ImageUrl}">
    </div>
    <div class="product-description">
      <p data-bind="text: Name"></p>
      <p data-bind="text: formattedPrice"></p>
      <p data-bind="text: quantity"></p>
    </div>
    <button data-icon="delete" class="km-big" data-bind="kendoMobileButton: $root.removeItem">Delete</button>
  </div>
</script>

Notice that the 'a' tag was changed to a simple button tag binding it to a kendo mobile button (https://rniemeyer.github.io/knockout-kendo/web/Button.html). Then the view has to be notified to use the Knockout template instead of the Kendo one:

<div data-role="view" id="cart" data-title="Cart" data-layout="main-layout">
  <div data-bind="if: items().length == 0">No items currently in cart</div>
  <ul data-role="listview" data-style="inset" data-bind="kendoListView: { data: items, template: 'cartListTemplate', useKOTemplates: true }"></ul>
</div>

Finally, in order to make the delete function work properly, bind it to the view model and receive the current item as parameter:

self.removeItem = function(item) {
    if (item.quantity > 1) {
      var cartItem = self.items().filter(function(element) {
        return element.Id == item.Id;
      })[0];
      cartItem.quantity--;
      self.items.valueHasMutated();
    } else {
      self.items.remove(item);
    }
    app.saveCart();
  }.bind(self);

The only thing to be aware is that the value has mutated does a view refresh which sometimes mess up with Kendo widgets, so the kendo knockout library uses the normal tags specifying the role in the data-bind attribute. This is because that currently the knockout kendo library is not supporting data-role initialisation and is using data-bind initialisation instead.

Please find the working example at: https://jsfiddle.net/aveze/Lykducos/

like image 119
user2844914 Avatar answered Oct 12 '22 11:10

user2844914