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

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 masked text boxes


One of the new widgets just introduced in Kendo UI is kendoMaskedTextBox. This is something that we have been waiting for long time… no need to keep waiting. We can start today enjoying it. Let’s see how.

Introduction to KendoUI MaskedTextBox

What is a maked input field? Their definition: Define and restrict the data to be entered in an html input field.

Let me show it with an example, you have to define an input field and you want to control that what the user types follows some specific format (v.g. telephone number, credit card number, date…) so you are implicitly defining some validation rules. Both the user types it with the format that you want and it gets displayed with the correct format.

kendoMaskedTextBox: Defining a mask for a phone number

In my country, the telephone number are 9 digits and you typically have them in the format 999-999-999. So I want to define an input fields that only allows me to type phones so they have to be all digits and as soon as you finish a group of three the cursor moves to the next group.

What you would have is something like:

Masked Telephone

or when empty:

Masked input empty

And the definition is as simple as:

<br />
$("#phone_number").kendoMaskedTextBox({<br />
    mask: "000-000-000",<br />
    value: "555123456"<br />
});<br />

or you can define using just HTML doing:

<div>
    <label for="phone_number">Phone number:</label>
    <input id="phone_number" data-role="maskedtextbox"
           data-mask="000-000-000" value="555123456">
</div>

Pretty simple and powerful!

kendoMaskedTextBox: Defining masks, the syntax

Kendo UI masked input predefines a series of mask rules (what to accept on each position of the mask). These are the predefined masks.

  1. 0 – Digit. Accepts any digit between 0 and 9.
  2. 9 – Digit or space. Accepts any digit between 0 and 9, plus space.
  3. # – Digit or space. Like 9 rule, but allows also (+) and (-) signs.
  4. L – Letter. Restricts input to letters a-z and A-Z. This rule is equivalent to [a-zA-Z] in regular expressions.
  5. ? – Letter or space. Restricts input to letters a-z and A-Z. This rule is equivalent to [a-zA-Z] in regular expressions.
  6. & – Character. Accepts any character. The rule is equivalent to \S in regular expressions.
  7. C – Character or space. Accepts any character. The rule is equivalent to . in regular expressions.
  8. A – Alphanumeric. Accepts letters and digits only.
  9. a – Alphanumeric or space. Accepts letters, digits and space only.
  10. . – Decimal placeholder. The decimal separator will be get from the current culture used by Kendo.
  11. , – Thousands placeholder. The display character will be get from the current culture used by Kendo.
  12. $ – Currency symbol. The display character will be get from the current culture used by Kendo.

kendoMaskedTextBox: Defining custom masks

But you do not have to use only those predefined mask rules, you might define your own.

Example: Defining a mask that allows typing only even numbers is as simple as:

$("#all-even").kendoMaskedTextBox({
    mask: "eeee",
    rules: {
        "e": /[02468]/
    }
});

Where I define that the input supports 4 characters which mask rule is an e and then I define what e means by defining a rule that says that e might be a digit in the group 02468.

But the rule might even be a function. So this can also be implemented as:

$("#all-even").kendoMaskedTextBox({
    mask: "eeee",
    rules: {
        "e": function(d) {
            return d % 2 == 0;
        }
    }
});

Simply cool!!!

kendoMaskedTextBox: assigning values

This is important to note. In widgets as KendoDateTimePicker when you initialize the widget with an invalid date, this is displayed as it is until you edit the field.

Example: I initialize the DatePicker to February 29th, 2014 but this day does not exist. What we see is:

Initializing mask text box with invalid value

And when we enter in edition mode we get:

Pick a date

But, kendoMaskedTextBox works in a different way, the characters defined in the initialization are like typed by you and those that do not met the rule restriction are simply ignored.

So if we define a value of 12345678 for our 4 even digits mask, what we will get is:

Initializing mask text box with invalid value

$("#all-even").kendoMaskedTextBox({
    mask: "eeee",
    rules: {
        "e": "/[02468]/"
    },
    value: 123456789
});

That shows that the date that we used during the initialization is just ignored.

kendoMaskedTextBox: Still some features missing

This is actually a great widget. The only limitation that I’ve found is that masks do not support repeating factor something like saying that a number repeated seven times (something like 9(3) meaning that a digit between 0 and 9 repeated 3 times) or that some character mask should be typed between n and m times (something like 9(1..3) meaning that a number between 0 and 9 repeated between 1 and 3 times).

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.

2013 in review


The WordPress.com stats helper monkeys prepared a 2013 annual report for this blog.

Here’s an excerpt:

The Louvre Museum has 8.5 million visitors per year. This blog was viewed about 70,000 times in 2013. If it were an exhibit at the Louvre Museum, it would take about 3 days for that many people to see it.

Click here to see the complete report.

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:

Follow

Get every new post delivered to your Inbox.

Join 68 other followers

%d bloggers like this: