- mvcJqGrid.js
Also you should add in the jqGridMVC.css file which is included in the JQGridAdditionalResources folder. This is included in the provided download.
All but the yellow can be downloaded from the jqGrid site. Or you can use the files included in the download package. Also you will need to add a project reference the jqGrid dll included in the package. Next, an addition to the web.config will be needed. The namespace JQGrid must be added so pages can pick up on the Html extensions.
<pages pageBaseType="System.Web.Mvc.WebViewPage"> <namespaces> <add namespace="System.Web.Helpers" /> <add namespace="System.Web.Mvc" /> <add namespace="System.Web.Mvc.Ajax" /> <add namespace="System.Web.Mvc.Html" /> <add namespace="System.Web.Routing" /> <add namespace="System.Web.WebPages" /> <add namespace="JQGrid" /> </namespaces> </pages>
That is all that is required for the controller to be set up. Now all that is needed is to create the Action that you are calling that will return a page with a grid.
public ActionResult ViewBlogs() { return GridSearch(c => c.BlogEntries /*TABLE. CAN MAKE 'INCLUDE()' statements here*/,c => c/*WHERE.ORDERBY STATEMENTS*/,c => c.Select(be => new { be.BlogEntryId /*id field must be included and be first field */, be.Title, be.PostDate })/*MOLD THE DATA HERE*/); }
The Id field is required as the first anonymous property in the third parameter. That is all that is required. This is a small and easy rule to follow. In this example I call the GridSearch method which returns an ActionResult for the View. On the View Side I call the below and it will pick up on the grid details and securely manage grid searching and ordering. Only the fields specified in the return set can be searched unless otherwise specified in another parameter I will go over later. Html.jqGrid<*EntityType*>(*Grid Title*,*settings & other methods*);
Now you should have a simple grid as shown below rendered into the view with the *optional* search functionality. The Blog Entry Id search field can be easily hidden and more can be added even if they are down the relationship path of the EntityModel.
NoEncode(*object*) will tell the column data provider to skip encoding the column if need be.
Good...but that actually looks yucky with the html like that. Lets clean that up a bit with a few shortcut methods called Link(*Url*,*Text*,*Boolean[should this show]*) and Space(*number of s*)
return GridSearch(c => c.BlogEntries,c => c,c => c.Select(be => new { be.BlogEntryId, Title = NoEncode(be.Title), be.PostDate, Actions = NoEncode( Link("/Blog/DoSomething","Do Something") + Space(2) + Link("/Blog/AndAnother","And Another")) }));
Wow that looks a lot better. The Link() method even has a third parameter that accepts a Boolean to judge whether it should show or not, so none of those inline x ? a : b statements are needed. You could actually use the below also. Notice the $. I picks up the current controllers name. So dont use this with a link that points to another controller.
Link("$DoSomething","Do Something")
So, I have search already providedbut I still want to filter this data further
The second parameter can handle filtering for the construction of the generated query that gets sent to the database. Just use it like you would handle Where() statements for any entity framework query.
var tenDaysAgo = DateTime.Now.AddDays(-10); return GridSearch(c => c.BlogEntries, c => c.Where(entry => entry.PostDate >= tenDaysAgo ), c => c.Select(be => new { be.BlogEntryId, Title = NoEncode(be.Title), be.PostDate }));
Above I am doing some extra filtering. Im telling it to find all blog entries within the past 10 days.
Wow. This grid looks too big, maybe too small. Lets give it some dimensions that feel Just Right
Alright, now we are getting into modifying the grid itself. This will be taking place on the View itself.
As you can see, the Grid Width and Grid Height is editable in their own manner. But I still need control over how many rows are pulled.
@(Html.jqGrid<BlogEntry>("Blogs",5, settings => { settings.GridWidth(500); settings.GridHeight("300px"); }))
As you can see, the method has an overload that has a 2nd parameter that accepts the number of rows per page.
The ColWidths method allows to quickly set the widths of many columns.
settings.columns.Title.width = 300; settings.columns.PostDate.width = 80;
The method above accesses a dynamic property on the settings called columns. From this you can access each column dynamically. The values placed on the columns here will get serialized and added to the default properties that are part of the javascript object used for the column in the grid generation. So any column/colModel property available from the jqGrid javascript api (http://www.trirand.com/jqgridwiki/doku.php?id=wiki:colmodel_options)
settings.FindCol("Title").Width = 300; settings.FindCol("PostDate").Width = 300;
The FindCol Method acceses the underling ViewGridColumn object which has quite a view properties of its own which I will dig deeper into further on. Columns can be styled quickly through multiple available ways as shown below:
settings.FindCol("Title").Style("color:blue;padding:8px;");
settings.ColStyle("Title,PostDate", "color:blue;padding:8px");
Rows can also be styled through a couple powerful manners as discussed below:
settings.RowCss(new { background = "yellow" });
settings.RowCss(new { background = "blue", color = "white" }, Browser.IE7_8); settings.RowCss(new { background = "yellow" },Browser.NotIE);
Filters style by value in column of grid. settings.FilterRowStyle("Title", "JQGrid MVC Part 1 of Blog Series", new { background = "green" });
Column Headers
Column headers can easily be changed. The method of approaching this is show below:
settings.FindCol("PostDate").FriendlyName = "Date of Post"; or settings.FriendlyName("PastDate", "Date of Post");
Hiding a Column
If you need to hide a column just use the hide method as show below.
settings.Hide("BlogId,PostDate");
This is very useful for only pulling very selective data from the database without loading everything into memory. If a column had an Image or Document stored in a column in binary, sometimes it would be better to ignore these columns and others that arent being pulled.
To create the actual search textboxes on the other side you can use the following:
settings.RelationalTextBox("First Name", "FirstName"); settings.RelationalTextBox("Last Name", "LastName");
Or manually use a textbox with a name of search$FirstName and search$LastName if you prefer to build them manually. You can append these manually built fields by doing things such as :
settings.AppendHtml("Last Name: <input type='text' name='search$LastName' />"); settings.PrependHtml("Last Name: <input type='text' name='search$LastName />");
These will be inserted into the search form. There are ways to build the search form completely manually and invoke searching on the grid but that is a more advanced topic that will be discussed later on.
Returning a SubGrid
Below I create an action on the controller called ViewUsers. This returns the users in the system. But lets just say I want the subgrid that is expanded using the plus icon on the far left of the grid to view the users blog entries. I create another method that accepts a parameter name of id. This is the name of the rowid sent during an ajax call. This returns a normal GridSearch. On the view I create the normal Html.jqGrid. One difference is the call to PageableSubGrid on the settings object. Inside this I call Html.jqSubGrid which works almost exactly like the normal jqGrid method except is handled as a subgrid. Subgrids can have subgrids of their own. These methods of course require the two generic parameters and the lamba method expression on the controller as seen in con => con.ViewUserBlogEntries(-1).
-------------CONTROLLER------------------------------------------------------------------
public ActionResult ViewUsers() { return GridSearch(c => c.Users, c => c, bes => bes.Select(b => new {b.UserId, b.FirstName, b.LastName })); }
public ActionResult ViewUserBlogEntries(int id = 0) { return GridSearch(c => c.BlogEntries.Include("User"), c => c.Where(be => be.User.UserId == id), c => c.Select(be => new { be.BlogEntryId, be.Title, Actions = NoEncode("Actions") })); } --------VIEW----------------------------------------------------------------------------@(Html.jqGrid<User>("Users",settings => { settings.PageableSubGrid( Html.jqSubGrid<User, BlogController>("UserBlogs", s => { }, con => con.ViewUserBlogEntries(-1), "/Blog/ViewUserBlogEntries")); }))
Creating a expandable Templated SubContent for rows. This accepts either a string or a @<text></text> format. This requires jquery template to be referenced. If you know anything about jquery tmpl then it should be easy to figure out. If not, search it up and download jquery tmpl( http://api.jquery.com/jquery.tmpl/ )
settings.SubContent(@<text> <div> Hello ${FirstName} ${LastName} <h1>Very Cool</h1> </div> </text>);
public static string Buffer(this System.Web.Mvc.WebViewPage<dynamic> page, string key, Func<object,HelperResult> content) { page.Response.Flush(); page.ViewData[key] = content.Invoke(new { }).ToString(); return ""; }
gridComplete(){ } Description from the jqGrid Wiki site: This fires after all the data is loaded into the grid and all other processes are complete. Also the event fires independent from the datatype parameter and after sorting paging and etc.
Load complete is executed after every server request for data. The data variable hold the data passed into each response depending on the datatype grid parameter.
Implementing an Event and/or function not already implemented by ASP.NET MVC jqGrid
To do this you must call on the dynamic functions object. From there just extend it with the name of the desired event/method and set it to the function itself as seen below.
settings.functions.ondblClickRow = "function(rowid, iRow, iCol,e) { Do Something }";
I have a date field, but I want to search for it between a range of dates.
Use the ConvertToDateRange method which accepts the name of the column.
settings.ConvertToDateRange("PostDate");
This will hook up the entire underlying entity framework searching for between date ranges.
To group by just provide the first parameter in the GroupBy method with the name of the field. Here I group by the date. The second parameter judges whether a group summary will be shown. If you wish to use the Group Summary use the GroupSummaryByField method in addition with the name of the field with the summary, the GroupSummaryType(all shown below) and the string formatter template where the {0} will be replaced by the summary types value.
From the above you can see that the third property allows you to change the group text and the fourth allows you to control whether or not to show the summary when the group is collapsed.
public enum GroupSummaryType { sum, count, avg, min, max }
As you can see there is a multitude of available options. You can find the sum,cound,avg,min,max of that columns values for all items in the group. You can use a Group Summary for each independent field as necessary.
This method AsDropdown accepts 4 parameters. The edit-column. The key name, the value name and then the lamba using the ObjectContext type provided to pull from the database in a quick manner.
Using a datepicker
By default, all date column field are hooked up by default with a datepicker, so there is no need to worry of thise.
On the action in the controller itself use the attribute DeletableRow as shown below.
[DeletableRow(Table="BlogEntries",IdFeild="BlogEntryId")]
If you need any more customizability like role checking,ect. you can just create another separate method on the controller and a link in a column that points to it. This is just available for rapid development and ease of use.
Need an inline search dropdown used for searching? Use the follow which accepts the following arguments: Column,Key,Value,Collection From EntityContext and two generic arguments which are the type of EntityContext and the typeof of objects contained within the return collection.
settings.InlineSearchDropdown<KashnEntities, BlogEntry>("Title", "Title", "Title", c => c.BlogEntries);
and
public static HtmlString GridSearchClick(string gridName)
I use the 2nd since this is posting to the same page for the searching process. The GridSearchClick method generates a onclick event handler so it will be something like onclick=gridReload().
<div> <p class="label">First Name: </p> <input type="text" name="search$FirstName" /> <p class="label">Title: </p>
<input type="text" name="Title_search" /> <p class="label">Post Date: </p> <input type="text" name="PostDate_search" class="datepicker" /> <input type="button" @jQGrid.GridSearchClick("Blog Entries") value="Search Blogs" /> </div>
For :
public ActionResult ViewBlogs() { return GridSearch(c => c.BlogEntries.Include("User"),c => c, set => set.Select(be => new { be.BlogEntryId, Title = be.Title, be.PostDate, Actions = NoEncode(CommandField("<b>Edit</b>",JQCommand.Edit) + " " + CommandField("<b>Cancel</b>",JQCommand.Cancel) + " " + CommandField("<b>Save Changes</b>",JQCommand.SaveChanges) + " " + CommandField("<b>Delete</b>",JQCommand.Delete)), }),"User.FirstName,User.LastName"); }
Now of course arises the question of if two fields have the name XX_search that might be for different grids. A way around this is to use the data-gridForm attribute on your container of the search fields. This will scope all internal searches for the input fields to only inputs within that container.
<div data-gridForm="Blog Entries"> <p class="label">First Name: </p> <input type="text" name="search$FirstName" /> <input type="button" @jQGrid.GridSearchClick("Blog Entries") value="Search Blogs" /> </div>
Creating a action to be taken after the view settings have been constructed in the View itself.
To change it to use the template in two columns use the 2nd parameter overload with a 2, though the better method is to use the floating tiles method, which is described next.
A better way to handle the Two Column/Multiple column situation is to use the FloatingTiles method as seen below.
All of the tiles generated are wrapped in a div with float:left and a class of jqGrid-stackCell.
settings.FloatingTiles(@<text> <div class='round-8 shadow product-tile' style='background:white;width:460px;margin:7px;'> <div > <table ><tr><td style="border:none;"> <img src="${ImageUrl}" style='border:1px solid silver;' /> </td><td style="border:none;"> <h1 style="font-size:18px;padding-left:0px;margin-left:0px;">${Name}</h1> <p><b>List Price</b>: ${ListPrice}</p> <div class="jqfilter-button-clear tile-button">View Product</div> <div class="jqfilter-button tile-button">Add to Cart</div>
return args.Query.Cast<BlogEntry>().Where(b => !b.Title.Contains("One")); } return null; }; QueryExtensions.PostSearchConstruction += (o, args) => { return null; }; OR EntityFrameworkQueryFactory.QueryFilterByTable<BlogEntry>(a => { return a.Where(b => !b.Title.Contains("One")); });
This will automatically apply across the board. If you use the CreateHeader() method on the view it will override this. Also if you want to inject content into the headerTemplate you can use the method AddToHeaderTemplate. This will replace the ${PlaceHolder} piece you input into the template.
settings.AddToHeaderTemplate(@<text> <p>Some Content</p> </text>);
The capture-box-tag is there when there are no columns in it. The captured-item-template container is a template for how the column name should be displayed in the box. Each item will have the captured-item class on it so you can style them. You just drag over a column to it and it will be added to the box and the column will be hidden. When you click the name it will show the column and disappear from the box. See the example picture below. These styles of course can be customized to the choosing using the classes provided that you can inspect through firebug. Currently there is a small bug with the inline searching where they disappear but will be resolved soon.
If you want to change the style of the capture box when hover over with a column being dragged just add a style in the stylesheet for .capture-box-hover. That is triggered when dragging a column into the capture box.
Lets say I create this on a grid for listing users and I want it so when a row is selected that another grid on the page called User Blogs to update to show the users blogs. The PushToGrid method takes two arguments, the name of the other grid and the field that will be used to filter the other grid down further. In this case every time User Blogs makes a call for data it will pass a parameter in the request called UserId containing the id of the user that was selected in the other grid making it an easy way to keep them in sync.
Every time a row is selected its column value for UserName of that row will be bound to the element with an id of lblUserName.
If you need deep control and a stronger guarantee your field wont be searchable you can use the SearchableColumnsAttribute on the action. This provided a black or white list approach. Either you mark those that are to be searched or those that cannot be searched as so.
[SearchableColumns(Include="BlogEntryId,Title,PostDate")] [SearchableColumns(Exclude="MySecretField")]
Lets say that there is a relational property being loaded that you want searchable but has the same property name such as Name that already exists on the Primary table being loaded. You might say new { Category = ProductCategory.Name }. Now this will work properly in creating your own search fields because you have control to tell it the field name and that its a property down the object path. But JqGrids own search form it constructs wont know this at all. So the way to reroute this issue is to add an the SearchableColumns attribute with the ReroutablePaths property set. This one says that the rule field Category is related to the ProductCategory.Name field and the CategoryModifiedDate is tied to ProductCategory.Modified date and should be bound as so.
[SearchableColumns(ReroutablePaths="Category=ProductCategory.Name;CategoryModifiedDate=Pr oductCategory.ModifiedDate")]
Advanced Usage Scenarios Using KnockoutJs & Jquery Tmpl & JqGrid Together
KnockoutJs has been getting a lot of attention lately. Especially as a MVVM javascript implementation developed by Steven Sanderson. If you wish to read up on it there is fantastic documentation and examples at http://knockoutjs.com/ or even Steven Sandersons personal webpage http://blog.stevensanderson.com/. KnockoutJs and the most recent Jquery Tmpl is required to use the below. Using the ConstructModel method we create the name of the model. Then we can bind the values we jsonify as hidden columns passed down to jqGrid as Knockouts observableArray properties. These properties will stay in sync at all times with the selected row values.
settings.ConstructModel("CModel", model => { model.Array("BlogEntries"); model.Array("Uploads"); });
To create a bound selected rows column value to a certain element you can just use:
s.BindRowColumnText("Title", "#Title");
s.BindRowColumnText("Content", "#Content");
And in javascript after any extra hookups have been created on the model call the below in the $(document).ready();
ko.applyBindings(CModel);
To show the bound information use the below or any other javascript template supported by KnockoutJs.
<!-- ko if: Uploads --> <table style="width: 100%;" class="date-table" data-bind="foreach:Uploads"> <tr style="width: 100%;"> <td data-bind="text:FileName"> </td> <td> <a href="#" data-bind="attr: { href: '/ /DownloadDocument?documentId=' + DocumentId }">Download</a> </td> </tr> </table> <!-- /ko -->
Below is a piece pulled out of the jqgrid wiki describing the operators used.
This option is used only in advanced single field searching and determines the operation that is applied to the element. If not set all the available options will be used. All available option are: ['eq','ne','lt','le','gt','ge','bw','bn','in','ni','ew','en','cn','nc'] The corresponding texts are in language file and mean the following: ['equal','not equal', 'less', 'less or equal','greater','greater or equal', 'begins with','does not begin with','is in','is not in','ends with','does not end with','contains','does not contain'] Note that the elements in sopt array can be mixed in any order.
Want to create a custom Insert String Action like the above :Session[key] and :Default[None]? This can be done by creating an UpdateString Resolver as seen in the session example below:
public class SessionUpdateStringResolver : UpdateStringResolver { public override bool IsMatch(string key) { Regex reg = new Regex(":Session\\[(?<key>[^']+)\\]"); return reg.IsMatch(key);
} public override object Resolve(string key) { Regex reg = new Regex(":Session\\[(?<key>[^']+)\\]"); return HttpContext.Current.Session[reg.Match(key).Groups["key"].Value]; } }
There are two ways for GridConstruction control from the controller. These methods are PostGridConstruction and PreGridConstruction.
jqp.PostGridConstruction.Add((o, e) => { e.GridSettings.global["thisIsCustom"] = "customSetting"; e.GridSettings.columns.ProductID.hidden = false; });
There are two way for GridSearchQuery Control control from the controller. These methods are PostGridQuery and PreGridQuery.
settings.PreGridQuery.Add((o,e) => { return e.Query.Cast<Product>().Where(p => p.ListPrice > 50); }); /* or */ settings.PreQueryByTable<Product>(p => p.ListPrice > 50);
After Setting up the JQGridPreSearchSettings object pass it in as the last parameter in the GridSearch(or equivalent) method.
return GridSearch(e => e.Products.Include("ProductCategory"), e => e, set => set.Select(p => new { p.ProductID, p.Name, p.ListPrice, p.ModifiedDate, Category = p.ProductCategory.Name, CategoryModifiedDate = p.ProductCategory.ModifiedDate }), .....,jqp);
*****************************************************************************
More documentation to comeIncluding tabbed sub content & more Future Features
Export Grid Data as Pdf/Excel Better Printing Support Including Templating JqGrid Data using Jquery Tmpl into a manageable print structure. Navigator Customizability Tree Grid Support