Binding Kendo ListViews to the Result of a Function

The complete and working code from this example can be found in this jsFiddle.

When it comes to writing large real-world applications, there is often a time when the data in your Kendo DataSource doesn’t exactly match what you want on your UI. There is also a limitation where the Kendo DataSource can only be filtered one way. What happens when you want to display the same list of data in two places, but sorted or filtered differently? This post is an attempt to provide a possible solution to those questions.

The real goal here is to demonstrate breaking the data in a DataSource into two separate lists, and display them in two different ListView widgets. We will do this by binding our ListViews to different functions on our View Model.

For this example, we are going to have a list of users. Some of the users are admins. Lets display two ListViews, one with admins, one with non-admins.

To get started, lets establish our test data. We do this just like any other DataSource.

var ds = new kendo.data.DataSource({
    data: [
        { name: "Dale", admin: false },
        { name: "Chris", admin: true },
        { name: "Andy", admin: true },
        { name: "Bob", admin: false }
    ]
});

Easy enough, right? OK, now lets set up our HTML.

<h3>Administrators:</h3>
<ul data-role="listview" data-bind="source: admins" data-template="template"></ul>

<h3>All Users:</h3>
<ul data-role="listview" data-bind="source: nonadmins" data-template="template"></ul>

<button data-bind="click: addUsers">Add New Admin</button>

<script id="template" type="text/x-kendo-template">
    <ul>Name: <span data-bind="text: name"></span></ul>
</script>

So this too is normal Kendo. We are just making 2 ListView widgets and MVVM binding them to admins and nonadmins.

Next lets set up our ViewModel:

var vm = kendo.observable({
    admins: function () {
    },

    nonadmins: function () {
    }
});

kendo.bind($(document.body), vm);

At this point we will just have 2 empty ListViews since the functions return no data.

Querying Kendo DataSources

Kendo 2013 Q1 (version 2013.1.*) added a convenience method for querying an ObservableArray of data. This is undocumented but does work against Kendo 2013 Q1. The Kendo team probably considers this ‘internal’ to Kendo so it might change in later versions.

There is a kendo.data.Query object that has what is basically a static function on it .process(observableArray, options) that returns a new Array of items that fit whatever options were passed in.

The fields that can go in options are the same as the ones documented on the kendo.data.DataSource.query() function here: (internally DataSource.query is calling Query.process).

So, in our ViewModel’s admins function, we want to return an array of items from our data source where admin === true, sorted by name. We can do this with the following code:

var vm = kendo.observable({
    admins: function () {
        return kendo.data.Query.process(ds.view(), {
            filter: {
                logic: "and",
                filters: [
                    { field: "admin", operator: "eq", value: true }
                ]
            },
            sort: [
                { field: "name", dir: "asc" }
            ]
        }).data;
    },

    nonadmins: function () {
        return kendo.data.Query.process(ds.view(), {
            filter: {
                logic: "and",
                filters: [
                    { field: "admin", operator: "eq", value: false}
                ]
            },
            sort: [
                { field: "name", dir: "asc" }
            ]
        }).data;
    }
});

I know this looks like a lot of code, but do notice that the filters and sort are identical to what you would normally pass the DataSource for filtering and sorting. Also note that I return the .data field off the result of Query.process(). The function itself returns an object in the form:

{
data: […],
total: 0 // the number of items returned. would match data.length.
}

DataSource Read

So if you run the code now, you will still not see any results. This is because I have been holding back one thing; When you bind a DataSource to a Widget like ListView, the ListView widget will automatically call .read() on your DataSource for you. But, in this example, we are no longer binding our ListViews directly to a DataSource. Instead, they are bound to the plain array returned by Query.process(). This means it is up to us to read our own data. Simply add this to your JavaScript:

ds.read();

If you run your code now, you should get 2 populated ListViews showing our admins and non-admins.

Handling Data Changes

So you think we are done, huh? No way! Lets add one more thing to our HTML and our ViewModel; a button to add a new user:

<button data-bind="click: addUsers">Add New Admin</button>
window.vm = kendo.observable({
    ...

    addUsers: function () {
        ds.add({ name: "New Admin", admin: true });
    }
});

Now run your code again and click that button… uh oh, no new user shows up in the Admins ListView!
Normally the ListView would bind its own .refresh() function to the DataSource, but again here we are not bound to a DataSource. When the user is added, no one is listening to the change event. However, since the ListViews are bound to the ViewModel’s admins and nonadmins functions, the ListViews are listening for the ViewModel’s change events for those fields (I hope that makes sense. I know it is a little hard to follow).

What we need to do is bridge the gap between the DataSource.change and the ViewModel’s ObservableObject.change. If by chance you have ever done any Microsoft WPF programming with MVVM you may remember always having to call “notifyPropertyChanged” all over your ViewModel, basically manually telling the View that a property has changed and it should update. This is almost the same. When the DataSource changes, we need to signal a change on the ViewModel.admins and ViewModel.nonadmins fields.

This is done by binding to the DataSource’s change event, and when triggered, tell the ViewModel to also trigger a change on its affected fields:

// trigger update of viewModel when DataSource changes
ds.bind("change", function () { vm.trigger("change", { field: "admins" }); });
ds.bind("change", function () { vm.trigger("change", { field: "nonadmins" }); });

Now clicking our “Add New Admin” button properly refreshes the ListViews!

Conclusion

Hopefully this helps you build some larger applications with Kendo without getting a big sense of frustration when things don’t “just work” or aren’t totally intuitive. Kendo does a great job of making simple things simple in code and handling most of the dirty work for you. Once in a while, you will run into MVVM refresh issues, and you just need to take the time to determine what is bound to what, figure out what change events need to be triggered, and occasionally take control yourself and trigger some change events to force a refresh.

The complete and working code from this example can be found in this jsFiddle.

Advertisements
Tagged with: , ,
Posted in KendoUI, Programming
3 comments on “Binding Kendo ListViews to the Result of a Function
  1. Elizabeth says:

    I’m trying to do something similar, but my data is coming from a hidden input in the page. I tried making the changes below to your sample, but no data is displayed. Do you have any suggestions?

    var temp = $(‘input[id=all]’).val();
    var ds = new kendo.data.DataSource(temp);

    Thanks.

    • rally25rs says:

      The parameter passed to the ‘DataSource’ constructor should be a config object containing a ‘data’ property set to an array that contains the data. So try something like: new kendo.data.DataSource({ data: [ valueFromInputElement ] }); though if you are just getting 1 value then you actually don’t need to return a DataSource at all. Just have your function return the shot of the call to .value() without putting it into a DataSource.

  2. You mentioned that the “…fields that can go in options are the same as the ones documented on the kendo.data.DataSource.query() function”, but from what I discovered it actually uses some of the parameters that can be passed to the parameterMap function [1]. The specific ones I was looking for were page and pageSize, but after smacking my head on the desk a few times, I dove into the source and noticed passing skip and take properties was what I really needed to do.

    [1]: http://docs.kendoui.com/api/framework/datasource#configuration-transport.parameterMap

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

CodingWithSpike is Jeff Valore. A professional software engineer, focused on JavaScript, Web Development, C# and the Microsoft stack. Jeff is currently a Software Engineer at Virtual Hold Technologies.


I am also a Pluralsight author. Check out my courses!

%d bloggers like this: