Using Plain HTML to Develop Web Widgets with Kendo UI
Codementor Javascript Expert Burke Holland works for Telerik has been working on Kendo UI, a popular HTML5, jQuery-based tool for building modern web apps, since before its release. He is the original author of Angular Kendo UI, and he works on educating developers on how to build mobile apps with Kendo UI.
This article was originally posted on his blog.
You already know that Kendo UI has magnificent widgets. UI is in the name, and Telerik has a long proud history of building amazing UI components. What you may not know, is that you can actually use Kendo UI (specifically MVVM) to work with plain HTML to construct your own widgets. This is sort of like running on the bare metal, but it’s tons of fun and thanks to Kendo UI’s streamlined MVVM, it’s quite easy as well.
Looping: How Rumors Get Started
Knockout provides the ability to loop over a collection by implementing a for
iterator. The power of this really can’t be underestimated since it allows you to take collections and render them visually with HTML getting fine grained control over how each of the items will look. The only problem with this, is that for
loops can get unwieldy and don’t mix very gracefully in HTML attributes. This earned some bad rep for Knockout in it’s early days through no fault of the framework itself. Regardless of it’s tendency to clutter HTML, it’s a powerful construct that is noticeably absent from Kendo UI’s MVVM implementation. Or is it?
Kendo UI will actually gladly loop over any collection and construct the contents based on HTML. You don’t need a widget to do this. Honestly! Kendo UI will take any element bound to a valid collection, and as long as a template has been specified, it will iterate over the collection appending a new item composed from the template for each item in the collection. That’s a lot of words, so let me show you a simple example.
HTML
<!DOCTYPE html>
<html>
<head>
<script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
<script src="http://cdn.kendostatic.com/2013.3.1119/js/kendo.all.min.js"></script>
<meta charset=utf-8 />
<title>JS Bin</title>
</head>
<body>
<ul data-bind="source: things" data-template="things-template"></ul>
<script type="x-kendo-template" id="things-template">
<li data-bind="html: data"></li>
</script>
</body>
</html>
Javascript
(function() {
var o = kendo.observable({
things: [ 'Thing 1', 'Thing 2' ]
});
kendo.bind(document.body, o);
}());
There is a ul
with a data-source
set to an array on a Kendo UI Observable. The source array things
contains only strings. We can repeat each one of these items and specify how it should look by using a template. Since there are no objects in the array and only strings, specifying data
is sufficient to display the value. If you’ve been working with Knockout or Angular, your very next thought is probably “If I update the the array, does the HTML update too?” Let’s find out.
HTML
<!DOCTYPE html>
<html>
<head>
<script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
<script src="http://cdn.kendostatic.com/2013.3.1119/js/kendo.all.min.js"></script>
<meta charset=utf-8 />
<title>JS Bin</title>
</head>
<body>
<ul data-bind="source: things" data-template="things-template"></ul>
<input class data-bind="value: value" />
<button class="btn" data-bind="click: add">Add</button>
<script type="x-kendo-template" id="things-template">
<li class="list-group-item" data-bind="html: data"></li>
</script>
</body>
</html>
Javascript
(function() {
var o = kendo.observable({
things: [],
value: null,
add: function(e) {
var array = this.get('things');
var value = this.get('value');
array.push(value);
console.log(value);
}
});
kendo.bind(document.body, o);
}());
The array on the Kendo UI Observable gets wrapped as an ObservableArray automatically. This means that we can work with it’s items and Kendo UI keeps the HTML in the loop. So far this is interesting, but not really impressive. Let’s build a more complex example and work with the existing items themselves adding some more in-depth markup. Let’s tweak this example so that we can edit each individual item to find out how detailed our bindings currently are.
As it turns out, each event that gets dispatched from a Kendo UI MVVM binding includes the associated data item if there is one. Lets turn to the over-used Todo list example to see this in action. Then we’ll walk through the code. The following example contains no widgets and is almost entirely powered by the keyboard. Enter a new item and press enter
to save it. Double-click an item to edit it, and click enter
to save your edit.
HTML
<!DOCTYPE html>
<html>
<head>
<script src="http://code.jquery.com/jquery.min.js"></script>
<link href="http://getbootstrap.com/dist/css/bootstrap.css" rel="stylesheet" type="text/css" />
<script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
<script src="http://cdn.kendostatic.com/2013.3.1119/js/kendo.web.min.js"></script>
<meta charset=utf-8 />
<title>JS Bin</title>
</head>
<body>
<div class="container">
<div class="row">
<div class="col-xs-10 col-xs-offset-1">
<input type="text" class="form-control" data-bind="events: { keypress: add }" />
</div>
</div>
<div class="row">
<div class="col-xs-10 col-xs-offset-1">
<ul class="list-group" data-bind="source: things" data-template="things-template"></ul>
</div>
</div>
</div>
<script type="x-kendo-template" id="things-template">
<li data-bind="events: { dblclick: edit }" class="list-group-item">
<span data-bind="html: title, invisible: editing"></span>
<input type="text" class="form-control edit-box" data-bind="visible: editing, events: { blur: save, change: save, keypress: enter }, value: title">
</li>
</script>
</body>
</html>
Javascript
(function() {
// constant for enter key code
var ENTER = 13;
// datasource starts empty and defines schema
var things = new kendo.data.DataSource({
schema: {
model: {
fields: [
{ title: "title" }
]
}
}
});
var o = kendo.observable({
things: things,
// adds a new item to the datasource
add: function (e) {
var item = $(e.target);
var value = item.val().trim();
// if the enter key was the key pressed, and
// the value entered is not whitespace or blank
if (e.which === ENTER && value.length) {
// add the value to the datasource
things.add({ title: value });
item.val('');
}
},
edit: function (e) {
e.data.set('editing', true);
$(e.target).next().focus();
},
enter: function(e) {
if (e.which === ENTER) {
this.get('save')(e);
}
},
save: function(e) {
console.log('save');
if (e.data.get('title').trim().length) {
e.data.set('editing', false);
}
else {
this.get('things').remove(e.data);
}
}
});
kendo.bind(document.body, o);
}());
First look at the HTML that goes into constructing this example.
<div class="container">
<div class="row">
<div class="col-xs-10 col-xs-offset-1">
<input type="text" class="form-control" data-bind="events: { keypress: add }" />
</div>
</div>
<div class="row">
<div class="col-xs-10 col-xs-offset-1">
<ul class="list-group" data-bind="source: things" data-template="things-template"></ul>
</div>
</div>
</div>
<script type="x-kendo-template" id="things-template">
<li data-bind="events: { dblclick: edit }" class="list-group-item">
<span data-bind="html: title, invisible: editing"></span>
<input type="text" class="form-control"
data-bind="visible: editing, events: { blur: save, keypress: enter }, value: title">
</li>
</script>
If you look through the HTML (there isn’t much of it), you will see a few interesting bindings; notably the keypress
and dblclick
bindings. You may not have used these bindings before, but they are incredibly powerful. In this example, they allow us to interact with the application without adding any buttons. Another interesting feature is a subtle one in the template. Each item in the template is going to be paired with an item from the collection in JavaScript. The span
holds the viewable value while the input
holds the editable one. Both are bound to the same value. But notice that the events like dblclick
and blur
are bound to events as well. If the title
property exists on each data item, do the functions edit
, save
and enter
need to exist there too? Lets look at the JavaScript and find out…
(function() {
// constant for enter key code
var ENTER = 13;
// datasource starts empty and defines schema
var things = new kendo.data.DataSource({
schema: {
model: {
fields: [
{ title: "title" }
]
}
}
});
var o = kendo.observable({
things: things,
add: function (e) {
var item = $(e.target);
var value = item.val().trim();
// if the enter key was the key pressed, and
// the value entered is not whitespace or blank
if (e.which === ENTER && value.length) {
// add the value to the datasource
things.add({ title: value });
// rest the input value
item.val('');
}
},
edit: function (e) {
e.data.set('editing', true);
// set the focus so the user doesn't have to do
// it manually
$(e.target).next().focus();
},
enter: function(e) {
// if the key pressed was the enter key
if (e.which === ENTER) {
// call the save function
this.get('save')(e);
}
},
save: function(e) {
// if the item has a value
if (e.data.get('title').trim().length) {
// toggle out of edit mode
e.data.set('editing', false);
}
else {
// its blank or whitespace so just remove it
this.get('things').remove(e.data);
}
}
});
kendo.bind(document.body, o);
}());
The events actually exist on the main view model, not each data item. However, Kendo UI is smart enough to map these bindings up the tree to find the matching handler. This is really nice because while we want access to each individual item from the collection in the template, we also want access to the root view model which is what Kendo UI provides us. Further, each bubbled up event actually contains the data item from the element on which it originated via e.data
. Also notice that there are virtually no jQuery selectors in this code. We are almost entirely concerned with updating the state of the model, which in turn controls the state of the HTML. This makes the code extremely portable. You could literally pick this up (HTML and JavaScript) and drop it on another page and it would “just work”.
Inheriting To Encapsulate
What if we wanted two of these lists on a page? If you duplicated the above HTML (not including the template) and bound the whole document to the same view model, you would get two lists. However, they would always display identical data. Whatever action you took in one would apply to the other. You probably wouldn’t want this. To make a reusable component, we can encapsulate all of our login in a class. Since JavaScript doesn’t have classes, you would normally have to do some wizardry to pull this off. Kendo UI does this black magic for you via the kendo.Class.extend
method so you can just work with classes as if they actually exist in JavaScript.
HTML
<!DOCTYPE html>
<html>
<head>
<script src="http://code.jquery.com/jquery.min.js"></script>
<link href="http://getbootstrap.com/dist/css/bootstrap.css" rel="stylesheet" type="text/css" />
<script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
<script src="http://cdn.kendostatic.com/2013.3.1119/js/kendo.web.min.js"></script>
<meta charset=utf-8 />
<title>JS Bin</title>
</head>
<body>
<div class="container">
<div id="list1" class="col-xs-6">
<div class="row">
<div class="col-xs-10 col-xs-offset-1">
<input type="text" class="form-control" data-bind="events: { keypress: add }" />
</div>
</div>
<div class="row">
<div class="col-xs-10 col-xs-offset-1">
<ul class="list-group" data-bind="source: things" data-template="things-template"></ul>
</div>
</div>
</div>
<div id="list2" class="col-xs-6">
<div class="row">
<div class="col-xs-10 col-xs-offset-1">
<input type="text" class="form-control" data-bind="events: { keypress: add }" />
</div>
</div>
<div class="row">
<div class="col-xs-10 col-xs-offset-1">
<ul class="list-group" data-bind="source: things" data-template="things-template"></ul>
</div>
</div>
</div>
</div>
<script type="x-kendo-template" id="things-template">
<li data-bind="events: { dblclick: edit }" class="list-group-item">
<span data-bind="html: title, invisible: editing"></span>
<input type="text" class="form-control edit-box" data-bind="visible: editing, events: { blur: save, change: save, keypress: enter }, value: title">
</li>
</script>
</body>
</html>
Javascript
(function() {
// create a base class
var Todo = kendo.Class.extend({
init: function (element) {
// constant for enter key code
var ENTER = 13;
console.log('new ds!');
// datasource starts empty and defines schema
var things = new kendo.data.DataSource({
schema: {
model: {
fields: [
{ title: "title" }
]
}
}
});
// viewmodel
var viewModel = kendo.observable({
things: things,
add: function (e) {
var item = $(e.target);
var value = item.val().trim();
// if the enter key was the key pressed, and
// the value entered is not whitespace or blank
if (e.which === ENTER && value.length) {
// add the value to the datasource
things.add({ title: value });
// rest the input value
item.val('');
}
},
edit: function (e) {
e.data.set('editing', true);
// set the focus so the user doesn't have to do
// it manually
$(e.target).next().focus();
},
enter: function (e) {
// if the key pressed was the enter key
if (e.which === ENTER) {
// call the save function
this.get('save')(e);
}
},
save: function (e) {
// if the item has a value
if (e.data.get('title').trim().length) {
// toggle out of edit mode
e.data.set('editing', false);
}
else {
// its blank or whitespace so just remove it
things.remove(e.data);
}
}
});
kendo.bind($(element), viewModel);
}
});
var list1 = new Todo("#list1");
var list2 = new Todo("#list2");
}());
Now a new todo list can be created by including the markup required to generate the component and creating a new Todo, which will create new instances of the datasource and the necessary view models. The template can be shared between both without issue.
Immediate Updates
One last issue that I wanted to touch on was a slight misunderstanding that I see from time to time regarding Angular. Angular is a fantastic framework. We think it’s well done and that’s why we are working hard on Kendo UI directives for Angular. I do sometimes hear folks say, “yes, but Angular updates AS YOU TYPE WHICH IS AMAZING!”. It is pretty neat isn’t it? Turns out, Kendo UI does this too.
Kendo UI will by default wait until a field blurs before it syncs changes. This is because it’s more efficient to wait until an edit is finished than to check every time you hit a key. Less checks are faster than more. That’s just common sense. You can override this behavior by using the data-value-update
attribute on the element that you want to update the binding on when a key is pressed.
<input type="text" data-bind="value: value" data-value-update="keyup">
<span data-bind="html: value"></span>
<script>
(function() {
var o = kendo.observable({
value: null
});
kendo.bind(document.body, o);
}());
</script>
HTML
<!DOCTYPE html>
<html>
<head>
<script src="http://code.jquery.com/jquery.min.js"></script>
<link href="http://getbootstrap.com/dist/css/bootstrap.css" rel="stylesheet" type="text/css" />
<script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
<script src="http://cdn.kendostatic.com/2013.3.1119/js/kendo.web.min.js"></script>
<meta charset=utf-8 />
<title>JS Bin</title>
</head>
<body>
<div class="container">
<div class="col-xs-12">
<input class="form-control" data-value-update="keyup" data-bind="value: text">
</div>
<div class="col-xs-12">
<h3 data-bind="html: text"></h3>
</div>
</div>
</body>
</html>
Javascript
(function() {
var o = kendo.observable({
text: null
});
kendo.bind(document.body, o);
}());
Which Framework Should I Use
People often ask me, “Which framework should I use with Kendo UI?”. If you’ve made it this far into the article, you can probably guess what my answer is; Kendo UI. You should be able to use your favorite JavaScript framework and Kendo UI widgets will work right along with it. That might be via the Knockout Kendo UI or Angular Kendo UI projects. However, if you haven’t yet chosen a framework, and you think you need to to structure your code, I urge you to think again. Kendo UI provides a full stack for developing rich desktop and mobile SPA’s and applications with JavaScript. Not only do you get the best UI widgets available, but they come with a powerful framework that has all of the features that you might find in other MV* frameworks.
Full Todo List Example
I’ve also just updated the Kendo UI TodoMVC example to include the new Router component in the Kendo UI framework. Make sure you check that project out for a really in-depth look at what you can make the framework do without any widgets at all.