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

GridView-like jQuery plugin with templates for CRUD operations using MVC and Entity Framework

By , 21 Jan 2014
Rate this:
Please Sign up or sign in to vote.

Introduction

In a previous article I was playing around with a Javascript object for displaying JSON data in a GridView-like way. It seems to work, however rewriting it as a jQuery plugin has some advantages, like improved browser compatibility, chainability of operations, simpler DOM selections etc. Some issues with multiple GridView objects on the same page were resolved, enabling reusing of CSS classes for multiple GridViews and encapsulating events within every instance of the GridView. This is an improved version of an earlier plugin with enabled data field look-up, link fields and is tied with an attempt for template based forms for record addition or editing.

This is an updated version of the code, adjusted to work on Internet Explorer. It turns out that you cannot set multiple form elements to empty with JavaScript or jQuery without crashing Internet Explorer. Setting the form elements to blank and then to empty seems to solve the problem. 

Background

This second version of a GridView plugin has some improvements and bug fixes, New features are: data look-up fields, link fields, templates for record addition or editing. Added were Entity Framework and MVC server side code for CRUD operations.

Using the code

Lets start with the server side code. There are two entities added to the project; User and Item with the following properties:

In the sample application view they are both attached to two different GridView instances and are using two separate templates for record creation and editing in the same view.

The controller methods are located in the file CPArticle2Controller.cs. For each entity there are four methods: retrieving of object lists (GetJsonItems) , adding new, updating or deleting entities from the model. Per example:

[HttpPost]
public JsonResult UpdateUser(Models.User oUser)
{  ... return Json(oEntUser, JsonRequestBehavior.AllowGet); }  //return updated record 

They are called with [HttpPost], accept data in JsonResult format, perform some operation on the data and return the updated entity in Json format back to the client. In this example application, there are no special provisions for error processing or server-side data checks. In a production grade application, they should be added though.

