Mar 15 2013

Data Moving Plugin Controls

Category: Asp.net | MVC | WebApiFrancesco @ 00:09

Data Moving Plugin Controls

Data Moving Plugin Styling

Data Moving Plugin Forms: Detail Views, Data Filtering UI, and Undo/Redo Stack

Single Page Applications 1: Manipulating the Client Side ViewModel

Single Page Applications 2: Validation Error Handling

Single Page Applications 3: TreeIterator

The Data Moving Plug-in is in its RC stage and will be released to the market within about one month. This is the first of 6 introductory tutorials with associated videos about it.

All Controls provided in the Data Moving Plug-in are compatible with both mouse based devices and with touch devices. Moreover all controls are accessible, and the user can interact with them with the only support of the keyboard.

From a conceptual point of view all controls can be classified into two big categories: Client Controls and Server Controls. Client Controls are bound to client side javascript data and create their Html dynamically on the client side, while the Html of Server Controls is rendered on the server side using data contained in a server side ViewModel.

The main advantages of server controls is that their ”static” Html is visible to the search engines, and that they require less “job” on the client side so they are efficiently rendered also by low performance browsers (such as the browsers of low quality mobile devices). On the other side client controls offer more flexibility, and a better interaction with all other html elements of the page. 

The Data Moving Plug-in contains both type of controls, in order to fit better the developers needs. Typically, server side controls communicate with the server through form submits, or through ajax posts that return new Html. Below a grid that filters, pages,sorts and updates data through form submits:

ServerGrid

Item editing may be performed either on line (on the rows themselves) or through a detail window that appears when the user clicks the edit button that is on the left of each row:

EditWindow

A similar windows is used also to filter data.

All these features come at 0 cost since they require no coding. We just need to specify the services required through a fluent interface:

  1. .DeclareQueryResolver(Html,  m => m.PreviousQuery, enablePrevPage: true,  enablePageSize:true)
  2. .CreateDynamicSubmitRetrievalManager("GridExample.QueryModel.RM", actionUrl: Url.Action("IndexQuery", "GridsDemo"),  newPrefix: "")
  3. .DetailFilterBuilder(out detailBuilder)

DeclareQueryResolver specifies the part of the server side  ViewModel where to store all query information (sorting, filtering, etc). The Data Moving Plugin offers the QueryViewModel<T> pre-defined class for this job but the user may choose to configure a custom class. We need just to add this class to our ViewModel, and the Data Moving Plug-in takes care of everything else. The services required (filtering, paging, sorting) are specified with optional arguments of the call: as default all services are enabled. The third argument of DeclareQueryResolver enables the user choice of the page size, while the second argument requires that the paging engine “remembers” the previous page, so that if a page change fails it can return to the previous page. Once we have enabled paging with DeclareQueryResolver, it is enough to add a pager column to a toolbar to have the a working paging service:

  1. .AddPagerColumn("FullCompletePager")

The name passed as argument to the pager column is a reference to a pager “prototype” we defined in a configuration class.

In fact, in order to simplify controls coding and to allow reusability of code, the developer has the option to store controls “options” in a configuration class by defining both controls “prototypes”, and row “prototypes”. Prototypes, may contain all control or row settings that are not specific for a particular data item.

Since, we required user choice of the page size, the pager will automatically show also a textbox to enter the page size.

The CreateDynamicSubmitRetrievalManager methods gives information on how to send queries to the server. The first parameter is a javascript property where to store the RetrievalManager javascript object that will take care of managing all queries-related communication with the server(this is useful if we would like to customize its behavior on the client side). The second parameter is the url of the action method that processes the query, and finally the last argument specifies a possible prefix to add to the quey ViewModel.  This parameter is useful if the model accepted by the action method is not just a QueryViewModel<T>, but  a more complex model containing QueryViewModel<T>  in one of its properties. In our case (and in most of the cases) the prefix must be empty.

The DetailFilterBuilder method provides an object we may use to render the edit/detail window. For more information about edit/detail windows please refer to Data Moving Plugin Forms: Detail Views, Data Filtering UI, and Undo/Redo Stack.

On the server side the query processing is straightforward:

  1. public ActionResult IndexQuery(QueryViewModel<HumanResource> query) //read-edit simple server grid
  2. {
  3.     prepareData();
  4.     IEnumerable<HumanResource> res = fakeDB.Get();
  5.     if (query != null)
  6.     {
  7.         res = query.ApplyToIEnumerable(res, true, true, pageSize);
  8.     }
  9.     var model = new TrackedGridDemoViewModel { Staff = res.Select(m => new Tracker<HumanResource> { Value = m, OldValue = m }), PreviousQuery = query, ViewSelector = null };
  10.     return View(selectViewTracked(model.ViewSelector), model);
  11. }

PrepareData just retrieve a fake Db (implemented with a dictionary)  stored in the Session. The query received from the client is applied to the fake Table with a single call to ApplyToEnumerable. In the case of an actual DB we must use the method ApplyToQueryable, instead.  Then all data are placed in the page ViewModel.

As you can see all items of the list obtained as result of the execution of the query are enclosed within a Tracker Object. The Tracker object enables Changes Tracking on the grid. There are two kind of server grids: the first one accepts directly a list of Tracker<T> objects and takes care automatically of changes tracking, the other one needs and adapter that adds the needed changes tracking capabilities. Below how to use the adapter:

  1. var h = Html.TrackedListRendering(m => m.Staff);
  2. var dBuilder = h.SimpleGrid(m => m.Values, false, true)
  3. .EnableAlternateStyle()
  4. ...........

As you can see the call to the adapter TrackedListRendering returns an Html helper that we can use to render the grid.

The user can add, delete and modify rows. When he hits the submit button, the whole form the Grid is enclosed in is submitted to the server. Below the Action Method that processes this submit:

  1. //Action methods to process item changes
  2. [HttpPost]
  3. public ActionResult TrackedIndex(TrackedGridDemoViewModel model)
  4. {
  5.     prepareData();
  6.     
  7.     if (ModelState.IsValid)
  8.     {
  9.         ModelState.Clear();
  10.         if (model != null)
  11.         {
  12.             IList<HumanResource> inserts, deletes, modified;
  13.             model.Staff.GetChanges(out inserts, out deletes, out modified, m => { m.Id = Guid.NewGuid(); });
  14.             fakeDB.Insert(inserts); fakeDB.Delete(deletes); fakeDB.Modify(modified);
  15.             QueryViewModel<HumanResource> startQuery = model.PreviousQuery != null ? model.PreviousQuery :  new QueryViewModel<HumanResource>().AddSortCondition(m => m.Surname, OrderType.Ascending);
  16.             model = new TrackedGridDemoViewModel { ViewSelector = model.ViewSelector, Staff = startQuery.ApplyToIEnumerable(fakeDB.Get(), true, true, pageSize).Select(m => new Tracker<HumanResource> { Value = m, OldValue = m }), PreviousQuery = startQuery };
  17.         }
  18.  
  19.     }
  20.     else
  21.     {
  22.         if (model != null && model.PreviousQuery != null)
  23.         {
  24.             model.PreviousQuery.Page = model.PreviousQuery.PreviousPage;
  25.             model.PreviousQuery.SortExpression = model.PreviousQuery.PreviousSortExpression;
  26.         }
  27.     }
  28.     return View(selectViewTracked(model.ViewSelector), model);
  29. }

 

Since we enclosed our items within Tracker objects we are entitled to call the GetChanges method that computes all changes in the IEnumerable items and returns the three lists of all inserted, deleted and modified items. The fourth argument of the GetChanges method, if provided, must be am Action that is applied to all inserted elements. Typically it is used to add a principal key to all Inserted elements (when this is possible before accessing the database).

 

In case we use a Client Side Grid the first step is to move a part of the Server Side ViewModel to the client side, and to get an Html helper that has the capabilities to render client side controls. Both tasks are accomplished with a simple call to the SendToClient method:

  1. @{var h = Html.SendToClient(m => m.ClientModel, "GridDemo.ViewModel");}

The second argument just says where to store the Client Side ViewModel.

Now we may use h to specify all options of our grid and to render it:

  1. var dBuilder = h.SimpleGrid(m => m.StaffToShow, true, true, hasRowDisplayAndRowEdit: true).......

Since our client side grid uses OData for the queries the method calls that enable the query engine are a little bit different:

  1. .CreateStandardQueryResolver(Model.PreviousQuery, enablePrevPage: true, enablePageSize: true)
  2. .StartODataRetrievalManager(false, "GridExample.QueryModel.RM", (uint)Model.PreviousQuery.PageSize, actionUrl: Url.HttpRouteUrl("DefaultApi", new { httproute = "", controller = "GridsDemoApi"}))

In this case we call the CreateStandardQueryReoslver method instead of DeclareQueryReolver because the server side models doesn’t need to contain any object to store query information, since all query handling is done on the client side. Accordingly an instance of QueryViewModel<T> is just created and inserted in the Client Side ViewModel in a way that is completely transparent to the developer. However, in our case we add an instance of QueryViewModel<T> also to the server side ViewModel: this instance of QueryViewModel<T> doesn’t take part to any query processing but we use it just to initialize the client side query by passing it as first parameter to the CreateStandardQueryReoslver.

StartODataRetrievalManager specifies the use of a RetrievalManager that is able to communicate with OData points. This javascript RetrievalManger object is stored in the GridExample.QueryModel.RM property. The first argument of StartODataRetrievalManager  set to false says we dont want to issue an initial query immediately after the page is rendered because the initial data have already been passed in the initial Client Side ViewModel. The third argument just says the initial page size to use for paging: this value will probably change during the grid operation since we enabled user choice of the page size. Finally, the last argument is the url of the WebApi controller that processes the queries.

Client grids are able to send items changes to the server with a simple form submit in exactly the same way a server grid would do, but the preferred way to communicate with the server is by sending data in json format with an ajax call. This job is done by the updatesManager javascript class that takes care of computing changes on the client side and to send Inserted, updated, and deleted items to the server in json format (just the keys of the deleted elements are sent to the server).

No javascript coding is required to use an updatesManager class; we need just to declare it with a fluent interface:

  1. .CreateUpdatesManager("UM").BasicInfos(m => m.Id).IsRoot(Url.HttpRouteUrl("DefaultApi", new { httproute = "", controller = "GridsDemoApi" })).EndUpdatesManager()

 

UM is the name of the property of the Client Side ViewModel where to store the newly created updatesManager class, m => m.Id specifies the principal key of the items. Finally, the IsRoot call specifies that this is a root updatesManager that takes care of “interfacing” directly the controller. In fact, there might be also children updatesManagers that takes care of the children of a one-to-many relation, and that delegate the communication with the server to the updatesManager of the father items. The only argument of IsRoot is the url of the action method that processes the updates. Children updatesManagers are discusses in this video.

The controller receives a standard OpenUpdater<M,K> model where M is the type of the items and K is the type of their principal key. In case the page contains several grids and/or several type of entities the controller may receive a complex model containing several OpenUpdater<M,K> inside different properties and possibly also other data the client needs to send to the server. For more information on complex updates please refer to Single Page Applications 1: Manipulating the Client Side ViewModel.

Processing of the OpenUpdater<M,K> is done quite automatically by the ApiResponseBuilder class that takes care of building a response in the format accepted as response by the updatesManger. This response might contain either validation errors or the keys of the Inserted elements computed by the server. In case of a complex model with several OpenUpdater<M,K> the ApiResponseBuilder is able to build a complex response whose parts will be dispatched adequately once the response reaches the client side. The way ApiResponseBuilder handles complex models is detailed in Single Page Applications 1: Manipulating the Client Side ViewModel.

In our simple case:

  1. public HttpResponseMessage Post(OpenUpdater<HumanResource, Guid?> model)
  2.         {
  3.             prepareData();
  4.             //ModelState.AddModelError("", "test error");
  5.             if (ModelState.IsValid)
  6.             {
  7.                 try
  8.                 {
  9.                     var builder = ApiResponseBuilder.NewResponseBuilder(model, ModelState, true, "Error in client request");
  10.                     builder.Process(m => m, m => m.Id);
  11.                     //call to business layer
  12.  
  13.                     fakeDB.Insert(model.Inserted); fakeDB.Delete(model.Deleted); fakeDB.Modify(model.Modified);
  14.                     //
  15.                     var response = builder.GetResponse();
  16.                     return response.Wrap(this.Request);
  17.                 }
  18.                 catch (Exception ex)
  19.                 {
  20.                     ModelState.AddModelError("", ex.Message);
  21.                     return
  22.                         Request.CreateResponse(System.Net.HttpStatusCode.InternalServerError,new ApiServerErrors(ModelState));
  23.                 }
  24.             }
  25.             else return
  26.                 Request.CreateResponse(System.Net.HttpStatusCode.InternalServerError, new ApiServerErrors(ModelState));
  27.         }

In case there is no error we create an ApiResponseBuilder object with a call to NewResponseBuilder. We pass to this object both, the whole model, the ModelState and a default error message to be used in case of hill formed requests. The third argument set to true, require to throw an exception in case of hill formed requests.

The Process method performs all needed key processing for the newly inserted elements. In our case the processing is simple since we have no one-to-many relations, so we need just to provide principal keys to the newly inserted elements. The first argument selects what OpenUpdater<M,K> to process (this argument is needed to handle also complex models with several OpenUpdater<M,K>); in our case it is trivial: m => m. The second argument selects the principal key of the items contained in the selected OpenUpdater<M,K>. A function that receives as input an item and returns a newly computed principal key for it may be passed as third optional argument. Since we have not provided this function, the default Guid key generator is used.

In case of errors we create an ApiServerErrors object that extracts automatically all errors from the ModelState and put them into an Api response with the Internal Server Error header.

In case the receiving controller is not a WebApi controller but a standard Mvc controller we have to substitute the ApiResponseBuilder class with the ResponseBuilder class and the ApiServerErrors class with the ServerErrors class. Moreover the response must be returned directly with return Json(response), and the errors with return Json(new ServerErrors(ModelState)).

 

The way to style and decide the appearance of both server and client grids is substantially the same. We may specify options with a fluent interface, add toolbars, and  define data rows options.

Both toolbars and data rows are composed of columns that are arranged according to a row template. The developer may choose between three pre-defined row templates:

  1. A <table> based template that may be used in case of tabular data.
  2. A <div style=”display: table”…based template if we need a table like appearance but the data are not “tabular”
  3. A div/float based template.

Moreover, the developer can furnish also its own custom row template, that either arranges the pre-existing column definitions or renders directly the item without taking care of column definitions. There are different types of columns:

  1. Standard columns. Their default template may be controlled with a fluent interface. They are able to render properly any .Net data type both in edit and in display mode. We may choose to use the Mvc Display and Edit templates defined for the given types, or Display only, and TypedTextBoxes based templates that are automatically optimized for each data type. It is also possible to specify the use of the new Html5 inputs; the selection of the input type is done according to the column data type and to a possible DataType attribute that may decorate the property associated to the column.
  2. Image columns. They extracts both the src and the alt of the image from the item data;
  3. Link columns capable of extracting both the href an the link text from the item data.
  4. Text Columns, to render constant text or constant Html.
  5. Button Columns, to render single buttons or buttons organized in bars.
  6. Pager Columns, to display a pager.
  7. RowCount columns, to count rows
  8. Error Column, to display errors associated to the whole row.

 

All columns have standard Header, Footer, edit, Display, Edit Detail, Display Detail,  and Filter templates that can be customized with a fluent interface, but for each of them the developer may provide a custom template.

