I am writing today in an effort to explain and provide a comprehensive example of a DataSource
instance to alleviate any obstacles developers might encounter when configuring a DataSource
instance for the first time.
DataSource
instance reflects the fact that it can be used with just about any sort of data and remote endpoint/service imaginable. In other words, its agility to adapt to whatever data/service situation you throw at it constitutes the complexity of the abstraction itself. Now, given the ground that the abstraction can cover, it’s possible you might find configuration of a DataSource
with CRUD operations an involved task, especially in light of the simplicity associated with configuring other Kendo UI abstractions.In this article, I am going to offer a complete demonstration of a DataSource
instance set up to perform CRUD operations on a remote RESTful JSON API via CORS. And, when I say “complete,” I mean a complete demo you can run locally containing both the code and the RESTful API.
If you are new to Node, what follows now is a short set of instructions to help you get Node installed and set up the server and the API used by the demo.
If you have experience with Node and npm, you can go now and get the entire demo from Github and skip the proceeding explanation. Once you have the code running locally you can use the remainder of this article to gain insight into the DataSource
implementation showcased by the demo.
DataSource
demonstration I will be discussing in this article will rely on Node and two npm packages to serve a web page and a RESTful JSON API. If you don’t have Node installed, install it now.
package.json
file.$ npm init
$ npm install -g browser-sync –save-dev
and:
$ npm install -g json-server –save-dev
package.json
file that calling the init
command created, and edit the scripts property so it contains the JSON below:"scripts"
: {
"www"
:
"browser-sync –files 'index.html' start –server –port 3002 –no-ghost-mode"
,
"api"
:
"json-server –watch ./json-api/db.json"
},
Next, in the same directory that is housing the package.json
file, create a new directory called json-api
and a new file called index.html
.
Inside the new json-api
directory create, a JSON file called db.json
. Inside of this file, place the following JSON.
{
"users"
: [
{
"name"
:
"John"
,
"id"
: 1
}
]
}
index.html
, one directory up from the db.json
file, place the following HTML:<!DOCTYPE html>
<
html
>
<
head
>
<
title
>Kendo UI DataSource CRUD Example</
title
>
<
meta
charset
=
"utf-8"
>
<
link
rel
=
"stylesheet"
href
=
"http://cdn.kendostatic.com/2015.1.318/styles/kendo.common.min.css"
/>
<
link
rel
=
"stylesheet"
href
=
"https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css"
/>
<
link
rel
=
"stylesheet"
href
=
"http://cdn.kendostatic.com/2015.1.318/styles/kendo.bootstrap.min.css"
/>
</
head
>
<
body
style
=
"margin:100px"
>
<
script
src
=
"https://code.jquery.com/jquery-2.1.4.min.js"
></
script
>
<
script
src
=
"http://cdn.kendostatic.com/2015.1.318/js/kendo.all.min.js"
></
script
>
</
body
>
</
html
>
index.html
) and a RESTful JSON API. Note the index.html
page is ready to rip with Kendo UI, Bootstrap and jQuery.index.html
file and start the API, you need to be in the same directory as the package.json
file and run the following commands. Enter the first command below in the terminal you’ve been using, then open another terminal prompt/tab/instance and run the other command:$ npm run api
$ npm run www
localhost:3002
and an API available at localhost:3000
. The graphical user interface for this demonstration will orchestrate adding (in other words, saving to the database) a named user. Obviously, this example is trivial on purpose so we can focus on the mechanics of the CRUD operations.
I will be relying on Bootstrap’s and Kendo UI framework’s ability to work together to create the UI.
Open the index.html
page and right after the opening <body>
element add the following HTML.
<
div
class
=
"panel panel-default"
>
<
div
class
=
"panel-body"
>
<
label
for
=
"name"
>Enter Users Name Click Add:</
label
>
<
div
class
=
"input-group"
>
<
input
type
=
"text"
id
=
"name"
class
=
"form-control"
placeholder
=
"name"
>
<
span
class
=
"input-group-btn"
>
<
button
class
=
"btn btn-primary"
id
=
"add"
type
=
"button"
>Add</
button
>
</
span
>
</
div
>
<
table
class
=
"table"
>
<
thead
>
<
tr
>
<
th
>id</
th
>
<
th
>name</
th
>
</
tr
>
</
thead
>
<
tbody
>
</
tbody
>
</
table
>
</
div
>
</
div
>
You should now see the following GUI at localhost:3003
.
If you look closely at the HTML, you’ll see we intend to list out all of the users in the database in an HTML table. To do this, we will need to create a Kendo UI template that for creating the table rows from the data in the database.
Add the following Kendo UI HTML template to the index.html
page right after the kendo.all.min.js
script include.
<script type=
"text/x-kendo-template"
id=
"template"
>
<tr>
<td>
#:id#</td>
<td>
<input value=
"#:name#"
>
<button type=
"button"
data-id=
"#:id#"
id=
"update"
class=
"btn btn-default btn-xs"
>update</button>
<button type=
"button"
data-id=
"#:id#"
id=
"delete"
class=
"btn btn-danger btn-xs"
>
delete
</button>
</td>
</tr>
</script>
Looking at the template, you should notice I am intending to pull the id
and name
data from the RESTful API.
With the template now available, let’s go ahead and create a DataSource
instance to get data from the database.
var
dataSource =
new
kendo.data.DataSource({
transport: {
read: {
url:
'http://localhost:3000/users'
,
dataType:
'json'
,
//not needed, shown anyway, jQuery sniffs it
type:
'GET'
//defined but, this is the default
}
}
});
DataSource
and configure the data source to read data from the URL, “http://localhost:3000/users." The next two properties passed in the read configuration object are not strictly necessary but I’ve added them so that I can call out the fact that all CRUD transport operations default to “GET” requests and the dataType
will be intelligently guessed if it is not defined. What you need to remember is Kendo UI framework simply passes these values off to the jQuery.ajax()
method. Thus, the options are determined by the jQuery API. My advice, when configuring CRUD transports, would be to make sure you are referencing the jQuery Ajax API for directions.
To actually “GET” the data we will have to tell the DataSource
instance with the read()
method to read data from the API.
Go ahead and add the following javascript right after the construction of the DataSource
instance.
dataSource.read();
If you view the index.html
in the browser and look at the network interactions you will now see that an XHR request is made, returning the one record in the database.
The read()
method will populate our data source with data, but it won’t yet display the data. To display the data, we will add an event handler that will fire when the data source instance changes (or, when it reads).
To do this, we will configure the data source instance with a change
event that will take the data from the DataSource
instance, create a string of HTML containing our data using our template, then place that HTML string in the tables <tbody>
element.
Add the following change
configuration property to the object passed to the DataSource
constructor.
change:
function
() {
$(
'tbody'
).html(
kendo.render(
kendo.template(
$(
'#template'
).html()
),
this
.view()
)
);
}
DataSource
instance should now look like the following:
var
dataSource =
new
kendo.data.DataSource({
transport: {
read: {
url:
'http://localhost:3000/users'
,
dataType:
'json'
,
//not needed jQuery figures it out, shown to be verbose
type:
'GET'
//defined but, this is the default
}
},
change:
function
() {
$(
'tbody'
).html(kendo.render(kendo.template($(
'#template'
).html()),
this
.view()));
}
});
Once you have a change
event defined, you should now see the one record from the database displaying in the browser.
Of course, the interface is only reading data at this point. We’ll need to add additional transport configuration values and code so that the interface will perform create, update and destroy functionality.
To create data (i.e. POST
), we add the following create
transport configuration values to the transport object.
Note that the type
property is set to 'POST.'
With the create transport logic in place, we now have to provide an event handler to respond when the add button is clicked. Add the following jQuery code directly after the dataSource.read();
code.
$(
'#add'
).on(
'click'
,
function
() {
dataSource.add({
name: $(
'#name'
).val()
});
dataSource.sync();
});
When the add button is clicked, we take the value from the HTML input and add that value to the data source by invoking the add()
method and passing it an object with a property of name
containing the value from the input.
Now, to complete the create routine, we have to tell the data source to sync with the API. To do this we will have to call dataSource.sync()
.
At this point you might think we are done. However, one last step is required. The schema configuration will have to be set so the data source knows how to sort out the id
of the record we are adding. Note we did not pass an id
property and value with the add()
method, just a value for name
.
Think of the schema configuration option as an object that informs the data source how the data should be parsed. Here, we are literally telling the data source which field in the database contains the id
. In our case that field is called id
.
Add the following schema configuration value to the object passed to the DataSource
constructor during instantiation.
schema: {
model: {
id:
'id'
}
}
With the schema correctly identified, you should now be able to add users to the database using the UI in the browser.
The JavaScript in its entirety at this point should look like so:
//create DataSource instance
var
dataSource =
new
kendo.data.DataSource({
transport: {
read: {
url:
'http://localhost:3000/users'
,
dataType:
'json'
,
//not needed jQuery figures it out, shown to be verbose
type:
'GET'
//defined but, this is the default
},
create: {
url:
'http://localhost:3000/users'
,
type:
'POST'
}
},
schema: {
model: {
id:
'id'
}
},
change:
function
() {
$(
'tbody'
).html(
kendo.render(
kendo.template(
$(
'#template'
).html()
),
this
.view()
)
);
}
});
dataSource.read();
$(
'#add'
).on(
'click'
,
function
() {
dataSource.add({
name: $(
'#name'
).val()
});
dataSource.sync();
});
We will now follow what should be a familiar pattern at this point to complete our CRUD interactions. First, update the transport
object with the following
update
and destroy
configuration values.
update: {
url:
function
(data) {
return
'http://localhost:3000/users/'
+ data.id;
},
type:
'PUT'
},
destroy: {
url:
function
(data) {
return
'http://localhost:3000/users/'
+ data.id;
},
type:
'DELETE'
}
Note again that I have changed the type
property to the appropriate HTTP verb (“PUT” and “DELETE”). Also note that instead of passing the url
property a URL string value, I have to instead use a function to create a URL that matches how the API interface works. To update and delete items, I will need to request a URL that contains the id
of the value I would like to update or delete. To do this, we provide the url
property with a function that returns the URL with the correct id
(i.e. http://localhost:3000/users/2).
Inside of the function, the DataSource
instance will pass me the data I am intend to change. Thus, data.id
in the scope of the function will be the id
of the value needing to be deleted or updated.
Add the following event handlers for the delete and update buttons right after the add event handler we added previously.
$(
'tbody'
).on(
'click'
,
'#delete'
,
function
() {
var
$
this
= $(
this
);
dataSource.remove(dataSource.get($
this
.data(
'id'
)));
dataSource.sync();
});
$(
'tbody'
).on(
'click'
,
'#update'
,
function
() {
var
$
this
= $(
this
);
dataSource.get($
this
.data(
'id'
)).set(
'name'
, $
this
.prev(
'input'
).val());
dataSource.sync();
});
Inside of both of these event handlers, I first acquire the id
representing the value I am updating or deleting from the HTML (i.e. data-id="#:id#"
).
For deleting, I take this id
and pass it to the get()
method to get a reference to the record in the data source. Next, I pass that reference to the remove()
method. This will, as you might guess, remove data from the data source.
For updating, I take the id
and get()
a reference to that recored in the data source. Then, using the set()
method I set the value of name
to the new value found in the input which previously displaying the old value.
Of course, in both handlers, I then have to sync()
with my back-end to make changes to the database.
The final JavaScript in the index.html page should look like this:
//create DataSource instance
var
dataSource =
new
kendo.data.DataSource({
transport: {
read: {
url:
'http://localhost:3000/users'
,
dataType:
'json'
,
//not needed jQuery figures it out, shown to be verbose
type:
'GET'
//defined but, this is the default
},
create: {
url:
'http://localhost:3000/users'
,
type:
'POST'
},
update: {
url:
function
(data) {
return
'http://localhost:3000/users/'
+ data.id;
},
type:
'PUT'
},
destroy: {
url:
function
(data) {
return
'http://localhost:3000/users/'
+ data.id;
},
type:
'DELETE'
}
},
schema: {
model: {
id:
'id'
}
},
change:
function
() {
$(
'tbody'
).html(
kendo.render(
kendo.template(
$(
'#template'
).html()
),
this
.view()
)
);
}
});
dataSource.read();
$(
'#add'
).on(
'click'
,
function
() {
dataSource.add({
name: $(
'#name'
).val()
});
dataSource.sync();
});
$(
'tbody'
).on(
'click'
,
'#delete'
,
function
() {
var
$
this
= $(
this
);
dataSource.remove(dataSource.get($
this
.data(
'id'
)));
dataSource.sync();
});
$(
'tbody'
).on(
'click'
,
'#update'
,
function
() {
var
$
this
= $(
this
);
dataSource.get($
this
.data(
'id'
)).set(
'name'
, $
this
.prev(
'input'
).val());
dataSource.sync();
});
At this point, the UI should be a fully functional CRUD UI for editing users in the database.
I have laid out a complete demo of a functioning CRUD interface using the Kendo UI DataSource abstraction interfacing with a JSON API. However, my implementation is just the tip of the iceberg. The wealth of configuration options runs deep with the DataSource and the numerous ways in which it could be configured to work with your specific API is limitless. Take what you have learned here, dive into the
, and put the DataSource
API documentsDataSource
widget to work in your own code. Trust me, it’ll be well worth it, given the heavy lifting that this abstraction provides.
Before you go, I'd like to mention that I also created three variations of this demo (with the same UI and function, but different code), which you can find in the github repository. These demos continue to showcase the versatility and power of the DataSource abstraction.
1. Using local data instead of a RESTful JSON API:
https://github.com/kendo-labs/datasource-restful-api-CRUD-demo/blob/master/local-data-CRUD-demo.html
2. Using local data that is saved locally using the DataSource
offline mode:
https://github.com/kendo-labs/datasource-restful-api-CRUD-demo/blob/master/local-data-CRUD-offline-demo.html
3. Replacing the HTML table with a Grid
widget that uses the DataSource
instance:
https://github.com/kendo-labs/datasource-restful-api-CRUD-demo/blob/master/grid-widget.html
Cody Lindley is a front-end developer working as a developer advocate for Telerik focused on the Kendo UI tools. He lives in Boise, ID with his wife and three children. You can read more about Cody on his site or follow him on Twitter at @codylindley.