Table of contents
- Introduction
- Model
- View
- Controller
- See in action
- References
Introduction
Dojo Toolkit is an open source modular JavaScript library (or more specifically JavaScript toolkit) designed to ease the rapid development of cross-platform, JavaScript/Ajax-based applications and web sites and provides some really powerful user interface features (Dojo Toolkit). One of the most powerful Dojo tools is DataGrid
(DataGrid demo).
I wanted to use Dojo DataGrid
with Entity Framework and ASP.NET MVC, but I couldn't find any complete sample about it. This article walks-through the process of creating a Dojo DataGrid
to perform CRUD operations on the entities.
Creating Blog Model
This demo uses an ASP.NET Web API Project.
This project use Entity Framework Database First approach. But this isn't the point, you could also use Entity Framework Code First or Model First. Here, you could find an introduction to Database First development using Entity Framework. Database First allows you to reverse engineer a model from an existing database. You could use the article until you've got your model, your classes and your database in place, nothing more. We will make our controllers and views. Your Model and Database should be something like this:
Home/Index View
Home/Index View should contain all the codes below:
Dojo Data Grid
You could see a complete article about the code below in here.
As a point, this is so important that you use idProperty: "Id"
in both JsonRest, Memory
because Dojo DataGrid
uses id
as idProperty
but our blog id is Id
. This wastes my time. Hopefully, this tip can save someone else some time later.
dojo.query("body").addClass("claro");
adds "claro
" theme to Grid
and grid.canSort = function () { return false };
disables sorting of the grid
because we didn't write any code in our controller to support it. You could find about sorting and paging in here.
<link rel="stylesheet"
href="http://ajax.googleapis.com/ajax/libs/dojo/1.9.1/dojo/resources/dojo.css" />
<link rel="stylesheet"
href="http://ajax.googleapis.com/ajax/libs/dojo/1.9.1/dijit/themes/claro/claro.css" />
<link rel="stylesheet"
href="http://ajax.googleapis.com/ajax/libs/dojo/1.9.1/dojox/grid/resources/Grid.css" />
<link rel="stylesheet"
href="http://ajax.googleapis.com/ajax/libs/dojo/1.9.1/dojox/grid/resources/claroGrid.css" />
<!---->
<script src="http://ajax.googleapis.com/ajax/libs/dojo/1.9.1/dojo/dojo.js"
data-dojo-config="async: true, isDebug: true, parseOnLoad: true">
</script>
<script>
var myStore, dataStore, grid;
require([
"dojo/store/JsonRest",
"dojo/store/Memory",
"dojo/store/Cache",
"dojox/grid/DataGrid",
"dojo/data/ObjectStore",
"dojo/query",
"dijit/form/Button",
"dojo/domReady!"
], function (JsonRest, Memory, Cache, DataGrid, ObjectStore, query, Button, domReady) {
myStore = Cache(JsonRest({ target: "/Api/Blog/",
idProperty: "Id" }), Memory({ idProperty: "Id" }));
grid = new DataGrid({
store: dataStore = ObjectStore({ objectStore: myStore }),
structure: [
{ name: "Blog Id", field: "Id", width: "50px" },
{ name: "Title", field: "Title", width: "200px" },
{ name: "Blogger Name",
field: "BloggerName", width: "200px" }
]
}, "grid"); // make sure you have a target HTML element with this id
grid.startup();
dojo.query("body").addClass("claro");
grid.canSort = function () { return false };
});
</script>
<div style="height: 300px; width: 600px; margin: 10px;">
<div id="grid">
</div>
</div>
The idea of code below came from Dojo Grid - Switching between editable and not editable, with more functionality. You should ask why Edit mode and Add/Remove mode are separated. It is because if "Add/Remove" be in Edit mode, you could add a blog and before saving, you could edit it but the server doesn't assign an Id to blog yet and an error will occur. Also Remove is with Add, because you could remove a newly added blog before saving it.
<div id="normalMode">
<script>
require(["dijit/form/Button", "dojo/dom",
"dojo/domReady!"], function (Button, dom) {
var editButton = new Button({
label: "Edit",
onClick: function () {
editMode();
}
}, "editButton");
});
</script>
<button id="editButton">
</button>
<script>
require(["dijit/form/Button", "dojo/dom",
"dojo/domReady!"], function (Button, dom) {
var addRemoveButton = new Button({
label: "Add / Remove",
onClick: function () {
addRemoveMode();
}
}, "addRemoveButton");
});
</script>
<button id="addRemoveButton">
</button>
</div>
<div id="editMode" class="dijitHidden">
<script>
require(["dijit/form/Button", "dojo/dom",
"dojo/domReady!"], function (Button, dom) {
var saveButton = new Button({
label: "Save",
onClick: function () {
saveTable();
}
}, "saveButton");
});
</script>
<button id="saveButton">
</button>
<script>
require(["dijit/form/Button", "dojo/dom",
"dojo/domReady!"], function (Button, dom) {
var cancelEditButton = new Button({
label: "Cancel",
onClick: function () {
cancelTable();
}
}, "cancelEditButton");
});
</script>
<button id="cancelEditButton">
</button>
</div>
<div id="addRemoveMode" class="dijitHidden">
<script>
require(["dijit/form/Button", "dojo/dom",
"dojo/domReady!"], function (Button, dom) {
var saveAddRemoveButton = new Button({
label: "Save",
onClick: function () {
saveTable();
}
}, "saveAddRemoveButton");
});
</script>
<button id="saveAddRemoveButton">
</button>
<script>
require(["dijit/form/Button", "dojo/dom",
"dojo/domReady!"], function (Button, dom) {
var addButton = new Button({
label: "Add New Blog",
onClick: function () {
addBlog();
}
}, "addButton");
});
</script>
<button id="addButton">
</button>
<script>
require(["dijit/form/Button", "dojo/dom",
"dojo/domReady!"], function (Button, dom) {
var removeButton = new Button({
label: " Remove Selected Rows",
onClick: function () {
removeBlog();
}
}, "removeButton");
});
</script>
<button id="removeButton">
</button>
<script>
require(["dijit/form/Button", "dojo/dom",
"dojo/domReady!"], function (Button, dom) {
var cancelAddRemoveButton = new Button({
label: "Cancel",
onClick: function () {
cancelTable();
}
}, "cancelAddRemoveButton");
});
</script>
<button id="cancelAddRemoveButton">
</button>
</div>
<div id="message">
</div>
<script>
function addBlog() {
var newBlog = { Title: "New Title",
BloggerName: "New Blogger Name" };
dataStore.newItem(newBlog);
}
function removeBlog() {
var items = grid.selection.getSelected();
if (items.length) {
dojo.forEach(items, function (selectedItem) {
if (selectedItem !== null) {
dataStore.deleteItem(selectedItem);
}
});
}
}
function saveTable() {
if (grid.edit.isEditing()) {
grid.edit.apply();
}
if (dataStore.isDirty()) {
dataStore.save();
}
onSaveComplete();
}
function cancelTable() {
if (grid.edit.isEditing()) {
grid.edit.apply();
}
dataStore.revert();
normalMode();
}
function onSaveComplete() {
dojo.byId("message").innerHTML = ("Save done.");
normalMode();
}
function normalMode() {
var theStructure = grid.structure;
theStructure[1].editable = false;
theStructure[2].editable = false;
grid.set('structure', theStructure);
dojo.removeClass("normalMode", "dijitHidden");
dojo.addClass("editMode", "dijitHidden");
dojo.addClass("addRemoveMode", "dijitHidden");
}
function editMode() {
var theStructure = grid.structure;
theStructure[1].editable = true;
theStructure[2].editable = true;
grid.set('structure', theStructure);
dojo.byId("message").innerHTML = ("");
dojo.removeClass("editMode", "dijitHidden");
dojo.addClass("normalMode", "dijitHidden");
dojo.addClass("addRemoveMode", "dijitHidden");
}
function addRemoveMode() {
dojo.byId("message").innerHTML = ("");
dojo.removeClass("addRemoveMode", "dijitHidden");
dojo.addClass("normalMode", "dijitHidden");
dojo.addClass("editMode", "dijitHidden");
}
</script>
BlogController
As Dojo sends and receives JSON data to perform CRUD operations on the entities, so we need RESTful service within an ASP.NET MVC. We use API controller to make our RESTful service. Because we need Json as out put, we must add the following codes to "App_Start/WebApiConfig.cs" to force API controller return Json as output:
var appXmlType = config.Formatters.XmlFormatter.SupportedMediaTypes
.FirstOrDefault(t => t.MediaType == "application/xml");
config.Formatters.XmlFormatter.SupportedMediaTypes.Remove(appXmlType);
and because sometimes Json failed to serialize the response in Web API, we must add the following codes:
GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings
.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
See here and here for detail.
Adding BlogController
When you click on Add, "BlogController.cs" will made and it must contains the following code that generated automatically. Here you could find complete articles about Web-Api and API Controller.
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web;
using System.Web.Http;
using DojoDataGrid.Models;
namespace DojoDataGrid.Controllers
{
public class BlogController : ApiController
{
private BloggingContext db = new BloggingContext();
public IEnumerable<Blog> GetBlogs()
{
return db.Blogs.AsEnumerable();
}
public Blog GetBlog(long id)
{
Blog blog = db.Blogs.Find(id);
if (blog == null)
{
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
}
return blog;
}
public HttpResponseMessage PutBlog(long id, Blog blog)
{
if (ModelState.IsValid && id == blog.Id)
{
db.Entry(blog).State = EntityState.Modified;
try
{
db.SaveChanges();
}
catch (DbUpdateConcurrencyException)
{
return Request.CreateResponse(HttpStatusCode.NotFound);
}
return Request.CreateResponse(HttpStatusCode.OK);
}
else
{
return Request.CreateResponse(HttpStatusCode.BadRequest);
}
}
public HttpResponseMessage PostBlog(Blog blog)
{
if (ModelState.IsValid)
{
db.Blogs.Add(blog);
db.SaveChanges();
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.Created, blog);
response.Headers.Location = new Uri(Url.Link("DefaultApi", new { id = blog.Id }));
return response;
}
else
{
return Request.CreateResponse(HttpStatusCode.BadRequest);
}
}
public HttpResponseMessage DeleteBlog(long id)
{
Blog blog = db.Blogs.Find(id);
if (blog == null)
{
return Request.CreateResponse(HttpStatusCode.NotFound);
}
db.Blogs.Remove(blog);
try
{
db.SaveChanges();
}
catch (DbUpdateConcurrencyException)
{
return Request.CreateResponse(HttpStatusCode.NotFound);
}
return Request.CreateResponse(HttpStatusCode.OK, blog);
}
protected override void Dispose(bool disposing)
{
db.Dispose();
base.Dispose(disposing);
}
}
}
As you can see, the BlogController
performs "GET
/POST
/PUT
/DELETE
" in a single URL "/Api/Blog/".
POST
used to adding new blog
PUT
used to editing a blog
GET
used to sending a JSON data to grid containing all blogs or a blog
DELETE
used to deleting a blog
If any error occurred, an error message will send with a Json data to grid.
See in Action
Now it's time to see the result. Build the solution and edit some blogs and add/remove some others.
As you could see in fireBug data will send or request throw Json REST.
References