Below the definition of a toolbar that contains a a button that opens the filter data dialog, and a pager:

  1. .StartToolbar(false)
  2.         .RowContainerLevel(ItemContainerLevel.Row)
  3.         .RowHeight("3em", true)
  4.         .AddButtonToolbar(ShowButtonIn.Both, CommandButtonType.FilterWindow).ColumnWidth(40).EndButtons()
  5.         .AddPagerColumn("FullCompletePager")
  6.         .EndToolBar()

 

Below the definition of a data row:

  1. .AddRowType(true, true)
  2.             .DeclareQueryResolver(Html,  m => m.PreviousQuery, enablePrevPage: true,  enablePageSize:true)
  3.             .CreateDynamicSubmitRetrievalManager("GridExample.QueryModel.RM", actionUrl: Url.Action("IndexQuery", "GridsDemo"),  newPrefix: "")
  4.             .DeclareDisplayStore()
  5.             .DetailFilterBuilder(out detailBuilder)
  6.             .RowContainerLevel(ItemContainerLevel.Row)
  7.             .DeclareInvisibleInnerRowContainer()
  8.             .HtmlTitle(ShowTitleIn.Display | ShowTitleIn.Edit)
  9.             .RowHeight("3em", true)
  10.             .AddRowCountColumn(false, width: 4, widthIsPercentage: true)
  11.             .AddButtonColumn(ShowButtonIn.Both, CommandButtonType.EditWindow, "GridExample.selectItem GridExample.unselectItem").ColumnWidth(4, true).HorizontalAlign(MVCControlsToolkit.Controls.Core.HAlign.center).EndButtons()
  12.             .StartColumn(t => t.Name, width: 46, widthIsPercentage: true).Queries(true, true).HorizontalAlign(MVCControlsToolkit.Controls.Core.HAlign.center).EndColumn()
  13.             .StartColumn(t => t.Surname, width: 46, widthIsPercentage: true).Queries(true, true).HorizontalAlign(MVCControlsToolkit.Controls.Core.HAlign.center).EndColumn()
  14.             .AddColumn(t => t.Address, true)
  15.             .AddImageColumn(t => t.AvatarAlt, t => t.AvatarUrl, new { style = "width: 100px; height: 100px;" }, true)
  16.             .StartColumn(t => t.Id).DeclareHidden().EndColumn()
  17.         .EndRowType();

 

It contains the query related methods we already discussed before, the DeclareDisplayStore() method call that require that the rows will work just in display mode, but that they store fields values in hidden fields, so that the row content can be edited with the help of an edit window.

The HtmlTitle() method requires to add an Html title attribute to all columns with the same content of the column field (useful if the content of the field is not fully visible, but it appears “dotted”). There is a RowCountColumn, and a ButtonColumn that opens an edit row dialog.

Then, we have the Name and Surname field columns. Both of them make the call Queries(true, true), that enables both sorting and filtering on them.

Interesting also the Image column for the avatar. It extracts both the src, and the alt from the item. The last argument set to true specifies that the image column must appear just in the detail window.

Finally, The column for the principal key is declared hidden because we don’t want to show the Id, but just to store it into an hidden field.

The Data Moving Plug-in includes 4 different types of grids(two are server side controls and the other two are client side controls), a TreeGrid/TreeView, and a menu control. The TreeGrid/TreeView working is shown in this video. The menu control may be displayed both horizontally and vertically; it works both with mouse hover or with mouse click (or touch in case of touch devices). Moreover, also all menu items use the Data Moving Plug-in row templates engine we described before for grids.

The Data Moving Plugin contains also a powerful Form Control, with undo/redo capabilities, and a DualSelect to select items from a pre-existing list, that are able to work both as server side controls and as client side controls. Both of them are described in Data Moving Plugin Forms: Detail Views, Data Filtering UI, and Undo/Redo Stack and in its associated video.

Included in the plug-in also powerful knockout bindings for jqPlot and tinyMce. Moreover, tinyMce has been enhanced with a “documents template engine” that give us the possibility to define document templates containing “variables” that can be instantiated either with the content of other input fields or with the content of javascript data structures. This video shows both the enhancements provided to jQplot, and tinyMvce and the documents template engine.

Finally, the developer has access also to all Mvc Controls Toolkit controls, such as TypedTextBox, TypedEditDisplay, DateTimeInput, etc. They can me mixed with all Data Moving Plug-in controls and they can be used also in all template definitions.

Below a video that shows a review of all Data Moving Plug-in Controls:

That’ all for now!

Stay tuned and give a look also to all other Data Moving Plugin introductory tutorials and videos

                      Francesco

Tags: , , , , , ,

Nov 14 2012

Data Moving Plug-in

Category: MVC | Asp.net | WebApiFrancesco @ 01:05

The new “Data Moving” Mvc Controls Toolkit plug-in is next to be released. In this post we will review its main and more interesting feautures.

The “Data Moving” plug-in is a complete set of out-of-the-box controls for Asp.Net Mvc, that can be styled with the jQuery UI styling framework, plus some interaction primitives. It contains, a menu 4 kind of grids a TreeView/TreeGrid and a powerful Mvc/knockout interface for jqPlot, and tinyMce. All Item controls can be configured with a simple fluent interface.

All Items controls have facilities to page, filter and sort data, either on the client side or on the server side, and for sending data to the server  in json format, or with normal (or Mvc ajax) form submits. Data from different controls may be grouped into a single client-side ViewModel before being sent to the server, in a quite automatic and simple way. There is also a Detail Form to be used either alone or to show the details of any item of an items control, and a fully out-of-the-box dual select box. The Detail Form has an undo/redo stack, and all items controls have changes tracking and undo capabilities, so that we can submit to the server just the changes done on all items.

 

However, the more interesting feature of this plug-in is that it allows users to modify complex data structures by dragging and manipulating UI elements. This means that all controls may interact among them and with other Html nodes on the page. Interactions can be triggered either programmatically or by means of Drag and Drop operations. Drag-Drop interactions are defined by declaring DragSources:

  1. public static MvcHtmlString DragSource(this HtmlHelper htmlHelper, string selector, IEnumerable<string> offer=null, DataDragOptions options=null)

 

  1. public static MvcHtmlString DragSourceItems(this HtmlHelper htmlHelper, string rootSelector, string itemsSelector, IEnumerable<string> offer=null, DataDragOptions options = null)

 

and DropSources:

  1. public static MvcHtmlString DropTarget(this HtmlHelper htmlHelper, string selector, IEnumerable<string> accept, DataDropOptions options = null)

 

  1. public static MvcHtmlString DropTargetItems(this HtmlHelper htmlHelper, string rootSelector, string itemsSelector, IEnumerable<string> accept, DataDropOptions options = null)

 

The helpers ending in “Items” are conceived to operate on list of items, where new items can be created. Namely they declare as Drag or Drop targets all Html nodes satisfying the the jQuery selector itemsSelector that are descendants of Html nodes satisfying the jQuery selector rootSelector. Also newly created descendants satisfying itemSelector are immediately Drag or Drop enabled. Their typical application is defining all(…or some, depending on itemsSelector) items of a grid or of a TreeView/TreeGrid as Drag or Drop targets.

offer and accept specify respectively what are the “infos” offered and what are the infos “accepted”. Only if there is a match between these two lists a drag and drop operation is possible. DataDragOptions and DataDropOptions specify several options of the operation, that include both pure graphic effects and processing callbacks(the result of a callback may also force the failure of the operation).

The more important property of DataDropOptions is:

  1. public DropBehaviour Behaviour { get; set; }

where:

  1. public enum DropBehaviour {DataBind=0, DataSync=1, InsertBefore=2, InsertAfter=3, Append=4, Replace=5};
  • DataBind. A DataBind operation is performed between the DragSource and the DopTarget: The content of input fields or complex controls of the DropTarget is filled with the content of the input fields/controls of the DragSource that match them. Input fields and complex controls content are processed in .Net Type aware fashion. The way input fields/controls match is decided according to naming conventions, declarations contained in Html5 data- attributes, and/or string expression contained in the custom Reference knockout binding
  • DataSync. A binding occurs as before, but in this case a bidirectional communication channel is built between the matching elements so that any change performed on one element is immediately reflected on the other ones.
  • InserBefore, InsertAfter, Append, Replace. A new item is created and inserted respectively before or  after the DropTarget, or as last element of the DropTarget siblings, or as a replacement for the DropTarget. After , a binding is performed between the DragSource and the newly created element.
    The way the new item is created depends on some item creation capability that is attached in someway  to the father of the DropTarget and on a selection callback specified in the DataDropOptions. For instance, if the DropTarget is an item of a grid the item is created by using the “create new item” capability of the grid, and if the grid has several items templates the template is chosen with the help of the selection callback. The Data Moving plugin  can take advantage also of the new item creation capabilities of all standard knockout bindings like the template binding.

Some controls have such as the SimpleGrid and the TreeView/TreeGrid have native item moving capabilities that are completely independent from the above Drag-Drop primitives.

The video below shows some of the Drag-Drop/Interaction that we have briefly described. The first example shows Reference binding based interactions that work only with knockout powered  controls and Html nodes. While the second example shows Html5 attributes based interactions.

See this video in High Resolution on the Data Moving Plug-in site

It is worth to say that all operations shown in the video works also on Tablets and mobile phones.

The Drag and Drop operation between a Customer entry and an E-Mail that is described in the previous video, and that results in the creation of a new E-Mail containing all relevant customer information is obtained with the following code:

  1. @Html.DragSourceItems("#Directory", ".directoryEntry", new string[] { "ContactInfos" },
  2.                         new DataDragOptions {
  3.                             DestroyOriginal=true,
  4.                             DestroyCallback = DataDragOptions.RemoveItemFromControl,
  5.                             Opacity = 0.8F
  6.                     
  7.                         })

 

  1. @Html.DropTargetItems("#Messages", ".internal-data-row",
  2.                 new string[] { "ContactInfos" },
  3.                 new DataDropOptions{
  4.                     Behaviour = DropBehaviour.InsertBefore,
  5.                     HoverClass = "HoverTarget",
  6.                     ActiveClass = "AcceptableTarget"
  7.                 })

 

Quite easy! A few lines of simple code, a so powerful effect!

 

This other video shows the TreeView dragging and node-moving capabilities, and how to submit just the changes to the server

See this video in High Resolution on the Data Moving Plug-in site

 

In the above video all changes are submitted in json format by some instances of the updatesManager class. These instances are created automatically by the TreeView with the help of a few information supplied with a fluent interface:

  1. .CreateUpdatesManager<HumanResourcesChangesViewModel>("DepUpdater").BasicInfos(m => m.Id, m => m.Departments).IsRoot(Url.Action("HumanResourcesChanges", "TreeView")).EndUpdatesManager()

 

The above settings define the main updatesManager of the left tree in the example. It takes care of the communication with the action method. They specify just the collection to work with(Departments), its principal key(Id) and the Url of the receiving action method.

  1. .CreateUpdatesManager<HumanResourcesChangesViewModel>("TeamUpdater").BasicInfos(m => m.Id, m => m.Teams).IsNotRoot(m => m.Father, 0).EndUpdatesManager()

 

  1. .CreateUpdatesManager<HumanResourcesChangesViewModel>("PersonUpdater").BasicInfos(m => m.Id, m => m.Persons).IsNotRoot(m => m.Father, 1).EndUpdatesManager()

The last two lines of code define the updatesManagers that takes care of all children entities of the left tree in the example. The children entities changes are inserted in the same ViewModel of the main entities that are in the root of the three so that they are submitted to the same action method of the main entities. Accordingly, the parameter containing the Url of the action method is substituted with a parameter containing row they are children of (0 and 1 in the above examples) and with another parameter containing the external key(Father, for both updatesManagers).

All items controls are  based on the concepts of rows and columns that can be configured exactly in the same way in all item controls. Rows are used both to show data, and to to build headers, footers, title bars and toolbars with the help of specialized columns such as: pager columns, parameters columns, button columns, etc. There are pre-defined buttons based on the jQuery UI styling framework icons, however the user may define custom icons, custom button operations and anything clickable with an Html5 data-operation attribute can be used as a button.

There are standard column templates and each control has its own default row template. Both of them can be configured easily with a fluent interface, but the developer can also override them with templates defined with partial views, Razor helpers, or simple code.

Below an example of a custom column template in the menu control:

CustomColumns

And the code to create it:

  1. .AddRowType()
  2.   .StartTextColumn("", htmlEncode:true).CustomTemplateFixed(TemplateType.Display, @<div id='switcher' class="ui-widget"></div>).EndColumn()
  3.   .AddTextColumn("<div id='themeBlender' style='width: 150px;' ></div>", htmlEncode:false)
  4. .EndRowType()

 

Below an example of custom Row template on the same menu:

CustomRow

And the code to create it:

  1. .AddRowType().CustomRowTemplateFixed(TemplateType.Display,
  2.   @<div class="about-info  ui-state-highlight">
  3.       <p>
  4.           MVCControlsToolkit & Data Moving are developed by Francesco Abbruzzese
  5.           <br />
  6.           @item.LinkFor(m => m.Text, m => m.Text, new { target=item.ViewData.Model.Target, tabindex="0"})
  7.       </p>
  8.       <p>
  9.           Copyright (c) 2010 Francesco Abbruzzese,
  10.           <br />
  11.           <a tabindex='0'  href="mailto:francesco@dotnet-programming.com">francesco@dotnet-programming.com</a>
  12.       </p>
  13.   </div>).EndRowType();

 

Another interesting feauture of DataMoving controls is the possibility to store controls and Row settings giving them a name. This way one can define named control styles that can be reused with completely different  type of datas  by “recalling” all their settings through their names.

For instance, the TreeView control can be shaped in very different ways, to implement also TreeGrids, and “classic” Grids.

Below a classic TreeView:

TreeView

And the code to store these settings with the “StandardTreeFile” name:

  1. h.ClientTreeView(m => m.SampleIEnumerableProperty).
  2.     TreeOptions().TreeViewStyle(TreeViewCssClasses.TreeViewFile).CustomNodeCssClasses(null, false).EndTreeOptions()
  3.     .SetAsDefault(DefaultScope.Named, "StandardTreeFile");

 

This is a TreeGrid implemented with the same control:

TreeGrid

And the code to store these settings:

  1. h.ClientTreeView(m => m.SampleIEnumerableProperty).
  2.     TreeOptions().DisableFatherSelection().CustomNodeCssClasses(null, false).EndTreeOptions()
  3.     .Container(ItemContainerLevel.Column, true, null, null, JQueryUICssClasses.ContentContainer)
  4.     .StartToolbar(false)
  5.     .RowContainerLevel(ItemContainerLevel.Row)
  6.     .AddTextColumn(null, null, 30, false, true)
  7.     .AddButtonToolbar(ShowButtonIn.Both, CommandButtonType.Add, "0 edit")
  8.     .AppendButton(ShowButtonIn.Both, CommandButtonType.GoEdit)
  9.     .AppendButton(ShowButtonIn.Both, CommandButtonType.GoDisplay)
  10.     .AppendButton(ShowButtonIn.Both, CommandButtonType.Delete)
  11.     .AppendButton(ShowButtonIn.Both, CommandButtonType.Undo)
  12.     .AppendButton(ShowButtonIn.Both, CommandButtonType.UndoWithChildren)
  13.     .ColumnWidth(180, false, true).EndButtons()
  14.     .EndToolBar()
  15.     .EnableWidthChange()
  16.     .EnableAlternateStyle()
  17.     .RootClass(TreeViewCssClasses.TreeViewCompact)
  18.     .SetAsDefault(DefaultScope.Named, "StandardTreeGrid");

