Sometimes you have business requirements that are not (gasp) straightforward. In the real world we don't all have a perfectly normalized Northwind databases and simplistic requirements that fit nicely inside the neat and tidy boundaries of any UI framework. 

When adding new features to Kendo UI we include functionality that people have told us loud and clear is needed. Unfortunately, we cannot possibly include ALL THE THINGS for all the people out of the box. That would be one ENORMOUS JavaScript library! For more information about how new goodness does make it into Kendo UI, see Brandon's roadmap post for a rundown on how those decisions are made. It turns out it DOES NOT involve a “jump to conclusions” mat.

Since not all of what you need to just drop some widgets in and be done with things can possibly be included in Kendo UI, it's likely that you are going to need to do some amount of customization.

Kendo UI was built knowing that this is the reality of software development. Telerik has been making developer tools for over a decade, and we know it's no help to you to assume that we can deliver you a control that will be everything to everyone. We work hard to make sure that Kendo UI is structured in such a way that it won't box you into a corner. It's endlessly customizable and extendable.

Today, we are going to pretend we are doing some real world development here and it's going to require some features from the AutoComplete widget that are not there out of the box. We'll use the Kendo UI Docs Site as our guiding light and examine the beauty and elegance of a framework that has anticipated your need to build in your own features.

Multiple Selection

The AutoComplete supports multiple selection out of the box. It's really easy to configure too. All you have to do is specify the separator value.

AutoComplete With Multiple Selection

var autoComplete = $("#auto").kendoAutoComplete({
    dataSource: {
        transport: {
            read: "Home/GetPeople"
        }
    },
    separator: ", "
}).data("kendoAutoComplete");

 

This will allow you to select an item and then select another item. Each item will be separated by a comma. Now let's apply some "real world" business rules to this because it's just way too simple as it is.

Let's suppose that you need to have multiple selection, but once a user has selected an item it should NOT be present again in the list. In other words, they should only be allowed to choose an option once. Out of the box, the AutoComplete doesn't have a switch or mechanism to do this. It would be nice if there were a allowSameValueMultipleTimes: false, but alas. You have a handful of choices here. You can head over to the forums to see if anyone has posted about this issue. With the recent move of the public forums to StackOverflow where visibility is much higher, there is an excellent chance someone else has had the same requirement and posted their own solution.

We can handle this one though! All we need is some basic knowledge about the Kendo UI DataSource and our JavaScript skills coupled with a dash of creativity. This is what makes programming fun.

It's All About The Filters

The first thing that we need to know here is how the DataSource is working. Right now, it's making an initial call to the Home/GetPeople method on the server. It's retrieving ALL of the data and then building the AutoComplete list and doing client-side filtering of the data depending on what is keyed into the AutoComplete. Assuming that we have control over the server method, we can do our sorting there by turning on serverSorting on the DataSource.

Turn On Server Side Sorting

var autoComplete = $("#auto").kendoAutoComplete({
    dataSource: {
        transport: {
            read: "Home/GetPeople"
        },
        serverSorting: true
    },
    separator: ", "
}).data("kendoAutoComplete");

 

Now the DataSource will not try and filter the AutoComplete, but will make calls to the server to have it do the sorting. The problem is that it's only ever going to send one filter to the server, and that's the one that contains the value that we keyed in. We need to send some additional filters depending on what's already in the AutoComplete. To do this, we are going to need to intercept the request that the DataSource is going to send and add some filters to the filters array. The parameterMap method on the DataSource is there just for this specific scenario. What we need to do is get any items that have already been selected in the AutoComplete and add them to the filters parameter array in the request that's about to be sent. This is going to require some creative JavaScript, but nothing complex. This is the basic algorithm for getting these values:

  • Get the text of the AutoComplete.  According to the docs, we can do this by calling its value method
  • Use the JavaScript split method on the string value to split it into an array of substrings based on the splitter that we define. In our case, it's the separator for the AutoComplete
  • Pop off the last value because it's either an empty string or the value that we are currently typing in, not one we have already selected
  • Add the remaining items from the split to the filters array on the DataSource

Before we do that, we need to know what the structure of the filters object is and how to access it. First let's have a look at the documentation for the parameterMap method. You can see from the docs that there are two parameters passed to the parameterMap function: data and type. It says the the data parameter Contains key/value pairs that represent the request.. That's what we need. We need to modify the filter parameters on that object. In order to see where to put them and what format to put them in, we need to know how the filter object is structured. Lets head back to the API docs for the DataSource and filter it by filter. If you scroll down, you will see the serverFiltering documentation and the structure of the filter object in the request.

filter_object

 

Another way to figure this out is to make a request with the AutoComplete after toggling on serverFiltering and you will be able to see it in the developer tools.

 

filters_request

You can see the parameters are being passed into the query string, but they are hard to read because they are url encoded (pink). However the Chrome Developer tools have them parsed out below (blue) for our convenience.

Now we know that the object we need off of the data parameter is filter which has an array of filters. Each filter has value, operator, field and ignoreCase properties. Now we know exactly what kind of object we need to pass in. But what operator do we need? We know it needs to be a "not equals" operator. To know for sure, let's check the docs again. The same serverFiltering topic on the API docs will tell us what our choices are.

operator

Now we have enough info to implement the algorithm to add additional filters.

Add Selected Items To Filter

