Using the jgGrid to enable a selection of a checkbox for row selection - which is easy to set in the jqGrid - but also only allowing a single row to be selectable at a time while adding events based on whether the row was selected or de-selected.
The jqGrid does not support the option to restrict the multi-select to only allow for a single selection. You may ask, why bother with the multi-select checkbox function if you only want to allow for the selection of a single row? Good question, as an example, you want to reserve the selection of a row to trigger another kind of event and use the checkbox multi-select to handle a different kind of event; in other words, when I select the row I want something entirely different to happen than when I select to check off the checkbox for that row.
Also the setSelection method of the jqGrid is a toggle and has no support for determining whether the checkbox has already been selected or not, So it will simply act as a switch - which it is designed to do - but with no way out of the box to only check off the box (as in not to de-select) rather than act like a switch.
Furthermore, the getGridParam('selrow') does not indicate if the row was selected or de-selected, which seems a bit strange and is the main reason for this blog post.
Solution:
How this will act:
When you check off a multi-select checkbox in the gird, and then commence to select another row by checking off that row's multi-select checkbox - I'm not talking there about clicking on the row but using the grid's multi-select checkbox - it will de-select the previous selection so that you are always left with only a single selection.
Furthermore, once you select or de-select a multi-select checkbox, fire off an event that will be determined by whether or not the row was selected or de-selected, not just merely clicked on. So if I de-select the row do one thing but when selecting it do another.
Implementation (this of course is only a partial code snippet):
multiselect: true,
multiboxonly: true,
onSelectRow: function (rowId) {
var gridSelRow = $(item).getGridParam('selrow');
var s;
s = $(item).getGridParam('selarrrow');
if (!s || !s[0]) {
$(item).resetSelection();
$('#productLineDetails').fadeOut();
lastsel = null;
return;
} var selected = $.inArray(rowId, s) != -1;
if (selected) {
$('#productLineDetails').show();
}
else {
$('#productLineDetails').fadeOut();
}
if (rowId && rowId !== lastsel && selected) {
$(item).GridToForm(gridSelRow, '#productLineDetails'); if (lastsel) $(item).setSelection(lastsel, false);
} lastsel = rowId;
},
In the example code above:
The "item" property is the id of the jqGrid.
The following to settings ensure that the jqGrid will add the new column to select rows with a checkbox and also the not allow for the selection by clicking on the row but to force the user to have to click on the multi-select checkbox to select the row: multiselect: true,
multiboxonly: true,
Unfortunately the var gridSelRow = $(item).getGridParam('selrow') function will only return the row the user clicked on or rather that the row's checkbox was clicked on and NOT whether or not it was selected nor de-selected, but it retrieves the row id, which is what we will need.
The following piece get's all rows that have been selected so far, as in have a checked off multi-select checkbox: var s;
s = $(item).getGridParam('selarrrow');
Now determine if the checkbox the user just clicked on was selected or de-selected: var selected = $.inArray(rowId, s) != -1;
If it was selected then show a container "#productLineDetails", if not hide that container away.
The following instruction populates a form with the grid data using the built-in GridToForm method (just mentioned here as an example) ONLY if the row has been selected and NOT de-selected but more importantly to de-select any other multi-select checkbox that may have been selected: if (rowId && rowId !== lastsel && selected) {
$(item).GridToForm(gridSelRow, '#productLineDetails');
if (lastsel) $(item).setSelection(lastsel, false);
}
As we all know the jqGrid examples in the demo and the Wiki always refer to static values for drop down boxes. This of course is a personal preference but in dynamic design these values should be populated from the database/xml file, etc, ideally JSON formatted.
Can you do this in jqGrid, yes, but with some custom coding which we will briefly show below (refer to some of my other blog entries for a more detailed discussion on this topic).
What you CANNOT do in jqGrid, referring here up and to version 3.8.x, is to load different drop down values for different rows in the jqGrid. Well, not without some trickery, which is what this discussion is about.
Issue:
Of course the issue is that jqGrid has been designed for high performance and thus I have no issue with them loading a reference to a single drop down values list for every column. This way if you have 500 rows or one, each row only refers to a single list for that particular column. Nice!
SO how easy would it be to simply traverse the grid once loaded on gridComplete or loadComplete and simply load the select tag's options from scratch, via Ajax, from memory variable, hard coded etc? Impossible! Since their is no embedded SELECT tag within each cell containing the drop down values (remember it only has a reference to that list in memory), all you will see when you inspect the cell prior to clicking on it, or even before and on beforeEditCell, is an empty .
When trying to load that list via a click event on that cell will temporarily load the list but jqGrid's last internal callback event will remove it and replace it with the old one, and you are back to square one.
Solution:
Yes, after spending a few hours on this found a solution to the problem that does not require any updates to jqGrid source code, thank GOD!
Before we get into the coding details, the solution here can of course be customized to suite your specific needs, this one loads the entire drop down list that would be needed across all rows once into global variable. I then parse this object that contains all the properties I need to filter the rows depending on which ones I want the user to see based off of another cell value in that row. This only happens when clicking the cell, so no performance penalty. You may of course to load it via Ajax when the user clicks the cell, but I found it more efficient to load the entire list as part of jqGrid's normal editoptions: { multiple: false, value: listingStatus } colModel options which again keeps only a reference to the single list, no duplication.
Lets get into the meat and potatoes of it. var acctId = $('#Id').val();
var data = $.Ajax({ url: $('#ajaxGetAllMaterialsTrackingLookupDataUrl').val(), data: { accountId: acctId }, dataType: 'json', async: false, success: function(data, result) { if (!result) alert('Failure to retrieve the Alert related lookup data.'); } }).responseText;
var lookupData = eval('(' + data + ')');
I only need the Id and Name for the drop down list, but the third column in the JSON object is important, it is the only that I match up with the OnlineName in the jqGrid column, and then in the loop during afterEditCell simply remove the ones I don't want the user to see. That's it!
Issue:
The code below worked before under version jQuery 1.4.2 but when I upgraded to version 1.4.4 it no longer worked as expected - it did not unselect the list box item, only setting "selectd" worked:
_handleClick: function(elem) {
var self = this; var initElem = this.element;
var checked = $(elem).attr('checked');
var myId = elem.attr('id').replace(initElem.attr('id') + '_chk_', ''); initElem.children('option[value=' + myId + ']').attr('selected', function() {
if (checked) {
return 'selected';
} else { return null; }
});
if ($.isFunction(self.options.onItemSelected)) {
try {
self.options.onItemSelected(elem, initElem.children('option').get());
} catch (ex) {
if (self.options.allowDebug)
alert('select function failed: ' + ex.Description);
}
}
},
Solution:
Under jQuery 1.4.4 you need to explicitly remove the attribute as in "removeAttr('selected'):
_handleClick: function(elem) {
var self = this; var initElem = this.element;
var checked = $(elem).is(':checked');
var myId = elem.attr('id').replace(initElem.attr('id') + '_chk_', ''); if (checked) { initElem.children('option[value=' + myId + ']').attr('selected', 'selected'); } else { initElem.children('option[value=' + myId + ']').removeAttr('selected'); }
if ($.isFunction(self.options.onItemSelected)) {
try {
self.options.onItemSelected(elem, initElem.children('option').get());
} catch (ex) {
if (self.options.allowDebug)
alert('select function failed: ' + ex.Description);
}
}
},
Storing data : $('selector').attr('alt', 'my data');
Retrieving data: $('selector').attr('alt');
The ALT attribute is designed to be an alternative text description. For images the ALT text displays before the image is loaded. ALT is a required element for images and can only be used for image tags because its specific purpose is to describe images.So therefore, "alt" is an HTML attribute meant to give the tag meaning and not to store data. Also if you want to store the same data for different DOM objects than the data is duplicated for every DOM property found in the selector
The solution:
Storing data : $('selector').data('key', 'my data'); $('selector').data('key', function() { do something } ); Retrieving data: $('selector').data('key');
The data is not stored in the DOM, it is stored jQuery's reference to that object, so for each target found by the selector only a reference is stored and the data is stored once and referenced as many times as needed.
Also, in the example above, triggering the checkbox does not change the checkbox value. As depicted above, based on a dropdown box changing and no contact being selected from the drop down box, automatically uncheck the checkbox and trigger it in invoke any event handlers on the checkbox.
You use the online compression utility jscompress.com to compress your js file but it fails with an error. Why this may be happening and how to fix it.
Possible causes:
Apparently not using open and closing curly brackets in an IF statement would cause this. Well turns out this is not the case. Look at the following example and see if you can figure out what the issue is :-)
function SetupDeliveredVPRecontactNotes($item, id) { var theData;
This will prompt you for input (like the JavaScript confirm pop-up menu). There is no way to override the text though.
Some concerns: This works great but it may not be what you want all the time, as no matter if you click a hyperlink, hit submit, etc will prompt the user. So here is an example where only when any input value on the screen has changed, I want to prompt the user only then and also only if the user decides to redirect from the page. It should not prompt the user when I try and save/delete or update the page:
function SetupChangeEventOnAllInputElementsAndSetGlobalFlag() { $(':input').change(function() { dataUpdated = true;
window.onbeforeunload = function(evt) {
var e = e || window.event;
// For IE and Firefox if (e) { if (DetermineSubmit(evt) == true) return; else e.returnValue = 'You have not saved your changes - OK to ignore or Cancel to remain on this page?'; } else { // For Safari if (DetermineSubmit(evt) == true) return else return 'You have not saved your changes - OK to ignore or Cancel to remain on this page?'; } }
});
}
//Determine if the original event is a submit button/control that triggered the window unload function DetermineSubmit(evt) { if ($(evt.explicitOriginalTarget).is(':submit')) { return true; }
Scenario: Hijack all my save buttons via the script below to override the value, from Save to Saving...., and then also disabling the button so they cannot hit it 1000 times.
The Issue: I have several submit type buttons on the page, like Save, Sand and Proceed, Delete, etc, so I use the Request.Form object to see what button was pressed to decide what to do. Well, the disable of the button by jQuery removes it from the Reuquest stream, even though it is only disabled after the click event has occurred, how strange.
The Solution: I don't know what the solution is other than removing the disable as below. Any ideas?
Scenario: You have an ASP.NET MVC application and don't want to custom build smart controls like the ASP.NET GridView, ListView, etc, that support sorting and paging, as well as filtering and searching for data, and all of this using Ajax.
Solution: The jQuery Grid plug-in. What tools/plug-ins do I need?
jqGrid version 3 and up (this post references version 3.4.4)
A strong cup of coffee
In the past you had to also add the jQuery modal and table row drag-and-drop plug-ins, but now the jqGrid team has made it easy and you can select to roll those into your download.
So where do I even start? Well, I am glad you asked.
Before we just jump in and start with the steps needed, let me just briefly summarize what you will need to do:
Create your ASP.NET MVC project in Visual Studio 2008 and add the three controller actions, one to render the account view, another to get the Ajax request's list of contacts, and lastly to support the update and delete operations of the contact.
Create your own jQuery file that will contain your contact's jQuery Grid.
Define the HTML needed to render the jqGrid (2 lines of HTML only!)
1.Download the plug-in.
It may seem obvious to some, but start downloading your plug-ins from these locations and save them to your hard-drive:
In short the jqGrid will make Ajax calls to your MCV controller actions. The application now has a HomeController and should have at least an Index action. In this article we will be demoing how to display data, so the Index will suffice to render our page, but we will be creating another action that will be responsible for rendering the content we need for our grid-list Ajax call later.
What we will be showing in this demo is an Account's page that has several tabs on it. One of the tabs will contain the list of all contacts for this account. In stead of just showing a list of someting, in this example you will be exposed to filtering a list, in this case by account, and showing how this can be done with the jqGrid (look out for the "postData" property of the jqGrid definition later in this post).
3.Add CSS and JavaScript files to project.
Before we start building the app, let’s find a place to store our CSS and jQuery files/plug-ins in our ASP.NET MVC project. For this I like to create a folder under the UI (my MVC project folder's name):
a.Content - contains all the CSS content, my own custom files as well as the jQuery Grid's and possibly other plug-in CSS files. This will also have an Images sub-folder that the CSS files’ image paths will need to point to, so you may have to do some find-replace-all to change the image paths in the CSS files to whatever your folder structure is.
b.Scripts - All my own and the jQuery, jqGrid and possibly other JavaScript files.
In this example there are a large amount of jQuery custom files for a CRM application, hence the CRM folder. Wherever minification of the scripts was important, a Min sub-folder was created that would be rendered in a production and/or QA environment where the requirement to debug scripts was not necessary. The jQuery Base folder contains all jQuery files as well as sub-folders for all jQuery plug-ins called Plugins. When the plug-in itself may contain more than one js file, like in this example the jqGrid, a separate sub-folder was created under the Plugins folder with the plug-in’s name, like jQuery.grid. Of course you may not want to create a folder structure with this level of detail, and is completely up to you, none of the plug-ins have any requirement either way.
4.Hook-up your files to the HTML page.
Now you need to let your web application's Index page know that it needs to load your scripts for the jQuery and jqGrid. There are other, and arguably better ways to do this, especially if you have a large number of plug-ins, but for now simply load them like this in your HTML header:
The first entry is a link to the CSS file. The jqGrid you downloaded came with a CSS file, you need to create a link to it. The second are the two js files, be sure to specify the jQuery-1.3.2 file before the jqGrid file as the jqGrid is dependent on the jQuery-1.3.2 file (JavaScript files are downloaded in sequence by our browser engine).
Now you are ready to define your jqGrid; provide a JSON object that will let the jqGrid know what columns to show, should it support paging, sorting, what is the URL to get the data and what is the URL to POST data back, and many other properties. Place this definition in your header, but since I have about a few of these grids and did not want to clutter the HTML header I created a separate js file that I link to.
We can get fancy here with drop downs, loading drop down dynamically, custom formatting and all kinds of built-in validations. We will not be getting into this in this post. Remember this is in its own js file, so no need for script tags:
Before we see what the definition of the grid looks like, we decided that we would group all the jqGrids into one function, SetupJqGridListOfAccountChildren. This function contains a call to twelve other functions, each one responsible for the definition of its own grid. One of these is our very own list of contact’s grid. You of course do not have to do this, especially if you only have one grid to display, in this demo we display twelve grids on one page! I kow…..keep it to yourselves.
One thing you may take away from this is that when you define the properties of the grid there is some repetition and passing in the parms shown here may (or not) make your maintenance easier, especially if you cal the same grid in different situations, with different URLs (list contacts with a different controller and action for example), no need to change the hard-coded properties in the gird or create two very similar or identical grids, just pass in a different URL, etc.
Now lets see what the actual jQuery Grid definition looks like. Note you may decide to use an XML formatted file, in this demo we went with a JSON formatted object. See the jqGriddocumentation for a sample of this.
function jqGridAccountContact(item, listURL, editURL, $rows, hideGrid) {
This looks scary, I know, but once you have a template you can copy and re-use it. You can also with jQuery extensions define it once, simply extend it as needed for customization. For example, we have dozens of lookup tables that we map using the jqGrid, we only have ONE, and simply change the URL as needed.
Let’s break this down somewhat. There are many more properties you can specify to tune your columns’ properties, like alignment, etc, but for brevity will not be getting into it.
·The function parameters:
·The URL property:
·The postData:
Define any additional data you want to post back. In this example since I do not want to or can hardcode the account’s ID as part of the URL, I use jQuery to find it and add it to the postData. This is critical otherwise the MVC action will fail since it is expecting an accounted value.
·The dataType:
The data type of the returned object, in this example it will be a JOSN serialized object, could be XML also. As a side note you could generate the static HTML table server-side if you waned to and have an option to point the jqGrid to it and all of the available paging and sorting, etc, will come out of the box, however, this requires that ALL data be loaded down to the client; if you have 500 contacts, all 500 will need to be specified in the HTML. I never use this option.
·The colNames:
Simply a list of all the header labels. The number of header labels MUST match the number of properties you specify for “colModel”, otherwise jqGrid will not be able to render your grid and will throw a run-time alert. These names do not have to match the names you want to use for sorting and searching that is what the “name” and “index” properties are for in the “colModel” definition. See the jqGriddocumentation for a sample of this.
·The colModel:
This where you define the CSS, validation and operations properties for each column. The available set of properties is enormous. Only a few are shown here, only name and index need to be specified, you could ignore the rest and rely on defaults. In case you were wondering, yes you can protect fields, hide some and show them only when editing the record, create SELECT tags with static or dynamically (loaded from the database) lists, validate input, etc, etc, etc. No need to worry just about everything imaginable is supported with callback functions to create your own. It is the index property that is used when sorting data, so make sure it is right and can be recognized by the server. See the jqGriddocumentation for a sample of this.
·Jsonreader
Optional, and can use it if you want to override the default JOSN properties for the JSON serialized DTO that will be passed back to the grid. In this example the property that will contain the actual list of data is called “root”. The “page”, “total” and “records” are used to support paging. Again entirely optional, but if not specified you need to make sure your DTO property names conform to the jqGrid definitions.
·Overriding default properties (again optional):
·All optional, but are very useful:
1.rowNum: How many rows to display by default (one of the parms to the function)
2.rowList: The tag.
4.pager: Important, the HTML tag ID of the
that contains the paging controls. Not sure why this was done this way, but as you will see later, you need to specify where the grid needs to be rendered in your HTML.
5.recordtext: The text to display next to the counter for the number of rows that exist (not the number of rows retrieved or loaded into the grid!)
6.sortname: The default sort column
7.sortorder: “asc” well ascending of course J
8.altRows: highlight alternate rows
·Event loadComplete:
One of many, this event fires once all the required data for the grid has been downloaded, before the grid is being rendered. In this example, although the column “EditUrl” does not exist shows a way some CSS can be passed into the jqGrid to change its color to red, align it right and give it a title. It also looks for any row that has “Decommissioned” and makes it red. It lastly also changes that text’s background color too. The possibilities are endless; you can pass in CSS or ATTR properties as key-value pairs as part of the “editOptions” property.
·Paging, Sorting, editing….
Included this, that although completely optional is important if you want to enable/disable certain operations. Like for example here the grid will allow the user to refresh the list, edit/update a contact, add a new contact, delete one and even search for a contact. The code definitions that follow in the code sample above can get a bit involved and not necessary to discuss here at this time, but is basically used to define how to perform the CRUD operations.
6.Now let’s define the HTML required to get this working.
" height="52" width="717">
That’s right, two lines of HTML! The first to specify the table name of the grid and the second the
container for the paging and where the other CRUD icons will appear. Remember the “item” parm in the “jqGridAccountContact” function? Well that’s the name for the “item” parm: “ListAccountContacts”. I prefer the naming convention for the pager to simply append Pager to the table name. Don’t ask why the jqGrid needs this, I honestly do not know. Well, that is all the HTML you will ever have to write. HA, that’s great you say, well which brings us to the next step, which is to hook up all this fancy stuff to the server that has to retrieve the data, perform a search if requested, convert the data to a serializable JSON object, and if requested also perform delete/update operations. To support delete/update operations we define a different MVC action to keep it clean (SRP).
7.Define the action to render the Account’s page.
8.Define the action to retrieve the list of contacts:
publicJsonResult ListAllContactsForAccount(int accountId, int page, int rows, string sord, string sidx,
This may seem like a lot, but it is quite remedial. The first thing you want to do it reformatted the “sidx”, the column to sort by if needed. Since we use NHibernate maps, the DAO that fetches the data used an alias called “l”, short for list, hence the “l.” notation. So for example if “sidx” is “Title” it is converted to “l.Title”, and in my NHibernate DAO’s HQL query then simply maps the title to the Contact alias “l”, thus NHibernate generates
select l.FirstName, l.LastName, l.Title…. from Contact as l where l.AccountId = 1
Notice the “JsonListDTO” generic object definition. You may select to use an anonymous definition; in this example the type of DTO that may be serialized must be specified.
Then get the account: IAccount account = Repository<IAccount>.Get(accountId);
If it does not exist, throw an exception.
Note the method returns a JsonReult, using the MVC built-in Json conversion to create the JSON serialized object.
Why a DTO? Well I am glad you asked. As with many examples, they are overly simplified and “haacked” together. The “Contact” entity has an “Account” property that because of recursion issues in the Json conversion cannot be serialized and converted directly (the account has list of contacts and contact has an account property). So in stead use NHibernate to refactor the list of contacts into a “dumb” DTO, that returns a DTO properties without any complex getters, just strings in this example, very light-weight, only retrieve what you need. You can create overloaded constructors for more scenarios.
using System;
[Serializable]
publicclassContactDTO
{
public ContactDTO(int id, string firstName, string lastName, string title, string phoneNumber, int totalRows)
{
Id = id;
FirstName = firstName;
LastName = lastName;
Title = title;
PhoneNumber = phoneNumber;
TotalRows = totalRows;
}
publicvirtualint Id { get; set; }
publicvirtualstring FirstName { get; set; }
publicvirtualstring LastName { get; set; }
publicvirtualstring Title { get; set; }
publicvirtualstring PhoneNumber { get; set; }
publicint TotalRows { get; set; }
}
An important note you is that the property names MUST match those of the “colModel” “name” and index” jqGrid properties.
As you can see form the example above, I have one HTML page that is very busy indeed. So busy I had to cut off some of the tabs. Behind most tabs is a jQuery grid. In one particular instance it has 5 grids. They perform very well.
Couple of pointers:
·The difference between traversing the object tree to get data, especially when using LINQ is not very practical in a surprising high number of instances. During the project the grid loaded very slowly. Once the LINQ queries were replaced by DTOs and light-weight HQL or ICriteria NHibernate queries, the performance increased exponentially.
·To create the update/delete functionality takes more server-side code, none more required on the client-side. All that is necessary for the jqGrid to work on the client has already been completed. I recommend creating another action on the HomeController that receives the parms that support CRUD operations fully, like the “oper” parm that is posted back together with the “Id” and other contact data. The “oper” parm needs to be evaluated by the server to determine what operation is requested. Then load the contact with the new data and save it to the database, and you’re done:
·To render server-side validation messages in the modal dialog (if that is the option you selected for CRUD, there are inline and cell-edit options as well), create the list of errors in your action and place them in the DTO’s “Errors” property (see jsonReader’s userdata properties).
I sincerely hope that this has been helpful and will get you started with the jQuery Grid quickly. Please let me know what you thought of this post and if you would like to see more specific examples. Any feedback and comments will be appreciated and will help us all to share pertinent and useful information; no need to re-invent the wheel all over again :-)