Finally, a “classic” grid:

ClientGrid

And the code to store the associated settings:

  1. h.ClientTreeView(m => m.SampleIEnumerableProperty).
  2.                 TreeOptions().DisableFatherSelection().DisableTreeModifications().DisableOpenClose().TreeViewStyle(TreeViewCssClasses.TreeViewSimpleGrid).EndTreeOptions()
  3.                 .Container(ItemContainerLevel.Column, true, null, null, JQueryUICssClasses.ContentContainer)
  4.                 .EnableAlternateStyle()
  5.                 .EnableWidthChange(true)
  6.                 .ChildrenContainer(200)
  7.                 .StartToolbar(false)
  8.                 .RowContainerLevel(ItemContainerLevel.Row)
  9.                 .StartParameterColumn(0, 0).CustomColumnClass(GenericCssClasses.NoWrap).EndColumn()//Title
  10.                 .EndToolBar()
  11.                 .StartToolbar(false)
  12.                 .RowContainerLevel(ItemContainerLevel.Row)
  13.                 .AddButtonToolbar(ShowButtonIn.Both, CommandButtonType.Add, "0 edit")
  14.                     .CustomColumnClass(GenericCssClasses.NoWrap)
  15.                     .AppendButton(ShowButtonIn.Both, CommandButtonType.GoEdit)
  16.                     .AppendButton(ShowButtonIn.Both, CommandButtonType.GoDisplay)
  17.                     .AppendButton(ShowButtonIn.Both, CommandButtonType.Delete)
  18.                     .AppendButton(ShowButtonIn.Both, CommandButtonType.Undo)
  19.                     .AppendButton(ShowButtonIn.Both, CommandButtonType.UndoAll)
  20.                 .EndButtons()
  21.                 .EndToolBar()
  22.                 .SetAsDefault(DefaultScope.Named, "StandardClientGrid");

Some controls are server side, that is their Html is created on the server, and other controls are client side, that is their Html is created on the client side with the help of client templates. However, both kind of controls are programmed exactly in the same way, and use the same kind of rows or columns templates. The only difference being that in the case of client side controls the server templates are compiled automatically into client templates by the control.

The SendToClient helper can be used to get a new HtmlHelper capable of generating “Client Side code”(which includes alsoa Client-Side ViewModel).  It works as a normal helper, and there is no difference in the way it is used.

The chart control based on jqPlot and theTreeView are client side controls, there is also a pure server side grid, and a grid called SimpleGrid that is able to render in both ways, that is, it detects the kind of HtmlHelper and behaves accordingly. The SimpleGrid has less out-of-the box feautures but it allows an higher degree of control of the Html that is created.

Also the Detail Form control as all other remaining controls is able to work both as a server side and a client side control. 

All client side controls are based on knockout.js, and the developer have the freedom to mix freely client side and server side controls in the same page.

 

That’s all for now !

Be the first one to build a futuristic application with the DataMoving plugin! Companies wishing to start immediately pioneering projects based on the DataMoving Plugin can contact me for consulence

                                              

                                               Francesco

Tags: , , , ,

Nov 15 2011

Mvc Controls Toolkit Datagrid Updated Tutorial

Category: MVC | Entity Framework | Asp.netFrancesco @ 23:42

 

This post is an updated  tutorial on how to use the Update/Delete/Insert Templated datagrid of the MVC Controls Toolkit. It contains information already published in previous tutorials updated to the new versions of the MVC Controls Toolkit. The full code used in this tutorial can be found in the Mvc3 Razor folder of the zipped file BasicTutorialsCode  here

The commercial version of the toolkit contains datagrids with several more options, and that may be configured and styled more quickly with a fluent interface.

Our Data model is simply a Todo List with four fields: Name(varchar), Description(varchar), DueDate(date), and the principal key id of type identity. First, we define the Database, and a table named ToDo with the previous four fields.

Next, we create an Entity Framework(EF) model based on this database. We then create a MetaClass to handle the constraints on our ToDo items. Let say that the DueDate can range from 3  months before today to 6 months after today. Since today is not an absolute value such a constraint cannot be interpreted as a Data Layer constraint, therefore it sholdn’t be put with the other data constraints of the data layer. We will take care of it in a few minutes ! First lets take care of the other simpler constraints, and add to our project a new file named Todo.cs just under the Models folder. What we put in this file is quite standard:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
using MVCControlsToolkit.DataAnnotations;

namespace Mvc_Examples.Models
{
    [MetadataType(typeof(MetaToDo))]
    public partial class ToDo
    {

    }
    public class MetaToDo
    {
        
        [Required, CanSort, Display(Name="Name")]
        public object Name { get; set; }
        [Required, Display(ShortName = "Description")]
        public object Description { get; set; }
        [CanSort, Display(ShortName = "Due Date"), Format(DataFormatString="{0:D}")]
        public object DueDate { get; set; }
    }
}

The Display attribute is  a standard DataAnnotation attribute and it just declares the string to be used in the labels for our properties. The CanSort attribute comes from the MVC Controls Toolkit and it declares that the field can be used in sorting operations. We will come back on this later on, when we will speak about the sorting helpers of the MVC Controls Toolkit.

Now let go to the View Model! What do we need to put into our View Model? For sure the ToDo items extracted from the database. However, we would like the items to be pageable, so we have to insert at least a Page property containing the current page. A single Page int is enough for the MVC Controls Toolkit pager to work; for a better paging experience you can supply the total number of pages.

Now…the user may edit some fields and then he may change page. What happens if there are validation errors in his editing? A good option is to go back to the previous page and force him to correct the errors. Therefore, we have to remember what page we came from. Thus we need also a previous page property in our View Model. Luckily we don’t need to put it in a hidden field because our pager already offers this type of service when a “previous page” property is available.

Summing Up, our View Model has the following properties: CurrPage, PrevPage, the optional TotalPages, plus a ToDoList property containing all ToDo items.

Now we have to decide how to handle the strange constraint on the DueDate. One way to handle it is to define a View Model version of the ToDo class where we can apply this Presentation Layer specific constraint. We can use the Mvc Controls Toolkit DateRangeAttribute, that offers the ability to define constraints based on expressions containing “Today” or “Now”:

[MetadataType(typeof(MetaToDo))]
    public partial class ToDoView
    {
        public int? id { get; set; }

        public string Name { get; set; }

        public string Description { set; get; }

        [DateRange(SMinimum = "Today-3M", SMaximum = "Today+6M")]
        public DateTime DueDate {set;get;}

       

    }

Note that we have used the same MetaData class of the original ToDo class. This is important in order to avoid code duplications! As a general rule when we build a View Model either we use  the original data classes, or for  each of the original data classes we define a ViewModel version of it and use it as a child of the page ViewModel. This way, we can use the same metaclasses. You should avoid putting fields from more than one data class into a single View Model Class. An exception to this rule is if data are someway aggregated and transformed into other properties. It is preferred to keep a one to one correspondence between the original data classes and the classes used in the View Model; this way we increase modularity and reuse the metadata classes of the original data classes(duplicating code often causes a lot of problems…).

Our next step is the design of the data access procedures. A best practices is to not insert them into the controller methods, because they might be useful to more than one controller, and because it is always better to keep the data layer separate from the controller layer. We create them as static methods of our View Model. This approach is the standard for accessing ToDo items (we will use it each time paged ToDo items are needed). This choice is acceptable for this simple application, but in different situations we might have introduced a new Repository class.

Summing up our View Model contains:

public int TotalPages { get; set; }
public int CurrPage { get; set; }
public int PrevPage { get; set; }
public List<Tracker<ToDoView>> ToDoList {get; set;}

Plus two static methods: GetToDoPage and UpdatePage, for retrieving one page of data, and for passing to the database the updates made by the user to one page of data.

In order to help passing changes to the database the MVC Controls Toolkit provides the class Tacker<T> that is a wrapper put around a data item. It maintains two versions of the data item, the original version and the one with the changes applied by the user. It also has a Boolean value Changed to signal that the two versions are different.

Comparison between the two versions yields the operation to be done on the database:

  • Old version null and new version non-null: Insert
  • Old version non-null and new version null: Delete
  • Both versions non-null: we have an update

Lets analyze the GetToDoPage method:

public static List<Tracker<ToDoView>> GetToDoPage(int pageDim, out int totalPages, ref List<KeyValuePair<LambdaExpression, OrderType>> order, int page = 1)
{
    List<Tracker<ToDoView>> result;
    if (order == null)
    {
        order = new List<KeyValuePair<LambdaExpression, OrderType>>();
       
    }
    if (order.Count == 0)//paging require ordering! Therefore we always need to add a default oredering
    {
        Expression<Func<ToDoView, DateTime>> defaultOrder = m => m.DueDate;

        order.Add(new KeyValuePair<LambdaExpression, OrderType>(defaultOrder, OrderType.Descending));
    }
    using (SiteDbEntities context = new SiteDbEntities())
    {
        int rowCount = context.ToDo.Count();
        
        if (rowCount == 0)
        {
            totalPages=0;
            return new List<Tracker<ToDoView>>();
        }
        totalPages = rowCount / pageDim;
        if (rowCount % pageDim > 0) totalPages++;
        if (page > totalPages) page = totalPages;
        if (page < 1) page = 1;
        int toSkip = (page-1) * pageDim;

        
            result = context.ToDo.Select(item =>
                new ToDoView() { Name = item.Name, Description = item.Description, DueDate = item.DueDate, id = item.id }).ApplyOrder(order).Select(viewItem =>
                new Tracker<ToDoView>
                        {
                            Value = viewItem,
                            OldValue = viewItem,
                            Changed = false
                        }).Skip(toSkip).Take(pageDim).ToList();
        
    }
    return result;
}

The return is of type List<Tracker<ToDoView>> since we have put our wrapper around each data item. We create a context, with the using keyword to guarantee it will be disposed at the end of the operation (because it contains a connection to the database that is not a managed object).

As a first operation we count the total rows, then we do some mathematics to compute the pages: the remainder operation % is needed to take into account partially filled pages. In order to reach the request page we use the Skip and Take methods. In the select construct we create our wrapper and fill it with two copies of the same data item; one for the previous and one for the updated version of the data item. Note we transfer data from the ToDo object into a fresh ToDoView object.

The order parameter contains field sorting information that are passed as a list of couples. Each couple contains a lambda expression that specifies a field, and an OrderType value that specifies the kind of sorting (ascending or descending). The sorting information is passed to the controller by the EnableSortingFor helper (discussed later in in this post). The MVC Controls Toolkit defines extension methods to apply the sorting information to any IEnumerable or IQueryable. You only need to include the namespace: MVCControlsToolkit.Linq and any IEnumerable or IQueryable will be enriched with the method ApplyOrder that accepts the above list as argument. If an ordering is already defined on either the IEnumerable or the IQueryable the new sorting will be chained Lexicographically with it.

The full code contains also the possibility for filter data. The interested reader may refer to my new tutorial on Data Filtering: Advanced Data Filtering Techniques in the Mvc Controls Toolkit and to my previous tutorial: Data Filtering in the New Mvc 3 Version of The Mvc Controls Toolkit

The UpdatePage method is a little bit more complex, but quite easy, too:

public static void UpdatePage(List<Tracker<ToDoView>> items)
{
    if (items == null) return;
    using (SiteDbEntities context = new SiteDbEntities())
    {
        bool aChange = false;
        foreach (Tracker<ToDoView> item in items)
        {
            if (item.Changed)
            {
                if (item.OldValue == null) //insertion
                {
                    if (item.Value != null)
                    {
                        ToDo curr=new ToDo()
                                 { Name = item.Value.Name, Description = item.Value.Description, DueDate = item.Value.DueDate };
                        aChange = true;
                        context.ToDo.AddObject(curr);
                    }
                }
                else if (item.Value == null) //deletion
                {
                    ToDo curr=new ToDo() { Name = item.OldValue.Name, Description = item.OldValue.Description, DueDate = item.OldValue.DueDate, id=item.OldValue.id.Value };
                    context.ToDo.Attach(curr);
                    context.ObjectStateManager.ChangeObjectState(curr, System.Data.EntityState.Deleted);
                    aChange = true;
                }
                else//update
                {
                    ToDo curr = new ToDo() { Name = item.Value.Name, Description = item.Value.Description, DueDate = item.Value.DueDate, id=item.Value.id.Value };
                    context.ToDo.Attach(curr);
                    context.ObjectStateManager.ChangeObjectState(curr, System.Data.EntityState.Modified);
                    aChange = true;
                }
            }
        }
        if (aChange)
        {
            try
            {
                context.SaveChanges();
                items.ForEach((item) => { item.Confirm(); });//confirm changes have been passed
            }
            catch
            {
            }
        }
    }
}

We have a loop on all modified items where we verify if the item has changed, and if it has changed we analyze what operation needs to be passed to the database as previously.explained. Note the use of the ObjectStateManger to set the correct state of the various objects in the different cases. Insertion is the only case that doesn’t require manually setting the state of the object.

If at least a data item has changed we do a SubmitChanges() that passes all changes in a single transaction to the database. If no exception occurs we call the Confirm() method of the Tracker<T> wrapper that sets the old version of the data item  equal to the new version of it since all changes have been persisted in the database. In case of exceptions an error message should be returned to the controller that instructs the user to make corrections and retry the post. We have not handled this to keep the sample simple.

Now we can go to the controller that has just two Action methods, one for handling the initial HttpGet and the second for handling the subsequent HttpPost:

public const int PageDim=5;//in actual application this should be put in a config file
public ActionResult Index()
{
    int totalPages;
    List<KeyValuePair<LambdaExpression, OrderType>> order = null;
    ToDoViewModel result = new ToDoViewModel()
        {
            ToDoList = ToDoViewModel.GetToDoPage(PageDim, out totalPages, ref order),
            TotalPages = totalPages,
            CurrPage=1,
            PrevPage=1,
            ToDoOrder=order
        };
    return View(result);
}

[HttpPost, System.Web.Mvc.OutputCache(NoStore = true, Duration = 0, VaryByParam = "*")]
public ActionResult Index(ToDoViewModel model)
{
    if (!ModelState.IsValid)
    {
        
        model.CurrPage=model.PrevPage; //cancel possible page change and force correcting errors
        return View(model);
    }
    else
    {
        ModelState.Clear();
        ToDoViewModel.UpdatePage(model.ToDoList);
        int totalPages;
        if (model.CurrPage < 1) model.CurrPage = 1;
        List<KeyValuePair<LambdaExpression, OrderType>> order = model.ToDoOrder;
        ToDoViewModel result = new ToDoViewModel()
        {
            ToDoList = ToDoViewModel.GetToDoPage(PageDim, out totalPages, ref order, model.CurrPage, model.ToDoFilter),
            TotalPages = totalPages,
            CurrPage = Math.Min(model.CurrPage, totalPages),
            PrevPage = Math.Min(model.CurrPage, totalPages),
            ToDoFilter = model.ToDoFilter,
            ToDoOrder=order
        };
        return View(result);
    }
}

The first method just displays the first page and it is quite trivial. The second method handles validation errors. If there are validation errors it cancels the page change by resetting the current page to the previous page, and then returns the same View Model it received in order to let the user corrects the errors. If everything is ok it passes the changes to the database, and retrieves the new page requested by the user. That’s all!

