Click here to Skip to main content
15,867,568 members
Articles / Web Development / HTML

DataGrid View with "CRUD operations" using Dojo DataGrid, JsonRest Store, Entity Framework, SQL Server, ASP.NET MVC Web API

Rate me:
Please Sign up or sign in to vote.
4.86/5 (6 votes)
7 Jul 2013CPOL4 min read 70.4K   3.8K   36   12
DataGrid View with "CRUD operations" using Dojo DataGrid, JsonRest Store, Entity Framework, SQL Server, ASP.NET MVC Web API

331920/DataGridDemo.png

Table of contents

  1. Introduction
  2. Model
  3. View
  4. Controller
  5. See in action
  6. 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.

Image 2

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:

331920/BlogModel.png

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.

HTML
<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" />
    <!-- load dojo and provide config via data attribute -->
    <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>

331920/grid.png

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.

HTML
<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>

normalMode

HTML
<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>

editMode

HTML
<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>

331920/addRemoveMode.png

JavaScript
<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);

        //Clear any previous messages
        dojo.byId("message").innerHTML = ("");

        dojo.removeClass("editMode", "dijitHidden");
        dojo.addClass("normalMode", "dijitHidden");
        dojo.addClass("addRemoveMode", "dijitHidden");
    }

    function addRemoveMode() {
        //Clear any previous messages
        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:

JavaScript
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:

JavaScript
GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings
                   .ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;

See here and here for detail.

Adding BlogController

331920/BlogController.png

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.

C#
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();

        // GET api/Blog
        public IEnumerable<Blog> GetBlogs()
        {
            return db.Blogs.AsEnumerable();
        }

        // GET api/Blog/5
        public Blog GetBlog(long id)
        {
            Blog blog = db.Blogs.Find(id);
            if (blog == null)
            {
                throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
            }

            return blog;
        }

        // PUT api/Blog/5
        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);
            }
        }

        // POST api/Blog
        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);
            }
        }

        // DELETE api/Blog/5
        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.

Image 9

As you could see in fireBug data will send or request throw Json REST.

References

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United Kingdom United Kingdom
“If you can't explain it simply, you don't understand it well enough.”
Albert Einstein

Comments and Discussions

 
QuestionLoad Grid on button click (or onChange event) rather than page load Pin
cave coder17-Jul-12 10:51
cave coder17-Jul-12 10:51 
AnswerRe: Load Grid on button click (or onChange event) rather than page load Pin
cave coder17-Jul-12 15:04
cave coder17-Jul-12 15:04 
QuestionAccessing the ?sort(+SORT_BY_COLUMN) parameter Pin
cave coder14-Jul-12 10:03
cave coder14-Jul-12 10:03 
AnswerRe: Accessing the ?sort(+SORT_BY_COLUMN) parameter Pin
Nikfazan14-Jul-12 10:43
professionalNikfazan14-Jul-12 10:43 
GeneralRe: Accessing the ?sort(+SORT_BY_COLUMN) parameter Pin
cave coder14-Jul-12 12:25
cave coder14-Jul-12 12:25 
GeneralRe: Accessing the ?sort(+SORT_BY_COLUMN) parameter Pin
cave coder14-Jul-12 15:35
cave coder14-Jul-12 15:35 
GeneralRe: Accessing the ?sort(+SORT_BY_COLUMN) parameter Pin
cave coder14-Jul-12 16:31
cave coder14-Jul-12 16:31 
GeneralRe: Accessing the ?sort(+SORT_BY_COLUMN) parameter Pin
cave coder14-Jul-12 18:04
cave coder14-Jul-12 18:04 
Here's what I did, which appears to work:

string QS = filterContext.HttpContext.Request.QueryString.ToString();
if(QS.Contains("sort"))
{
filterContext.ActionParameters["sortParm"] = QS;
}
SuggestionRe: Accessing the ?sort(+SORT_BY_COLUMN) parameter Pin
Nikfazan15-Jul-12 7:29
professionalNikfazan15-Jul-12 7:29 
GeneralRe: Accessing the ?sort(+SORT_BY_COLUMN) parameter Pin
cave coder16-Jul-12 6:44
cave coder16-Jul-12 6:44 
GeneralMy vote of 5 Pin
natural-code10-Jun-12 13:07
natural-code10-Jun-12 13:07 
GeneralRe: My vote of 5 Pin
Nikfazan10-Jun-12 22:01
professionalNikfazan10-Jun-12 22:01 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.