var autoComplete = $("#auto").kendoAutoComplete({
    dataSource: {
        transport: {
            read: "Home/GetPeople",
            parameterMap: function(data, type) {
                // split the values into an array
                values = autoComplete.value().split(autoComplete.options.separator);

                // pop off the last one as its not a selected value
                values.pop();

                // loop through the selected values and add them to
                // the filter criteria to be sent to the server
                $.each(values, function(index, item) {
                  data.filter.filters.push({ field: "Name", ignoreCase: true, operator: "neq", value: item });
                });
            }
        },
        serverSorting: true
    },
    separator: ", "
}).data("kendoAutoComplete");

 

You can see this in action here working with the Netflix OData Source. Notice that you cannot select the same movie twice from the AutoComplete.

Thicken The Plot

But this is still too easy. Netflix has a nice OData API that lets me send parameters and filter/page without much issue. Being able to implement this server side is just too easy. Even more real world requirements might dictate that our list of data to be shown in the AutoComplete is a local set of data. An array that is populated from a model on the server during page load or perhaps just a static list of items.

Our current implementation won't work anymore. The reason why is that when the DataSource is bound to local data, it does all the operations locally and only reads one time. Meaning serverFiltering has no effect and parameterMap never gets called. That's a problem because we need the DataSource to read every time in order to apply our additional filters to it. Maybe we can listen for a change event in the AutoComplete. Let's check the docs to see what events are there. I'm going to head over to the API docs and look under the AutoComplete. If I hover over the Events link at the top I can see a snapshot of what events are available.

autocomplete_events

Well, the only event I see there that may help me is the change event. If I click on events and go down to the change event, it says:

Fires when the value has been changed.

Is that what I need? Let me attach a change event listener to a simple AutoComplete. I'll just throw up an alert every time it changes. I just want to see what constitutes an actual change.

Ok, so it fires when I actually select something from the dropdown. That's not going to help me. By then it's already too late. What I need to do is read from the local DataSource like I do from a remote DataSource. Then I could filter the results out. Let's check the read method on the DataSource transport and see if there is some way we can intercept the reading of local data. According to the docs, I can specify a function for the read settings.

custom_transport

This is what we need! This way we can have the DataSource act like it's calling a remote source, but we'll override the read method to specify a local source instead and return that as the source of the data. The official name for this is a Custom Transport.

But how do we let Kendo UI know what data to use? If you look at the image above you can see that they are passing the results of their AJAX request to options.success(). That means that we could pass a local set of data to that same method. We just need to filter it first and then pass it in. What we actually need is two DataSources. The first one will just hold the local data and the second will have a function defined as it's read. The reason why we use a second DataSource is that it will do things like filter local data for us. How do I know this? I checked the DataSource API docs and it has a filter method.

datasource_filter

It says *Gets current filter or filters the data *. That's what we want to do. Filter the data. If you scroll down a bit more you see some examples of filtering where they are passing an array of filter objects which look EXACTLY like the ones we are composing.

filtering_examples

The new logic looks like this:

  • Create a DataSource bound to a local array
  • Set the read method on the transport of the AutoComplete DataSource to a function
  • Inside the function
    • Call our parameterMap method to build the filters
    • Read the local data source
    • call the filter method on the local datasource passing in the filters
    • return the results to the options.success method

Custom Read Function

var people = [ "Korchev", "Brandon", "Todd", "Derick", "Rob", "Ryan", "Burke" ];

// create a datasource bound to the local data       
var peopleDS = new kendo.data.DataSource({ data: people });

var autoComplete = $("#auto").kendoAutoComplete({
    minLength: 3,
    separator: ", ",
    dataSource: {
        transport: {
            read: function(options) {

                // call the paramterMap passing in the key/value options
                this.parameterMap(options.data);

                // read from the local datasource
                peopleDS.read();

                // filter the local datasource
                peopleDS.filter({ logic: "and", filters: options.data.filter.filters });

                // pass the result of the local data source to the options
                // success method
                options.success(peopleDS.view());
            },
            parameterMap: function (data) {

                // split the values into an array
                values = autoComplete.value().split(autoComplete.options.separator);

                // pop off the last one as its not a selected value
                values.pop();

                // loop through the selected values and add them to
                // the filter criteria to be sent to the server
                $.each(values, function(index, item) {
                    data.filter.filters.push({ field: "", ignoreCase: true, operator: "neq", value: item });
                });

                return data;
            }
        },
        serverFiltering: true
    }
}).data("kendoAutoComplete");

And with that we have added the missing functionality to our widget without any hacking of Kendo UI code! All we did was use the docs to determine what API's, options and methods were available and then we added what we needed. A simple and elegant solution if I do say so.

Use The Docs Luke

When we first started just over a year ago, we had a lot of material that we needed to add to the documentation. Since then, we have chocked it FULL of useful examples and mapped out virtually every nook and cranny. Chances are that if you are looking for something, it's there. You just might not have found it yet. Once you get used to using the documentation in conjunction with your developer tools, you will find yourself moving through the framework with the greatest of ease. What’s more is that you will see a certain cadence in Kendo UI and eventually you will be able to blindly guess at parameters and know what they will be because consistency is the strong driving force of the API design behind Kendo UI.

Remember that if you do find a spot where you think the docs could be better, submit an issue over at our GitHub repo.  This way we can continue to refine the docs to better suit you based on your feedback.

Download Kendo UI today and tame those wild business rules with the Kendo UI Docs and a little bit of that developer creativity that got you into this line of work to begin with.


Burke Holland is the Director of Developer Relations at Telerik
About the Author

Burke Holland

Burke Holland is a web developer living in Nashville, TN and the Director of Developer Relations at Telerik. He enjoys working with and meeting developers who are building mobile apps with jQuery / HTML5 and loves to hack on social API's. Burke works for Telerik as a Developer Advocate focusing on Kendo UI.

Related Posts

Comments

Comments are disabled in preview mode.