We finally arrived to the datagrid. In order to have the datagrid working we need to prepare 4 templates:

  1. displayTemplate: it displays a row of data when the grid is in display mode
  2. editTemplate: it displays a row of data when the grid is in edit mode
  3. addDisplayTemplate: it defines the look of the insert new row component, normally it just displays an insert button.
  4. gridTemplate: it displays the general container where all data items will be inserted. In our case it is just a table.

As discussed in the documentation about templates, Templates can be passed both as: Partial Views names, Razor Templates, Razor in-line templates, or Lambda Expressions. Here, we will use Razor in-line templates. The reader interested in Partial Views may refer to my previous post: Defining Mvc Controls 2: Using the DataGrid.

Razor in-line templates can be passed directly in the DatagridFor call as arguments. They receive an HtmlHelper<ToDoView> object as the value of a standard parameter called item. We can use this item  variable in exactly the same way we use the Html variable in a standard View.

Lets see in detail each template.

displayTemplate
@<text>         
    <td class="ToDo">
        @item.ValidationMessageFor(m => m.Name, "*")
        @item.DisplayField(m => m.Name)
    </td>
    <td class="editor-field">
        @item.ValidationMessageFor(m => m.DueDate, "*")
        @item.DisplayField(m => m.DueDate)
    </td>
    <td class="ToDo">
        @item.ValidationMessageFor(m => m.Description, "*")
        @item.DisplayField(m => m.Description)
    </td>
    <td class="ToDoTool">
        @item.DetailLink(Ajax, "Edit Details", DetailType.Edit, "ToDoSubTasks", "Home",
            new {
                id = item.ViewData.Model.id},
                                null)
        @item.ImgDataButton(DataButtonType.Edit, "../../Content/edit.jpg", null)
    
    </td>
    <td class="ToDoTool">
        @item.ImgDataButton(DataButtonType.Delete, "../../Content/delete.jpg", null)
    </td>
</text>
)

The display item View Model is just a data item, it is not a Tracker<T> wrapper. The wrapper is handled automatically by the DataGrid.

We don’t need to put the <tr> tag in each data item: the container tag for each item is defined in the DataGrid helper and it is automatically inserted by the DataGrid. We have also the option to supply a delegate that returns a different item container as a function of the data item and of its position in the DataGrid.

It is worth discussing the two data buttons: the first one switches the row to edit mode, while the second one just deletes the row. In the example below an image button is used, but you can also use link and button helpers.

editTemplate
_S.H<ToDoView>(
@<text>         
     <td class="ToDo">
        @item.ValidationMessageFor(m => m.Name, "*")
        @item.TextBoxFor(m => m.Name)
     </td>
     <td class="ToDo">
        @item.ValidationMessageFor(m => m.DueDate, "*")
        @item.DateTimeFor(m => m.DueDate, DateTime.Today).Date()
     </td>
     <td class="ToDo">
        @item.ValidationMessageFor(m => m.Description, "*")
        @item.TextBoxFor(m => m.Description)
     </td>
     <td class="ToDoTool" colspan="2">
        @item.HiddenFor(m => m.id)
        @item.ImgDataButton(DataButtonType.Cancel, "../../Content/undo.jpg", null)
     </td>
</text>
)

The edit template is completely analogous to the display template. The only difference being that it contains input field allowing you to edit fields.

Please, notice the Hidden field containing the key! It is necessary! Here we have a cancel button that undoes all changes done to the data item and put the row in display mode again.

Last thing worth pointing out is the DateTimeFor helper that is able to take as input, date, time or date and time. It is able to read the DateRange attribute and to enforce its constraints. This means the user is allowed to insert just dates that conforms with the constraints. More information about the DateTimeFor Helper can be found here.

addDisplayTemplate
_S.H<ToDoView>(
               @<td colspan="5" class="ToDo">@item.ImgDataButton(DataButtonType.Insert, "../../Content/add.jpg", null)</td>
                  )

The insert template just contains an insert button that when clicked causes a new row to appear in edit mode.

gridTemplate
   _S.H<ToDoView>(
     @<table class="ToDo" >
       <tr>
       <td class="ToDoHeader"><strong>@item.SortButtonFor(m => m.Name, sortButtonStyle: SortButtonStyle.Button)</strong></td>
       <td class="ToDoHeader"><strong>@item.SortButtonFor(m => m.DueDate, sortButtonStyle: SortButtonStyle.Button)</strong></td>
       <td class="ToDoHeader"><strong>@item.ColumnNameFor(m => m.Description)</strong></td>
       <td class="ToDoHeader"><strong></strong></td>
       <td class="ToDoHeader"><strong></strong></td>
       </tr>
       @item.ViewData["Content"]
   </table>
)

It defines the look of the container of all items. In our case it just displays the <table> tag and the header of the table. The ColumnNameFor helper displays the name of a column, taking it from the Display attribute(ShortName if available, otherwise Name).

The SortButtonFor helper renders sort buttons on the columns where sorting is allowed. They can be used only together with the EnableSortingFor helper that we will discuss below.

The @item.ViewData[“Content”] construct  defines where all data items have to be inserted. It is a kind of placeholder and it needs to be inserted “as it is” in any template that describes a datagrid container.

It is worth pointing out that this template is passed an empty data item object to help the automatic construction of the container. Specifically, we can use reflection to extract all columns and we can also automatically decide some facts about the look of the container by extracting the attributes of each data item property (as for instance the display attribute).

The Datagrid

Summing up the whole call to the DataGridFor helper is:

@Html.DataGridFor(m => m.ToDoList, ItemContainerType.tr,
            _S.H<ToDoView>(
            @<text>         
                 <td class="ToDo">
                    @item.ValidationMessageFor(m => m.Name, "*")
                    @item.TextBoxFor(m => m.Name)
                 </td>
                 <td class="ToDo">
                    @item.ValidationMessageFor(m => m.DueDate, "*")
                    @item.DateTimeFor(m => m.DueDate, DateTime.Today).Date()
                 </td>
                 <td class="ToDo">
                    @item.ValidationMessageFor(m => m.Description, "*")
                    @item.TextBoxFor(m => m.Description)
                 </td>
                 <td class="ToDoTool" colspan="2">
                    @item.HiddenFor(m => m.id)
                    @item.ImgDataButton(DataButtonType.Cancel, "../../Content/undo.jpg", null)
                 </td>
            </text>
            ),
             _S.H<ToDoView>(
            @<text>         
                <td class="ToDo">
                    @item.ValidationMessageFor(m => m.Name, "*")
                    @item.DisplayField(m => m.Name)
                </td>
                <td class="editor-field">
                    @item.ValidationMessageFor(m => m.DueDate, "*")
                    @item.DisplayField(m => m.DueDate)
                </td>
                <td class="ToDo">
                    @item.ValidationMessageFor(m => m.Description, "*")
                    @item.DisplayField(m => m.Description)
                </td>
                <td class="ToDoTool">
                    @item.DetailLink(Ajax, "Edit Details", DetailType.Edit, "ToDoSubTasks", "Home",
                        new {
                            id = item.ViewData.Model.id},
                                            null)
                    @item.ImgDataButton(DataButtonType.Edit, "../../Content/edit.jpg", null)
                
                </td>
                <td class="ToDoTool">
                    @item.ImgDataButton(DataButtonType.Delete, "../../Content/delete.jpg", null)
                </td>
            </text>
            ),
            _S.H<ToDoView>(
              @<table class="ToDo" >
                <tr>
                <td class="ToDoHeader"><strong>@item.SortButtonFor(m => m.Name, sortButtonStyle: SortButtonStyle.Button)</strong></td>
                <td class="ToDoHeader"><strong>@item.SortButtonFor(m => m.DueDate, sortButtonStyle: SortButtonStyle.Button)</strong></td>
                <td class="ToDoHeader"><strong>@item.ColumnNameFor(m => m.Description)</strong></td>
                <td class="ToDoHeader"><strong></strong></td>
                <td class="ToDoHeader"><strong></strong></td>
                </tr>
                @item.ViewData["Content"]
            </table>
         ),
         _S.H<ToDoView>(
               @<td colspan="5" class="ToDo">@item.ImgDataButton(DataButtonType.Insert, "../../Content/add.jpg", null)</td>
                  ))

The first argument of the helper, as usual, defines the property to display in the DataGrid. The second property defines the kind of item container to be used (in our case a <tr>), and the last ones are the templates we discussed before.

There are also optional arguments to define html attributes(also as a function of the data item) and to pass a function to change dynamically the item container.

Finally, the pager is composed of various parts that can be used also  separately. Here I used link buttons but one can use also image or normal buttons:

  <div class="ToDoPager">
             @{ var pager = Html.PagerFor(m => m.CurrPage, m => m.PrevPage, m => m.TotalPages);}
             @pager.PageButton("<<", PageButtonType.First, PageButtonStyle.Link)
             @pager.PageButton("<", PageButtonType.Previous, PageButtonStyle.Link)
             @pager.PageChoice(5)
             @pager.PageButton(">", PageButtonType.Next, PageButtonStyle.Link)
             @pager.PageButton(">>", PageButtonType.Last, PageButtonStyle.Link)
             @pager.PageButton("Go To", PageButtonType.GoTo, PageButtonStyle.Button)
             @pager.GoToText(new { style = "width:50px;" })
</div>

The first call gets an object to be used to render all parts. The PageChoice method displays five page links before the current page, 5 page links after the current page and the number of the current page. There are buttons for the next page, last page, etc., and also a button plus a textbox to jump to any page.

Sorting

The first step to enable sorting with a mouse click on the columns is the insertion of a new property in our ViewModel to receive sorting information:

public List<KeyValuePair<LambdaExpression, OrderType>> ToDoOrder { get; set; }

The toolkit datagrid is able to exchange directly the lambda expressions to be used in LINQ queries with the Controller. Thus, we use a list of couples LambdaExpression OrderType. Where OrderType is an enumeration that specifies ascending or descending order. The whole list specifies a lexicographic sorting that can be based also on several columns. As previously discussed, sorting information can be used directly with LINQ expressions by means of the IEnumerable and IQueryable extension ApplyOrder defined in the namespace MVCControlsToolkit.Linq , .

It is worth pointing out that the sorting information come from the client, therefore a malicious user might try a denial of service attack by sending a manipulated sorting request involving  columns that are too difficult to sort (columns with no indexes defined on them, for instance). In order to defend ourselves from such an attack the Transformation Handler that receives the data from the DataGrid automatically discards columns that are not decorated with the CanSortAttribute that is defined in the MVCControlsToolkit.DataAnnotations namespace.

Sorting is defined by the user through the column sort buttons we have already seen in the grid header. By default, sort buttons do not cause a post. However they have a parameter to require an immediate post. When a new sorting is applied if there is a pager operating on the grid the page is automatically reset to the first page.

The up and down arrows of the sort buttons are defined in CSS classes specified in the sorting helper:

@Html.ManipulationButton(ManipulationButtonType.ResetGrid, "Reset", m => m.ToDoList, null, ManipulationButtonStyle.Button)
        @Html.EnableSortingFor(m => m.ToDoList, m => m.ToDoOrder, "NormalHeaderToDo", "AscendingHeaderToDo", "DescendingHeaderToDo", page: m => m.CurrPage)

In order to do its job the EnableSortingFor helper needs the collection to be sorted, the property that will contain all sorting information and also the page property. As the sort behavior of the column changes, the three CSS classes specified in the EnableSortingFor helper are applied to the buttons in order to change their look.

Another interesting feature is the ManipulationButtonType.ResetGrid that undoes all changes done to the datagrid that are not yet committed. Here commitment means commitment to a database or to any other storage, not simply a post: the grid remembers its previous values through several posts! We declare that commitment took place either by calling the Confirm method of a Tracker<Item> associated to a row or by simply reloading the grid with fresh data.

In order to undo a single row delete we have introduced the undelete data button and a template to substitute deleted rows. The undelete is easy: just put the undelete button in this template! However I prefer seeing my rows disappear completely! Therefore I have not used this feature in this example (no panic….in case of errors we have the reset button..).

That’s all! The deveoper’s that don’t want to add each time, pagers, sort buttons, filters, etc to their DataGrids can use Theming to define once and for all the look and features of their DataGrids,…or they can simply modify the DataGrid Theme I already defined in the RazorThemedGrid file in the download area. A tutorial on theming is here.

Stay Tuned !

Francesco

Tags: , , , , , , ,

Apr 24 2011

Designing a Themed Control in the Mvc Controls Toolkit

Category: MVCFrancesco @ 07:09

Mvc Controls Toolkit Datagrid Updated Tutorial (more recent version of Defining MVC Controls 2: Using the DataGrid)

Advanced Data Filtering Techniques in the Mvc Controls Toolkit

Defining MVC Controls 3: Datagrid, Sorting, and Master-Detail Views

All controls of the Mvc Controls Toolkit are designed to allow the maximum flexibility. For instance, the DataGrid is not table based but allows a generic template to be used as item template. Other controls,such as the DateTimeInput, and the DualSelectBox are composed of several parts that can be rendered separately. Features like, Sorting, Paging and Filtering are not included in controls like the DataGrid and the SortableList but are provided through separate helpers. Finally, all controls can be styled with Css. While this approach ensures an high level of flexibility, the time needed to set-up completely a control is higher compared to the time needed to setup a pre-configured control.

In order to promote re-usability of efforts made to set up a control, the Mvc Controls Toolkit allow the definition of Themes. Each Theme is composed of a Css enriched with an image folder, and of a set of Partial Views. Each Partial View features an already set-up control. While the layout and all parts each control is composed of are defined in the Partial Views, the Css define completely the style of all controls. Thus, the controls of each theme are ready to be used, with no further effort; we just need to pass data to them! Better configuration and style options are offered by the commercial version of the Mvc Controls Toolkit.

In this post I will show how to design a control to be inserted into a theme. I assume a basic knowledge of the Theming features of the Mvc Controls Toolkit. Therefore, readers are encouraged to read the documentation page about theming here before reading this post.

We will call “Test” the theme we are going to define, and we will analyze just how to design a Table based Themed Datagrid offering paging, sorting and column filtering services.,

As a first step we set-up our folders as shown below:

css_folder_treefolder_tree

Now let open the DataGrid.cshtml file. The Themed Datagrid is invoked by the helper:

  1. public static MvcHtmlString ThemedDataGridFor<M, TItem>(
  2.             this HtmlHelper<M> htmlHelper,
  3.             Expression<Func<M, List<Tracker<TItem>>>> expression,
  4.             GridFeatures gridFeatures,
  5.             Columns<TItem> fields,
  6.             Expression<Func<M, List<KeyValuePair<LambdaExpression, OrderType>>>> orderExpression = null,
  7.             Expression<Func<M, int>> page=null,
  8.             Expression<Func<M, int>> prevPage=null,
  9.             Expression<Func<M, int>> pageCount=null,
  10.             Expression<Func<M, Expression<Func<TItem, bool>>>> filter=null,
  11.             string title=null,
  12.             string name = "DataGrid"
  13.             )
  14.             where TItem:class, new()

expression defines the collection to be rendered in the Grid, filter will collect the filtering criteria (see here), orderExpression the sorting criteria(see here), and page, prevPage, and pageCount the paging (see here). gridFeatures is a bit flag enumeration that defines the desired features selected by the user of the Themed DataGrid:

[Flags]
    public enum GridFeatures {None = 0, Edit=1, Display=2, Insert=4, Delete=8, ResetRow=16, UndoEdit=32, InsertOne=64, Paging=128, Sorting=256, Filtering=512}

For more information see the Documentation page about theming.

