Click here to Skip to main content
Click here to Skip to main content

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

, 7 Jul 2013 CPOL
Rate this:
Please Sign up or sign in to vote.
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.

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.

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

    <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

    <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

    <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

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

    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

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.

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.

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)

Share

About the Author

Nikfazan
Software Developer (Senior)
Iran (Islamic Republic Of) Iran (Islamic Republic Of)
نیک فزان
 
“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 Pinmemberdavem117-Jul-12 10:51 
AnswerRe: Load Grid on button click (or onChange event) rather than page load Pinmemberdavem117-Jul-12 15:04 
QuestionAccessing the ?sort(+SORT_BY_COLUMN) parameter Pinmemberdavem114-Jul-12 10:03 
AnswerRe: Accessing the ?sort(+SORT_BY_COLUMN) parameter PinmemberNikfazan14-Jul-12 10:43 
GeneralRe: Accessing the ?sort(+SORT_BY_COLUMN) parameter Pinmemberdavem114-Jul-12 12:25 
Hi again, and thanks for such a quick response.   I understand what you said above, but my real question is what modifications do I need to make to pass the sort(+BloggerName) into the action controller?   For example, I was successful in adding another parameter to your RestHttp filter and I can see the parameter there with a breakpoint, but don't know how to get it into the action controller where I can decode it and apply to the query (change ORDER BY in SQL).
 
But I'm not sure if using the RestHttp filter is the correct way or if there is a better way to get querystring parameters directly into the action controller when using JsonRest from dojo.   Please offer your thoughts and thanks again!
GeneralRe: Accessing the ?sort(+SORT_BY_COLUMN) parameter Pinmemberdavem114-Jul-12 15:35 
GeneralRe: Accessing the ?sort(+SORT_BY_COLUMN) parameter Pinmemberdavem114-Jul-12 16:31 
GeneralRe: Accessing the ?sort(+SORT_BY_COLUMN) parameter Pinmemberdavem114-Jul-12 18:04 
SuggestionRe: Accessing the ?sort(+SORT_BY_COLUMN) parameter [modified] PinmemberNikfazan15-Jul-12 7:29 
GeneralRe: Accessing the ?sort(+SORT_BY_COLUMN) parameter Pinmemberdavem116-Jul-12 6:44 
GeneralMy vote of 5 Pinmembernatural-code10-Jun-12 13:07 
GeneralRe: My vote of 5 PinmemberNikfazan10-Jun-12 22:01 

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

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

| Advertise | Privacy | Mobile
Web02 | 2.8.141022.1 | Last Updated 8 Jul 2013
Article Copyright 2012 by Nikfazan
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid