Mar 18 2013

Single Page Applications 1: Manipulating the Client Side ViewModel

Category: Asp.net | MVC | WebApiFrancesco @ 21:46

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

This is the first of 3 introductory tutorials about the features for handling Single Page Applications offered by The Data Moving Plugin. The Data Moving Plugin is in its RC stage and will be released to the market in about one month.

Firs of all just a little bit theory….then we will move to a practical example. If you want, you may give a look to the video that shows the example working before reading about the theory:

See this Video in High Resolution on the Data Moving Plug-in Site

 

Typically an SPA application allows the user to perform several tasks without leaving its physical Html page. This is result may be achieved by defining several “virtual pages” inside the same physical page. During the whole lifetime of the application just an active virtual page is visible, all other pages are either hidden or completely out of the Dom because they are created dynamically by instantiating client templates. The active page interacts with just a part of the Client Side ViewModel, and only that part is kept in synchrony with the server. 

One can use also different techniques to enable the user to perform several tasks without leaving the l Html physical page; in any case the client ViewModel may be partitioned into subparts that are the smallest “units” that may be sinchronized with the server independently of the remainder of the client side ViewModel. We call such elementary units Workspaces because they are the “data units”  manipulated by the user while he is performing one of the several tasks allowed by the SPA.

A workspace, in turn, is composed of two conceptually different sub-parts: a kind of short living data structure that is used just to carry out the current task and a set of long living data structures that are typically connected with a Database on the server side. Typically, on the client side, we don’t have all long living entities  of a “given type” but just a small window of the whole Entity Set. We call Entity Set Window, the set of all long-living entities of the same type stored in the Client Side ViewModel, and we call Core Workspace the part of the Workspace that remains after removing all Entity Set Windows.

Summing up, the Client side ViewModel is composed of partially overlapping Workspaces, that are in turn composed of a Core WorkSpace and several Entity Set Windows.

In general we can’t assume that all data of the Workspace are someway visible in the user interface. In fact the task being performed currently by the user may be composed of several steps (just think to the steps of a wizard), and substantially just the data “used” in the current step are visible to the user. Accordingly, each Workspace may be further split into partially overlapping UI Units, where each UI Unit is a part of the workspace that is “visible” in the user interface at a given time.

The concept of UI Unit is very important in error handling because, while all UI Units belonging to a Workspace must be submitted simultaneously to the server, only the errors that refer to the current UI Unit can be shown to the user.

The Data Moving Plug-in, offers tools to handle properly Entity Set Windows, Core Workspaces, and for handling properly UI Units during validation error processing:

  1. Retrieval Managers takes care of browsing Entity Sets in the Entity Set Windows, while updatesManagers take care of keeping the Entity Set Windows synchronized with the server by processing updates performed by the users to the Entity Set Windows, and by dispatching the principal key of newly created entities returned by the server.
  2. Whole WorkSpace updatesMangers take care of keeping a whole Workspace synchronized with the server, by automatically issuing commands to the updatesManagers of all Entity Set Windows contained in the Workspace and by taking care “personally” of the Core Workspace.
    The communication protocol between a whole Workspace updatesManager and the server includes the possibility for the server to issue “remote commands” that modify the Core Workspace on the client side. In fact, often, it is not possible for the server, to send a whole “updated” Core Workspace to the client that substitutes completely the old one, because the Core Workspace might have “links” with UI elements and with other Client Side ViewModel data, and a similar substitution would break them.
  3. The Data Moving Plug-in provides a powerful dom element-to-dom element data binding engine that enables the user to trigger “interactions” between dom elements,  and provides also a  Reference Knockout binding that maps UI elements to sub-parts of the Workspace, in such a way that the user “move” such parts of the Workspace by simply moving the UI elements that represent them in an intuitive way. The dom element-to-dom element data binding engine has been already described in a previous tutorial and in a previous video, so in this tutorial we will focus mainly on the Reference binding.
  4. Error Bubbling, Entities in Error Filtering, and other enhancements of the standard Asp.net Mvc validation engine help in associating errors to data that are not immediately visible on the screen. Error handling will be described in the second tutorial about SPA applications:  Single Page Applications 2: Validation Error Handling.

Let understand better how all this works with a simple example(the same shown in the video above).

Suppose we have a list of artists and a list of programmers, that are completely stored in the client side view model, and let suppose we would like to build a team to face a web project made of both artists and programmers. The team will have both a leader programmer and a leader artist, and not all people are entitled to cover the role of leader. Below a screen shot with an indication of the UI elements that represent data of the Core Workspace and data of the Entity Set Windows:

TeamBuilding

In the programmers tab there is another Entity Set Window containing Programmers Entities. Since we said all programmers and all artists are contained in the client ViewModel the Entity Set Windows  contain the whole Entity Sets. Moreover, since both the list of all programmers and the list of all artists are paged, not all all programmers and not artists belong to the current UI Unit; this means we will have difficulties in showing possible errors related to artists and programmers that are not in the current page.

As we can see in the video new people may be added to the Team being built by simply dragging them in the “Members” area. if a new Leader is selected, the old leader is automatically moved back in the original list. People that can cover the role of leader have a yellow border, and the two Leader areas accept only people entitled to cover the Leader role. Moreover, the artists area of the team accepts just artists while the programmers area of the team accepts just programmers.

The whole team building UI logics with the constraints listed above has been obtained without writing a single line of procedural code, but by just declaring reference bindings, Drag Sources, and Drop Targets.

For instance, below the definition of the leader programmer area:

  1.   <div id="leader_programmer"class='leader-container programmers ui-widget-content' data-bind="@bindings.Reference(m => m.ProposedTeam.LeaderProgrammer).Get()">
  2.     @ch._withEmpty(m => m.ProposedTeam.LeaderProgrammer, ExternalContainerType.div, existingTemplate: "ProgrammerTemplate0")
  3. </div>
  4. @Html.DropTarget("#leader_programmer", new List<string> { "LeaderProgrammer" }, rolesDropOptions)

The reference binding maps the div named “leader-programmer” with the property of the Core Workspace ProposedTeam.LeaderProgrammer, while the DropTarget declaration makes it accepts Drag Sources tagged as “LeaderProgrammer”. As a consequence of this two declarations when a UI element representing a programmer entitled to cover the role of leader (ie that has the “LeaderProgrammer” tag) is dragged over this are it is “accepted”, and the data item tied to the dragged UI element with another reference binding, is moved into the knockout observable of the ProposedTeam.LeaderProgrammer property. This in turn triggers the instantiation of the programmerTemplate0 client template because of the _withEmpty instruction that is an enhancement of the knockout with binding.

The ProgrammerTemplate0 templates is the client template automatically built by the grid on the left of the page that lists all programmers. As a consequence the chosen leader programmer is rendered in the “Leader Programmer” area with the same appearance he had in the grid. Each member area works in a similar way:

  1. <div class="members-container programmers ui-widget-content" id ="all_programmers" data-bind="@bindings.Reference(m => m.ProposedTeam.Programmers).Get()">
  2.     @ch._foreachEmpty(m => m.ProposedTeam.Programmers, ExternalContainerType.div, existingTemplate: "ProgrammerTemplate0")
  3. </div>
  4. @Html.DropTarget("#all_programmers", new List<string> { "Programmer" }, rolesDropOptions)

However in this case the ProposedTeam.Programmers property used in the reference binding is an observable array, so the dragged element is pushed into this array. Instead of the _withempty, we have a _foreachEmpty that is an enhancement of knockout foreach binding.

 

To makes everything works properly all programmers must be declared as Drag Sources tagged with “Programmer”. Moreover, all programmers entitled to cover the role of leader must have also the “LeaderProgrammer” tag:

  1. @Html.DragSourceItems(".programmers", ".simple-list-item", new List<string> { "Programmer" }, new DataDragOptions { DestroyOriginal = true, AppendTo = "body" })

The above declaration basically says: “define all elements marked with the class “simple-list-item”  that are descendants of the dom element with class “programmers” as Drag Sources with tag “Programmer”. Now since the whole grid containing all programmers is under a div with class “programmers” and since all rows of this grid have the class “simple-list-item” all programmers are all defined as Drag sources.

The request is extended also to future elements that will be added as descendants of the element with class “programmers“, thus if we insert new elements in the grid they will be automatically declared as Drag Sources.

The “simple-list-item” class is added to each row of the grid as a part of its row definition instructions with:

  1. .ItemRootHtmlAttributes(new Dictionary<string, object> { { "class", "simple-list-item" } })

About the “LeaderProgrammer” tag, it must be added to all data items with the property CanBeTeamLeader set to true. Since this property may change during processing we must add it with a knockout binding attached to the CanBeTeamLeader property:

  1. .KoBindingsGenerator(bb => bb.CSS("LeaderProgrammer", l => l.CanBeTeamLeader)
  2.     .Reference(m => m)
  3.     .Get().ToString())

The KoBindingsGenerator is a method of the fluent interface of the grid row definition. It accepts a function of the type

  1. Func<IBindingsBuilder<U>, string> knockoutBindings

and applies the knockout bindings defined in the body of the function to all rows of the grid, by adding them to the client row template being built by the grid. We use the IBindingsBuilder interface received as argument to build a standard Knockout Css binding that adds the css class “LeaderProgrammer” whenever the property CanBeTeamLeader is true, and a Reference binding that bind each row to its associated data item. The Reference binding enables the “Dragged” programmer to “release” its referred data to the data item referred by the “Drop Traget”.

Since in the options of the DragSourceItems declaration we set DestroyOriginal to true a dropped programmer is removed from the programmers list.

When we put a new Leader Programmer in the Leader Programmer area, the old Leader programmer returns back to the programmers list because we defined the Programmers list as mirroring pool for the programmers entities (this is done in javascript):

  1. ko.mirroring.pool = function (obj) {
  2.     var dis = obj["MainCompetence"];
  3.     dis = ko.utils.unwrapObservable(dis);
  4.     if (dis === "Artist") return TeamBuilding.ClientModel.AllArtsist.Content;
  5.     else if (dis === "Programmer") return TeamBuilding.ClientModel.AllProgrammers.Content;
  6.     else return null;
  7. };

All mirroring pools are defined by assigning a javascript function to the ko.mirroring.pool configuration variable. This function is passed all items that were removed from their places because of a Reference binding based interaction and that were put in no other place, so they “disappeared” from the client side ViewModel. This function is their last chance to find an “home”. This function analyze all properties of each item and possibly find a new “home” for it.

Moving either an artist or a programmer in the detail area assigns a reference to its associated data item to the knockout observable CurrentDetail in the client ViewModel without detaching the data item from its previous place, because in this case the DestroyOriginal option of the drop target is not set to true. This triggers the instantiation of a template that shows the data item in detail mode:

  1. @ch._with0(m => m.CurrentDetail,
  2.     @<text>
  3.         <p>
  4.         <span class='ui-widget-header'>@item.LabelFor(m => m.Name)</span>
  5.         :
  6.         @item.TypedEditDisplayFor(m => m.Name, simpleClick: true)
  7.         @item.ValidationMessageFor(m => m.Name, "*")
  8.         </p>
  9.         <p>
  10.         <span class='ui-widget-header'>@item.LabelFor(m => m.Surname)</span>
  11.         :
  12.         @item.TypedEditDisplayFor(m => m.Surname, simpleClick: true)
  13.         @item.ValidationMessageFor(m => m.Surname, "*")
  14.         </p>
  15.         <p>
  16.         <span class='ui-widget-header'>@item.LabelFor(m => m.EMail)</span>
  17.         :
  18.         @item.TypedEditDisplayFor(m => m.EMail, simpleClick: true)
  19.         @item.ValidationMessageFor(m => m.EMail, "*")
  20.         </p>
  21.         <p>
  22.         <span class='ui-widget-header'>@item.LabelFor(m => m.Address)</span>
  23.         :
  24.         @item.TypedEditDisplayFor(m => m.Address, simpleClick: true)
  25.         @item.ValidationMessageFor(m => m.Address, "*")
  26.         </p>
  27.         <p>
  28.         <span class='ui-widget-header'>@item.LabelFor(m => m.CanBeTeamLeader)</span>
  29.         :
  30.         @item.CheckBoxFor(m => m.CanBeTeamLeader)
  31.         </p>
  32.     </text>
  33. , ExternalContainerType.koComment, afterRender: "mvcct.ko.detailErrors", forceHtmlRefresh: true, isDetail: true)

The _with0 instruction is a different enhancement of the knockout with binding, that accepts an in-line razor helper as client template. Among its arguments there is one named isDetail that we set to true, to inform the Data Moving Plug-in engine that the template is the detail view of a data item. This declaration triggers a synchronization behavior between the original UI of the data item and its detail view.

 

Having finished describing how the user can manipulate the Workspace we can move to see how server-client interaction takes place. The two Entity Set Windows of the Workspace are implemented with two grids. For a detailed description about how to “code” grids you may refer to Data Moving Plugin Controls. Here we point out just that since all data items are already on the client side we must use a local retrievalManager to execute the paging, sorting and filtering queries:

  1. .StartLocalRetrievalManager(m => m.AllProgrammers.Content, true, "TeamBuilding.programmersRM").EndRetrievalManager()

The first argument is the source of all items to be queried(a property of the client side ViewModel), the second argument set to true requires the execution of an initial query as soon as the page is loaded (in order to show some initial data in the grid), and the third argument is where to put the newly created retrievalManager.

The updatesManager of the two grids are both root updatesManager since our items are not children of any one-to-many relation, as in all other examples we have seen in Data Moving Plugin Controls. However, in this case they don’t communicate directly with the server, because we will define a whole WorkSpace updatesManager that will take care of collecting data from the two grids updatesManagers, handling the updates of the Core WorkSpace, communicating with the server, and dispatching the responses of the server to the two grids updatesManagers.

The definition of the programmers updatesManager is:

 

  1. .CreateUpdatesManager<TeamBuildingDestinationViewModel>("TeamBuilding.programmersUpdater", true)
  2.     .BasicInfos(m => m.Id, m => m.ProgrammersChanges, "TeamBuilding.DestinationModel")
  3.     .IsRoot(Url.Action("UpdateTeam"))
  4. .EndUpdatesManager()

It appears more complex of the updates managers we have seen in Data Moving Plugin Controls. The first call to CreateUpdatesManager contains the whole path where to store the updatesManager on the client side instead of the name of the property of the ViewModel where to store it, that’s why the second optional parameter is set to true. Moreover, the method call contains a generic type instantiation, the viewmodel we will use to submit all changes to the server:

  1. public class TeamBuildingDestinationViewModel
  2. {
  3.     public Team ProposedTeam { get; set; }
  4.     public OpenUpdater<Employed, Guid?> ProgrammersChanges { get; set; }
  5.     public OpenUpdater<Employed, Guid?> ArtistsChanges { get; set; }
  6. }

The first property will be filled with the whole Core Workspace, while the other two properties will be filled with the programmers and artists change sets. That’s why the call to BasicInfos has two more parameters after the specification of the principal key: the first parameter is the property of the destination ViewModel where to store the programmers change set, and the third parameter is the property of the whole client ViewModel where to put the destination viewmodel before sending it to the server. The IsRoot method contains a fake url since the destination ViewModel will be posted to the server by the whole Workspace updatesManager.

The whole Workspace updatesManager must be defined in javascript since it is not tied to any specific Data Moving Plugin control:

  1. $('#outerContainer').attr('data-update'),
  2.  TeamBuilding.ClientModel, "ProposedTeam", TeamBuilding.DestinationModel, "ProposedTeam",
  3.  { updatersIndices: [TeamBuilding.programmersUpdater, TeamBuilding.artistsUpdater],
  4.      classifyEntity: function (x) {
  5.          if (x['Id'] && x['MainCompetence'])
  6.              return x.MainCompetence() == "Programmer" ? 0 : 1;
  7.          else
  8.              return null;
  9.      },
  10.      ........

The first argument is the url where to submit the destination ViewModel that is extracted from an Html5 attribute of a dom element. The second argument is the whole client ViewModel and the third argument is the path where to find the Core Workspace within the whole client ViewModel. The fourth argument is the destination ViewModel, and the fifth argument is the path to the place where to store the Core Workspace within the destination ViewModel. Then we have an option argument with several properies. Here we analyze just three of them, since all others are connected to error handling that will be discussed in Single Page Applications 2: Validation Error Handling.

UpdatersIndices is an array containing all updatersManager of the Entity Set Windows of the Workspace; in our case the updatesManagers of the two grids.

classifyEntities is a function that given an entity must return the index in the previous array of its Entity Set Windows updatesManager. This function enables the whole Workspace updatesManager to process adequately all entities it find inside the Core WorkSpace.

That’s enough for everything to work properly! When the user clicks submit the update method of TeamBuilding.updater is invoked, the destination ViewModel is filled, and submitted to the server. When the server send the response the parts of the response destined to the Entity Set Windows updatesManagers will be automatically dispatched to them, and processed automatically. As a consequence, modifications to the the Core Workspace returned as remote commands by the server are applied, keys created for newly inserted entities are dispatched each to its entity, and errors associated to various data elements are dispatched next to the adequate UI elements:

  1. $('#submit').click(function () {
  2.         var form = $(this).closest('form');
  3.         TeamBuilding.ClientModel.CurrentDetail(null);
  4.         if (form.validate().form()) {
  5.             TeamBuilding.updater.update(form);
  6.         }
  7.     });

Let give a look to the action method:

  1. public HttpResponseMessage Post(TeamBuildingDestinationViewModel model)
  2.         {
  3.             if (ModelState.IsValid)
  4.             {
  5.                 try
  6.                 {
  7.                     var builder = ApiResponseBuilder.NewResponseBuilder(model, ModelState, true, "Error in client request");
  8.                     builder.Process(m => m.ArtistsChanges, model.ProposedTeam, m => m.Id);
  9.                     builder.Process(m => m.ProgrammersChanges, model.ProposedTeam, m => m.Id);
  10.                     //business processing here
  11.  
  12.                     var response = builder.GetResponse();
  13.                     return response.Wrap(Request);
  14.                 }
  15.                 catch (Exception ex)
  16.                 {
  17.                     ModelState.AddModelError("", ex.Message);
  18.                     return
  19.                         Request.CreateResponse(System.Net.HttpStatusCode.InternalServerError, new ApiServerErrors(ModelState));
  20.                 }
  21.             }
  22.             return Request.CreateResponse(System.Net.HttpStatusCode.InternalServerError, new ApiServerErrors(ModelState));
  23.         }

This code is very similar to the code we have seen in the action methods that processes the grids updates in Data Moving Plugin Controls: we create an ApiResponseBuilder and then we call the Process method on each of the Entity Set change sets we received in the destination ViewModel. Since our principal key are Guids we dont need to specify a custom key generation function, so the indication of which property is the principal key suffices. However, now the Process methods has 3 arguments, instead of two. We used a different overload! An overload that accepts a Core WorkSpace as second argument. Why we need this further argument? Simple, because we have to process also the changes of the entities that are contained in the Core WorkSpace. In fact, a programmer or artist that we added to the Team might have been modified, or it might be a newly inserted item. The addition of the new argument enables the Process method to include also the entities contained in the Core WorkSpace  in the change sets in their appropriate places if this is necessary.

 

Now suppose we want to modify the client side Core WorkSpace by changing all programmers names with the suffix “Changed” and by adding two more programmers to the team. We need to add adequate remote commands in the response. How to built them? Quite easy! It is enough to create a changes builder object and then to mimic these operations on it:

  1. var changer = builder.NewChangesBuilder(model.ProposedTeam);
  2.  
  3. changer.Down(m => m.Programmers)
  4.     .UpdateModelIenumerable(m => m, m => m.Name, (m, i) => m.Name + "Changed");
  5. changer.UpdateModelField(m => m.LeaderProgrammer, m => m.Name, model.ProposedTeam.LeaderProgrammer.Name + "Changed");
  6. changer.Down(m => m.Programmers)
  7.     .AddToArray(m => m[0],
  8.         new Employed()
  9.         {
  10.             Id = Guid.NewGuid(),
  11.             MainCompetence = "Programmer",
  12.             Name = "John1",
  13.             Surname = "Smith1",
  14.             Address = "New York, USA",
  15.             EMail = "John@dummy.us",
  16.             CanBeTeamLeader = true
  17.         }, 0, true).AddToArray(m => m[0],
  18.             new Employed()
  19.             {
  20.                 Id = Guid.NewGuid(),
  21.                 MainCompetence = "Programmer",
  22.                 Name = "John2",
  23.                 Surname = "Smith2",
  24.                 Address = "New York, USA",
  25.                 EMail = "John@dummy.us"
  26.             }, 0, true);

The first instruction modifies the names of the programmers that are simple members of the team(actually it just creates the remote command to do this). We go down the Programmers properties of the Core Workspace and then call the UpdateModelIEnumerable that applies a modifications to all elements of an IEnumerable. The first argument specify the IEnumerable to be modified; since we already moved into the IEnumerable it is just m => m. The second argument specifies the property of each element that must me modified and the third argument specifies how to modify it.

The second instruction modifies the LeaderProgrammer name.  It is self-explanatory.

Finally, the third instruction adds two more programmers. We move down the Programmers property, and then we call AddToArray twice. The first argument of each call specifies the place in the javascript array where to place the newly added element: in our case we place it at index 0….but wait …wait wait, since the last argument of the call is set to true the enumeration starts from the bottom, so we are just queuing the new elements at the bottom of the array.

Now in order to include all “remote commands” in the response we must substitute:

  1. var response = builder.GetResponse();

with:

  1. var response = builder.GetResponse(changer.Get());

However, we have a problem, the two programmers that we added to the team might be already contained in the programmers list so we might have an entity duplication in the client side View Model. Luckily, the Data Moving Plug-in offers tools to enforce  uniqueness.we may trigger the processing that enforce uniqueness of entities in onUpdateComplete callback of the whole WorkSpace updatesManager (defined in its options):

  1. onUpdateComplete: function (e, result, status) {
  2.     if (!e.success) return;
  3.     var hash = {};
  4.     mvcct.updatesManager.utils.entitiesInWorkSpace(TeamBuilding.ClientModel.ProposedTeam, hash);
  5.     TeamBuilding.programmersUpdater.filterObservable(hash);
  6.     TeamBuilding.artistsUpdater.filterObservable(hash);
  7. },

The mvcct.updatesManager.utils.entitiesInWorkSpace method extracts all entities contained in the core workspace, and index them into an hash table. After that, each Entity Set updatesManager ensures they are not contained in the Entity Set Window it takes care of. The task is carried out efficiently because of the indexing performed by mvcct.updatesManager.utils.entitiesInWorkSpace .

That’ all for now!

Stay tuned and give a look also to all other Data Moving Plug-in introductory tutorials and videos

                      Francesco

Tags: , , , , , ,

Mar 17 2013

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

Category: MVC | Asp.netFrancesco @ 23:38

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

Data Moving Forms provide standard detail, edit and filter views, but they can be customized with any Html. Moreover, all actions performed in edit mode can be undone and re-done with the typical editors undo/ redo buttons. The developer can either provide a custom column template or the whole edit, detail or filter templates. Forms may work either stand-alone or attached to the items of a grid, TreeView or of any other control. They provide filtering, editing and detail capabilities to a single model or to a collection of items. You may see Data Moving Plugin Forms working in the following video associated to this tutorial:

When Forms are attached to another control they import all settings (columns definitions, detail, edit detail templates, etc.) from a row definition of that control through the DetailFilterBuilder method of the fluent row definition interface:

  1. .........
  2. .DetailFilterBuilder(out detailBuilder, modelName: "GridExample.EditDetail.ViewModel", undoredo: "GridExample.EditDetail.undoRedo")
  3. .........

In this case we passed also the name of the overall page client ViewModel properties where to store respectively the Form client ViewModel and the undo/redo object, because we decided to create a form with client side capabilities.

The detailBuilder object already contains all information that are needed to render all input fields because they were imported from the row definition object that invoked the DetailFilterBuilder method. However we may continue customizing our form by adding toolbars, buttons, Titles, etc:

  1. @{
  2.         detailBuilder.DeclareViews(true, true, true)
  3.            .OnClose("GridExample.unselectItem")
  4.            .EncloseInValidationForm()
  5.            .StartToolbar(ShowToolbarIn.All).RowContainerLevel(ItemContainerLevel.Row)
  6.                   .StartTextColumn("Human Resource Detail", "Edit Human Resource", "Filter Human Resources ").NoWrap(true).EndColumn()
  7.                   .AddButtonToolbar(ShowButtonIn.Filter, CommandButtonType.Filter)
  8.                   .AppendButton(ShowButtonIn.Display, CommandButtonType.GoEdit)
  9.                   .AppendButton(ShowButtonIn.Edit, CommandButtonType.GoDisplay)
  10.                   .AppendButton(ShowButtonIn.Edit, CommandButtonType.Save, "validate close")
  11.                   .AppendButton(ShowButtonIn.Edit, CommandButtonType.Undo)
  12.                   .AppendButton(ShowButtonIn.Edit, CommandButtonType.Redo)
  13.                   .EndButtons()
  14.            .EndToolBar()
  15.            .DeclareDialog(new MVCControlsToolkit.Controls.DataGrid.Dialog { CloseOnEscape = true, Draggable = true, Resizable = true, MinWidth = 320, Width = 320, Title = "Human Resources" });
  16.     }

The three true in the DeclareViews method declare that we need both Filter, Edit and Detail views. The OnClose method specifies a javascript action to perform when the dialog is closed; in our case the javascript function removes  a Css class from the item the form was attached to in order to edit or to show the item details. EncloseInValidationForm encloses the edit view into an html form and enables client side validation on all form fields.

Then we define a toolbar that contains a text column with a short description of each view (detail, edit,and filter), and several buttons. The first parameter of each button definition specifies which view the button will appear in, while the second parameter specifies the action to be performed by the button. The definition of the save button has a third argument containing two parameters to be passed to the save action: validate and close. This way when the user saves its changes the form is validated and the dialog window that contains the form is closed.

Finally, the DeclareDialog method requires to enclose the form within a Dialog window.

Once we have finished the form definition we can render it with:

  1. @detailBuilder.Render()

The resulting Edit and Filter views are:

EditForm

 

and

FilterView

As you can see the edit view contains 3 string fields and an image field. The undo-redo buttons work like the undo and redo buttons of a standard text editor: they allow to undo and redo all changes done to the input fields.  All four column definitions have been imported from the grid row definition, with no need to specify any custom template. The filter view, instead, contains just two columns: Name and Surname, because these are the only columns that required the filtering (and sorting) services through the Queries method:

  1. .StartColumn(t => t.Name, width: 46, widthIsPercentage: true).Queries(true, true).HorizontalAlign(MVCControlsToolkit.Controls.Core.HAlign.center).EndColumn()
  2. .StartColumn(t => t.Surname, width: 46, widthIsPercentage: true).Queries(true, true).HorizontalAlign(MVCControlsToolkit.Controls.Core.HAlign.center).EndColumn()

 

A form can be defined also as a stand-alone control, without importing the row definition from another control. Also in this case the form can be attached to one or more other controls by exploiting a feature called dynamicEdit that is able to handle concurrent access to the same form from different controls. The form containing just a tinyMce editor in the E-Mail examples of the Data Interactions and Dragging video uses dynamicEdit to work as a text editor for various fields of various controls in the page.

 

Below the definition of a stand-alone form with some custom column definition:

  1.       var h = Html.SendToClient(m => m, "FormExample.ClientViewModel", undoRedo: "FormExample.formUndoRedo");
  2.       h.DeclareStringArray(new string[] { "no", "yes" }, "standardBoolean", true);
  3.       var formR = h.FormFor(m => m)
  4.           .TopHtmlAttributes(new { @class = JQueryUICssClasses.ContentContainer })
  5.           .OnOperationExecuting("customCommands")
  6.           .StartToolbar()
  7.           .RowContainerLevel(ItemContainerLevel.Row)
  8.               .AddButtonToolbar(ShowButtonIn.Edit, CommandButtonType.GoDisplay)
  9.               .AppendButton(ShowButtonIn.Display, CommandButtonType.GoEdit)
  10.               .AppendButton(ShowButtonIn.Edit, CommandButtonType.Reset)
  11.               .AppendButton(ShowButtonIn.Edit, CommandButtonType.UndoAllSteps)
  12.               .AppendButton(ShowButtonIn.Edit, CommandButtonType.Undo)
  13.               .AppendButton(ShowButtonIn.Edit, CommandButtonType.Redo)
  14.               .AppendButton(ShowButtonIn.Edit, CommandButtonType.RedoAllsteps)
  15.               .AppendButton(ShowButtonIn.Both, CommandButtonType.Custom, "operation-submit").Texts("Submit")
  16.           .EndButtons().EndToolBar()
  17.           .AddRowType()
  18.               .AddColumn(m => m.Title)
  19.               .AddColumn(m => m.Description)
  20.               .StartColumn(m => m.Date).CustomTemplateFixed(TemplateType.DetailEdit,
  21.                   @<text>
  22.                   @item.TypedTextBoxFor(m => m.Date,
  23.                       new {data_role_default=DateTime.Today.ToString()},
  24.                       GenericCssClasses.Watermark,
  25.                       ContentAlign.Left,
  26.                       calendarOptions: new CalendarOptions())
  27.                   </text>)
  28.             .EndColumn()
  29.             .StartColumn(m => m)
  30.             .CustomTemplateFixed(TemplateType.DetailEdit,
  31.               @<div class='ui-widget-content custom-column'>
  32.                   <h4>@item.LabelFor(m => m.EnableComments)</h4>
  33.                   @item.CheckBoxFor(m => m.EnableComments)
  34.                   &nbsp;
  35.                   <h4>@item.LabelFor(m => m.Publish)</h4>
  36.                   @item.CheckBoxFor(m => m.Publish)
  37.               </div>)
  38.             .EditDetailColumnCompletelyCustom()
  39.             .CustomTemplateFixed(TemplateType.DetailDisplay,
  40.               @<div class="ui-widget-content custom-column">
  41.                   <h4>@item.LabelFor(m => m.EnableComments)</h4>
  42.                   @item._D(m => m.EnableComments, null, "standardBoolean")
  43.                   &nbsp;
  44.                   <h4>@item.LabelFor(m => m.Publish)</h4>
  45.                   @item._D(m => m.Publish, null, "standardBoolean")
  46.               </div>)
  47.              .DetailColumnCompletelyCustom()
  48.             .EndColumn()
  49.             .AddColumn(m => m.Tags)
  50.             .StartColumn(m => m.Content)
  51.               .CustomTemplateFixed(TemplateType.DetailEdit,
  52.                   @<div class="ui-widget-content custom-column content-container">
  53.                       <div class='left-content'>
  54.                           @item.DualSelectExtFor(m => m.Categories).ImportDefault(DefaultScope.Named, "DetailDualSelect").Header("Categories").ContainerHtmlAttributes(new { @class = "dual-select-column" }).ControlAttributes(new { data_role_default = "0" }).Render(item.CreateChoiceList(m => m.AllCategories, m => m.Id, m => m.Description))
  55.                       </div>
  56.                       <div class='right-content'>
  57.                           @item.TextAreaFor(m => m.Content, new{data_element_type="tinyMCE", rows=20, @class="tinyMCE"})
  58.                            
  59.                       </div>
  60.                   </div>)
  61.               .EditDetailColumnCompletelyCustom()
  62.               .CustomTemplateFixed(TemplateType.DetailDisplay,
  63.                   @<div class="ui-widget-content custom-column content-container">
  64.                         <divdata-bind="html: Content" class='display-content'></div>
  65.                       
  66.                       <h4>Categories: </h4>
  67.                       <span data-bind = "text: FormExample.displayCategories(Categories())"></span>
  68.                       
  69.                       
  70.                   </div>)
  71.              .DetailColumnCompletelyCustom()
  72.             .EndColumn()
  73.           .EndRowType();
  74.   }
  75.  
  76.   @Html.ValidationSummary()
  77.   @formR.Render()

The SendToClient Html helper manages everything is needed to transfer the whole ViewModel to the Client Side, in order to take advantage of client side techniques; It returns an html helper that is able to render controls with client side capabilities. Then, we render a javascript array of strings to be used to display the values of boolean fields.

h.FormFor(m => m) starts the actual Form definition.

TopHtmlAttributes specifies the Css class of the form container. OnOperationExecuting specifies a custom javascript handler for the click button events of the form toolbars. In our case this handler just recognizes the operation-submit custom button command that submits the whole Html page to the server, and then pass the control to the default button click handler that handles all other standard buttons.

Then we have a toolbar definition similar to the one we have already seen in the previous example.

Now since the form is stand-alone we have to provide the columns definitions. For the first two columns we use the standard template. For the Date field we specify a custom template just for the edit view. This is necessary to provide a value for the data-role-default Html5 attributes This attribute declares the default value a field must be set to when the user clicks the form reset button in the edit view (CommandButtonType.Reset). When no such attribute is provided a standard value that depends just on the field data type is used (This suffices in most of the cases).

Then we have an anonymous column (m => m) that we use for the two small boolean fields EnableComments and Publish. In this case we specify both edit and detail custom templates. The edit template is based on two checkboxes, while the detail template uses the array of strings (“no”, “yes”) we previously rendered. The EditDetailColumnCompletelyCustom method call avoids that the html produced by these two templates is enclosed within a standard column container Html.

The Tags column uses standard column templates, while the Content column specifies both edit and detail custom templates. The edit template contains a DualSelect that enables the user to select categories from a pre-existing list and a TextArea that is enhanced with the tinyMce text editor. The detail template contains a div with an html knockout binding to display the html produced by tinyMce, and a text knockout binding that displays all categories selected by the user.

The resulting Edit View is:

StandAloneEdit

While the resulting display view is:

StandAloneDisplay

That’ all for now!

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

                                                      Francesco

Tags: , , , ,

Mar 16 2013

Data Moving Plugin Styling

Category: Asp.net | MVCFrancesco @ 00:00

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 supports both the Jquery UI and the JQuery Mobile styling frameworks. Moreover, the developer has the option to customize the standard Jquery UI, or JQuery Mobile styling, and/or to define new styling frameworks. Different styling options can be mixed in the same page, and also in the same control.

Styling settings are contained in instances of the class MVCControlsToolkit.Controls.CssSettings, and can be activated by calling the static method void CssSettings.Define( CssSettings x). Once activated the styling settings are used in the rendering of all Data Moving Plug-in controls, till a new instance of CssSettings is activated.

If we need to activate new settings only for a few lines of code, after which we need to return to the previous settings we may use the two static methods: CssSettings.Begin( CssSettings x).  and CssSettings.End().

Style settings include information, on how to style normal content, header content, buttons, alternate grids rows, etc. The Data Moving Plug-in comes with three predefined style settings:

  • CssSettingsJQueryUI: It uses JQuery UI  Css classes to style Data Moving Plug-in Controls.
  • CssSettings.JQueryMobile: It uses JQuery Mobile Css classes to style Data Moving Plug-in Controls.
  • CssSettings.JQueryMobileExt: It uses JQuery Mobile Css classes + an extended set of icons (see here) to style Data Moving Plug-in Controls.

JQuery Mobile settings need some parameters that may be provided with the Specify method:

  1. CssSettings.JQueryMobileExt.Specify("b", "a", "e", "e")

The first parameter is the swatch to use for the normal content, the second parameter the swatch for the headers, the third parameter the swatch to use for all clickable elements (mainly buttons), and the last parameter is the swatch for the alternate grid rows.

The developer may customize the default settings either by filling a custom instance of the CssSettings class (or an instance of a custom subclass of it), or by changing just some properties of an existing CssSettings instance with the help of the CssSettings Modify(Action<CssSettings> changes) method.

For instance we would like to change just the alternate row grid class we may write:

  1. CssSettings.Define(CssSettings.JQueryUI.Modify(m => { m.DefaultAlternateClass = "myClass"; }));

Below a grid styled with CssSettingsJQueryUI:

DesktopGrid

And a similar grid styled with CssSettings.JQueryMobileExt:

MobileGrid

In the CssSettings.JQueryMobileExt Grid we used different swatches to render the buttons in the header (black buttons) and the buttons in the normal rows(yellow buttons). In fact yellow buttons in the header would have been unacceptable because of the yellow/black contrast, while yellow buttons on the normal  rows are nice. This result has been obtained by changing the settings of the toolbar and of the headers of the grid:

  1. .StartToolbar(false)
  2.     .CustomCssSettings(CssSettings.JQueryMobileExt.Specify("b", "a", "a", "e"))
  3.     .RowContainerLevel(ItemContainerLevel.Row)
  4.     .RowHeight("3em", true)
  5.     .AddButtonToolbar(ShowButtonIn.Both, CommandButtonType.FilterWindow).ColumnWidth(40).EndButtons()
  6.     .AddPagerColumn("FullCompletePager")
  7. .EndToolBar()

 

  1. .CustomCssSettingsHeaderFooter(CssSettings.JQueryMobileExt.Specify("b", "a", "a", "e"))

Below a video showing some Data Moving Plug-in controls styled in different ways:

That’ all for now!

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

                      Francesco

Tags: , , , , , ,

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

Sep 10 2012

Asp.net Mvc and the Nightmare of Dates and Time Zones

Category: MVC | Asp.netFrancesco @ 15:06

Who never suffered because of Time zones problems in his application? I am speaking of dates that returns changed to the server without being modified by the user! This is just one of the two nightmares connected to the dates handling in web applications, the other one being the different formats of dates in different cultures. I aready discussed the second nightmare in this post about globalization, so in this post I will focus just on how to handle properly Time zones in web applications.

Someone, might say: “I am not interested in this subject because in my applications both the browser and the server share the same Time zone!”. Unluckly this is not the case! If you don’t care of Time zone related issues you might have problems also when browser and the server share the same Time zone!!

The point is that all json serializers try to send the date to the client in UTC format(The 0 Time zone date), so they may transform the date by adding the Time zone offset. Now when the json date reaches the client a javascript date is created using the UTC format, but it shows up in the Time zone of the Borwser, so if both browser and server are on the same Time zone the dates appear to be exactly the same in both the browser and the server. For instance, say you have 11:00 time on the server that is in the +02 Time zone, this date will be converted into 09:00 before being serialized into json. When this date reaches the client the browser creates a 09:00 UTC date, but before showing it, or before returning the Hour it will add again the Time zone offset of the browser, so the date will show again as 11:00 in the browser if browser and server share the same Timzone! And what in case browser and server are in different Time zones? Simple: the browser will show the time of the server converted into the Time zone of the bowser.

That’s right! It is exactly what we want…so what is the problem? Why sometimes we receive the wrong date on the server?…Because when the client sends the time to the server it sends to the server the UTC time, that is 09:00 , but this time the de-serializer doesn't transform automatically the date in the local Time zone…so we receive 09:00…that appears to be the wrong hour…I said “appears” because it is not the wrong hour!

In order to understand why the above behaviour is correct, we need to know that in .Net DateTime structures have a property called Kind whose possible values are:

  • DateTimeKind.Unspecified
  • DateTimeKind.Utc
  • DateTimeKind.Local

All deserializers just return a date in Utc format with the Kind set to DateTimeKind.Utc simply because they dont know how the remainder of the system processes the dates, so they use the neutral Utc format.

So if we need the date in local time we just need to convert it by calling the ToLocalTime method of the DateTime structure!

So, it is quite simple…or…not? Unluckly this doesnt solve the Time zone nightmare! In fact as one might expect the DateTimeKind.Unspecified value creates problems, and unluckly most of data sources (such as databases) set the Kind property just to DateTimeKind.Unspecified .

So…what if the kind is set to DateTimeKind.Unspecified ? The serializer dont know if the Date Time is expressed in the local Time zone or in Utc, so some serializers assume it is already in Utc format and will not subtract the Time zone offset before sending it to the client while others will subtract the Time zone offset. In particular:

  • The JSON.NET serializer used as default by WebApi controllers assumes that the date is in Utc format and doesnt subtract the Time zone offset
  • The Microsoft JavaScriptSerializer used by the remainder of the Asp.net Mvc framework assumes the date is in the local Time zone format and do subtract the Time zone offset.

As a conclusion to avoid problems, please substitute all Datetimes with DateTimeKind.Unspecified with DateTimes with the right Kind before serializing them to json.

Unluckly this substitution cannot be done easily in case of WebApi action methods that return an IQueryable<T> and are decorated with the [Queryable] attribute. In fact the IQueryable is executed after the query contained in the Url is applied to it by the [Queryable] action filter, so the substitution can be done only after this event by another action filter!

In the Mvc Controls Toolkit we defined an action filter that do this job. Below an example of usage:

  1. [Queryable, DateConversionFilter(DateConversionFilterOperation.DeclareLocale)]
  2. public IQueryable<ToDoView> Get()
  3. {
  4.  
  5.     return ToDoViewModel.GetToDoQueryable();
  6. }

Obviously we can also use DateConversionFilterOperation.DeclareUTC if we know all dates are in the UTC format.

Finished?….Wait….Wait….There are two more issues to discuss to get rid of the Time zone nightmare.

  1. As we discussed before dates that are sent in json format to the browser are shown in the local Time zone of the client, but dates that are processed directly on the server and put into input html fields are shown in the Time zone of the server, since the conversion into a string is done on the server. Accordingly, it is not adviced to mix the two techniques to send dates to the browser if browser and server may be in different Time zones. However, also dates rendered on the server maybe expressed in the client Time zone if we detect someway the Time zone of the browser.
  2. Dates only fieds whose time part is not processed on the client may create problems because of the truncation of the time part if server and browser are in different Time zones.

To understand better the problem stated in point 2. let suppose we have a day stored in the server database, say, 2012-01-10. When this date is transferred into a DateTime it becomes: 2012-01-10 00:00:00. Now suppose the Time zone of the server is +02, when this DateTime is transformed into  Utc before the serialization its value becomes : 2012-01-09 22:00:00. Now if the browser Time zone is +01, this date shows up as 2012-01-09 23:00:00 into an input fields,…but since the time part is truncated…it becomes simply 2012-01-09 …that is 2012-01-09 00:00:00. Thus it is sent to the server as an Utc date whose value is 2012-01-08 23:00:00. The sever adds 2 hours to the above value to get the date in local time, yielding: 2012-01-09 01:00:00, that is stored again in the database as 2012-01-09  with an error of one day!

Adding an offset to avoid this problem doesnt solve since this will cause problems with other client Time zones. There are just two ways two solve the problem properly:

  1. Avoiding truncations. This means that the time part that is not shown to the user must be stored someway and added to whatever input day the user insert. In the Mvc Contols Toolkit if we use Client Blocks and we decorate the input field with the data-date-only=”true” Html5 attribute, this job is done automatically by the Client Blocks engine.
  2. Rounding the result at midnight of the nearest day after each conversion of the DateTime into a different format. It can be proved mathematically, that the above technique yields the right result. Now if the date is stored with a 0 time part, rounding must occurr just after the date is transformed into the local time of the client, on the client side, and after the date is received again on the server. The two roundings must be done in exactly the same way. In the Mvc Controls Toolkit we defined the following helper methods to perform easily this job:
    • On the client side: MvcControlsToolkit_DateTimeToDate(date).
    • On the server  side: ControllerUtilities.RoundDateTimeToDate(DateTime x), and ControllerUtilities.RoundDateTimeToDate(DateTime? x). There is also a mehod that rounds all dates contained in a ViewModel: ControllerUtilities.RoundDateTimesToDates<T>(T x).  The rounding can be performed also while transforming the date from Utc to local time: ControllerUtilities.DatesToLocale<T>(T x, bool round=false) that converts to local time and rounds all dates contained in a ViewModel.

 

Summing up, in order to handle poperly dates we need:

  1. To ensure all dates have the right Kind property attribute before serializing them, possibly with the help of an action filter.
  2. To transform all dates received by the server from their Utc format into the desired format.
  3. To deal adequately with dates only fields with the help of the data-date-only=”true” attribute and of the rounding technique.

Now we have…actually finished…examples showing the techniques discussed in this post are contained in the Mvc4 Client- Filtering -Paging -Sorting-updating and Advanced JSon Communication files of the download area of the Mvc Controls Toolkit.

 

                       That’s All

                                     Francesco

Tags: , , ,

Jan 17 2012

Customizing Error Messages in Asp.Net MVC

Category: Asp.net | MVCFrancesco @ 07:07

 

Error messages that are automatically displayed by Asp.Net Mvc in response to user wrong inputs come from 3 sources:

  • Errors that the developer adds manually to the ModelState. Such errors are under the full control of the user since they are handled manually, so there is not too much to say about them: I just advice to put them in a resource file, instead of mixing them with the code. This way the application can be easily globalized, and all messages can be easily changed without putting the fingers in the code.
  • Errors added automatically by the Model Binder during the Model Binding process. The Model Binder adds substantially two kind of error messages:
    • Wrong Format Error messages: they are added when the Model Binder is not able to parse an input string coming from the client into its destination type. This happens, for instance, when the user inserts alphabetic characters into an integer field. The standard error message used in this case by the Model Binder is taken from an internal resource file, and it is used just for the server side validation. Client Side validation uses a messages taken from a different source.
    • Implicit Required Field Error Message: properties that can’t have a null value, such as integers or floats properties are considered Required also if they are not explicitely marked as Required with a RequiredAttribute. On the server side this check is automatically performed by the Model Binder. The standard error message used in this case is taken from an internal resource file, and it is used for both server side and client side validation.
  • Error Messages coming from different Validator Providers:

Summing up, the messages automatically provided by the framework that we might want to customize are:

  1. Messages coming from validation attributes. In this case the same message is used on both the Client Side and the Server Side.
  2. Implicit Required Field Error Message. In this case the same message is used on both the Client Side and the Server Side.
  3. Wrong Format Error message on the Server Side
  4. Wrong Format Error message on the Client Side

All the above messages share a common format: they are not simple strings, but the are format strings with an hole {0}, that is automatically filled with the DisplayName of the property they refer to. Accordingly, when we customize them, we may put the same hole in our customized messages to ensure a better user experience.

The DisplayName is taken from the Name property of the DisplayAttribute, or from the DisplayNameAttribute.if the ViewModel property is decorated with any of these attributes. In the case of the DisplayAttribute if the ResourceType property is null the string contained in Name is used directly as DisplayName otherwise the Name string is used as a key to retrieve the value of the DisplayName from the resource file specified in ResourceType. The DisplayNameAttribute.instead, is more “primitive” and doesn’t allow the use of resource files, but just a simple string that must be passed as parameter. However, the DisplayNameAttribute is the only one supported in Mvc 2.

If the ViewModel property is not decorated with either the DisplayAttribute or the DisplayNameAttribute the DisplayName is set to the name of the ViewModel property.

Customizing error messages coming from validation attributes.

All validation attributes inherits from the ValidationAttribute class. Accordingly, they all have three properties that enable the developer to customize their error messages: ErrorMessage, ErrorMessageResourceType, and ErrorMessageResourceName. ErrorMessage can be set directly to a custom error message (with a hole for the DisplayName). The other two properties are useful if we prefer to store the custom message in a resource file;  ErrorMessageResourceType specifies the type associated to the resource file (typically the type name is equal to the the file name), and ErrorMessageResourceName is the key to lookup in the resource to get the error message.

Below two examples using the ErrorMessage:

[Required(ErrorMessage="Please insert a valid {0}") ]
[DisplayName("User name")]
public string UserName { get; set; }

or without the “hole”:

[Required(ErrorMessage="Please insert a valid user name") ]
[DisplayName("User name")]
public string UserName { get; set; }

and an example that uses a resource file:

[Required(ErrorMessageResourceType=typeof(Resource1), ErrorMessageResourceName="fieldRequired") ]
[DisplayName("User name")]
public string UserName { get; set; }

Customizing the Implicit Required Field Error message and the Wrong Format Error message on the Server Side

These messages can be customized by defining them in a Resource file and then assigning the name of the resource file into the static string property ResourceClassKey of the class DefaultModelBinder. The Implicit Required Field custom error message has to be put under a key named DefaultModelBinder_ValueRequired and the Wrong Format custom error message under a key named DefaultModelBinder_ValueInvalid. While the Implicit Required Field custom error message is used on both client side and server side the Wrong Format custom error message is used just on the server side.

The message has two “holes” {0} is for the value and {1} for the DisplayName of the field. The standard message for the English is: The value '{0}' is not valid for {1}.

A good place where to assign a value to the ResourceClassKey property is the global.asax:

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();
    DefaultModelBinder.ResourceClassKey = "MyResources";
    RegisterGlobalFilters(GlobalFilters.Filters);
    RegisterRoutes(RouteTable.Routes);
}

Customizing the Wrong Format Error messages on the Client Side

This messages are provided by the ClientDataTypeModelValidatorProvider, and have just one “hole” for the DisplayName of the property in contrast with its server side counterpart that has two holes, one for the value and the other for the DisplayName. The reason of this difference is simple: the “holes” are filled on the server side when the value is still unknown.

Before Mvc4 the ClientDataTypeModelValidatorProvider had no extension point to customize the error message. Accordingly, the only clean way to customize this message is by substituting the ClientDataTypeModelValidatorProvider. Starting from  Mvc 4 the ClientDataTypeModelValidatorProvider  was enriched with a ResourceClassKey static string property that may be filled with the name of a custom resource class, that works similarly works as   the ResourceClasseKey property of the DefaultModelBinder. In this case the wrong numeric format error and the wrong date format errors must be provided respectively under the FieldMustBeNumeric and FieldMustBeDate keys.

In the Mvc Controls Toolkit we have substituted the ClientDataTypeModelValidatorProvider with another provider called ClientDataTypeModelValidatorProviderExt that works properly also with Mvc versions before version 4. It has extension points for specifying errorr message for both the Wrong Numeric Format error and another for the Wrong Date and Time Format error, and specifies the resource class as a type instead instead of as a string in order to increase the maintainability of the code. The customization may be performe by passing both a Resource file type and the key for the two error messages to static properties of the ClientDataTypeModelValidatorProviderExt class as shown below:

protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
            MVCControlsToolkit.Core.ClientDataTypeModelValidatorProviderExt.ErrorMessageResources = typeof(MVCNestedModels.Models.Resource1);
            MVCControlsToolkit.Core.ClientDataTypeModelValidatorProviderExt.NumericErrorKey = "NumericError";
            MVCControlsToolkit.Core.ClientDataTypeModelValidatorProviderExt.DateTimeErrorKey = "DateError";
            RegisterGlobalFilters(GlobalFilters.Filters);
            RegisterRoutes(RouteTable.Routes);
        }

for more details see here.

 

That’s all!

Stay Tuned

Francesco

Tags: , ,

Dec 14 2011

Globalization, Validation and Date/Number Formats in Asp.Net MVC

Category: MVC | Asp.netFrancesco @ 16:18

This post is a tutorial about how to handle number and date formats in different cultures on both client and server side in Asp.Net Mvc. A good tutorial about the Internationalization of web site contents through resource files can be found here.

On the server side, cultures, and culture aware date and number formats are properly handled automatically by the asp.net framework both in WebForms and in Mvc through the culture attribute of the <globalization/> tag of the Web.Config. For instance, <globalization culture=’en-US’/> forces the use of the United States culture in all Web requests. With culture=’auto (the default) a different culture is used in each request because the culture is taken from the culture settings of the Browser that issued the request. The uiCulture attribute of the globalization tag controls the language of the resource files in a completely analogous way(see here for more details).

Both the culture and the uiCulture attributes work by autonatically setting the CurrentCulture and CurrentUICulture of the thread that is serving the current request with something like:

System.Threading.Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
System.Threading.Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-US");

If we use Client Validation, we are forced to reflect the server side culture settings also on the client side, otherwise numbers and dates are not validated correctly on the client and may cause a well formatted form to fail validation.

if we use MicrosoftaAjax and MicrosoftMvcValidation the server culture is easily reflected on the client with:

<% AjaxHelper.GlobalizationScriptPath = "http://ajax.microsoft.com/ajax/4.0/1/globalization/"; %>
<%: Ajax.GlobalizationScript() %>

Where the link http://ajax.microsoft.com/ajax/4.0/1/globalization/ is a public directory containing the globalization scripts for all supported cultures.

If we use Mvc 3 with the jQuery validation plugin, we need an external globalization library, since globalization is not supported natively, neither by the jQUery validation plugin nor by the interface layer between Mvc3 and the jQuery validation plugin. A good choice is the Globalize library.

This library furnishes the javascript methods to parse and format dates and numbers with the rules of a given culture: we need to connect it with both the server side culture and with the validation rules of the of the jQuery validation plugin to globalize our application.

In the Mvc Controls Toolkit I wrote a simple extension method that do this job:

private static string script = @"
        <script type='text/javascript' src='{0}'></script>
        <script type='text/javascript'>
            jQuery.global = Globalize;
            $.validator.methods.number = function (value, element) {{
                if (value == '' || !isNaN(jQuery.global.parseFloat(value))) {{
                    return true;
                }}
                return false;
            }}
            $.validator.methods.date = function (value, element) {{
                if (value == '' || !isNaN(jQuery.global.parseDate(value))) {{
                    return true;
                }}
                return false;
            }}
           
            $(document).ready(function () {{
                jQuery.global.culture('{1}');
                var culture=Globalize.culture();
                culture.calendar.patterns['G']=culture.calendar.patterns.d+' '+culture.calendar.patterns.T;
            }});
public static MvcHtmlString GlobalizationScript(this HtmlHelper htmlHelper, string globalizationFolder = "~/Scripts/cultures/", string localizationFolder = "~/Scripts/localization/")
{
    string cultureName = System.Threading.Thread.CurrentThread.CurrentCulture.Name;

    string culturePath = string.Format("{0}globalize.culture.{1}.js", globalizationFolder, cultureName);
    if (culturePath[0] == '~') culturePath = UrlHelper.GenerateContentUrl(culturePath, htmlHelper.ViewContext.HttpContext);

    return MvcHtmlString.Create(string.Format(script, culturePath, cultureName));

}

The above extension method does what follows:

  1. Adds a reference to the javascript globalization file associated with the same culture that is set on the server.
  2. Redefines the jQuery plugin rules for the right format of numbers and dates in terms of the parsing functions of the Globalize library
  3. Sets the default culture of the client to the same culture set on the server side.
  4. Defines the ‘G’ date format (short date plus long time) that is not among the standard formats provided by the Globalize library. This is an important step since the ‘G’ format is the default .Net format for dates.

Further globalization settings are needed if we use also the jQuery DateTimePicker. In the Mvc Controls Toolkit such settings are provided with the extension method below:

private static string scriptDataPicker = @"
            <script type='text/javascript' src='{0}'></script>          
            
        ";
public static MvcHtmlString JQueryDatePickerGlobalizationScript(this HtmlHelper htmlHelper, string cultureName=null, string globalizationFolder = "http://jquery-ui.googlecode.com/svn/trunk/ui/i18n/")
        {
            if (cultureName==null)
                cultureName = System.Threading.Thread.CurrentThread.CurrentCulture.Name.Substring(0, 2);
            string culturePath = string.Format("{0}jquery.ui.datepicker-{1}.js", globalizationFolder, cultureName);
            if (culturePath[0] == '~') culturePath = UrlHelper.GenerateContentUrl(culturePath, htmlHelper.ViewContext.HttpContext);


            return MvcHtmlString.Create(string.Format(scriptDataPicker, culturePath));

        }

It takes the globalization javscript file assocaited with the same culture that is set on the server from the public url http://jquery-ui.googlecode.com/svn/trunk/ui/i18n/ (see here).

Summing up we may globalize our web application by adding just:

<script type='text/javascript' src="@Url.Content("~/Scripts/globalize.min.js")"></script>
@Html.GlobalizationScript()
@Html.JQueryDatePickerGlobalizationScript()

However, this is not enough to have all our validation rules working properly! In fact the client side code of the RangeAttribute still uses the standard javascript parsing functions for numbers and dates instead of their globalized counterparts.

The Mvc Controls Toolkit autodetects the gloabalization library used, if any, and uses its parsing functions in all client side validation rules. Therefore, if we use the Mvc Controls Toolkit the few lines of code I showed before are enough to put in place the globalization of the whole validation library.

The FormatAttribute of the Mvc Controls Toolkit enables the developer to define the format to be used in the representation of a property on the client side, as shown in the example below:

[Format(Prefix="Date of birth is: ", ClientFormat = "d", NullDisplayText="No date of birth available")]
public DateTime? BirthDate { get; set; }

The “d” format just displays the date without the time in its short format, that depends on the culture that is set. We can use other standard formats like the  “d” format or we can specify a custom format by writing something like “M/d/yyyy” where M represents the month number, d the day number and y a digit of the year.

The Prefix is added before the formatted value just in case the helper we are rendering supports a display-only mode. In particular it is used with the display only helper _D. However it is used also with the TypedTextBox and with the TypedEditDisplay when they are in display mode. Also the NullDisplayText is used just in display-only mode.

We can specify also a Postfix to add after the formatted value.

If we set the ExtractClientFormat property of the FormatAttribute to true(the default is false) when the ClientFormat is not specified, both Prefix, Postfix and ClientFormat are extracted from the DataFormatString property, that specifies the format to be used on the server side,  according to the rule: “Prefix{0:ClientFormat}Postfix”. Therefore, in the above example we obtain the same effect by specifying DataFormatString=”Date of birth is: {0:d}”.

If ClientFormat is specified but DataFormatString is not, DataFormatString is always computed from Prefix, ClientFormat, and Postfix according to the same rule.

Prefix and Postfix can be also taken from a resource file by using a different constructor of the FormatAttribute that accepts the type of the resource file and two resource keys as first parameters, as shown belowe:

[Format(typeof(Resource1), "BirthdayPrefixKey", "BirthdayPostfixKey", ClientFormat = "d")]
public DateTime? BirthDate { get; set; }

This way, the strings can be globalized as explained here.

Below the list of all supported date format:

Format Meaning "en-US"
f Long Date, Short Time dddd, MMMM dd, yyyy h:mm tt
F Long Date, Long Time dddd, MMMM dd, yyyy h:mm:ss tt
G Short Date, Long Time M/d/yyyy h:mm:ss tt
t Short Time h:mm tt
T Long Time h:mm:ss tt
d Short Date M/d/yyyy
D Long Date dddd, MMMM dd, yyyy
Y Month/Year MMMM, yyyy
M Month/Day yyyy MMMM

The above table adds just the G format to the analogous table reported in the Globalize library. Both the custom date format and all number formats supported in the  Mvc Controls Toolkit are the exactly the same ones of the Globalize library.

That’s all! Examples of globalization settings and formatting can be found in the binary distribution in the download area of the Mvc Controls Toolkit web site.

Stay Tuned! Next post will be about the customization and globalization of validation errors!

Francesco

Tags: , ,

Oct 28 2011

Low BandWidth Transfers with The Client Side Templates of the Mvc Controls Toolkit

Category: MVCFrancesco @ 23:07

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

Handling Big Amounts of Data with Client-Side Templates

Handling Big Amounts of Data with Client-Side Templates 2

This post is a tutorial on the new Client-Side templates, and on the ClientBlockRepeater of the Mvc Controls toolkit. I assume that everyone is already familiar with the Client Block feature of the Mvc Controls Toolkit.

Client-Side Templates allow a rich UI to be defined just once in a template, and then to use this template to render each of the items in a big collection contained in a Client-Side ViewModel. This way the rich interface is obtained with a low Internet BandWidth cost, since code needed to create the rich UI is sent to the client just once in a template.

Templates are defined as normal Mvc Controls Tool kit templates, by the developer, then they are rendered on the sever and transformed into client-side templates. The template is enriched with some extra code that enables both client and server side validation on the input fields that are created dynamically when the template is instantiated on each Javascript item on the client-side.Once on the client, they are instantiated with each of the data items contained into an array of the Client-Side ViewModel.