While fields is a collection of column definitions. Each column contains the column features(if sorting, and filtering are allowed, and an optional column header to override the one provided through DataAnnotations), and possibly Edit and Display templates for the column. One can also define custom columns that are not connected with specific fields.For more information see the Documentation page about theming.

The ThemedDataGridFor helper just packs all information received into a GridDescription object defined as:

public class GridDescriptionBase
    {
        public GridFeatures Features { get; set; }
        public List<Column> Fields { get; set; }   
    }
    
    public class GridDescription:GridDescriptionBase
    {
        public dynamic ToShow {get; set;}
        public dynamic ToOrder { get; set; }
        public string Title { get; set; }
        public dynamic HtmlHelper { get; set; }
        public dynamic Page { get; set; }
        public dynamic PrevPage { get; set; }
        public dynamic PageCount { get; set; }
        public dynamic Filter { get; set; }

    }

Then it passes this object to our DataGrid.cshtml Partial View into: ViewData["ThemeParams"]

Thus our first step is to “unpack” this object:

@{
    var options = ViewData["ThemeParams"] as GridDescription;

    Func<dynamic, HelperResult> editTemplate = null;
    if ((options.Features & GridFeatures.Edit) == GridFeatures.Edit)
    {
        editTemplate = EditItem;
    }
    Func<dynamic, HelperResult> displayTemplate = DisplayItem;
    Func<dynamic, HelperResult> insertTemplate = null;
    if ((options.Features & GridFeatures.Insert) == GridFeatures.Insert)
    {
        insertTemplate = InsertItem;
    }
}

The above instruction just verifies if the Insert and Edit services are required, and if they are required assign the name of the Insert template(InsertItem), and of the Edit template(EditItem) to the variables inserItem, and editItem. The Display template, that is not optional, is always put into the displayItem variable.

Templates are defined through Razor helpers(see the documentation about templates) that use the columns information provided by the GridDescription class to render each single item.

Below the Insert template that just renders an insert button:

@helper InsertItem(dynamic htmlHelper)
    {
        var options = htmlHelper.ViewData["ThemeParams"] as GridDescription;
    int count = 0;
    bool hasCustomField = false;
    foreach (Column column in options.Fields)
    {
        if ((column.Features & FieldFeatures.Hidden) != FieldFeatures.Hidden)
        {
            count++;
        }
        else if (column.DispalyTemplate != null)
        {
            hasCustomField = true;
        }
    }
    if ((options.Features & GridFeatures.Edit) == GridFeatures.Edit
        || hasCustomField
        || (options.Features & GridFeatures.Delete) == GridFeatures.Delete) { count++; }
    <td colspan='@count' class="Theme-DataGrid-Column">
        @DataGridHelpers.ImgDataButton(
            htmlHelper, DataButtonType.Insert,
            Url.Content("~/Content/themes/test/images/add.jpg"), null)</td>
}

 

 

 

Let’s forget for just a minute about the hasCustomField variable, we will discuss about it when analyzing the Display template.The foreach loop just counts the number of columnsto be inserted in the colspan to format properly the table.If the grid is required to work also in Edit mode or if it is required to allow row deletes we adds one column more for the some command buttons. Columns marked as hidden are jumped since they contains just hidden field that will not be rendered together with the other columns.

The imgDataButton helper that renders the insert button takes an image form the Css that is part of the “Test” theme. Its is rendered in a quite strange way, namely,  it is not invoked as an extension method but just as a normal static method of the DataGridHelpers class. This is because we are using dynamic  variables(see the GridDescription definition) to handle Types that will be known just a run-time since our DataGrid is required to work with any class. In such a case extension methods cannot be used!

In the Display template we do some initialization and then we start a foreach loop on all columns:

