KendoUI Q1’14 Release just announced


Telerik just announced their KendoUI Q1’14 Release and this time they bring a lot of cool new widget and some very interesting new options for old ones.

In this post I will list some of this cool options that I’ve already been able to test while in Beta and now I can enjoy in its full flavor.

New Widgets

There are some cool and very nice widget as:

  1. Notification: Very easy and non blocking notifications for your sites. Forget about alerts or have to handcraft popup windows. They provide you new notifications even with custom categories (not just warning and errors) and you can choose where to display them (top, bottom, left, right, fixed position, contained, floating…), for how long (they can autohide after some period of time), they can be templated,…
  2. MaskedTextBox: This is likely my favorite one because of the long time waited. Yes, you can finally have masked text box for telephones, credit cards, or you can even create your own. This actually deserves a post by itself that I’m already writing.
  3. Sortable: This is actually a widget per se but it is also used in Grid were you can move rows and sort them. Really nice effect!!!
  4. “Real-time” DataSource: Wow!!! this is also cool. Have two grids, listviews… and see what you do in one being updated in the other. Do you remember what I did with Dropbox in the post ? Now you have the same effect with plain KendoUI. This is a new level of Observable Objects
  5. Grid Frozen Columns: another new options for making Kendo UI Grids even better. Now you might have some columns stick where they are when you scroll horizontally. Perfect when you have large amount of data or even better when your screen size is not that large.

Please, go to their demo web site and see them in action. It’s worthy.

KendoUI MultiSelect with server side filtering


Today I got a question where someone wanted to use KendoUI AutoComplete but wanted it to look like a KendoUI MultiSelect. When I wonder why he/she wanted to use an AutoComplete the only reason was because the list of elements was huge. So, why not using Server Side Filtering in a MultiSelect?.

KendoUI Server Side Filtering

When you need to manage a large list of elements the solution doing some filtering in the server and just transfer a few of them.

The advantages of this are multiple:

  1. Save bandwidth sending only few elements
  2. Do not render an HTML element for each element returned
  3. Save memory due to less DOM elements
  4. Save memory due to a smaller KendoUI DataSource
  5. Increase performance do to a smaller DOM

So why not always using Server Side Filtering? Sometimes the fact of going back and forth getting new elements is not worthy for a small list of elements and this increases the complexity in the server that needs to deal with filtering.

Think about the number of elements and how they are going to be displayed in screen, if there is no good reason for having all pre-loaded, try doing it in the server.

KendoUI MultiSelect with server side filtering: MySQL table structure

This example will show a KendoUI MultiSelect that allows to choose countries from a MySQL database.

The structure of the countries table in MySQL is:

mysql> desc countries;
+-------+---------------+------+-----+---------+-------+
| Field | Type          | Null | Key | Default | Extra |
+-------+---------------+------+-----+---------+-------+
| id    | decimal(10,0) | NO   | PRI | NULL    |       |
| name  | varchar(128)  | NO   |     | NULL    |       |
+-------+---------------+------+-----+---------+-------+
2 rows in set (0,00 sec)

mysql> 

And one sample of the content is:

mysql> select * from countries order by name limit 10;
+----+---------------------+
| id | name                |
+----+---------------------+
|  0 | Afghanistan         |
|  2 | Albania             |
|  3 | Algeria             |
|  4 | American Samoa      |
|  5 | Andorra             |
|  6 | Angola              |
|  7 | Anguilla            |
|  8 | Antarctica          |
|  9 | Antigua and Barbuda |
| 10 | Argentina           |
+----+---------------------+
10 rows in set (0,00 sec)

mysql> 

KendoUI MultiSelect with server side filtering: PHP select code

The PHP code for selecting the elements is:

<?php
// Create connection to the database 
// db server:  127.0.0.1 (localhost)
// db user: so
// db password : password
// db name : so
$con = mysqli_connect("127.0.0.1", "so", "password", "so");

// Initialize the list of countries to return
$countries = [];
// Check that we actually get a filter condition
if ($_GET && $_GET['filter']['filters'][0]) {
    // Get the value
    // NOTE: We are not checking other fields in filter since autocomplete will
    //       always send it as "starts with" and ignore case.
    $filter = $_GET['filter']['filters'][0]['value'];
    $where  = "where name like '" . $filter . "%' ";
}
else {
    // Did not get filter, do not build a where clause
    $where = "";
}
// Query MySQL and limit the list of results to 5
$result = mysqli_query($con, "SELECT name FROM countries " . $where . " order by 'name' limit 5");

// Build the array of results
while ($row = mysqli_fetch_array($result)) {
    array_push($countries, array("name" => $row['name']));
}
mysqli_close($con);

// Return it as JSON
header("Content-type: application/json");
echo json_encode($countries);
?>

KendoUI MultiSelect with server side filtering: JavaScript

And finally the MultiSelect definition:

// DataSource where the read URL is the PHP code above
var ds = new kendo.data.DataSource({
    transport      : {
        read: "select.php"
    },
    // Server Side Filtering
    serverFiltering: true
});

// MultiSelect Widget initialization
$("#items").kendoMultiSelect({
    dataValueField: "name",
    dataTextField : "name",
    dataSource    : ds
});

KendoUI DataSources + Dropbox DataStore: Change Listener


My last posts were about Dropbox DataStore and Kendo UI DataSource. I have demonstrated how to read, create, delete and update records.

And I have shown how the original example provided with Dropbox was able to listen for changes in my application and automatically synchronize those changes.

I want to do the same! When Dropbox sample code changes the DataStore content, I want that my grid automatically gets updated.

Event listeners in DataStore Dropbox

We have seen in previous videos how when we create, update or delete a record from our Grid, the HTML sample code from Dropbox magically gets updated. This is because Dropbox provide a mechanism for registering a function (a listener) to changes in the DataStore. What Dropbox library does is that when you updates the copy in Dropbox server, it notifies the library and this calls you.

dataStore.recordsChanged.addListener(function(ev) {
    // Event handler code...
});

What we do in this event handler is either getting the changes and update the affected rows or fully update the table.

In my case and for simplicity, I’m going to update the complete Grid by invoking `DataSource.read()` method. Something like:

// Add listener for changes
dataStore.recordsChanged.addListener(function(ev) { taskTableDS.read() });

NOTE: If I just add these lines of code to what I have in my previous post, I will get an error because in the current implementation I was expecting that the DataStore was opened only one: so I was not closing it and I could try opening it twice if DataStore.read happen to be called twice.

So I need to slightly change readTask code to control that the DataStore is alreayd open and not try to do it again.

function readTasks(op) {
    if (client.isAuthenticated()) {
        // Client is authenticated. Display UI.
        if (dataStore === null) {
            var datastoreManager = client.getDatastoreManager();
            datastoreManager.openDefaultDatastore(function (error, datastore) {
                if (error) {
                    alert('Error opening default datastore: ' + error);
                }
                dataStore = datastore;

                // Add listener for changes
                dataStore.recordsChanged.addListener(function (ev) { taskTableDS.read() });

                taskTable = datastore.getTable('tasks');
                op.success(taskTable.query());
            });
        } else {
            op.success(taskTable.query());
        }
    }
}

And the complete code is:

// Insert your Dropbox app key here:
var DROPBOX_APP_KEY = '0sh....';

// Exposed for easy access in the browser console.
var client = new Dropbox.Client({key: DROPBOX_APP_KEY});
var taskTable = null;
var dataStore = null;

// Try to finish OAuth authorization.
client.authenticate({interactive: true}, function (error) {
    if (error) {
        alert('Authentication error: ' + error);
    }
});

function parseItem(elem) {
    return {
        id       : elem.getId(),
        taskname : elem.get("taskname"),
        created  : elem.get("created"),
        completed: elem.get("completed")
    };
}

function readTasks(op) {
    if (client.isAuthenticated()) {
        // Client is authenticated. Display UI.
        if (dataStore === null) {
            var datastoreManager = client.getDatastoreManager();
            datastoreManager.openDefaultDatastore(function (error, datastore) {
                if (error) {
                    alert('Error opening default datastore: ' + error);
                }
                dataStore = datastore;

                // Add listener for changes
                dataStore.recordsChanged.addListener(function (ev) { taskTableDS.read() });

                taskTable = datastore.getTable('tasks');
                op.success(taskTable.query());
            });
        } else {
            op.success(taskTable.query());
        }
    }
}