The insertion of the actual data into the template can be done statically by inserting them just once when the template is instantiated with the data item, or dynamically, in which case we create a permanent client-side binding between the data field of the data item and an html element of the template. In the case of dynamic byndings each data item acts as a ViewModel for the instance of the template that is associated to it. Dynamic instantiation of a data fields has the advantage that each time the data is changed, the html element it is bound to is automatically updated without requiring the re-instantiation of the whole template. Moreover, dynamic binding can be also two-way, in which case also modifications of the html element can be reflected back on the data field the html elements is bound. Two way bindings are necessary when we want to collect input from the user into the Client-side ViewModel.

As a conclusion dynamic bindings are more flexible, but they are also slower to render, expecially two way bindings, so if one would actually like to put on the screen thousands of items they should be planned carefully.

Suppose we want to show a list of products that the user can purchase by just inserting the quantity he would like to receive together with the desired delivery date in the same row displaying the product. This approach is quite rough and it is not efficient, however the main purpose of this tutorial is to show how client-side template works, so we will use it. In a further tutorilal we will show a better approach.

For simplicity we will just display the description of  the product. Thus our simple ViewModel description of each product is:

public class ProductView
    {
        public int Code { get; set; }
        public string Description { get; set; }
        [Range(0, 4), Display(Name="Quantity", Prompt="Insert Quantity")]
        public int? Quantity { get; set; }
        [DateRange(DynamicMinimum="MinDeliveryDate", DynamicMaximum="MaxDeliveryDate")]
        public DateTime DeliveryDate { get; set; }
        [MileStone, ScriptIgnore]
        public DateTime MaxDeliveryDate
        {
            get
            {
                return DateTime.Today.AddMonths(2);
            }
            
        }
        [MileStone, ScriptIgnore]
        public DateTime MinDeliveryDate
        {
            get
            {
                return DateTime.Today.AddDays(2);
            }
            
        }
    }

We put a range attribute on the Quantity, to allow a maximum purchase of 4 instances of the same product, than we added two read only properties just to define some constraints on the delivery date through the DateRangeAttribute as already explained in a previous tutorial.The Milestone attribute declares that the two Date bounds will not be rendered, and the ScriptIgnoreAttribute, avoid that they are rendered when the model is serialized in json.

Our full ViewModel is:

public class ToBuyViewModel
    {
        public List<ProductView> Products { get; set; }
        public int LastKey { get; set; }
    }

The LastKey property is needed to handle our paging technique: we put on the View an “AddNewPage” button, that when clicked add a new page of products to the ones already shown on the screen. Thus we have no page changes, but just the addition of new products…just a marketing technique to sell more products :)

Since we want to handle our products with cliemt-side templates, we define the whole View as a Client Block. Accordingly our Action method will return a ClientBlockViewResult by calling the ClientBlockView controller extension method:

return this.ClientBlockView(shopping, "productsClientView");

Where shopping is our ViewModel filled with data. We will use a ClientBlockRepeater to render our instantiated Client-Side templates as child of a tbody tag of a table:

@Html.ValidationSummary(false)
              <table >
              <thead>
                  <tr>
                  <td><strong>Product</strong></td>
                  <td><strong>Quantity</strong></td>
                  <td><strong>Delivery Date</strong></td>
                  </tr>
              </thead>
              
              @Html.ClientBlockRepeater(m => m.Products,
                   _S.H<ProductView>(
                        @<tr>
                            <td>
                            @item._D(m => m.Description)
                            </td>
                            <td>
                            @item.TypedTextBoxFor(m => m.Quantity)
                            @item.ValidationMessageFor(m => m.Quantity, "*")
                            </td>
                            <td>
                            @item.DateTimeFor(m => m.DeliveryDate, DateTime.Today,
                                        false).Date()
                            </td>
                     </tr>
                    ),
                    ExternalContainerType.tbody,
                    new { id = "ProductsContainer"})           
              </table>

As you can see the template passed to the repeater is a normal server side template. It is compiled and transformed into a client side template in a way that is completely transparent to the developer.

The bindings of the TypedTextBox with the Quantity, and the one of the  DateTimeInput with the DeliveryDate of each ProductView object in the Products collection of our Client-Side ViewModel are deined automatically by the Client Block engine of the Mvc Controls Toolkit, since the input fields are defined through expressions(the Client Block engine define automatically bindings according to the same name convention used by the default model binder). The Description is inserted into the template statically (that is once for all when the template is instantiated) with the _D helper.This helper renders its parameter enclosed in a span element and with the formatting defined in a FormatAttribute.

There is also an _F helper that displays its parameter with formatting but without the span element, and the _P helper that displays its parameter in a rough format without any formatting.

As you can see, thanks, to both the implicit binding definition of input fields performed by the Client Block engine and to The _D helper we have defined the Client-Side template in exactly the same way we would have defined a Server-Side template! This make the use of Client-Side template straigthforward and enhance the re-usability of templates.

Let’ handle paging now. We need just to ask a new page of products through an Ajax call to an action method: As a first step we build the Url string of the request. We pass a ViewHint= “json” parameter to our action method, just to say we want the answer in JSON. Since our action method has been decorated with the attribute:

[AcceptViewHint(JsonRequestBehavior.AllowGet)]

as explained here, the result of the action method is automatically transformed into a JsonResult.

At run time we add also the parameter that specifies the code of the product to start with. When data arrive we push it in our original Products collection…and miracle of the Client Templates, new templates are instantiated automatically for the new products! Finally we update, our LastKey property to be ready for the next page request.

@{
                    string nextPageUrl = Url.Action("Index", "Shopping", new { ViewHint = "json" });
                }

                <script language='javascript' type='text/javascript'>
                    $(document).ready(function () {
                        $('#Button1').bind('click', function () {
                            $.ajax({
                                url: "@MvcHtmlString.Create(nextPageUrl)" + "&startFrom=" + productsClientView.LastKey(),
                                dataType: "text",
                                success:
                                function (textData) {
                                    var data = $.parseJSON(textData);
                                    if (data != null && data.Products != null) {
                                        data = ko.mapping.fromJS(data);
                                        var unwrapped = productsClientView.Products();
                                        for (var i = 0; i < data.Products().length; i++) {
                                            unwrapped.push(data.Products()[i]);
                                        }
                                        productsClientView.Products(unwrapped);
                                        productsClientView.LastKey(data.LastKey());
                                    }
                                }
                            });
                        });
                    });

When the user clicks the “submit” button, in order to avoid sending a big amount of useless data to the server, we copy into another Client-Side ViewModel, that is located into another form, just the items with a Quantity greater than 0, and then submit this other form. This way, since we are submitting another form, we avoid sending all input fields that we have created to the server, and we avoid also sending our main Client-Side ViewModel that might have become very big after a lot of new page requests.

Below the other form with the secondary ViewModel:

@using (Html.BeginForm("Confirm", "Shopping"))
            {
                var confirmModel = Html.SAClientViewModel(
                    "confirmModel",
                    new PurchaseConfirmationView(),
                    initialSave: false,
                    applyBindings: false);
                    
            }

 

We rendered our secondary Client-Side ViewModel differently from our main Client-Side ViewModel since it is not part of the Server-Side ViewModel, but it is destined to an Action method that uses a different type of ViewModel. The SAClientViewModel helpers accepts directly an instance of an object and defines a Client ViewModel with it. Finally, below the javascriot function that fill the secondary Client-Side ViewModel:

<script language='javascript' type='text/javascript'>
        productsClientView.submitProductsChosen = function () {
            confirmModel.Products = new Array();
            for (var i = 0; i < this.Products().length; i++) {
                var quantity = this.Products()[i].Quantity();
                if (quantity != null && quantity != 0) {
                    confirmModel.Products.push(this.Products()[i]);
                }
            }
            if(confirmModel.Products.length>0)
                confirmModel.saveAndSubmit();
        };
    </script>

Now we need just to attach the javascript function that submits the form to the click event of some button. We can do this by defining manually a click binding for this button. We ask for a IBindingsBuilder interface to the helper of our View by calling the ClientBindings extension method, and use it to define our click binding:

@{
                    var clientModel = Html.ClientBindings();
                    var sumbitButtonBindings = clientModel.Click(m => m, "submitProductsChosen").Get();
                }
                <input type="button" value="Submit" data-bind='@sumbitButtonBindings'/>

 

That’s all! The full code is contained in the RazorClientTemplating file in the Mvc Controls Toolkit download page. Download it and enjoy!

                                                                    Francesco

Tags: , , , , ,

Sep 26 2010

Defining MVC Controls 1

Category: MVCfrancesco @ 10: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: , , , , ,