@helper DisplayItem(dynamic htmlHelper)
    {
        var options = htmlHelper.ViewData["ThemeParams"] as GridDescription;
        bool hasCustomField=false;
        foreach (Column column in options.Fields)

We will see what the hasCustomField variable is needed for in a short time, let’s see first how “normal” columns(the ones that are not marked as Hidden) are dealt with:

if ((column.Features & FieldFeatures.Hidden) != FieldFeatures.Hidden)
            {
            <td class="Theme-DataGrid-Column">
            @{
                if (column.DispalyTemplate == null)
                {
                      @DisplayExtensions.DisplayFor(htmlHelper, column.Field)
                }
                else
                {
                      @CoreHTMLHelpers.TemplateFor(htmlHelper, column.DispalyTemplate)
                }
             }
            </td>
            }

 

Very simple: if the user has provided a Display template for that column we just invoke it through the TemplateFor helper otherwise we simply invoke the standard Mvc DisplayFor helper. It is worth to point out that column templates are passed information on the whole row, since we passed the htmlHelper for the whole row to the TemplateFor helper. This way, each column can use information about other columns in the rendering. Moreover, we may define “custom” columns that are associated to no specific field by by using  m => m as filed selector.

Now let’s see what happens to fields marked as Hidden:

else if(column.DispalyTemplate != null){
                hasCustomField = true;
            }

If they have a custom display template we just set the hasCustomField variable, defined at the beginning of the helper to true, just to remember we have found one of them. We have done the same thing in the Insert template. Why?? Simple! Because hidden fields are rendered in the edit Templates, since they just store data for a future post, thus a display template associated with an hidden field for sure is needed to render a command button. Probably a command button that uses the principal key of the row to render either a detail view of the record or some other children objects by means of either an action link or an Ajax call. Therefore, in such a case we need to display a further column reserved for all command buttons:

if ((options.Features & GridFeatures.Edit) == GridFeatures.Edit
            || hasCustomField
            || (options.Features & GridFeatures.Delete) == GridFeatures.Delete)
        {
        <td>
            <table>
                <tr>
                    @if ((options.Features & GridFeatures.Edit) == GridFeatures.Edit)
                    {         
                        <td>
                            @DataGridHelpers.ImgDataButton(
                                htmlHelper, DataButtonType.Edit,
                                Url.Content("~/Content/themes/test/images/edit.jpg"), null)
                        </td>
                    }
                    @if ((options.Features & GridFeatures.Delete) == GridFeatures.Delete)
                    {
                        <td>
                            @DataGridHelpers.ImgDataButton(
                                htmlHelper, DataButtonType.Delete,
                                Url.Content("~/Content/themes/test/images/delete.jpg"), null)
                        </td>
                    }
                    @if (hasCustomField)
                    {
                        foreach (Column column in options.Fields)
                        {
                            if ((column.Features & FieldFeatures.Hidden) == FieldFeatures.Hidden &&
                                column.DispalyTemplate != null)
                            {
                                <td>
                                    @CoreHTMLHelpers.TemplateFor(htmlHelper, column.DispalyTemplate)
                                </td>
                            }
                        }
                    }
               </tr>
            </table>
        </td>
        }

The Edit template is completely analogous, just the command buttons are different:

@helper EditItem(dynamic htmlHelper)
    {
        var options = htmlHelper.ViewData["ThemeParams"] as GridDescription;

        foreach (Column column in options.Fields)
        {
            if ((column.Features & FieldFeatures.Hidden) != FieldFeatures.Hidden)
            {
            <td class="Theme-DataGrid-Column">
            @{
                if (column.EditTemplate == null)
                {
                      @EditorExtensions.EditorFor(htmlHelper, column.Field)
                }
                else
                {
                    if (column.EditTemplate is string)
                    {
                        string templateName = column.EditTemplate as string;
                        templateName = "Themes/Test/" + templateName;
                        @CoreHTMLHelpers.TemplateFor(htmlHelper, templateName)
                    }
                    else
                    {     
                        @CoreHTMLHelpers.TemplateFor(htmlHelper, column.EditTemplate)
                    }
                }
             }
            </td>
            }
        }
        <td>
            <table>
                <tr>          
                    <td>
                        @DataGridHelpers.ImgDataButton(
                            htmlHelper,
                            DataButtonType.Cancel,
                            Url.Content("~/Content/themes/test/images/undo.jpg"), null)
                    </td>
                    @if ((options.Features & GridFeatures.UndoEdit) == GridFeatures.UndoEdit)
                    {
                        <td>
                            @DataGridHelpers.LinkDataButton(
                                htmlHelper,
                                DataButtonType.ResetRow,
                                ThemedControlsStrings.Get("Item_ResetRow", "DataGrid"), null)
                        </td>
                    }
                    @foreach (Column column in options.Fields)
                    {
                        if ((column.Features & FieldFeatures.Hidden) == FieldFeatures.Hidden)
                        {
                            if (column.EditTemplate != null)
                            {
                                @CoreHTMLHelpers.TemplateFor(htmlHelper, column.EditTemplate)
                            }
                            else
                            {
                                <td style="width:0px">
                                    @InputExtensions.HiddenFor(htmlHelper, column.Field)
                                </td>
                            }
                        }
                    }
               </tr>
            </table>
        </td>
}

It is convenient to define also a Razor helper that renders the Header of the table, with all column names, sort, and filtering logics:

@helper DisplayHeader(dynamic htmlHelper, GridDescription options)
    {
    <tr>
        @{bool hasCustomField=false;}
        @foreach (Column column in options.Fields)
        {
            if ((column.Features & FieldFeatures.Hidden) != FieldFeatures.Hidden)
            {
            <td class="Theme-DataGrid-Header">
                <table>
                <tr>

The above Razor Helper is completely analogous to the other templates. In each column we have 2 sections. The first one is dedicated to filtering and the second one is dedicated either to a sort button with the column name inside it or just to the column name if the column is not sortable.

Let’s start with the filtering:

@if ((options.Features & GridFeatures.Filtering) == GridFeatures.Filtering && (column.Features & FieldFeatures.Filtering) == FieldFeatures.Filtering)
                {
                    <td >
                    <div class="Theme-DataGrid-Filter">
                        <div class="MvcControlsToolkit-Hover">
                            @{
                            var h1 = DataFilterClauseHelpers.DataFilterClauseFor(
                                htmlHelper,
                                options.Filter,
                                column.Field,
                                "first");
                            var h2 = DataFilterClauseHelpers.DataFilterClauseFor(
                                htmlHelper,
                                options.Filter,
                                column.Field,
                                "second");
                            bool oldClientSetting = ViewContext.ClientValidationEnabled;
                            ViewContext.ClientValidationEnabled = false;
                                }
                              <table>
                                <tr>
                                    <td>
                                        @InputExtensions.CheckBox(h1, "Selected", h1.ViewData.Model.Selected)
                                    </td>
                                    <td>
                                        @DataFilterClauseHelpers.FilterClauseSelect(h1, h1.ViewData.Model.Condition, column.Field)
                                    </td>   
                                </tr>
                                <tr>
                                    <td colspan="2">
                                        @{object val1=h1.ViewData.Model.Search;
                                          if (! h1.ViewData.Model.Selected)
                                          {
                                              val1 = string.Empty;
                                          }
                                        }
                                        @InputExtensions.TextBox(h1, "Search", val1)
                                        @{ValidationExtensions.Validate(h1, "Search"); }
                                    </td>
                                </tr>
                              </table>
                              <table>
                                <tr>
                                    <td>
                                        @InputExtensions.CheckBox(h2, "Selected", h2.ViewData.Model.Selected)
                                    </td>
                                    <td>
                                        @DataFilterClauseHelpers.FilterClauseSelect(h2, h2.ViewData.Model.Condition, column.Field)
                                    </td>   
                                </tr>
                                <tr>
                                    <td colspan="2">
                                        @{object val2=h2.ViewData.Model.Search;
                                          if (!h2.ViewData.Model.Selected)
                                          {
                                              val2 = string.Empty;
                                          }
                                         }
                                        @InputExtensions.TextBox(h2, "Search", val2)
                                        @{ValidationExtensions.Validate(h2, "Search"); }
                                        @{ViewContext.ClientValidationEnabled=oldClientSetting;}
                                    </td>
                                </tr>
                              </table>    
                            </div>
                        </div>
                    </td>
                    
                }

Filters are implemented through an hover menu that appears on mouse hover a filtering icon. All hover menu logic is contained in the Css, so we don’t have to worry about this while coding the filtering logics. Each column may contains two filter clauses, this is the minimum to implement min-max conditions. Accordingly we create the two helpers h1 and h2 to renders the filters by means of the DataFilterClauseFor helper.

We use each helper to render a checkbox to enable the filter criterion, a dropdown to select the filtering condition, and finally a TextBox for inserting the Filter parameter and the associated validation helper. We ensure that if the filter is disabled the TextBox is empty.

What’s beautiful about the DataFilterClauseFor helper is that once we have Rendered input fields, for its “Search” field, for its filter criterion(our DropDown), and for its enabled condition…it handles all filtering logics for us….We will receive the complete filter obtained combining all columns filters in our the filter property of our ViewModel..So we need just to apply it to an IQueryable to filter it (see also here for more information).

Let’s move to the second section (sorting and column names):

<td>
                <strong>
                @if ((column.Features & FieldFeatures.Sort) == FieldFeatures.Sort)
                {
                    @DataGridHelpers.SortButtonForTrackedCollection(
                        htmlHelper,
                        options.ToShow,
                        column.Field,
                        sortButtonStyle: SortButtonStyle.Button)
                }
                else
                {
                    if (column.ColumnHeader != null)
                    {
                        @column.ColumnHeader
                    }
                    else
                    {
                       @DataGridHelpers.ColumnNameForTrackedCollection(
                       htmlHelper,
                       options.ToShow,
                       column.Field)
                    }
                }
            </strong>
            </td>

options.Toshow contains the lambda expression that defines the collection to be shown in the grid. If the column sorting is required we render a sort button otherwise we render just the column name either taken from Data Annotations or from the column definition.

For all columns marked as Hidden as usual we have:

else if (column.DispalyTemplate != null)
            {
                hasCustomField = true;
            }

and finally:

@if ((options.Features & GridFeatures.Edit) == GridFeatures.Edit
            || hasCustomField
            || (options.Features & GridFeatures.Delete) == GridFeatures.Delete)
        {
            <td class="Theme-DataGrid-Header"></td>
        }

If there is the column for the command buttons, we just render an empty <td> to have the right number of <td>.in the header.

Now we are ready to render the DataGrid:

<table>
@DisplayHeader(options.HtmlHelper, options)
@DataGridHelpers.DataGridFor(options.HtmlHelper,
    options.ToShow,
    ItemContainerType.tr,
    editTemplate,
    displayTemplate,
    null,
    insertTemplate,
    enableMultipleInsert: (options.Features & GridFeatures.InsertOne) != GridFeatures.InsertOne,
    itemCss: "Theme-DataGrid-ItemCss",
    altItemCss: "Theme-DataGrid-AlternateItemCss",
    toTrack: options.GetToTrack())
</table>

toTrack contains the list of all properties we need to save in hidden fields for the changes tracking performed by the grid. This argument is optional, but if we don’t pass it the DataGrid helper just save the original values of all properties of each item. Since, the list of all properties we are using can be easily computed from the columns definition, we get it by calling the GridDescription class method GetToTrack().

Finally we add Pager, and Sorting helper if these services are required:

@if ((options.Features & GridFeatures.Paging) == GridFeatures.Paging)
{
    <div class="Theme-DataGrid-Pager">
        @{ var pager = PagerHelper.PagerFor(options.HtmlHelper, options.Page, options.PrevPage, options.PageCount);}
        @pager.PageButton(ThemedControlsStrings.Get("Page_First", "DataGrid"), PageButtonType.First, PageButtonStyle.Link)
        @pager.PageButton(ThemedControlsStrings.Get("Page_Prev", "DataGrid"), PageButtonType.Previous, PageButtonStyle.Link)
        @pager.PageChoice(5)
        @pager.PageButton(ThemedControlsStrings.Get("Page_Next", "DataGrid"), PageButtonType.Next, PageButtonStyle.Link)
        @pager.PageButton(ThemedControlsStrings.Get("Page_Last", "DataGrid"), PageButtonType.Last, PageButtonStyle.Link)
    </div>
}

@if (((options.Features & GridFeatures.Sorting) == GridFeatures.Sorting) && options.ToOrder != null)
{
        @DataGridHelpers.EnableSortingFor(
            options.HtmlHelper, options.ToShow,
            options.ToOrder, "Theme-DataGrid-NormalHeader",
            "Theme-DataGrid-AscendingHeader", "Theme-DataGrid-DescendingHeader",
            page: options.Page)
}

The whole code can be downloaded from the download page of the Mvc Controls Toolkit here.

That’s all! Not so difficult, isn’t it?

The full code is contained in the RazorThemedGrid file in the Mvc Controls Toolkit download page. Download it and enjoy!

                                                    Stay Tuned

                                                                 Francesco

Tags: , , , , ,

Feb 6 2011

Data Filtering, in the New Mvc 3 Version of the Mvc Controls Toolkit

Category: Entity Framework | MVC | Asp.netFrancesco @ 05:11

See also an updated version of this post:  Advanced Data Filtering Techniques in the Mvc Controls Toolkit

Mvc Controls Toolkit Datagrid Updated Tutorial (updated version of Defining MVC Controls 2: Using the DataGrid)

Defining MVC Controls 3: Datagrid, Sorting, and Master-Detail Views

The Mvc Controls Toolkit offers an interesting data filtering feature: the controller can receive directly a LinQ expression defining the filtering criteria chosen by the user when a view is posted. This way the user can choose among several filtering options by clicking buttons, or selecting checkboxes and then filling the input fields of the selected filtering options. No need to define ViewModel properties for each of the available options, the action method of the controller receives always the same data structure: a LinQ Expression. New filtering options can be easily added modularly with the no need to modify the controller and the code behind it. This increases modularity and makes easier  software maintenance.

Let see how this works in practice with the help of the same example used in my previous posts about the Mvc Controls Toolkit library(see the links next to the title). The whole code used in this example can be downloaded here(the file named BasicTutorialsCode), while screenshots of the software running are here. .

First of all we add the property destined to contain the LinQ expression to the ViewModel:

         public int TotalPages { get; set; }
        public int CurrPage { get; set; }
        public int PrevPage { get; set; }
        public List<Tracker<ToDoView>> ToDoList {get; set;}
        public List<KeyValuePair<LambdaExpression, OrderType>> ToDoOrder { get; set; }
        public System.Linq.Expressions.Expression<Func<ToDoView, bool>> ToDoFilter { get; set; }

The LinQ Expression is a map from ToDoView, that is the class the filter will be applied to, and bool, since formally it defines a function that verifies if an instance of ToDoView satisfies the criterium of the filter.

Now we can use this expression directly in the method that retrieves the items:

if (filter == null)
                {
                    result = context.ToDo.Select(item =>
                        new ToDoView() { Name = item.Name, Description = item.Description, DueDate = item.DueDate, id = item.id }).ApplyOrder(order).Select(viewItem =>
                        new Tracker<ToDoView>
                                {
                                    Value = viewItem,
                                    OldValue = viewItem,
                                    Changed = false
                                }).Skip(toSkip).Take(pageDim).ToList();
                }
                else
                {
                    result = context.ToDo.Select(item =>
                        new ToDoView() { Name = item.Name, Description = item.Description, DueDate = item.DueDate, id = item.id }).Where(filter).ApplyOrder(order).Select(viewItem =>
                        new Tracker<ToDoView>
                        {
                            Value = viewItem,
                            OldValue = viewItem,
                            Changed = false
                        }).Skip(toSkip).Take(pageDim).ToList();
                }

The code above shows that it was enough to add a Where(filter) instruction to our original code to get the filtered data.

Now we need to define a class for each filtering option. For sake of semplicity let just define a single filtering option. Our class needs just to implement an interface:

public interface IFilterDescription<TData>
    {
        Expression<Func<TData, bool>> GetExpression();
    }

The GetExpression function returns the actual Filter. The input fields that the user is required to fill are represented by properties of this class, and are used in the implementation of GetExpression():

public class ToDoItemByNameFilter: 
        IFilterDescription<ToDoView>
    {
        [Required]
        [Display(Prompt="chars the name of item starts with")]
        public string Name {get; set;}
        public System.Linq.Expressions.Expression<Func<ToDoView, bool>> GetExpression()
        {
            System.Linq.Expressions.Expression<Func<ToDoView, bool>> res = null;
            Name=Name.Trim();
            if (!string.IsNullOrEmpty(Name))
            {
                Name=Name.Trim();
                res= m => (m.Name.StartsWith(Name));
                
            }
            return res;
        }
    }

In our case we would like to select all items whose Name field starts with the characters inserted in the Name property defined in our ToDoByNameFilter class.

We just Trim our input and then we use it in the LinQ expression returned by GetExpression.

Our ToDoItemByNameFilter plays also the role of ViewModel for a partial view that takes care of the user interface for our filter, so we can decorate its Name property with attributes defining both validation and appearance constraints. In our case we defined a WatermarK through the Prompt property of the Display attribute.

In order to make the watermark actually apppears and the textbox add some adequate formatting we can use the TypedTextBox of the Mvc Controls Toolkit in the partial view that defines the graphical appearance of our filter:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Mvc_Examples.Controls.ToDoItemByNameFilter>" %>
<%@ Import Namespace=" MVCControlsToolkit.Core" %>
<%@ Import Namespace=" MVCControlsToolkit.Controls" %>
<%: Html.TypedTextBoxFor(m => m.Name, watermarkCss:"watermark") %>

Now it remains just to insert the helper of the filter in our View. In order to let the user selects among several filters we can use the ViewList defined in the Mvc Controls Toolkit. This way either by clicking some mutual exclusive checkboxes or by clicking some button, the user let the Partial View associate to a Filter to appear. In our case for sake of simplicity we give to the user just the choice between our previous Name filter and no filter at all:

<div >
    <%: Html.ViewList("ToDoFilter", "ToDoFilterSelected") %> 
        <input id="Checkbox1" type="checkbox" class = 'ToDoFilter_checkbox ByNameFilter_checkbox'/>  &nbsp; Filter by name 
        <span id='ByNameFilter' class='ToDoFilter'><%:Html.DataFilter(m => m.ToDoFilter, 
                                new Mvc_Examples.Controls.ToDoItemByNameFilter(),
                                           "ToDoFilterByName")%></span>  
    </div>
    <div >
    <input id="Checkbox2" type="checkbox" class = 'ToDoFilter_checkbox NoFilter_checkbox'/>  &nbsp; NoFilter
    <span id='NoFilter' class='ToDoFilter'></span>
    </div>

The arguments of our DataFilter helper are just an expression that selects the ViewModel property to fill with the filtering expression, an instance of the class that defines the filter, and the name of the Partial View that we defined before.

The ViewList helper selects one of two spans, one contains the filter helper and the other is simply empty. The selection is done with two checkboxes.Details on how to use of the VieList can be found in the documentation here.

By adding a new argument to our DetailView Helper we can make also our detail view appears in a jQuery UI dialog:

<% Html.DetailFormFor(Ajax, m => m.ToDoList, ExternalContainerType.div,
           "ToDoSubTasks", "Home", null, "isChangedToDo", "isDeletedToDo", detailDialog:
           new MVCControlsToolkit.Controls.DataGrid.Dialog
           {
               Title = "prova dialogo",
               Show = "slide",
               Hide = "slide",
               MinWidth=800

           });%>
</asp:Content>

THAT’S ALL!  Download the code and enjoy!

                             

                                       Stay Tuned!

                                               Francesco

Tags: , , , , , ,

Nov 15 2010

Defining MVC Controls 3: Datagrid, Sorting, and Master-Detail Views

Category: Asp.net | Entity Framework | MVCFrancesco @ 00:15

Defining MVC Controls 1

Mvc Controls Toolkit Datagrid Updated Tutorial

Advanced Data Filtering Techniques in the Mvc Controls Toolkit

I am back with the second tutorial about using the Datagrid of the  MVC Controls Toolkit . We are going to modify the the example of my previous post to include sorting on a mouse click on the columns.

The code of this tutorial is containe in the download page of MVC Controls Toolkit, that is here and is named BasicTutorialsCode.

As a first step we need a new parameter in our View Model to exchange sorting information with the View:

public List<KeyValuePair<LambdaExpression, OrderType>> ToDoOrder { get; set; }

The toolkit datagrid is able to exchange directly the lambda expressions to be used in the Linq queries with the Controller. Thus, we use a list of couples Lambda Expression OrderType. Where OrderType is an enumeration that specifies ascending or descending order. The whole list specifies a lexicographic sorting that is possibly based on several columns.

MVC Controls Toolkit defines extension methods to apply directly the above list either to  an IEnumerable or to an IQueryable.It is just to include the namespace: MVCControlsToolkit.Linq and any IEnumerable or IQueryable will be enriched with the method ApplyOrder that accepts the above list as argument. If an ordering is already defined on either the IEnumerable or the IQueryable the new sorting will be chained Lexicographically with it.  

Now our paged search query becomes:

result = context.ToDo.Select(item =>
                    new ToDoView() { Name = item.Name, Description = item.Description, DueDate = item.DueDate, id = item.id }).ApplyOrder(order).Select(viewItem =>
                    new Tracker<ToDoView>
                            {
                                Value = viewItem,
                                OldValue = viewItem,
                                Changed = false
                            }).Skip(toSkip).Take(pageDim).ToList();

It is worth to point out that the sorting information comes from the client, therefore a malicious user might try a denial of service attack by sending a manipulated request of sorting on a column that is too  difficult to sort (a column with no index defined on it, for instance). In order to defend ourselves from a similar attack the Transformation Handler that receives the data from the DataGrid automatically discard columns that are not decorated with the CanSortAttribute that is defined in the MVCControlsToolkit.DataAnnotations namespace. In our case we have the following Data annotations:

        [Required, CanSort, Display(Name="Name")]
        public object Name { get; set; }
        [Required, Display(ShortName = "Description")]
        public object Description { get; set; }
        [CanSort, Display(ShortName = "Due Date"), Format(DataFormatString="{0:D}")]
        public object DueDate { get; set; }

The DisplayAttribute is a standard .Net. Our DataGrid uses ShortName or Name (if ShortName is not specified) for the header of both the unsortable and the sortable columns.The FormatAttribute  inherits from the standard .Net DisplayAttribute and extends it with some properties that we will use in the 0.8 release of the MVC Controls Toolkit, where we will define typed input fields. At moment it has the same behavior of its parent. In this example we use it to specify a nice format for the date.

Now our datagrid template becomes:

<table class="ToDo" >
<tr>
<td class="ToDoHeader"><strong><%:Html.SortButtonFor(m => m.Name, sortButtonStyle: SortButtonStyle.Button) %></strong></td>
<td class="ToDoHeader"><strong><%:Html.SortButtonFor(m => m.DueDate, sortButtonStyle: SortButtonStyle.Button) %></strong></td>
<td class="ToDoHeader"><strong><%: Html.ColumnNameFor(m => m.Description) %></strong></td>
<td class="ToDoHeader"><strong></strong></td>
<td class="ToDoHeader"><strong></strong></td>
</tr>
<%:ViewData["Content"] as MvcHtmlString %>
</table>

Where we can see the use of the sort buttons, and of the ColumnNameFor helper. As default, sort buttons do not  cause post-backs, however they have a parameter to require immediate post-back. Personally I prefer avoiding immediate post-back because this way I can set up my complete lexicographic sorting and only then I require a post-back to update my grid. In any case if there is a pager operating on the grid the change of the sorting causes the page to be reset to the first page automatically.

The up and down arrows of the sort buttons are defined in CSS classes specified in the sorting helper:

  <%:Html.DataGridFor(m => m.ToDoList, ItemContainerType.tr,  "ToDoEditItem",  "ToDoDisplayItem", "ToDoGrid", "ToDoInsertItem")%>
    </div>
    <div class="ToDoPager">
                    <% var pager = Html.PagerFor(m => m.CurrPage, m => m.PrevPage, m => m.TotalPages); %>
                    <%:pager.PageButton("<<", PageButtonType.First, PageButtonStyle.Link) %>
                    <%:pager.PageButton("<", PageButtonType.Previous, PageButtonStyle.Link) %>
                    <%:pager.PageChoice(5) %>
                    <%:pager.PageButton(">", PageButtonType.Next, PageButtonStyle.Link) %>
                    <%:pager.PageButton(">>", PageButtonType.Last, PageButtonStyle.Link) %>
                </div>
    <div>
    <input type="submit" value="Save" />
    <%:Html.ManipulationButton(ManipulationButtonType.ResetGrid, "Reset", m => m.ToDoList, null, ManipulationButtonStyle.Button) %>
    <%: Html.EnableSortingFor(m => m.ToDoList, m => m.ToDoOrder, "NormalHeaderToDo", "AscendingHeaderToDo", "DescendingHeaderToDo", page: m => m.CurrPage) %>
    <%:Html.HiddenFor(m => m.TotalPages) %>
To do its job the EnableSortingFor helper needs the collection, the sorting property and also the page property. As the sort behavior of the column changes, the three CSS classes specified in the EnableSortingFor helper are applied to the buttons in order to change their look.

Another new feature is the ManipulationButtonType.ResetGrid that undos all changes done to the datagrid and not yet committed. Here commitment means commitment to a database or to any other structure, not simply a post-back: the grid remembers its previous values through post-backs! We declare that commitment took place either by calling the Confirm method of a Tracker<Item> associated to a row or by simply reloading the grid with fresh data.

In order to undo a single row delete we have introduced the undelete data button and a template to substitute deleted rows. The undelete is easy: just put the undelete button in this template! However I prefer seeing my rows disappear completely! Therefore I have not used this feature in this example (no panic….in case of errors we have the reset button..).

The edit row template has not changed from our previous tutorial, but there are some changes in the display row template:

            <td class="ToDo">
                <%: Html.ValidationMessageFor(m => m.Name, "*")%><%: Html.DisplayField(m => m.Name) %>
            </td>
            <td class="editor-field">
                <%: Html.ValidationMessageFor(m => m.DueDate, "*")%><%: Html.DisplayField(m => m.DueDate)%>
            </td>
            <td class="ToDo">
                <%: Html.ValidationMessageFor(m => m.Description, "*")%><%:  Html.DisplayField(m => m.Description)%>
            </td>
            <td class="ToDoTool">
                <%:Html.DetailLink(Ajax, "Edit Details", DetailType.Edit, "ToDoSubTasks", "Home",
                    new { 
                        id = Model.id},
                                        null)%>
                <%: Html.ImgDataButton(DataButtonType.Edit, "../../Content/edit.jpg", null)%>
                
            </td>

The use of the DisplayField helper to display not editable fields not only ensures right formatting as the use of the DisplayFor standard Mvc helper, but also it enables fields representing the same data be synchronized automatically in the view! In particular it enables detail data retrieved via Ajax not only to display in a separate form but also to update the corresponding data in the master grid.

We have just introduced our next feature: the Master-Detail Ajax helper. Next to the Edit button that performs on line editing, there is also a Detail Link helper. In order to do its job this helper needs the Ajax object of the View, a name to put on the link, the name of an Action method returning a Partial View, the name of the associated Controller, and the route parameters. Moreover we need to specify also if the detail form we are going to render is a display or an edit form.

The Partial View returned by the Action method must contain neither an Ajax  nor a standard form: an Ajax form is already supplied by the Master Detail helper(this way we enhance re-usability because the same Partial View can be used with both Ajax and normal child calls):

<% Html.DetailFormFor(Ajax, m => m.ToDoList, ExternalContainerType.div,
           "ToDoSubTasks", "Home", null, "isChangedToDo", "isDeletedToDo");%>

The arguments passed to the helper are: the View Ajax object, the Master collection, the type of container where we woluld like to receive the Ajax content, an Action method and  a Controller to receive the post, some Html attributes and two CSS classes. The first class (in our case “isChangedToDo") is applied to the display data-field(the ones defined with the DisplayField helper) of the Master grid when they are changed because of the synchronization between detail form and Master data. The edit field of the grid are not updated because they contains new data provided by the user that this way can decide if continuing with  the change in the Master grid or if accepting the changes provided by the Ajax call.

Please note that the DetailFormHelper needs to be put out of the main form, don’t forget it!!!

The grid accepst also another optional row template to be used with rows that are updated because of the synchronization between detail and master View.

The second CSS class (in our case “isDeletedToDo")  is applied to a whole row when a data item retrieved by the Ajax call is discovered to be already deleted. If the user updates a deleted row a new row is created. Obviously the final choice is left to the Controller that may decide to abort the changes in a similar case.

In order to enhance re-usability of controllers (in particular of child controllers that returns Partial Views) we defined the [AcceptViewHint] Action Filters that enables a father view to require an action method of a controller uses a particular Partial View, in a way that is analogous to a client requiring that a Web Service uses a specific protocol or a client selecting a specific endpoint. The Partial View Hint is transmitted in either a Route parameter or a post parameter whose name is ViewHint.The name passed to the filter is scanned for acceptable length and may contain only alphanumeric characters in order to prevent attacks from malicious users.

I will not go into the code of the Detail View of the example that is completely analogous to the one of the Main View that we already discussed in detail in my previous post. The only things worth to note is the use of the SortableListFor helper to allows editing of the list of subtasks associated with a ToDo item. The SortableListFor helper allows delettion/in place editing/insertion of subtasks and allows also sorting of the subtasks by simply dragging them with the mouse .  The SortableListFor uses JQuery Sortable UI element, but transferring the order from the DOM into a collection of complex type on the server side….is not an easy task to accomplish.

Below the use of the helper:

<%: Html.SortableListFor(m => m.SubTasks, "SubTasksToSort", "SubTasksToSortAddItem", 0.7f,
                                        htmlAttributesContainer: new Dictionary<string, object> { { "class", "SortableList" } }
                    )%>

0.7f is the opacity used by the JQuery Sortable, the first argument is the collection to operate on, while “SubTasksToSort” is the Item template and “SubTasksToSortAddItem” is the template for inserting a new element.

Below the Item template:

<div id='<%: Html.PrefixedId("InnerContainer") %>'>
<%: Html.TextBoxFor(m => m.Name, new Dictionary<string, object> { { "class", "ToDoDetailName" } })%><%: Html.ValidationMessageFor(m => m.Name, "*") %>
<%: Html.TextBoxFor(m => m.WorkingDays, new Dictionary<string, object> {{"class", "ToDoDetailDuration"}})%><%: Html.ValidationMessageFor(m => m.WorkingDays, "*") %>
<%: Html.SortableListDeleteButton("Delete",  ManipulationButtonStyle.Link) %>
</div>

As one can see the SortableListFor helper has it own delete buttons.

Finally the Add Item template:

<div >
<%: Html.SortableListAddButton("Insert New", ManipulationButtonStyle.Link)%>
<span id='<%: Html.SortableListNewName() %>' style="visibility:hidden">
<%: Html.TextBoxFor(m => m.Name, new Dictionary<string, object> { { "class", "ToDoDetailName" } })%><%: Html.ValidationMessageFor(m => m.Name, "*") %>
<%: Html.TextBoxFor(m => m.WorkingDays, new Dictionary<string, object> {{"class", "ToDoDetailDuration"}})%><%: Html.ValidationMessageFor(m => m.WorkingDays, "*") %>
<%: Html.SortableListDeleteButton("Delete",  ManipulationButtonStyle.Link) %>
</span>
</div>

The Add Button of the SortableListFor needs a target container that it will make visible when it is pressed. We can specify it by giving the id returned by the helper

 Html.SortableListNewName() to a span, div or other container.

At moment we have finished……Next time we will speak about typed input fields and about the advanced filtering capabilities of the 0.8 release of the MVC Controls Toolkit.

                                          Stay Tuned……

                                           Francesco

For more information or consulences feel free to contact me,

Tags: , , , , ,

Oct 30 2010

Defining MVC Controls 2: Using the DataGrid

Category: MVC | Entity Framework | Asp.netFrancesco @ 00:46

See a more recent version of this tutorial: Mvc Controls Toolkit Datagrid Updated Tutorial

Defining MVC Controls 1

Defining MVC Controls 3: Datagrid, Sorting, and Master-Detail Views

Data Filtering, in the New Mvc 3 Version of the Mvc Controls Toolkit

This post is a tutorial un how to use the Update/Delete/Insert Templated datagrid of the MVC Controls Toolkit. You can download the full code used in this tutorial here (the package is named BasicTutorialsCode and it contains also the database)..

Our Data model is simply a Todo List with three fields: Name(varchar), Description(varchar) and DueDate(date), plus the principal key id of type identity. As first step we define the Database, and a table named ToDo  with the previous four fields.

Then, we create an entity framework model based on the database just created: I called it MeetingsModel because in a future post I will extend it with the management of a meeting room.

Now it is time to create a MetaClass to handle the constraints on our ToDo items. Let say that the Name and Description fields are required (i.e they can’t be null), while for the date we have a strange constraints: the DueDate can range from 1 month before today to 6 months after today. Since today is not a definite value such constraints can not be interpreted as a Data constraints, thus its natural place is not together with the other data constraints of the data layer: we will take care of it in a few minutes! First let take care of the the other simpler constraints, and add to our project a new file named Todo.cs just under the Models folder. What we put in this file is quite standard:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
using MVCControlsToolkit.DataAnnotations;

namespace Mvc_Examples.Models
{
    [MetadataType(typeof(MetaToDo))]
    public partial class ToDo
    {

    }
    public class MetaToDo
    {
        [Required]
        public object Name { get; set; }
        [Required]
        public object Description { get; set; }
        
    }
}

Now let go to the View Model! What we need to put into the View Model? For sure the ToDo items extracted from the database. However, we would like the items be paged, so we have to insert at least a Page property containing the current page. A single Page int is enough for the MVC Controls Toolkit pager to work, but for a better paging experience it is better to supply also the total number of pages. It is ok, our pager control handle also this information, if available.

Now…the user might edit some fields and then he might change page. What does we do if there are validation errors in its editing? The better thing to do is to go back to the previous page and force him to correct the errors. Therefore, we have to remember what page we come from. Thus we need also a previous page property in our View Model. Luckily we don’t need to put it in a hidden field because our pager already offers  this type of service when a “previous page” property is available.

Summing Up, our View Model needs the following properties: CurrPage, PrevPage, and TotalPages, plus a ToDoList property containing all ToDo items.

Now we have to decide how to handle the strange constraint on the DueDate. One way to do it is to define a View Model version of the ToDo class where we can apply it. In this case we might solve in a simpler way but I prefer to be more general:

using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
using MVCControlsToolkit.DataAnnotations;
 
namespace Mvc_Examples.Models
{
    [MetadataType(typeof(MetaToDo))]
    public partial class ToDoView
    {
        public int? id { get; set; }
 
        public string Name { get; set; }
 
        public string Description { set; get; }
 
        [DateRange(DynamicMinimum="MinDate", DynamicMaximum="MaxDate")]
        public DateTime DueDate {set;get;}
 
        [MileStone]
        public DateTime MaxDate
        {
            get 
            {
                return DateTime.Today.AddMonths(6);
            }
            set 
            {
            }
        }
 
        [MileStone]
        public DateTime MinDate
        {
            get 
            {
                return DateTime.Today.AddMonths(-1);
            }
            set 
            {
            }
        
        
        }
 
    }
}
 

Please notice that we have used the same MetaData class of the original ToDo class. This is important in order to avoid code duplications! As a general rule when building a View Model one has to use either the original data classes, or for each data class we define a modified version and use it as a son of the View Model: this way, we can use the same metaclasses. Please avoid putting fields from more than one data class into a single View Model Class. This can be done only if data are someway aggregated and transformed into other properties, otherwise it is better to keep a one to one correspondence between data classes and classes used in the View Model: this way we increase modularity and we can reuse the metadata classes of the original data classes(duplicating code always causes a lot of problems…).

We have defined two new properties MinData and MaxData that are always equal to Today minus one month and Today plus 6 months: they define the range of possible values for the DueDate property.

The Milestone attribute that decorates these two properties is defined in the MVCControlsToolkit.DataAnnotations namespace and it do exactly nothing…

It is just an hint for the View not to put these properties  into input fields  because their only purpose in the View is giving help to the user: the view designer can use them to display a message for the user, for example.

Our constraint on the DueDate is enforced by using the DateRange attribute defined in the MVCControlsToolkit.DataAnnotations namespace. This attribute act very similarly to the normal Range validation attribute, but accepts also dynamic minimum and maximum, that are essentially the names of the properties containing the actual minimum and maximum.

Next step is the design of the data access procedures. It is not a good idea to insert them into the controller methods, because they might be useful to more than one controller, and because it is always better to keep the data layer separate from the controllers layer. We will put them as static methods of our View Model that this way becomes a kind of  a standard for accessing ToDo items(we will use it always when paged ToDo items are needed). This choice is acceptable for this application, but in different situations we might have introduced a new class.

Therefore our View Model now contains the properties we discussed before:

        public int TotalPages { get; set; }
        public int CurrPage { get; set; }
        public int PrevPage { get; set; }
        public List<Tracker<ToDoView>> ToDoList {get; set;}

Plus two static methods, say GetToDoPage and UpdatePage, respectively for retrieving one page of data, and for passing to the database the updates made by the user to one page of data.

In order to help passing changes to the database the MVC Controls Toolkit furnishes the class Tacker<T> that is a wrapper to put around a data item. It maintains two versions of the data item, the original version and the one with the changes applied by the user. Moreover, it has also a Boolean value Changed to signal that the two versions are different.

Comparison between the two versions yields easily the operation to be done on the database:

  • Old version null and new version different from null: we have an insertion
  • Old version different from null and new version null: we have a deletion
  • Both versions different from null: we have an update

After this short premise let go analyze the GetToDoPage method:

        public static List<Tracker<ToDoView>> GetToDoPage(int pageDim, out int totalPages, int page = 1) 
        {
            List<Tracker<ToDoView>> result;
            using (SiteDbEntities context = new SiteDbEntities())
            {
                int rowCount = context.ToDo.Count();
                if (rowCount == 0)
                {
                    totalPages=0;
                    return new List<Tracker<ToDoView>>();
                }
                totalPages = rowCount / pageDim;
                if (rowCount % pageDim > 0) totalPages++;
                int toSkip = (page-1) * pageDim;
                result = (from item in context.ToDo
                          orderby item.DueDate ascending
                          select new Tracker<ToDoView>()
                          {
                           Value = new ToDoView() { Name = item.Name, Description = item.Description, DueDate = item.DueDate, id = item.id },
                           OldValue = new ToDoView() { Name = item.Name, Description = item.Description, DueDate = item.DueDate, id=item.id }, 
                          Changed=false }).Skip(toSkip).Take(pageDim).ToList();
            }
            return result;
        }

The result is of type  List<Tracker<ToDoView>> since we have used our wrapper around each data item. We create a context, with the using keyword to be sure it will be disposed at the end of the operation since it contains a connection to the database that is not a managed object.

As first operation we counts the total rows, then we do some mathematics to compute the pages: the remainder operation % is needed to take into account partially filled pages.In order to reach the request page we use the Skip and Take methods. In the select construct we create also our wrapper and fill it with two copies of the same data item, that will become the previous and actual version of the data item. Please notice also that we transfer data from the ToDo object into a fresh ToDoView object.

The UpdatePage method is a little bit more complex, but quite easy, too:

        public static void UpdatePage(List<Tracker<ToDoView>> items)
        {
            using (SiteDbEntities context = new SiteDbEntities())
            {
                bool aChange = false;
                foreach (Tracker<ToDoView> item in items)
                {
                    if (item.Changed)
                    {
                        if (item.OldValue == null) //insertion
                        {
                            if (item.Value != null)
                            {
                                ToDo curr=new ToDo() 
                                         { Name = item.Value.Name, Description = item.Value.Description, DueDate = item.Value.DueDate };
                                aChange = true;
                                context.ToDo.AddObject(curr);
                            }
                        }
                        else if (item.Value == null) //deletion
                        {
                            ToDo curr=new ToDo() { Name = item.OldValue.Name, Description = item.OldValue.Description, DueDate = item.OldValue.DueDate, id=item.OldValue.id.Value };
                            context.ToDo.Attach(curr);
                            context.ObjectStateManager.ChangeObjectState(curr, System.Data.EntityState.Deleted);
                            aChange = true;
                        }
                        else//update
                        {
                            ToDo curr = new ToDo() { Name = item.Value.Name, Description = item.Value.Description, DueDate = item.Value.DueDate, id=item.Value.id.Value };
                            context.ToDo.Attach(curr);
                            context.ObjectStateManager.ChangeObjectState(curr, System.Data.EntityState.Modified);
                            aChange = true;
                        }
                    }
                }
                if (aChange)
                {
                    try
                    {
                        context.SaveChanges();
                        items.ForEach((item) => { item.Confirm(); });//confirm changes have been passed
                    }
                    catch
                    {
                    }
                }
            }
        }
                    

We have a loop an all modified items where we verify if the item has changed, and if changed we analyze what operation needs to be done on the database, as previously.explained. Please notice the use of the ObjectStateManger to set the correct state of the various objects in the different cases.  The case of the insertion is the only one that doesn’t require to set manually the state of the object.

If at least a data item has changed we do a SubmitChanges() that passes all changes in a single transaction to the database. In  case no exception is fired we call the Confirm() method of the Tracker<T> wrapper  that sets the old version equal to the new version of the data item since all changes has been passed to the database.In case of exceptions an error message should be returned to the controller that need to say to the user to retry the post in a few minutes….however we have not handled this for sake of simplicity(a too complex example doesn’t help the understanding).

Now we can go now to the controller that has just two Action methods, one for handling the initial get and the second for handling the subsequent posts:       

         public const int PageDim=5;//into an actual application this should be put in a config file
        public ActionResult Index()
        {
            int totalPages;
            ToDoViewModel result = new ToDoViewModel()
                {
                    ToDoList = ToDoViewModel.GetToDoPage(PageDim, out totalPages),
                    TotalPages = totalPages,
                    CurrPage=1,
                    PrevPage=1
                };
            return View(result);
        }

        [HttpPost]     
        public ActionResult Index(ToDoViewModel model)
        {
            if (!ModelState.IsValid)
            {
                
                model.CurrPage=model.PrevPage; //cancel possible page change and force correcting errors
                return View(model);
            }
            else
            {
                ToDoViewModel.UpdatePage(model.ToDoList);
                int totalPages;
                ToDoViewModel result = new ToDoViewModel()
                {
                    ToDoList = ToDoViewModel.GetToDoPage(PageDim, out totalPages, model.CurrPage),
                    TotalPages = totalPages,
                    CurrPage = model.CurrPage,
                    PrevPage = model.CurrPage
                };
                return View(result);
            }
        }

 

The first method just displays the first page and it is quite trivial. The second method just controls if there are validation errors. In case there are it cancels a possible page change by resetting the current page to the previous page, and then returns the same View Model it received in order to let the user correct the errors. In case everything is ok it passes the changes to the database, and retrieve the new page requested by the user. That’s all!

We finally arrived to the datagrid. In order to have the datagrid working we need to prepare 4 templates:

  1. ToDoGrid: it displays the general container where all data items will be inserted. In our case it is just a table.
  2. ToDoDisplayItem: it displays a row of data when the grid is in display mode
  3. ToDoEditItem: it displays a row of data when the grid is in edit mode
  4. ToDoInsertItem: it defines the look of the insert new row component, normally it just displays an insert button.

Well, Let go see in detail each single template.

ToDoGrid
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Mvc_Examples.Models.ToDoView>" %>

<table class="ToDo" >

<tr>
<td class="ToDoHeader"><strong>Name</strong></td>
<td class="ToDoHeader"><strong>Due Date</strong></td>
<td class="ToDoHeader"><strong>Description</strong></td>
<td class="ToDoHeader"><strong></strong></td>
<td class="ToDoHeader"><strong></strong></td>
</tr>
<%:ViewData["Content"] as MvcHtmlString %>
</table>

It just displays the <table> tags and the header of the table. The <%:ViewData["Content"] as MvcHtmlString %> construct defines where all data items have to be inserted. It is a kind of placeholder and it needs to be inserted “as it is” in any template that describes a datagrid container.

It is worth to point out that this template is passed an empty data item object to help the automatic construction of the container. Specifically, we can use reflection to extract all columns and we can also automatically decide some facts about the look of the container by extracting the attributes of each data item property (as for instance the display attribute).

ToDoDisplayItem
          <%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Mvc_Examples.Models.ToDoView>" %>
          <%@ Import Namespace=" MVCControlsToolkit.Core" %>
          <%@ Import Namespace=" MVCControlsToolkit.Controls" %>
          
                      <td class="ToDo">
                          <%: Html.ValidationMessageFor(m => m.Name, "*")%><%: Model.Name %>
                      </td>
                      <td class="editor-field">
                          <%: Html.ValidationMessageFor(m => m.DueDate, "*")%><%: Model.DueDate.ToString("D")%>
                      </td>
                      <td class="ToDo">
                          <%: Html.ValidationMessageFor(m => m.Description, "*")%><%:  Model.Description %>
                      </td>
                      <td class="ToDoTool">
                          <%: Html.ImgDataButton(DataButtonType.Edit, "../../Content/edit.jpg", null)%>
                      </td>
                      <td class="ToDoTool">
                          <%: Html.ImgDataButton(DataButtonType.Delete, "../../Content/delete.jpg", null)%>

          The display item View Model is just a data item, it is not a Tracker<T> wrapper. The wrapper is handled automatically by the DataGrid.

          We don’t need to put the <tr> tag in each data item: the container tag for each item is defined in the DataGrid helper and it is automatically inserted by the DataGrid. We have also the option to supply a delegate that returns a different item container as a function of the data item and of its position in the DataGrid. 

          The last thing worth to discuss are the two data buttons: the first one switches the row in edit mode, while the second one just delete the row. I used here an image button but there are also link and normal buttons.

          ToDoEditItem
          <%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Mvc_Examples.Models.ToDoView>" %>
          <%@ Import Namespace=" MVCControlsToolkit.Core" %>
          <%@ Import Namespace=" MVCControlsToolkit.Controls" %>
                      
                      <td class="ToDo">
                          <%: Html.ValidationMessageFor(m => m.Name, "*")%><%: Html.TextBoxFor(m => m.Name) %>
                      </td>
                      <td class="ToDo">
                          <%: Html.ValidationMessageFor(m => m.DueDate, "*")%><%: Html.DateTimeFor(m => m.DueDate, DateTime.Today).Date()%>
                      </td>
                      <td class="ToDo">
                          <%: Html.ValidationMessageFor(m => m.Description, "*")%><%: Html.TextBoxFor(m => m.Description) %>
                      </td>
                      <td class="ToDoTool" colspan="2">
                          <%: Html.HiddenFor(m => m.id) %>
                          <%: Html.ImgDataButton(DataButtonType.Cancel, "../../Content/undo.jpg", null)%>
                      </td>

          The edit template is completely analogous to the display template. The only difference being that it contains input field to allow user to edit fields.

          Please, notice the Hidden filed containing the key! It is necessary! Here we have a cancel button that undo all changes done to the data item and put the row in display mode.

          Last thing worth to point out is the DateTimeFor helper that is able to take as input, date, time or date and time. It is able to read the DateRange attribute and to enforce its constraints. This means the user is allowed to insert only dates that conforms with the constraints. More information about the DateTimeFor Helper can be found here.

          ToDoInsertItem
          %@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Mvc_Examples.Models.ToDoView>" %>
          <%@ Import Namespace=" MVCControlsToolkit.Core" %>
          <%@ Import Namespace=" MVCControlsToolkit.Controls" %>
          
          <td colspan="5" class="ToDo"><%: Html.ImgDataButton(DataButtonType.Insert, "../../Content/add.jpg", null)%></td>
          The insert template just contains an insert button that when clicked causes a new row to appear in edit mode.
          The Datagrid

          Once we have all templates defined we just need to use the datagrid and pager helpers:

          <div>
              <%:Html.DataGridFor(m => m.ToDoList, ItemContainerType.tr,  "ToDoEditItem",  "ToDoDisplayItem", "ToDoGrid", "ToDoInsertItem")%>
              </div>
              <div class="ToDoPager">
                              <% var pager = Html.PagerFor(m => m.CurrPage, m => m.PrevPage, m => m.TotalPages); %>
                              <%:pager.PageButton("<<", PageButtonType.First, PageButtonStyle.Link) %>
                              <%:pager.PageButton("<", PageButtonType.Previous, PageButtonStyle.Link) %>
                              <%:pager.PageChoice(5) %>
                              <%:pager.PageButton(">", PageButtonType.Next, PageButtonStyle.Link) %>
                              <%:pager.PageButton(">>", PageButtonType.Last, PageButtonStyle.Link) %>
                          </div>
              <div>
              <input type="submit" value="Save" />
              <%:Html.HiddenFor(m => m.TotalPages) %>

          The first argument of the helper is the definition of the property to display in the DataGrid, as usual, the second defines the item container, and the last ones are the names of the templates defined before.

          There are also optional arguments to define html attributes(also as a function of the data item) and to pass a function to change dynamically the item container.

          The pager is composed of various parts that can be also used separately. Here I used link buttons but one can use also image or normal buttons.

          Well we have finished this short adventure,,,,

          Next post will be about the new features of the new release of the datagrid:, sorting, master-detail helpers…….

                                                                  Stay Tuned !

                                                                  Francesco

          For more information or consulences feel free to contact me

          Tags: , , , , , ,

          Sep 25 2010

          Defining MVC Controls 1

          Category: MVCfrancesco @ 22:35

          Defining MVC Controls 2: Using the DataGrid

          Defining MVC Controls 3: Datagrid, Sorting, and Master-Detail Views

          Data Filtering, in the New Mvc 3 Version of the Mvc Controls Toolkit

          This is the first of a series of posts where I will describe the features of the MVCControlsToolkit Library for MVC that can be found here. The MVC Controls Toolkit contains some Controls for MVC, and a toolset for defining easily new controls. In this first post I describe the needs that lead me to develop the MVCControlsToolkit, and its foundations.

          One of the main foundational principles of MVC is the separation of concerns between the Controller that specifies what to show and the View that specifies How to show it. In the View you have access to all data that the Controller want to display through the View Model, and you can take advantage of the full power of the .Net framework to manipulate the data contained in the View Model to give to your web page the look that you prefer and also a structure that is quite different from the way data are organized in the View Model. For instance you can decide to display a date as 3 separate inputs, one for the Year, one for the Month and the other for the Day, instead of using a single TextBox for the whole date. Unluckily, if you do this the Data Binder will not be able to recompose this three separate fields into the original DateTime object when the View is posted back.

          From one side you have the maximum freedom in rendering the View Model in the View and you can define helper methods to handle complex rendering tasks, but from the other side if there isn’t a one to one correspondence between the elements to post back in the model and the properties of the View Model, and a one to one correspondence between the hierarchical structure of the input fields names and the hierarchical structure of the model, the Model Binder isn’t able to reconstruct the View Model from the data posted by the View. Clearly this is not a design error but a conceptual problem: the Model Binder  has simply no information about how map one structure into the other!

          The example of the date can be solved by defining a custom Model Binder for the DateTime type. There, you can put the information on how to recompose the three separate fields into the original DateTime. However, this will be a poor choice for the following reasons:

          1. All DateTime properties will be mapped in the same way! We have not gained freedom in the Map between the View and the View Model! We have simply moved from a standard map to another standard map! The way a property is displayed may depend on the context, so we need the freedom to specify dynamically this map for each instance of a type.
          2. The right place where to specify information about this map is the same place where we defined what map to use: the View! Not the Global.asax where we add the custom Model Binders.
          3. Conceptually the only information that is needed to reconstruct the View Model is the way the two structures are mapped one into the other. By contrast to write a model binder we are required to handle MVC specific data structure. This means two things:
            • More code to write
            • Difficulties in the testing because of the dependencies from complex data structures that needs to be simulated by the test classes

          Conceptually, the only way to solve the above problems is by putting references to the descriptions of the maps used in the Html pages. This way, we can define dynamically the maps when we render the involved elements in the View.

          The MVC Controls Toolkit uses a default Model Binder that is able to retrieve this map references and to use them to rebuild the View Model. The programmer is only required to write the code that furnishes the minimum information possible for the reconstruction of the model: the map!

          The map is defined by providing an implementation of the  IDisplayModel interface:

          
          namespace MVCControlsToolkit.Core
          {
              public interface IDisplayModel:ISafeCreation
              {
                  object ExportToModel(Type TargetType, params object[] context);
                  void ImportFromModel(object model, params object[] context);
                  
              }
          }

          The ImportFromModel method is called by the method that renders the control. The original data structure to be transformed is passed in the model parameter.

          The ExportToModel function, instead, is called by the Data Binder to reconstruct the original data structure after that the class implementing the interface has been filled with the data extracted from the View that was posted back.

          The programmer may invoke the transformation defined by the interface implementation with the use of the InvokeDisplay helper method. The class returned by this method invocation can be chained with another transformation by calling another overload of the InvokeDispaly helper that accepts as parameters the newly created class and the interface implementation of the other transformation. At the end the transformed data are rendered into a template by passing them to the RenderIn helper.

          The use of the InvokeDisplay helper ensure that the inverse transformation will be applied by the Model Binder when the View is posted back.

          Let go see the details of the process described above with the example of the DateTime field that we already referenced before. Obviously, you don’t need to actually implement a DateTime control because the MVCControlsToolkit already comes with a sophisticate DateTimeInput control. This is just a simple example to help the understanding of the concepts exposed.

          In this case a possible simple implementation of the IDisplayModel interface is:

          public class TestDateTimeDisplay : IDisplayModel
          {
           
                  [Range(1000, 3000, ErrorMessage = "wrong year")]
                  
                  public Nullable<int> Year { get; set; }
          
                  [Range(1, 12, ErrorMessage = "wrong month")]
                  public Nullable<int> Month { get; set; }
          
                  [Range(1, 31, ErrorMessage = "wrong day")]
                  public Nullable<int> Day { get; set; }
          
                  public object ExportToModel(Type targetType, params object[] context)
                  {
                      if (Year == 0 && Month == 0 && Day == 0) return null;
                      if (!Year.HasValue && !Month.HasValue && !Day.HasValue) return null;
                      try
                      {
                          return new DateTime(Year.Value, Month.Value, Day.Value);
                      }
                      catch (Exception ex)
                      {
                          throw (new Exception(" {0} has an incorrect date format", ex));
                          
                      }
                  }
          
                  public void ImportFromModel(object model, params object[] context)
                  {
                      Nullable<DateTime> date = model as Nullable<DateTime>;
                      if(date.HasValue && date.Value != DateTime.MinValue)
                      {
                          Year = date.Value.Year;
                          Month = date.Value.Month;
                          Day = date.Value.Day;
                      }
          
                  }
              }

          The call to the ImportFromModel  call is used to fill the values of the of the Year, Month and Day properties from the original DateTime. This three properties will be rendered instead of the original DateTime property. When the View is posted back, the Model Binder first fill the Year, Month and Day properties with its normal algorithm for extracting a model from the post back data and then calls the ExportToModel method of the interface to recover the initial DateTime property. 

          The exception thrown when the date format is wrong is intercepted by the Model Binder and its message is used to produce a validation error associate to the initial DateTime property.

          The code to put in the View for rendering our control is;

          <%: Html.RenderIn("YearMonthDayDate", Html.InvokeTransform(m => m.BirthDate, new TestDateTimeDisplay()))  %>

          YearMonthDayDate is a name of a strongly typed template for the type TestDateTimeDisplay.

          In order to test this example and to do other experiments you can use a generic template for rendering classes, like this one:

          <%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<dynamic>" %>
          <table cellpadding="0" cellspacing="0" border="0">
              <% foreach (var prop in ViewData.ModelMetadata.Properties.Where(pm => pm.ShowForEdit && !ViewData.TemplateInfo.Visited(pm))) { %>
                  <% if (prop.HideSurroundingHtml) { %>
                      <%= Html.Editor(prop.PropertyName) %>
                  <% } else { %>
                      <tr>
                          <td>
                              <div class="editor-label" style="text-align: right;">
                                  <%= prop.IsRequired ? "*" : "" %>
                                  <%= Html.Label(prop.PropertyName) %>
                              </div>
                          </td>
                          <td>
                              <div class="editor-field">
                                  <%= Html.Editor(prop.PropertyName) %>
                                  <%= Html.ValidationMessage(prop.PropertyName, "*") %>
                              </div>
                          </td>
                      </tr>
                  <% } %>
              <% } %>
              </table>

          The template can be put either on the Shared folder or in the folder for a specific controller. However if you put it either within a Display or Edit folder the template will not be retrieved,

          In order to test the example you need just:

          1. Download the MVCControlsToolkit from the CodePlex site here
          2. Create a new MVC 2 project
          3. Add the interface implementation to the project
          4. Add the above template or a specific template as explained above
          5. Add a DateTime property to one of the View Models of the predefined pages that you find into a newly created project. I have chosen the RegisterModel model in the AccountModels.cs file, that is rendered into the Register.aspx View.
          6. Add the code for rendering the control into the adequate View, in my case the Register.aspx View. Add also a reference to the namespace where you defined your interface implementation.
          7. Put a breakpoint in the controller method that handles the post back of the View. In my case, the Register method of the AccountController.cs file, to see how the separated Year, Month, Day fields you insert are recomposed into the original DateTime Property, in my case the BirthDate property.

          In the next post I will analyze more advanced features of the MVCControlsToolkit.

                                                                               Stay Tuned!

                                                                                Francesco

          Tags: , , , , ,