Now let's look at the client side. Let's begin with the includes:

  • jquery-1.10.2.min.js - the jQuery Library
  • jquery-ui.js - jQuery UI, a useful library of tools and widgets. In this example it is used to make the GridViews generated by the GridView pulgin draggable around the page and perform some sample animations on the GridView if you wish to switch them on.
  • bpopup.js - a small jQuery plugin for modal pop-ups, that is used by callback functions in case you want to add or edit some data entities.
  • gridview.js - the GridView plugin described here along with four other helper plugins - addNewElement, formatDate, setCountryProvince and populateSelect. In this file there is a dataArrays JSON data object with some data, used for looking up data or populating drop-downs, more about it below.
  • The HTML body includes two divs Grid_Users and Grid_Items that are used as root elements for two instances of the GridView and two templates GridPopUp_User and GridPopUp_Item for entity processing.

    <div id="Grid_Users" class="Grid_Root"></div>
    <div id="Grid_Items" class="Grid_Root"></div>
    
    <!-- user pop-up template--> 
    <div id="GridPopUp_User">
            
       <input type="text" id="divDataUser_0" data-fieldname="FirstName" />
       <input type="text" id="divDataUser_1" data-fieldname="LastName" />
       <input type="text" id="divDataUser_2" data-fieldname="Email" />
       <input type="text" id="divDataUser_3" data-fieldname="Address1" />
       <input type="text" id="divDataUser_4" data-fieldname="Address2" />
       <input type="text" id="divDataUser_5" data-fieldname="PostalCode" />
       <input type="text" id="divDataUser_6" data-fieldname="City" />
       <select id="divDataUser_7" data-fieldname="Province"></select>
       <select id="divDataUser_8" data-fieldname="Country"></select>
       <select id="divDataUser_9" data-fieldname="Status" data-lookup="statusCodes"></select>
    
       <input type="hidden" data-fieldname="UserID" />
    
       <input type="button" id="btnSubmit_User" value="Submit" class="btnSubmit" />
    
    </div>
    
    <!-- Item pop-up template--> 
    <div id="GridPopUp_Item">
    
       <input type="text" id="divDataItem_0" data-fieldname="Name" />
       <textarea rows="5" cols="30" id="divDataItem_1" data-fieldname="Description"></textarea>
       <select id="divDataItem_2" data-fieldname="CountryOfOrigin" data-lookup="Countries">       </select>
       <input type="text" id="divDataItem_3" data-fieldname="Price" />
       <input type="text" id="divDataItem_4" data-fieldname="Weight" />
       <input type="text" id="divDataItem_5" data-fieldname="VendorName" />
    
       <input type="hidden" data-fieldname="ItemID" />
    
       <input type="button" id="btnSubmit_Item" value="Submit" class="btnSubmit" />
    
    </div>      

    Let's look at the two templates: They consist of a bunch of form elements, enclosed in a div. The form elements have two new HTML5 compliant attributes: "data-fieldname" and "data-lookup".

    The data-fieldname attribute is used to map the form control value to the data field of the corresponding entity. Per example the data-fieldname="FirstName" of the GridPopUp_User template will be automatically linked to the field FirstName of the User entity. The mapping, parsing and form processing is performed by the function popUp, included in the Index.cshml view. Please make sure to type correctly the field names. They have to match exactly the entity names and ultimately the database record names.

    The data-lookup attribute is used by the helper jQuery plugin populateSelect to populate drop-downs with data from a JSON object dataArrays, included in the gridview.js file. Per example this line of code, called on page load:

    $("[data-lookup]").populateSelect(null);   

    will populate all selected drop-downs with data from the dataArrays object, matching the name of the array, specified in the data-lookup attribute: data-lookup="Countries" will cause the plugin to try to find the JSON array Countries in the object dataArrays, reset and populate the drop down with data from the array Countries.

    The dataArrays object contains by default arrays for Countries and Provinces, but can be extended on the fly to add new arrays as needed. Per example this line of code:

    dataArrays["allProvinces"] = $.extend({}, dataArrays.ProvincesCA, dataArrays.ProvincesAU,  dataArrays.StatesUS);    

    will combine the arrays ProvincesCA, ProvincesAU and StatesUS and will create a new array within the dataArrays object with the name allProvinces.

    Let's look now at the Javascript code and the different plugins and Javascript objects. The actual GridView plugin, described here is located in the include gridview.js

     (function ($) {
        $.fn.addGridView = function (dataTable, jsonConfig, cssID, addNewCallBack) {
            try {
                /*public variables and methods*/
                var oGrid = this;
                oGrid.pageSize = 8;
                oGrid.pageIndex = 0;
                oGrid.dataTable = dataTable;
                oGrid.addButtonText = "Add New";
                oGrid.objName = "";
                oGrid.applyEffects = false;
     
                this.displayPage = function () { .... }
    .....
                this.onPagingClick = function (pageIX) { .... }
    ..... etc.
     
      

    The GridView plugin accepts four parameters:

    • dataTable - the JSON data object being displayed
    • jsonConfig - optional JSON configuration object that allows specifying which data columns will be displayed in what order, along with some additional properties, more about that below. If you want to omit jsonConfig, you have to specify null instead.
    • cssID - a string that will be used as a prefix to specify the CSS classes that will be attached to the individual elements of the GridView. It allows reusing of CSS classes for multiple GridView instances.
    • addNewCallBack is an optional parameter. It should be an existing function or null if you want to omit it. If you specify it, the plugin will attach a button to the right bottom side of the GridView and will link the click event of the button to the specified function. This function should handle the addition of new entities and will receive as parameter a reference to the GridView object.

    The GridView plugin has six public properties and two public methods:

    • pageSize - number of data rows in the Grid
    • pageIndex - currently displayed page
    • dataTable - reference to the passed JSON data object
    • addButtonText - allows customization of the add new button if you specify an addNewCallBack parameter
    • objName - the name of the entity that is being displayed. It is being used to identify the entities that are being processed if there are add edit, add or delete functions, If the Grid is being used for display only, it can be omitted.
    • applyEffects - is a boolean switch, Default is false. If you set it to true, there will be some sliding effects performed during page refresh.
    • displayPage() - refreshes the GridView display. This method is not called on object creation, you have to call it after plugin initialization and populating pageSize and pageIndex.
    • onPageClick(pageIndex) - optional way of refreshing the GridView display. This method is called also by the navigation click events.

    The GridView plugin works by taking a root element from the jQuery selector, per example jQuery("#Grid_Items") and adds the elements of an HTML table as childElements to it. It generates and populates a header row either with the field names of the JSON object, or it would use a configuration JSON object if one is provided. It populates the cell elements with the data from the the JSON dataTable object and generates a navigation set of links, depending on the selected page number. The plugin internally generates jQuery objects and uses them to manipulate the DOM. The jQuery objects are generated by a helper plugin addNewElement, included in the gridview.js as well.

    The optional grid layout JSON object allows customization of the GridView colums.

    var GridLayout_User = [
     { "DataField": "FirstName", "HeaderText": "First Name", "Width": 80 },
     { "DataField": "LastName", "HeaderText": "Last Name", "Width": 80 },
     { "DataField": "City", "HeaderText": "City", "Width": 80 },
     { "DataField": "Province", "HeaderText": "Province", "Width": 80, "LookUp": "allProvinces" },
     { "DataField": "Country", "HeaderText": "Country", "Width": 80, "LookUp": "Countries" },
     { "DataField": "Status", "HeaderText": "Status", "Width": 80, "LookUp": "statusCodes" },
     { "DataField": "DateJoined", "HeaderText": "Date Joined", "Width": 120 },
     { "StaticText": "Edit", "HeaderText": "", "Width": 60, "HyperLinkField": onLinkEditClick },
     { "StaticText": "Delete", "HeaderText": "", "Width": 60, "HyperLinkField": onLinkDeleteClick }
    ] 
    • "DataField": "FirstName", - specifies the data field name of column, as it is in the data object. The field name has to exist in the JSON data object.
    • "HeaderText": "First Name", - appears in the Header of the first column
    • "Width": 100 - column width, please note that it is a number and not a text as in the configuration object of the previous article.
    • "HyperLinkField": onLinkEditClick - optional field. if you wish to link a GridView column with a callback function, you can specify it here. GridView will add a link to this column, the click event of this link will be wired to the specified function (onLinkEditClick) and the function will receive as parameters a reference to the GridView object and the row index of the clicked row. These parameters can be used to look-up data record(s) or perform operations on the GridView object.
    • "StaticText":"Edit" - a static text that will be populated in the column
    • "LookUp" :"Countries" - if this option is specified the GridView will try to find the specified array ("Countries") in the dataArrays object, included in the gridview.js and will try to replace the displayed data with values from the look-up array. Per example instead of "US", the displayed data will be "United States".

    How the plugin is initialized and used in a page:

    function getUsers() {
      try {
        $.getJSON("/CPArticle2/GetJsonUsers", null, function (data) {
          try {
            var dtUsers = eval(data);
            var oGrid_Users = 
            $("#Grid_Users").addGridView(dtUsers, GridLayout_User, "Grid_", onAddNewClick);
            oGrid_Users.pageIndex = 0;
            oGrid_Users.pageSize = 5;
            oGrid_Users.objName = "User";
            //oGrid_Users.applyEffects = true;
            oGrid_Users.displayPage();
            oGrid_Users.draggable({ stack: ".Grid_Root" });
          }
          catch (ex1) {
            alert(ex1);
          }
        });
      }
      catch (ex) {
        alert(ex);
      }
    }

    there are two GridViews, generated in this example, with two different JSON arrays: Users and Items.

    var oGrid_Users = $("#Grid_Users").addGridView(dtUsers, GridLayout_User, "Grid_",          onAddNewClick);  

    $("#Grid_Users") is the jQuery selector for the root element.

    addGridView(dtUsers, GridLayout_User, "Grid_", onAddNewClick); is initializing the plugin with the four parameters:

    • dtUsers is the JSON data object retrieved from the controler "/CPArticle2/GetJsonUsers"
    • GridLayout_User is the JSON array, specifying the formatting options
    • "Grid_" is the string used as a prefix to attach the CSS classes to the GridView elements.
    • onAddNewClick is the function, used to add new entities.

    If you wish to omit the jsonConfig object and the add new functionality, you have to specify null instead:

    var oGrid_Users = $("#Grid_Users").addGridView(dtUsers, null, "Grid_"); 

    oGrid is storing the jQuery object, returned by the plugin and can be used to specify pageIndex, pageSize and other options, and refresh the GridView display with displayPage().

    oGrid.draggable({ stack: ".Grid_Root" }); is just a demo how the plugin can be used by various widgets and libraries, per example the jQuery UI library to make it draggable around the page. Please note that in this example the {stack: ".Grid_Root" } is used to bring to the front the GridView that is being currently dragged around. That means, that the root elements have to have the CSS class .Grid_Root attached to them.

    The customization of the GridView plugin appearance is done entirely by CSS classes:

    /* Header Properties */
    .Grid_HeaderRow {
      border: 1px solid black;
      font-weight: bold; font-size: 15px; color: #FFFFFF;
      height: 35px; background-color: #777777;
    }
     
    .Grid_HeaderCell {
      border: 1px solid black; padding: 3px;
      text-overflow: ellipsis; overflow: hidden;
      white-space: nowrap; text-align: center;
    }
     
    /* Table Body Properties - Row and Cell elements*/
    .Grid_Row {
      height: 35px;
    }
     
    .Grid_Row:nth-child(odd) {
      background-color: #DDDDDD;
    }
     
    .Grid_Cell {
      border: 1px solid #777777; padding: 5px; text-align: center;
    }
     
    /* Footer Properties */
    .Grid_FooterRow {
      height: 40px;
    }
    ........ etc. 
    

    The convention is to use the parametr cssID - a string that is used as a prefix to specify the CSS classes:

    If you pass "Grid_" as cssID parameter, the classes that have to be used will be:

  • Grid_Table
  • Grid_HeaderRow
  • Grid_HeaderCell
  • Grid_Row and Grid_Row:nth-child(odd)
  • Grid_Cell
  • Grid_FooterRow
  • Grid_FooterCell
  • Grid_AddNew - css for the add new button
  • Grid_Paging - css for the navigation wrapper
  • Grid_NavigationLink
  • Grid_NavigationLink:hover
  • Grid_NavigationCurrentPage
  • Grid_CellLink
  • Grid_CellLink:hover
  • Grid_Error - css for the error message div
  • Let's look now at the editing and addition functionality. On page load there are two pop-up objects initialized:

    oPopUp_User = new popUp("GridPopUp_User");  //initialize pop-ups  
    oPopUp_Item = new popUp("GridPopUp_Item");  
    

    GridPopUp_User and GridPopUp_Item are two wrapper div for the two templates that will be displayed and populated with the data from the entities User and Item. The popUp template is activated with the function:

    $pop.Show = function (operationMode, dataRecord, callBack) {      ......  } 
    • callBack is a function that will be called on clicking the submit button of the template. It will receive the updated JSON data object passed to it with the dataRecord parameter or it will generate a new JSON object populate it with the data from the template if the operation mode is "Add"
    • operationMode specifies if the template is used in editing or addition mode. Possible values are "Add" or "Edit". In addition mode on submit a new JSON object will be created, in "Add" mode you have to specify null for the dataRecord parameter
    • dataRecord is the JSON data object that will be used to populate the template controls and will be returned with the updated data to function, specified in the callBack parameter.

    Before the Show function is being called, the button id of the template has to be specified, per example:

    window["oPopUp_" + callBackObject.objName].btnSubmitID = "btnSubmit_" +                     callBackObject.objName;  

    where CallBackObject is the GridView reference, passed to the Edit or Add function and the objName is the name of the entity name passed to the GridView on initialization, in this example, User or Item.

    There are three functions. handling the CRUD operations:

    function onAddNewClick(callBackObject)
    function onLinkEditClick(rowIX, callBackObject)
    function onLinkDeleteClick(rowIX, callBackObject)
    

    I will briefly describe how they work:

     function onAddNewClick(callBackObject) {
      var $self = this;
      //Controller for adding a record
      $self.ajaxURL = "CPArticle2/AddNew" + callBackObject.objName;  
    
      this.onAjaxReturn = function (dataRecord) {
        //insert a row with the returned data record as a first record
        callBackObject.dataTable.unshift(dataRecord); 
        callBackObject.pageIndex = 0;
        callBackObject.displayPage();
        //close and reset the pop-up form fields
        window["oPopUp_" + callBackObject.objName].close();
      }
    
      this.onFormReturn = function (dataRecord) {
        //send form data to the server
        ajaxCall($self.ajaxURL, dataRecord, $self.onAjaxReturn); 
      }
    
      if (callBackObject.objName == "User") {
        //set the drop-down element ids for Country and province
        window["oPopUp_" + callBackObject.objName].countryElementID = "divDataUser_8";  
        window["oPopUp_" + callBackObject.objName].provinceElementID = "divDataUser_7";
      }
      
      window["oPopUp_" + callBackObject.objName].btnSubmitID = "btnSubmit_" +                       callBackObject.objName;
      window["oPopUp_" + callBackObject.objName].Show("Add", null, $self.onFormReturn);
    } 

    onAddNewClick(callBackObject) is called by the GridViw when the "Add New" button is clicked. callBackObject is a reference to the calling GridView object. The function uses the controller name: "CPArticle2/AddNew" + callBackObject.objName, that means you have to build your server side methods with the convention: "AddNew" + objName, per example for the entity User: "CPArticle2/AddNewUser".

    If you specify the options: countryElementID and provinceElementID, the popUp template object will populate the controls with these IDs with data from the dataArrays object included in the gridview.js file. The onChange event of the countryElementID drop down will refresh the provinces automatically, depending on the country selection.

    The callback function .onFormReturn is called on popUp submit and will receive the new data record and will send it to an ajaxCall to the server for processing. The ajaxCall will return the updated entity, specifically with the new ID of the newly created entity to the callback function onAjaxReturn. The newly created entity will be inserted as a first record of the GridView dataTable array and it will be refreshed with displaying the first page.

    The other two methods for editing or deleting GridView rows work in a similar way, An Ajax call is being performed and the GridView is getting updated and refreshed.

    Points of Interest

    The CRUD operations are not linked to any existing existing framework like Angular.js or Backbone.js. I have played around with both of them trying to implement the CRUD functionality and it seems to me that Angular.js is the better choice, so in future I may update this with an Angular.js based template for CRUD.

    History

    There is a previous version of this as a pure Javascript object. The plugin seems to be technically superior though.

    License

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

    About the Author

    Kamen Nik
    Software Developer (Senior) GL
    Canada Canada
    No Biography provided

    Comments and Discussions

     
    -- There are no messages in this forum --
    | Advertise | Privacy | Mobile
    Web03 | 2.8.140415.2 | Last Updated 21 Jan 2014
    Article Copyright 2013 by Kamen Nik
    Everything else Copyright © CodeProject, 1999-2014
    Terms of Use
    Layout: fixed | fluid