function parseDropboxRecords(d) {
    var res = [];
    $.each(d, function (idx, elem) {
        res.push(parseItem(elem));
    });
    return (res);
}

var taskTableDS = new kendo.data.DataSource({
    transport: {
        read   : function (op) {
            readTasks(op);
        },
        update : function (op) {
            var data = op.data;
            var id = data.id;
            // Remove id to do not have it duplicated
            delete op.data.id;
            var record = taskTable.get(id).update(data);
            op.success([record]);
        },
        destroy: function (op) {
            taskTable.get(op.data.id).deleteRecord();
            op.success();
        },
        create : function (op) {
            // Remove id to do not have it duplicated
            delete op.data.id;
            var record = taskTable.insert(op.data);
            op.success([record]);
        }
    },
    schema   : {
        model: {
            id    : "id",
            fields: {
                id       : { type: "string" },
                taskname : { type: "string" },
                created  : { type: "date", editable: false },
                completed: { type: "boolean" }
            }
        },
        parse: parseDropboxRecords
    }
});

$("#grid").kendoGrid({
    dataSource: taskTableDS,
    editable  : "popup",
    toolbar   : ["create"],
    columns   : [
        { command: ["edit", "destroy"], width: 180 },
        { field: "taskname", width: 80 },
        { field: "created", format: "{0:G}", width: 200 },
        { field: "completed", width: 70 }
    ]
});

Which looks like:

KendoUI DataSources + Dropbox DataStore: Create


After checking how to read data from Dropbox DataStore, update, and delete. It is time for creating new records.

Creating records into Dropbox DataStore from KendoUI Grid

Now, the important question to remember is that when KendoUI creates a record in a Grid, the value assigned to id is default value defined in the model, which typically is not defined and as consequence stays to null.

But, when the record is created in the server, it has to return an id not null to be used.

Grid modification for creating

What I am going to do is adding a button to the toolbar for creating a new record. Something like:

$("#grid").kendoGrid({
    dataSource: taskTableDS,
    editable  : "popup",
    toolbar   : ["create"],
    columns   : [
        { command: ["edit", "destroy"], width: 180 },
        { field: "taskname", width: 80 },
        { field: "created", format: "{0:G}", width: 200 },
        { field: "completed", width: 70 }
    ]
});

First step easy!

Add create method to transport

Now, the second step is adding the transport.create method that saves the data into Dropbox (using insert method) and informs KendoUI about the id of the newly created record.

create : function (op) {
    // Remove id to do not have it duplicated
    delete op.data.id;
    var record = taskTable.insert(op.data);
    op.success([record]);
}

It is important to note that KendoUI success will actually call schema.model.parse for converting record from Dropbox format into KendoUI format.

parse: function (d) {
        var res = [];
        $.each(d, function (idx, elem) {
            res.push(parseItem(elem));
        });
        return (res);
}

Now, the complete code of the example is:

// Insert your Dropbox app key here:
var DROPBOX_APP_KEY = '0sh............';

// Exposed for easy access in the browser console.
var client = new Dropbox.Client({key: DROPBOX_APP_KEY});
var taskTable = null;

// Try to finish OAuth authorization.
client.authenticate({interactive: true}, function (error) {
    if (error) {
        alert('Authentication error: ' + error);
    }
});

function parseItem(elem) {
    return {
        id       : elem.getId(),
        taskname : elem.get("taskname"),
        created  : elem.get("created"),
        completed: elem.get("completed")
    };
}

function readTasks(op) {
    if (client.isAuthenticated()) {
        // Client is authenticated. Display UI.
        var datastoreManager = client.getDatastoreManager();
        datastoreManager.openDefaultDatastore(function (error, datastore) {
            if (error) {
                alert('Error opening default datastore: ' + error);
            }
            taskTable = datastore.getTable('tasks');
            var records = taskTable.query();
            op.success(records);
        });
    }
}

var taskTableDS = new kendo.data.DataSource({
    transport: {
        read   : function (op) {
            readTasks(op);
        },
        update : function (op) {
            var data = op.data;
            var id = data.id;
            // Remove id to do not have it duplicated
            delete op.data.id;
            var record = taskTable.get(id).update(data);
            op.success([record]);
        },
        destroy: function (op) {
            taskTable.get(op.data.id).deleteRecord();
            op.success();
        },
        create : function (op) {
            // Remove id to do not have it duplicated
            delete op.data.id;
            var record = taskTable.insert(op.data);
            op.success([record]);
        }
    },
    schema   : {
        model: {
            id    : "id",
            fields: {
                id       : { type: "string" },
                taskname : { type: "string" },
                created  : { type: "date", editable: false },
                completed: { type: "boolean" }
            }
        },
        parse: function (d) {
            var res = [];
            $.each(d, function (idx, elem) {
                res.push(parseItem(elem));
            });
            return (res);
        }
    }
});

$("#grid").kendoGrid({
    dataSource: taskTableDS,
    editable  : "popup",
    toolbar   : ["create"],
    columns   : [
        { command: ["edit", "destroy"], width: 180 },
        { field: "taskname", width: 80 },
        { field: "created", format: "{0:G}", width: 200 },
        { field: "completed", width: 70 }
    ]
});

And the example looks like:

KendoUI DataSources + Dropbox DataStore: Delete


After seeing first how to read data from Dropbox DataStore into Kendo UI DataSource and then how to update it, now it is time for deleting records.

Deleting records from Dropbox DataStore

Start adding a delete button to the Grid.

$("#grid").kendoGrid({
    dataSource: taskTableDS,
    editable  : "inline",
    columns   : [
        { command: ["edit", "destroy"], width: 180 },
        { field: "taskname", width: 80 },
        { field: "created", format: "{0:G}", width: 200 },
        { field: "completed", width: 70 }
    ]
});

Deleting a record from DropBox is actually pretty simple, just invoke deleteRecord and it is gone! Then invoke success in Kendo UI side and also gone from Kendo UI DataSource.

So, my DataSource transport.destroy method is:

destroy: function (op) {
    taskTable.get(op.data.id).deleteRecord();
    op.success();
}

And the complete code is:

// Insert your Dropbox app key here:
var DROPBOX_APP_KEY = '0sh............';

// Exposed for easy access in the browser console.
var client = new Dropbox.Client({key: DROPBOX_APP_KEY});
var taskTable = null;

// Try to finish OAuth authorization.
client.authenticate({interactive: true}, function (error) {
    if (error) {
        alert('Authentication error: ' + error);
    }
});

function parseItem(elem) {
    return {
        id       : elem.getId(),
        taskname : elem.get("taskname"),
        created  : elem.get("created"),
        completed: elem.get("completed")
    };
}

function readTasks(op) {
    if (client.isAuthenticated()) {
        // Client is authenticated. Display UI.
        var datastoreManager = client.getDatastoreManager();
        datastoreManager.openDefaultDatastore(function (error, datastore) {
            if (error) {
                alert('Error opening default datastore: ' + error);
            }
            taskTable = datastore.getTable('tasks');
            var records = taskTable.query();
            op.success(records);
        });
    }
}

var taskTableDS = new kendo.data.DataSource({
    transport: {
        read   : function (op) {
            readTasks(op);
        },
        update : function (op) {
            var data = op.data;
            var id = data.id;
            // Remove id to do not have it duplicated
            delete op.data.id;
            var record = taskTable.get(id).update(data);
            op.success([record]);
        },
        destroy: function (op) {
            taskTable.get(op.data.id).deleteRecord();
            op.success();
        }
    },
    schema   : {
        model: {
            id    : "id",
            fields: {
                id       : { type: "string" },
                taskname : { type: "string" },
                created  : { type: "date", editable: false },
                completed: { type: "boolean" }
            }
        },
        parse: function (d) {
            var res = [];
            $.each(d, function (idx, elem) {
                res.push(parseItem(elem));
            });
            return (res);
        }
    }
});

$("#grid").kendoGrid({
    dataSource: taskTableDS,
    editable  : "popup",
    columns   : [
        { command: ["edit", "destroy"], width: 180 },
        { field: "taskname", width: 80 },
        { field: "created", format: "{0:G}", width: 200 },
        { field: "completed", width: 70 }
    ]
})

What I get is:

KendoUI DataSources + Dropbox DataStore: Update


In my previous post I showed you how to read data from Dropbox DataStore. Now, it is time for updating it.

Kendo UI DataSource and Dropbox DataStore: Updating

Reading data was pretty simple, we used openDefaultDatastore for getting access to the DataStore, then getTable for accessing the table and finally query for retrieving the selected data.

When we get to update there are two main modes that we might choose:

  1. Save each field of a record independently using set: which might be fine for attributes on a structure.
  2. Use update for updating a record in a single transaction.

For my grid, I opted for the second, despite some fields might not have changed I do prefer to transfer all and reduce number of transaction with Dropbox server. Actually, there is a second reason that is that KendoUI keeps track of dirty records but not dirty fields.

DataSource grid editable

Now, my Grid definition is:

$("#grid").kendoGrid({
    dataSource: taskTableDS,
    editable  : "popup",
    columns   : [
        { command: ["edit" ], width: 90 },
        { field: "taskname", width: 80 },
        { field: "created", format: "{0:G}", width: 200 },
        { field: "completed", width: 70 }
    ]
})

The difference is that I’ve added a button edit for opening a popup window where I can edit the record.

Now, I should work in the KendoUI DataSource transport.update method.

DataSource transport update

Updating a record in Dropbox server is basically transforming the data from KendoUI DataSource format into Dropbox DataStore keeping in mind a couple of questions.

  1. If the JSON field containing the new record data contains an attribute called id, then Dropbox will save it as attribute but this is not going to be the same than the id that Dropbox uses for the record. So for having it nicer, I’m going to delete the id from the JSON before sending it to Dropbox server.
  2. The result of updating the record is expected to be sent to KendoUI invoking success or error on the object received as argument of KendoUI transport.update
update : function (op) {
    var data = op.data;
    var id = data.id;
    // Remove id to do not have it duplicated
    delete op.data.id;
    var record = taskTable.get(id).update(data);
    op.success([record]);
},

Now, I can update my grid records and I will see them immediately updated in Dropbox example of task list.

As you can see, since Dropbox example adds listener for detecting changes done by other devices or application, what I modify in KendoUI grid is immediately visible in their example but not viceversa (stay tuned since I will get there).

KendoUI + jStorage: saving data in your browser (part 2)


In my previous post I showed how to save data in the browser and how to use two DataSources: one remote and the other in the browser; so you can work online and switch from one datasource to the other.

But there was one question pending: saving the data back to the remote datasource. I left this part for today’s blog since I did not want to synchronize the grid but only the entries that have been changed.

KendoUI + jStorage: keeping track of the changes

Kendo UI datasources use a field for each entry that stores if the data is dirty and then needs to be synchronized (sent) to the server (remote datasource). And then, this flag is reset.

We want something similar when sending the data to the server but `dirty` flag is reset after saving data so we loose that information and we don’t know what to send to the remote datasource.

I will modify the function for saving data in the browser for saving this information so it is available when needed.

Now my saving function is:

function saveData(op) {
    var modData = op.data.models;
    var dsData = localDataSource.data();
    $.each(modData, function (idx, elem) {
        localDataSource.get(elem.ProductID).sync = false;
    });
    $.jStorage.set(DbKey, dsData);
    op.success([]);
}

Where I introduce a look that iterates through the modified data and updates the datasource updating a `sync` field that is false when the data in that element is not synchronized (i.e. synchronization is needed).

Then the code for saving the data when I click on Switch DB button in the toolbar is:

$(".k-grid-switch-db").on("click", function () {
    if (!isLocal) {
        // Get Data from remote dataSource and save it into local dataSource
        var data = remoteDataSource.data();
        $.jStorage.set(DbKey, data);
        dataSource = localDataSource;
        // Refresh Grid
        grid.setDataSource(dataSource);
        grid.dataSource.read();
    } else {
        // Get Data from local dataSource and save it into remote dataSource.
        dataSource = remoteDataSource;
        data = localDataSource.data();
        dataSource.data(data);
        grid.setDataSource(dataSource);
        $.each(data, function(idx, elem) {
            elem.dirty = (elem.sync === false);
        });
        dataSource.sync();
    }
    isLocal = !isLocal;
    $.jStorage.set(IsLocalKey, isLocal);
});

Where I look through the locally saved datasource and check if synchronization is pending for each record from the `DataSource`. If it is pending, I set the dirty to true. Finally I invoke dataSource.sync() and the data gets save in the remote datasource.