KendoUI Context Menu: too cool!


Today KendoUI announced its new release KendoUI Professional Q2 2014 and during the video presentation they have shown the new Widget ContextMenu.

Despite of attending the presentation I did want to play with it and learn more about this Widget. What is the best way for quickly getting familiar with Kendo UI Widgets? Yes, you are right, their Demo web site but there is no demo about this widget, is that possible? Sure they will fix it pretty soon.

KendoUI Context Menu

What is a Context Menu? According Wiki definition:

A context menu (also called contextual, shortcut, and popup or pop-up menu) is a menu in a graphical user interface (GUI) that appears upon user interaction, such as a right-click mouse operation.

What is a ContextMenu for Kendo UI?

For Kendo UI is an HTML unordered list (pre) that might happen to have more than one level (another unordered list).

Something like:

<ul id="context-menu">
    <li>Option 1
        <ul>
            <li>Option 1.1</li>
            <li>Option 1.2</li>
            <li>Option 1.3
              <ul>
                <li>Option 1.3.1</li>
                <li>Option 1.3.2</li>
                <li>Option 1.3.3</li>
                <li>Option 1.3.4</li>
              </ul>
          	</li>
        </ul>
    </li>
    <li>Option 2
        <ul>
            <li>Option 2.1</li>
            <li>Option 2.2</li>
            <li>Option 2.3</li>
        </ul>
    </li>
</ul>

And this looks like:

KendoUI Context Menu
KendoUI Context Menu

Defining a ContextMenu

In the previous paragraphs I have shown how to define the content of the Context Menu but we should say which are the target elements on which this Context Menu is activated. To do so, you define in the initialization the target elements using a jQuery Selector.

Example:

$("#context-menu").kendoContextMenu({
    target: "#area1,#area2"
});

Here I define that the context menu is going to be displayed if you right-click on an HTML element which id is “area1” or “area2”. Any other part of your HTML will not display it.

Defining a ContextMenu as DataSource

Despite I’ve shown how to initialize the content of the Context Menu using an HTML unordered list (ul) you can also use a DataSource or an array as follow:

var ds = [
    {
        text: "Europe"
    },
    {
        text : "Americas",
        items: [
            {
                text : "North America",
                items: [
                    { text: "Canada" },
                    { text: "United States" },
                    { text: "Mexico" }
                ]
            },
            { text: "Central America" },
            {
                text : "South America",
                items: [
                    { text: "Argentina" },
                    { text: "Brazil" },
                    { text: "Chile" }
                ]
            }
        ]
    },
    {
        text: "Asia"
    }
];
$("

“).kendoContextMenu({ dataSource: ds, target: “#area1” });

KendoUI Context Menu using a DataSource
KendoUI Context Menu using a DataSource

Where each option is an entry in the array and nested option are nested elements stored in an array called “items”.

Since this context menu is actually not defined in HTML I’ve used “$(‘

‘).kendoContextMenu({…});” for creating an HTML element that will contain the menu.

Additional options for the menu items

I’ve shown how to just display a text but you can actually decorate the text using any HTML by doing:

    { text: "United States", encoded: false },

Here I’ve decorated the text as bold (b) and I have set “encoded” to “false” indicating that “<” and “>” should actually not be displayed as a character but being part of the HTML tag.

The following image shows what happen if I define the content of the context menu as:

...
    items: [
        { text: "Canada", encoded: true },
        { text: "United States", encoded: false },
        { text: "Mexico" }
    ]
...
KendoUI Context Menu using HTML codes
KendoUI Context Menu using HTML codes

You can also define a CSS class:

.ob-red {
    background : red;
}
.ob-green {
    background : green;
}
...
    items: [
        { text: "Argentina", cssClass: "ob-red" },
        { text: "Brazil" },
        { text: "Chile", cssClass: "ob-green" }
    ]
...
KendoUI Context Menu using cssClass
KendoUI Context Menu using cssClass

Or you can also add images using “spriteCssClass” or “imageUrl”:

.ob-flag-argentina {
    background-image: url(/images/flags/argentina.png);
}
...
    items: [
        { text: "Argentina", spriteCssClass: "ob-flag-argentina"  },
        { text: "Brazil", imageUrl: "/images/flags/brazil.png" },
        { text: "Chile" }
    ]

That looks like:

KendoUI Context Menu with images
KendoUI Context Menu with images

Reusing KendoUI Menu

The new Context Menu widget actually is pretty similar to Kendo UI Menu, it accepts most of the Menu options and the DataSource are reusable as well. This is how the Menu looks like if I use previous DataSource for defining a Menu.

KendoUI Menu
KendoUI Menu
Advertisements

KendoUI Release Q2’2014 Toolbar (basics)


Today, just few minutes back, Telerik has announced their new version of KendoUI: Q2’2014. Some new widgets, features and AngularJS framework integrated in the official code.

In this release there are some very powerful widgets, actually I do consider them as full applications more than just simple widgets. Focusing in Kendo UI Web we find the following new Widgets:

  • Pivot Grid, see demo here, with functionality similar to the one that you can find in Microsoft Excel and very useful for analyzing data in a numeric way.
  • Gant, see demo here, with functionality similar to Microsoft Plan Project, and good for displaying project status.
  • And, my favorite one, ToolBar, see demo here. A ToolBar similar to the one that you can find in Editor Widget and something that we missed for a long time.

Toolbar

Kendo UI new ToolBar widget includes the following features:

  • Button: regular button that might include an icon, a text or both.
  • Button Group: set of buttons rendered together (no space between buttons)
  • Split Button: a button that include a text plus a drop down menu with more buttons.
  • Separator: separate a group of buttons by a vertical bar.
  • If it does not fit in your screen it includes an overflow button with the hidden buttons.

Defining a toolbar

A toolbar is basically an array of items where each item is a Button, a Button Group, a Split Button or a Separator. When the element is a Button Group, then it will include the different buttons that compose the group.

Basic buttons are defined as:

$("#toolbar").kendoToolBar({
  items: [
    { type: "button", icon: "plus", text: "Add" },
    { type: "button", icon: "dash", text: "Remove" },
  ]
});

Each element in the array includes:

  • A “type” that indicates the type of element. Supported types are “button”, “splitButton”, “buttonGroup”, “separator”.
  • A “text” the displayed text in the button. If not defined there is no text (only icon).
  • An “icon” the displayed icon in the button. If not defined there is no icon (only text).

It might also include other properties as “id” (assign a unique identifier to the button), “click” event handler that will execute when the button is clicked.

Defining a button group is as simple as defining a button.

$("#toolbar").kendoToolBar({
  items: [
    { 
      type: "buttonGroup", 
      buttons: [
        { type: "button", icon: "clock" },
        { type: "button", icon: "note" },
        { type: "button", icon: "refresh" }
      ]
    }
  ]
});

Define the “type” as “buttonGroup” and then define an array of buttons defined as before.

A “separatore” is equally very easy to define. Simply try:

$("#toolbar").kendoToolBar({
  items: [
    { type: "button", icon: "plus", text: "Add" },
    { type: "separator" },
    { type: "button", icon: "dash", text: "Remove" },
  ]
});

Really simple and really useful Widget!

NOTE: Default supported icons are found in Styling / Icons

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: 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 Grid: custom sorting algorithms


There was a couple of times that I have had to sort columns in a KendoUI Grid but using my custom order. I mean, not using out-of-the-box ordering but doing something especial. Example: order ignoring accents, upper-lower case… but I couldn’t find how to do easily do it and end-up implementing server side filtering and sending the data sorted from the server. For what I was looking for, that was acceptable but this is not always desirable.

This week Atanas Korchev (@korchev) from Telerik, show us how to do it in Stack Overflow: easy and neat! Let me show you how.

KendoUI Grid: defining sortable columns

This is pretty easy, just define sortable: true in your Grid and it will work.

$("#grid").kendoGrid({
    dataSource: {
        data    : [
            { id: 1, name: "one" },
            { id: 2, name: "two" },
            { id: 3, name: "three" },
            { id: 4, name: "four" },
            { id: 5, name: "five" }
        ],
        pageSize: 10,
        schema  : {
            model: {
                fields: {
                    id  : { type: 'number' },
                    name: { type: 'string' }
                }
            }
        }
    },
    editable  : false,
    pageable  : false,
    sortable  : true,
    columns   : [
        { field: "id", title: "Number" },
        { field: "name", title: "Name" }
    ]
});

And you get:

Sortable Grid
Sortable Grid

and if you click on the column header it will sort the column numerically or alphabetically.

KendoUI Grid: Defining custom order

But, what about if you want to order the column by Name but you want that it should be considered as numbers…

If we look at KendoUI documentation about sortable we can define it at Grid level:

Sortable Grid option
Sortable Grid option

and at column level:

KendoUI Grid column sortable option
KendoUI Grid column sortable option

But despite the first says Can be set to a JavaScript object which represents the sorting configuration it does not provide any detail.

At least now, we have some details. Here is how to define a custom compare function.

KendoUI Grid: Custom compare function

Despite the documentation says that columns.sortable needs to be a Boolean it actually can be an object and one of the members of this object is a compare function that return -1 if first argument is less than seconde, 0 if both are the same or +1 if the second is greater that then first.

{
    field   : "name",
    width   : 200,
    title   : "Name",
    sortable: {
        compare: function (a, b) {
            return numbers[a.name] - numbers[b.name];
        }
    }
}

Now, for the sake of this example, what I do is defining an associative array for translating number names into numbers (this is only an example!).

var numbers = {
    "one"  : 1,
    "two"  : 2,
    "three": 3,
    "four" : 4,
    "five" : 5
};

Now, my grid definition is:

var grid = $("#grid").kendoGrid({
    dataSource: {
        data    : [
            { id: 1, name: "one" },
            { id: 2, name: "two" },
            { id: 3, name: "three" },
            { id: 4, name: "four" },
            { id: 5, name: "five" }
        ],
        pageSize: 10,
        schema  : {
            model: {
                fields: {
                    id  : { type: 'number' },
                    name: { type: 'string' }
                }
            }
        }
    },
    editable  : false,
    pageable  : false,
    sortable  : true,
    columns   : [
        { field: "id", title: "Number" },
        {
            field   : "name",
            title   : "Name",
            sortable: {
                compare: function (a, b) {
                    return numbers[a.name] - numbers[b.name];
                }
            }
        }
    ]
}).data("kendoGrid");

And when sorting what I get is:

KendoUI Grid custom sorted
KendoUI Grid custom sorted

KendoUI Grid: custom sorting strings

Have you ever tried sorting a column with string with mixed upper-lower case, accents…? Seems that KendoUI engineers have considered that we want alphabetical order (which is very useful) and not ASCII order.

Let’s consider that we have the following data in our DataSource

[
    { id: 1, name: "Johan" },
    { id: 2, name: "john" },
    { id: 3, name: "Jöhän" },
    { id: 4, name: "john" },
    { id: 5, name: "John" },
    { id: 6, name: "john" }
]

Where we see John in uppercase (record with id 5) and in lowercase (record with id 2, 4 and 6) or Johan with different punctuations.

If we define a Grid with this data and sort it by column name

var grid = $("#grid").kendoGrid({
    dataSource: {
        data    : [
            { id: 1, name: "Johan" },
            { id: 2, name: "john" },
            { id: 3, name: "Jöhään" },
            { id: 4, name: "john" },
            { id: 5, name: "John" },
            { id: 6, name: "john" }
        ],
        pageSize: 10,
        schema  : {
            model: {
                fields: {
                    id  : { type: 'number' },
                    name: { type: 'string' }
                }
            }
        }
    },
    editable  : false,
    pageable  : false,
    sortable  : true,
    columns   : [
        { field: "id", title: "Number" },
        { field: "name", title: "Name" }
    ]
}).data("kendoGrid");

What I get is:

Sorting name with default compare function
Sorting name with default compare function

That show that “Jöhään” is lower than “Johan” and “john” is always lower than “John” (odd?!)

Let’s define a sorting function using standard order. What I will do show side by side two grids with the same data.

My Grid definition is:

var grid1 = $("#grid1").kendoGrid({
    dataSource: {
        data    : [
            { id: 1, name: "Johan" },
            { id: 2, name: "john" },
            { id: 3, name: "Jöhään" },
            { id: 4, name: "john" },
            { id: 5, name: "John" },
            { id: 6, name: "john" }
        ],
        pageSize: 10,
        schema  : {
            model: {
                fields: {
                    id  : { type: 'number' },
                    name: { type: 'string' }
                }
            }
        }
    },
    editable  : false,
    pageable  : false,
    sortable  : true,
    columns   : [
        { field: "id", title: "Number" },
        { field: "name", title: "Name" }
    ]
}).data("kendoGrid");
var grid2 = $("#grid2").kendoGrid({
    dataSource: {
        data    : [
            { id: 1, name: "Johan" },
            { id: 2, name: "john" },
            { id: 3, name: "Jöhään" },
            { id: 4, name: "john" },
            { id: 5, name: "John" },
            { id: 6, name: "john" }
        ],
        pageSize: 10,
        schema  : {
            model: {
                fields: {
                    id  : { type: 'number' },
                    name: { type: 'string' }
                }
            }
        }
    },
    editable  : false,
    pageable  : false,
    sortable  : true,
    columns   : [
        { field: "id", title: "Number" },
        {
            field   : "name",
            title   : "Name",
            sortable: {
                compare: function (a, b) {
                    return a.name === b.name ? 0 : (a.name > b.name) ? 1 : -1;
                }
            }
        }
    ]
}).data("kendoGrid");

You can se that for the first Grid I do not define any “special” sortable option while for second, I defined a compare function that returns 0, 1 or -1.

compare: function (a, b) {
    return a.name2 === b.name2 ? 0 : (a.name2 > b.name2) ? 1 : -1;
}

Now, if I order incrementally by name both Grids, I get:

String column with custom compare function.
String column with custom compare function.

Where now uppercase come before lowercase and non accented character also before than same with accent or punctuation.