Why is ListView not automatically refreshed on dataItem change, when bound to a ViewModel?

4 posts, 0 answers
  1. tomalla5120x
    tomalla5120x avatar
    2 posts
    Member since:
    Mar 2015

    Posted 02 Aug 2015 Link to this post

    The title might be longish, but I couldn't be more concise. Yet the problem is rather simple and semi-popular: let's suppose we have a `kendoListView` with some data, bound to a view model (and thus data-initialized) like this:

    HTML:

    <ul data-role="listview" data-bind="source: dataSource" data-template="temp"></ul>
     
    <script id="temp" type="text/x-kendo-template">
       <li>#=id# #=name#</li>
    </script>

    JavaScript:

    var dataSource = new kendo.data.DataSource({
        data: [
            {id: 1, name: 'John Clausky'},
            {id: 2, name: 'Filippo Divoni'},
            {id: 3, name: 'Foo Bar'},
        ]
    });
     
    var viewModel = kendo.observable({
        dataSource: dataSource
    });
     
    kendo.bind($("body"), viewModel);

    When adding a new item to the dataSource, kendoListView is refreshed correctly. Same with filtering the dataSource. However, changing an existing item in the dataSource causes no visible changes in the widget. Here's the demonstration: http://dojo.telerik.com/AgEhi/2

    When researching the problem, I've found plenty of answers on the internet, that the kendoListView widget simply has to be refreshed manually with `refresh() function. But why the widget is properly refreshed when adding a new data item then? Moreover, when kendoListView was not bound via view model, everything works just fine (demonstration: http://dojo.telerik.com/EgidE).

    I've found the problematic moment in the source code. It turns out the refresh() function is called always when manipulating the ​underlying DataSource, however it's not always refreshing the widget itself (kendo.listview.js:184, at least in the copy I have; I commented out the parts which I find irrelevant to the problem):

     

    01.refresh: function(e){
    02. 
    03.    // ...
    04.     
    05.    if (e.action === "itemchange") {
    06.        if (!that._hasBindingTarget() && !that.editable) {
    07.            data = e.items[0];
    08.            item = that.items().filter("[" + kendo.attr("uid") + "=" + data.uid + "]");
    09.     
    10.            if (item.length > 0) {
    11.                idx = item.index();
    12.     
    13.                that.angular("cleanup", function() {
    14.                    return { elements: [ item ]};
    15.                });
    16.     
    17.                item.replaceWith(template(data));
    18.                item = that.items().eq(idx);
    19.                item.attr(kendo.attr("uid"), data.uid);
    20.     
    21.                that.angular("compile", function() {
    22.                    return { elements: [ item ], data: [ { dataItem: data } ]};
    23.                });
    24.     
    25.                that.trigger("itemChange", {
    26.                    item: item,
    27.                    data: data
    28.                });
    29.            }
    30.        }
    31.     
    32.        return;
    33.    }
    34.     
    35.    // ...
    36.     
    37.    for (idx = 0, length = view.length; idx < length; idx++) {
    38.        if (idx % 2) {
    39.            html += altTemplate(view[idx]);
    40.        } else {
    41.            html += template(view[idx]);
    42.        }
    43.    }
    44.     
    45.    that.element.html(html);
    46.     
    47.    // ...
    48.},

    The problem is in the first if statement (line ​5). When a data item is modified, the condition is met (a.action is equal "itemchange"). Then that._hasBindingTarget() evaluates to true, when the widget is data-initialized (is using a view model) and thus skips straight to the return statement. This is also why the item is updated when no view models are involved (line ​7) and why the widget is rebuilt when adding new data item or filtering the data (line 37).

    As suspected, modifying the first if statement to always refresh the data, miraculously solves the problem, despite everyone saying it has to be done manually (and thus calling the refresh() function twice actually, which also solves the problem, "but" ...).

     What was the reasoning for such blockade? Can I safely modify the if statement to solve my problem? What other features can potentially fail afterwards? All information about this will be invaluable.

    Cheers, Thomas

  2. Nikolay Rusev
    Admin
    Nikolay Rusev avatar
    2285 posts

    Posted 05 Aug 2015 Link to this post

    Hello Thomas,

     

    Thank you for the feedback and sorry for the late replay.

     

    Here is what happens - all data bound widgets relays on change event of the DataSource.

     

    `change` event with reason/action `itemchange` is triggered on changing field value.

     

    When change event is triggered it will repaint itself. This is true for all widgets excepts the ListView. It has special treatment of the itemchange action. This wasn't initially the case and the widget just repaints itself same as the Grid will do.

     

    However as it wasn't just used as data bound widget, but for building dynamic layouts and due to numerous requests we've end up with this implementation for Q2 2013 SP I believe. 

    This should explain why the widget behaves this way and indeed it is valid approach to call refresh in order to show the changes made to the model.

     

    I hope this makes sense to you.

     

    Regards,
    Nikolay Rusev
    Telerik
     
    Join us on our journey to create the world's most complete HTML 5 UI Framework - download Kendo UI now!
     
  3. Kendo UI is VS 2017 Ready
  4. tomalla5120x
    tomalla5120x avatar
    2 posts
    Member since:
    Mar 2015

    Posted 06 Aug 2015 Link to this post

    Thank you for the reply, Nikolay!

    What you said is really surprising to me. In my opinion every DataBoundWidget should react to itemchange event and refresh itself by default (as the name of the widget would suggest). If one wanted to use DataSource as a one-time source of data during initialization only, he could easily just copy raw data from the DataSource and pass it during initialization of the widget with nearly minimal effort.

    However, deciding to prioritize one solution and discarding another, assuming that "from now on, ListView will be for layout only, when bound to a view model", is a rather ... surprising decision, to say at least. Firstly, because of how easy it is for the end developer using Kendo UI to write code that works for him (as I suggested in the first paragraph above) and secondly, to allow additional functionality without completely ignoring the other, there could be introduced another boolean parameter in the ListView configuration, like: "layoutOnly" or something similar, which would simply ignore the DataSource changes and fixate at being designed solely for layout purposes.

     

    To sum up, forcing one particular ListView to ignore DataSource changes is easy and straightforward. Way easier, than correcting the source code manually to bring the functionality back now. And we're talking about automatic repaint - calling refresh() function manually completely defeats the purpose.

  5. Nikolay Rusev
    Admin
    Nikolay Rusev avatar
    2285 posts

    Posted 10 Aug 2015 Link to this post

    Hello Thomas,

    Thank you for the feedback.

    However changing this behavior at this stage will be a major breaking change. 

    Regards,
    Nikolay Rusev
    Telerik
     
    Join us on our journey to create the world's most complete HTML 5 UI Framework - download Kendo UI now!
     
Back to Top
Kendo UI is VS 2017 Ready