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

Jul 3 2012

Mvc Controls Toolkit Support to Mvc4 WebApi 3: Retrieval Manager and other New Nice Features

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

Mvc Controls Toolkit Support to Mvc4 WebApi

Mvc Controls Toolkit Support to Mvc4 WebApi 2: Handling Relations

In my previous posts about the Mvc4 WebApi support I described how the jQueryable and updatesManager Javascript classes may help us in all tasks related respectively with the retrieval of data from the server, and their updates. The jQueryable helps us in building the query to send to an Api Controller or to any oData source, either with a fluent LinQ like interface or by extracting the query information from the Mvc Controls Toolkit filtering, sorting, and paging controls. However, it doesn’t handle all aspects of the ClientViewModel update once we receive the data from the server. Moreover, there is also some query building job to be done “manually”.

In the 2.2 release of the Mvc Controls Toolkit we factored out all this “manual” jobs into the new mvcct.retrievalManager javascript class. Now all we need to do is just intercepting all events triggered by the Mvc Controls Toolkit filtering, sorting, and paging controls, and passing their data to the event method of the mvcct.retrievalManager instance we have created:

  1. $('#root').bind('queryChanged', function (e, data) {
  2.         ClientToDoView.retrieval.event(data);
  3.     });

Where the retrieval property of our client side ViewModel contains our instance of the mvcct.retrievalManager, and root is the id of a div containing all   filtering, sorting, and paging controls inside it. Since the filtering controls doesn’t trigger any event, we triiger the “filter” event with the refresh button of the forrm:

  1. <input id="Button1" type="button" data-bind='click: function(x, y){$(y.target).trigger("queryChanged", {type: "filter", filterPrefix: "ToDoFilter"});}' value="Refresh" />

The data object of the “queryChanged”  event triggered when the filtering criteria change must have a type of “filter”, and must contain the name of the server side ViewModel property that we use for fitering in its filterPrefix property:

  1. @{var hName = Html.DataFilterClauseFor(
  2.                   m => m.ToDoFilter, f => f.Name,
  3.                   "byNameFilter",
  4.                   MVCControlsToolkit.Linq.FilterCondition.StartsWith);}

As you can see from the code above this name is just ToDoFilter.

The instance of mvcct.retrievalManager can be created in the document ready event:

  1. $(document).ready(function () {
  2.     if (!window['ClientToDoView']) return;
  3.  
  4.     ClientToDoView.retrieval = mvcct.retrievalManager(query, ClientToDoView.DataPage.CurrPage, ClientToDoView.DataPage.TotalPages,
  5.     {
  6.         pageSize: 4,
  7.         entitiesContainer: ClientToDoView.DataPage.ToDoList,
  8.         updatesManager: ClientToDoView['updater'],
  9.         jFormsToClear: $('#mainForm'),
  10.         onError: function (args, x) {
  11.             var exception = $.parseJSON(x.responseText);
  12.             var message = mvcct.utils.isString(exception) ? exception : (exception.Message || "Internal Server Error");
  13.             alert("status code: " + x.status + "; " + message);
  14.         }
  15.     });
  16.  
  17.  
  18.     $('#root').bind('queryChanged', function (e, data) {
  19.         ClientToDoView.retrieval.event(data);
  20.     });
  21.     //populate initial results
  22.     ClientToDoView.DataPage.CurrPage(1);
  23.     ClientToDoView.retrieval.event({
  24.         type: "page",
  25.         page: 1
  26.     })
  27.     
  28. });

Where:

  • query, is the jQueryable object to be used to issue the queries.
  • The second argument is the observable that contains the current page number
  • The third argument is the observable that contains the total number of pages. It can be also null.
  • pageSize, contains the desired page size. This value can be changed at a later time by calling the changePageSize(newPageSize) method.
  • entityContainer, contains the observable where to put the array of objects returned by the server.
  • updatesManager, contains the updatesManager that handles the updates of the same array of objects, if any. If no update is needed, there is no need to pass this argument.
  • jFormsToClear, when specified is a jQuery object that contains some forms to clear from previous errors after having received new data from the server(once new data arrive, orld errors become obsolete).
  • onError, contains the function to execute in case of errors. It accepts 4 arguments: args, x1, x2, x3. args contains the data of the queryChanged event that triggered the request to the server, while x1, x2, x3 are the arguments passed to the error function of the ajax jQuery method.

There are are also other properties of the option object that we have not specified in this example:

  • onSuccess, if specified, overrides the standard model update behaviour of the retrievalManager. It accepts 4 arguments: args, x1, x2, x3. args contains the data of the queryChanged event that triggered the request to the server, while x1, x2, x3 are the arguments passed to the success function of the ajax jQuery method.
  • We can also keep the standard model update behaviour of the retrievalManager while simultaneously executing some custom code after the model has been updated, by specifying the onAfterSuccess function that accepts the same arguments of the onSuccess function. In this case we can specify also the dataTransform function that is passed all items returned by the server, as javascript objects before their properties are turned into ko observables. It is expected to apply a transformation to this data, and then return the array of transformed items.
  • onSubmit, if specified is executed before submitting the request to the server. It accepts two arguments: args, data. args give us the opportunity to cancel the request to the server, by setting args.cancel=true. data is the object we can provide in the data property of the option object.
  • immediateSubmit, has a default value of true. This means that each time a new event is passed to the retrievalManager a new request to the server is issued. If we set this property to false, the request to the server(with the query by processing  all events received so far) is issued manually by calling the submit() method of the retrievalManager.
  • resultsField and countField have respectively default values "Results" and "TotalCount". They are not used if the server returns just an array of objects, but they are used just if the server returns also the total count of the objects satisfying the query(information needed to improve the paging experience). In this case they specify the name of the properties containing the results and the total count. As default ApiControllers doesnt return the total count, but it is quite easy to write an action filter to add this value. Complete oData sources, such as Wcf oData web services return the total count if the query requires it. In such a case the default names suffices

The 2.2 release of the Mvc Controls toolkit improved also the updatesManage. Now there is no need to declare separately a child updatesManager. All information needed can be specified when adding it as a child to its father updatesManager. Below the updatesManagers of my previous posts Mvc Controls Toolkit Support to Mvc4 WebApi 2: Handling Relations rewritten with the new api:

  1. ClientToDoView.updater = mvcct.updatesManager(
  2.         '@Url.RouteUrl("DefaultApi", new { httproute = "", controller = "ToDo"})',
  3.          ClientToDoView,
  4.          'DataPage.ToDoList',
  5.          'id', DestinationViewModel, "ToDoCS");

 

  1. ClientToDoView.childUpdater = ClientToDoView.updater.addChildUpdateManager({ expression: 'Tasks', external: 'FatherId' }, 'Key', "TaskCS");

As you can see, there is no need to create separately, the updatesManager that handles the children objects of our ToDo items. In fact a single instruction declares the the information needed to handle the one to many relation(‘Tasks’ and ‘FatherId’ are respectively the collection of the ToDo item containing the children objects, and the external key of the children objects), together with the key of the children entities (‘Key’) and the property of the destination model that will contains the updates of the children entities(‘TaskCS’) to send to the server. This is enough for the child updater to work properly

The  Mvc4 Client-Filtering -Paging-Sorting-updating file in the download area of the download area of the Mvc Controls toolkit contains both the new and old versions of the examples.

There are also other improvements that enhances the capability of the updatesManager to edit one to many relations. Namely:

  • The move method to move a child object from a father to another. This operation can be undone, as all other insert, update, and delete operations.
  • Now an updatesManager can be declared to be child of itself. This means that now the updatesManager is able to handle a one to may relation of an object set with itself. For instance we can handle a database table of tasks items where each task can be a subtask of another task in a recursive fashion.

 

See how the the controls of upcoming Data Moving plugin can take advantage of the new capabilities of the updatesManager to allow the user to edit one to many relations with the help of sophisticated TreeViews:

Below the updatesManager that handles the updates of the tasks TreeView in the video:

  1. HumanResourcesViewModel.TaskUpdater = mvcct.updatesManager(
  2.         '@Url.Action("TaskChanges", "TreeView")',
  3.          HumanResourcesViewModel,
  4.          'AllTasks',
  5.          'Id');

 

  1. HumanResourcesViewModel.TaskUpdater.addChildUpdateManager({ expression: 'Children', external: 'Father', updater: HumanResourcesViewModel.TaskUpdater });

As you can see the updatesManager is declared child of itself

That’s all for now!

                        Stay Tuned

                       Francesco

Tags: , , , , , , , , ,

Apr 11 2012

Mvc Controls Toolkit Support to Mvc4 WebApi 2: Handling Relations

Category: WebApi | MVC | Entity Framework | Asp.netFrancesco @ 21:51

In this post we will see how to handle one-to-many relations among entities with the advanced tools introduced in the last 2.1 release of the Mvc Controls Toolkit. We will give also some hints on how to handle some types of many-to-many relations that one might face in practical applications. I assume that everyone already read my previous post: Mvc Controls Toolkit Support to Mvc4 WebApi. The code of this example together with the code of my previous post is available in the  Mvc4 Client-Filtering -Paging-Sorting-updating file in the download area of the Mvc Controls Toolkit web site. Since, the update features we are going to discuss are not specific for ApiControllers but they are available also to standard Mvc 3 controllers, I provided also similar code examples that works with Mvc 3 controllers. They are in the AdvancedJSonCommunication file in the download area of the Mvc Controls Toolkit web site

As first step let modify our IQueryable to include also child entities of our ToDo items:

  1. return context.ToDo.Include("SubTasks").Select(item =>
  2.             new ToDoView()
  3.             {
  4.                 Name = item.Name,
  5.                 Description = item.Description,
  6.                 DueDate = item.DueDate,
  7.                 id = item.id,
  8.                 Tasks = item.SubTasks.Select(x => new SubTasksView { Name = x.Name, WorkingDays = x.WorkingDays, Key = x.code, FatherId = x.idTask })
  9.             });

We added a SubTasks table to our DB that is related in a one-to-many fashion to our ToDo table, and we are asking LinQ to add the children entities of each ToDo item through the Include clause.

We changed also the code of the WebApi method that exposes the IQueryable on the web:

  1. public IQueryable<ToDoView> Get()
  2. {
  3.     return new HttpSafeQuery<ToDoView>(ToDoViewModel.GetToDoQueryable(), true);
  4. }

We pass true as second argument of the constructor of our HttpSafeQuery. This way we instruct it to accept filtering criteria that are the logical and of clauses only. In fact, our application only needs such criteria, so by blocking all other kinds of queries we have a better protection against malicious users.

 

We show the children entities just in the detail window of our application:

Children

To achieve this result we can use just a simple client side  for statement:

  1. @{var h=item._foreach(m => m.Tasks, ExternalContainerType.tbody);}
  2. @h._begin()
  3. <tr>
  4.      <td>@h.TextBoxFor(m => m.Name)</td>
  5.      <td>@h.TypedTextBoxFor(m => m.WorkingDays, new { @class = "smallNumbers" })</td>
  6.      <td><input id="btnDetailDelete" type="button" value="Delete" data-bind='click: function(item){detailToDo.removeTask(item);}'/></td>
  7.      <td><input id="btnDetailUndo" type="button" value="Undo" data-bind='click: function(item){detailToDo.undoTask(item);}, enable: _inserted() || _modified()'/></td>
  8. </tr>
  9. @h._end()

 

The above code renders our children entities inside a table tbody(ExternalContainerType.tbody). The remainder of the table is written in Html.

Displaying the children entities was easy. The difficult part is handling their updates. The good news is that we don’t have code too much: the Mvc Controls Toolkit takes care of everything.

We need just to add a new ChangeSet property to the action method that receives the updates:

  1. public HttpResponseMessage<ApiServerErrors<int>> Post(UpdateViewModel model)

 

  1. public class UpdateViewModel
  2. {
  3.     public Updater<ToDoView, Int32> ToDoCS { get; set; }
  4.     public ChildUpdater<SubTasksView, Int32> TaskCS { get; set; }
  5. }

ChildUpdater is a subclass of the base Updater class we use to receive a change set that contains a property more. we will speak about it later onin this post. We are not forced to use the ChildUpdater and/or the Updater classes. we can use any class containing property to receive inserted, deleted,  modified…etc,  entities () by adequately declaring the names of such properties in our UpdateManagers.

We need also a another UpdateManager that takes care of the SubTasks entities and a destinationViewModel that is the  javascript equivalent of our UpdateViewModel. The destinationViewModel will be filled automatically by our UpdateManagers and submitted to the Post method of our controller.

We can declare the destinationViewModel as an empty object because the needed properties will be created automatically by the UpdateManagers that fill them:

  1. var DestinationViewModel = {};

You can find the above instruction in the EditDisplayToDo.js file while the definition of the UpdateManagers is contained in the IndexEdit.cshtml class to take advantage of the Url.RouteUrl method to compute the Url of the receiving action method:

  1. ClientToDoView.childUpdater = mvcct.updatesManager(
  2.     '@Url.RouteUrl("DefaultApi", new { httproute = "", controller = "ToDo"})',
  3.      ClientToDoView,
  4.      'DataPage.TasksList',
  5.      'Key', DestinationViewModel, "TaskCS");
  6.  
  7. ClientToDoView.updater = mvcct.updatesManager(
  8.     '@Url.RouteUrl("DefaultApi", new { httproute = "", controller = "ToDo"})',
  9.      ClientToDoView,
  10.      'DataPage.ToDoList',
  11.      'id', DestinationViewModel, "ToDoCS");

Each UpdateManager just specifies:

  1. The Url where to submit the change sets
  2. The source Client ViewModel that contains the entities
  3. A string expression that locates the property that contains the entities to handle within the source Client ViewModel.
  4. A string expression that identifies the principal key of each entity.
  5. The destination ViewModel
  6. A string expression that locates the change set within the destination ViewModel.

When the UpdateManagers compute the change sets they creates automatically the TaskCS and ToDoCS properties if they are not already defined in the destination ViewModel. Actually there is no DataPage.TasksList property in the source ViewModel since the SubTasks entities are contained in their father ToDo entities. The DataPage.TasksList is automaically created and filled with all the SubTasks entities just before computing the change sets.

 

Now we declare that the ClientToDoView.childUpdater works as a child of the main ClientToDoView.updater:

  1. ClientToDoView.updater.addChildUpdateManager({ expression: 'Tasks', external: 'FatherId', updater: ClientToDoView.childUpdater });

The object passed as argument of the call contains:

  1. expression: a string expressions locating the collection that contains the children entities within each ToDo item. In our case: Tasks.
  2. external: the external key of the child entities.
  3. updater: the updateManager to add as a child.

Newly created ToDo items need to be prepared and inserted with the Insert method:

  1. var item = {
  2.     DueDate: ko.observable(this.DueDate()),
  3.     Name: ko.observable(this.Name()),
  4.     Description: ko.observable(this.Description()),
  5.     id: ko.observable(null),
  6.     Tasks: ko.observableArray(this.Tasks())
  7. };
  8. ClientToDoView.updater.prepare(item, true); //newly created entity prepare it
  9. ClientToDoView.updater.inserted(ClientToDoView.DataPage.ToDoList, item);

Where the second argument of the prepare method ask to start changes tracking immediately.

While newly created SubTasks are added to the collection of their father entities with the addChild method of the father updateManager:

  1. detailToDo.createTask = function () {
  2.     var item = {
  3.         Name: ko.observable(''),
  4.         WorkingDays: ko.observable(0),
  5.         Key: ko.observable(null),
  6.         FatherId: ko.observable(null)
  7.     };
  8.     //newly created entity preparation is done when adding to father with addChild
  9.     ClientToDoView.updater.addChild(this, 'Tasks', item, true);
  10. };

 

Where:

  • this, denotes the father ToDo item
  • Tasks is the collection where to add the newly created item
  • item, is the newly created item
  • true, starts changes tracking immediately.

As already discussed in my previous post, deletes are performed by calling the deleted method:

 

  1. ClientToDoView.updater.deleted(ClientToDoView.DataPage.ToDoList, item);

 

  1. ClientToDoView.updater.deleted(detailToDo.Tasks, item);

As discussed in my previous post each time the user performs some modifications on an entity we must call the modified method that verifies if the entity actually changed and mark it as modified. For the ToDo items we do this in the save method of our detail window:

  1. detailToDo._save = function () {
  2.     var item = this.DetailOf();
  3.     if (!item) return true;
  4.     if (!$('#detailForm').validate().form()) return false;
  5.     mvcct.utils.restoreEntity(this, item, true);
  6.     ClientToDoView.updater.modified(item, true, true);
  7.     if ((!item._modified()) && (!item.tasksChanged()))
  8.         ClientToDoView.childUpdater.refreshErrors($('#mainForm'), null, item);
  9.     return true;
  10. };

For a description of the arguments of the modified method, please refer to my previous post.

For the SubTasks we have no Done button that is clicked after the user finished modifications, so we have to call automatically the modified function each time a property is modified. We can do this by attaching a function to each observable poperty of the SubTasks through the knockout subscribe method:

  1. ClientToDoView.childUpdater.options({
  2.     isoDate: true,
  3.     prepareCallback: function (item) {
  4.         var prev = false;
  5.         function subscription() {
  6.             ClientToDoView.childUpdater.modified(item, true, true);
  7.             if (prev && !item._modified())
  8.                 detailToDo.undoTask(item);
  9.             prev = item._modified();
  10.         };
  11.         item.Name.subscribe(subscription);
  12.         item.WorkingDays.subscribe(subscription)
  13.     }
  14. });

We have done this job in the prepareCallback function that its automatically called immediately after an entity is prepared. The preparedCallback can be declared in the updateManager options that are passed to the updateManager either as last argument of the constructor or through the options method.

We define a prepareCallback also for the ToDo entities:

  1. ClientToDoView.updater.options({
  2.     isoDate: true,
  3.     updateCallback: function (e, result, status) { alert("status: " + status); },
  4.     prepareCallback: function (item) { item.tasksChanged = ClientToDoView.updater.arrayChanged(item.Tasks); }
  5. });

It attaches a knockout computed to the taskChanged property of each ToDo item. This computed returns true if any child SubTask of the Tasks collection has been marked as modified. The knockout computed is returned by calling the arrayChanded method of the updateManager and passing it the collection we would like to check. Each time the state of a child entity changes the taskChanged property is recomputed automatically by the knockout engine. We can use the arraChanged property together with the standard _modified and _inserted properties, added to each entity, to enable the undo button if and only if there are changes to undo:

  1. <input type="button" value="Undo" data-bind='click: function(item){detailToDo.undo(item);}, enable: _inserted() || _modified() || tasksChanged()'/>

The Undo method calls the reset method of the main update manager that undoes all changes performed to the ToDo item and to all its children entities:

  1. detailToDo.undo = function (item) {
  2.     this.resetIfSelected(item);
  3.     ClientToDoView.updater.reset(item, $('#mainForm'));
  4. };

The reset method accepts a form as second argument, because when changes are undone possible server errors associted to thel undone entities are cancelled.

That is enough! Now when we submit the ClientToDoView.updater main updateManager the receiving controller receives the change set of the SubTasks entities, together with the change set of the ToDo entities automatically. Moreover, possible updateCallback and updatingCallback associated with the children updateManagers are called as appropriate. Summing up all we need to do is just:

  1. ClientToDoView.updater.update($('#mainForm'));

Once the change sets reach the controller they can be processed separately as needed by the business layer…However, we are committed to return to the client the newly created keys of the Inserted records. Thus our business methods must return them in the same order of the inserted entiies received in the change set.

There is another complication: inserted records that are children of inserted records! This records need the external key from their father entities before being processed and stored in the DB. Therefore we process the fater entity to get their principal keys:

  1. ToDoKeys = ToDoViewModel.UpdatePage(model.ToDoCS.Inserted, model.ToDoCS.Modified, model.ToDoCS.Deleted);

and then we pass them to possible children entities that might need them by calling the ImportExternals method of the ChildUpdater class:

  1. //imports the external keys of the newly created father entities into their children
  2. if (model.TaskCS != null) model.TaskCS.ImportExternals(ToDoKeys, m => m.FatherId)

Now we can process the children entities:

  1. if (model != null && model.TaskCS != null)
  2. {
  3.     TaskKeys=ToDoViewModel.UpdatePageTasks(model.TaskCS.Inserted, model.TaskCS.Modified, model.TaskCS.Deleted);
  4. }

everything is enclosed into a single transaction:

  1. try
  2. {
  3.     using (var t=new TransactionScope())
  4.     {
  5.         if (model != null && model.ToDoCS != null)
  6.         {
  7.             ToDoKeys = ToDoViewModel.UpdatePage(model.ToDoCS.Inserted, model.ToDoCS.Modified, model.ToDoCS.Deleted);
  8.  
  9.             //imports the external keys of the newly created father entities into their children
  10.             if (model.TaskCS != null) model.TaskCS.ImportExternals(ToDoKeys, m => m.FatherId);
  11.             //here the same for other children collections
  12.         }
  13.         if (model != null && model.TaskCS != null)
  14.         {
  15.             TaskKeys=ToDoViewModel.UpdatePageTasks(model.TaskCS.Inserted, model.TaskCS.Modified, model.TaskCS.Deleted);
  16.         }
  17.         t.Complete();
  18.     }
  19.     
  20. }
  21. catch (Exception ex)
  22. {
  23.     ModelState.AddModelError("", ex.Message);
  24.     return
  25.         new HttpResponseMessage<ApiServerErrors<int>>(
  26.             new ApiServerErrors<int>(ModelState, new ApiKeyInfos<int>[0]), System.Net.HttpStatusCode.InternalServerError);
  27. }

All new keys must be returned to the client to keep the client synchronized with the server:

  1. // if keys have different simple types such as one is int, and one other is string, use ApiServerErrors<object>
  2. return new ApiServerErrors<int>(ModelState,
  3.     new ApiKeyInfos<int>[] {
  4.         new ApiKeyInfos<int>{destinationExpression="ToDoCS", keys=ToDoKeys},
  5.         new ApiKeyInfos<int>{destinationExpression="TaskCS", keys=TaskKeys}
  6.     }).Wrap();

The keys handling algorithm complicates the interaction between the presentation layer and the business layer. This “too strong” interaction between layers maybe avoided by using Guids as keys. In fact, in this case all keys can be computed by the controller, so the whole key handling can be performed by the controller avoiding the need of  a “too strong“ cooperations between the presentation and the business layer. It is worth to point out that, due to security problems, Guids cannot be computed in the browser, so we are forced to compute them in the controller and to return them to the client.

For a better user experience the 2.1 release of the Mvc Controls Toolkit introduces two new features to handle the errors returned by the server:

  1. Automatic errors delete
  2. Errors bubbling

When an entity is undone, someway, either by pressing the undo button or by undoing manually all modifications, or by doing everytning else that might set its _modified status to false, all errors associated with it are deleted by the list of all errors returned by the server. This way, the next time the refreshErrors method of the updateManager is called they are deleted from the UI. The reset method of the updateManager calls automatically the refreshErrors method after having performed the undo, passing it the form that it receives as argument. This way all errors are cancelled from that form immediately. If the same error is displayed in several forms we need to call manually the refreshErrors method for all other forms:

  1. detailToDo.undoTask = function (item) {
  2.     ClientToDoView.childUpdater.reset(item, $('#mainForm'), this.Tasks);
  3.     ClientToDoView.childUpdater.refreshErrors($('#detailForm'));
  4. };

The first argument of the reset method is the item to undo, the second argument the form to refresh, and finally the third argument is the collection the item belongs to. The third argument is needed only in case of children entities, because not-children entities are not contained in any father collection.

Due to error bubbling, errors in children entities affect also the UI of their father entities, thus we have to refresh two forms: one is refreshed automatically by the reset method, and the other is refreshed by calling manually the refreshErrors method.

The errors associated with an entity can be deleted also manually by calling the refreshErrors method and passing it: the form as first argument, a null second argument, and the item we would like to delete the errors of as third argument. By passing null as second argument we ask the refreshErrors method to use the errors returned by the last call to the server.

Errors of any child entity are bubbled up to the property collection of the father entity that the children entity belongs to. However, just the fact that there was at least one error in the collection is bubbled up, not all error messages. This means that a ValidationMessageFor helper for the collection property will display just the error message passed as second argument. In our example we placed a ValidationMessageFor with an * near the edit button:

  1. <td>
  2.     <input type="button" value="Edit" data-bind='click: function(item){detailToDo.edit(item);}'/>
  3.     @item.ValidationMessageFor(m => m.Tasks, "*")
  4. </td>

Let see how errors work in practice. As first step let uncomment the error messages in the action method:

  1. //uncomment to experiment server side error handling
  2. //ModelState.AddModelError("ToDoCS.Modified[0].Name", "Fake error");
  3. //ModelState.AddModelError("TaskCS.Modified[0].Name", "Fake error1");

Then let start the application and let modify a ToDo item together with one of its children entities:

ErrorBubblingStart

Then let it Submit All Changes:

ErrorBubbligEnd

As you can see the error in the child entity has been bubbled up near to the edit button.

Now if we click the edit button, in the detail window we will just see the error of the child entity.

Why? Simple, the ToDo entity is copied into another ToDo entity that is bound to the detail window with the mvcct.utils.restoreEntity(x, y, visitRelations), while its children entities are used directly without creating a copy of them since setting visitRelations to true causes all children entity to be copied by reference into a new observableArray without being cloned. Since the errors are tied to the entity they belong to, just the errors of the child entity is shown. This was decided by design, to avoid duplication of the errors in the UI:

errorBubblingDetail

If we would like to show the ToDo entitiy errors also in the detail window we need to istantiate a template on the original ToDo entity by means of the _with helper instead of copying its data into another object that is already bound to UI elements.

Now let click the undo button of the children entity, or better simply delete the “modified” word we added previously. “Fake error 1” disappears from both the main window and from the detail window:

ErrorDeleteDetail

Now, if we click the undo button of the father entity, also “Fake error” completely disappears from the UI!

 

What if we have a many-to-many relation?

In the case of the one-to-many relation we just query our controller to get the “father entities” we need to process. As result we get the “father entities” and their related children entities “attached” to them. In the case, of a many-to-many relation we have no easy way to decide which entities of the second endpoint of the relation to move to the client. For this reason typically many-to-many relations cannot be processed in a batch fashion but they require a continuous interaction with the server. However, there is a common pattern that allows their batch processing, namely when one of the two endpoints of the relation is small enough to be transferred completely to the client.

This happens, for instance, when we have a collection containing all US States that we can select and attach to other entities. In other words, when one of the two related collections is used just for selection with a dropdown, or a multiselect or with some more complex UI. In this case the second collection of entities is used for display only. So we basically process just the collection R that represents the relation by means key pairs .

We can easily handle the situation depicted above with the same techniques we used for the one-to-many relations as follows:

  1. We move completely the second entity set to the client.
  2. We move to the client the entities of the first entity set we would like to process
  3. We move the entitis of R related to the entities at point 2 to the client. This can be accomplished in two ways
    • Add the entities of R as children of the entities of the first entity set they are related to,
    • Move all the entities of R we need as a separate collection and then connect them to the related entities of the first set of entities on the client with the help of the addRelated method of their updateManager (see my previous post).
  4. Define the updateManager of R as child of the updateManager of the first entity set.
  5. Connect the enties of R with the entities of the second entity set with the help of the addRelated method.
  6. To Add a new element to R, add it to the adequate entity of the first entity set, then set the external key of the second entity set, and add to it the pointer to the related entity of the second entity set.
  7. Process the entities of the first entity set + the entities of R with substantially the same techniques we used for the one-to-many relations, by accessing the pointer from each entity in R to the related entity of the second entity set when we need to display the related data.

That’s all for now! In a short time the team of the jsAction project will give us a very easy way to use the updateManager, by inspecting all controllers and providing automatically the right instances of the updateManager already configured to work with each specific controller.

                                               Stay Tuned

                                               Francesco

Tags: , , , , , , ,

Apr 3 2012

Mvc Controls Toolkit Support to Mvc4 WebApi

Category: Asp.net | MVC | WCF | WebApiFrancesco @ 03:57

The new Mvc 4 ApiController, and in particular the possibility it offers to expose an IQueryable on the web simplifies the exchange of data between server and an Html page, a lot.

Now it is possible to pass a dynamically created query from the client to the server with a simple GET operation. We need just to fill the $filter, $orderby, $skip, and $top query string parameters according to the oData protocol.

The upshot.js library may help us on the client side to write the code to interact with the server. Upshot does actually all the job, and make the developer interacts with the data as they were on the client, hiding all hard plumbing, needed to retrieve them, and to update them on the server.

However, the price we pay, to take advantage of all upshot.js features is that we can’t have substantially any business layer on the server side, whose role becomes a kind of “Web Database”.

What if we need a “robust” Business Layer, that manipulates data coming from several collections and/or objects before passing a transaction to the database?

At moment upshot.js contains DataProviders for interacting with:

  1. DbDataController. That is a particular ApiController specialized in interfacing DataBase data.
  2. oData sources. A complete oData source can be implemented with Wcf Data Services. In fact both a generic ApiController exposing an IQueryable, and the specialized DbDataController doesn’t implement the full oData protocol, but just support the $filter, $orderby, $skip, and $top operations, that are the more useful operations in business applications.
  3. Wcf Ria Services. The famous Ria services we use also with Siverlight.

One might think to build a custom DataProvider, and pass it to Upshot.js….However, a similar solution would never give us the “kind of customization” we need in this case. The point is that we can no more look at the server like a kind of “Web DB”, so we cannot use upshot and the DbDataController at all, and we need to proceed manually by building our custom jQuery ajax calls to generic WebApi methods.

However, there is some “hard plumbing job” we can factor out also in this case, namely:

  1. Building oData queries in a more friendly fashion with a LinQ like fluent syntax
  2. Changes tracking on the data. Recording the initial state of our data, so that we can undo all changes performed on them
  3. Computation of the Change Set to send to the server. This way we can save bandwidth by sending to the server just the modifications performed on data, instead of a whole collection of data
  4. Dispatching the errors returned by the sever in the right place in the User Interface, that is near the UI that displays the “wrong” inputs. Possibly, in the right labels rendered by the ValidationMessageFor helpers, and in the ValidationSummary.
  5. Since Json is not able to encode references, that is several properties pointing to the same instance of an object, but just a flat tree of objects, wee need someway to re-compute references links on the client after different collections containing related data have been sent by the server. Some tool that might help us in performing this job on the client might be very useful

Finally if we would like to take full advantage of WebApi also in business applications that manipulate big quantity of data we need to protect someway the IQueryables that we expose on the web to prevent denial of service attacks.

We can decorate our methods with the ResultLimitAttribute to limit the maximum number of items retrieved by the DB, However this is not enough, since a malicious user might try a denial of service attack by requiring a filtering operation on a column without indices of a big DB table. In this case also if no result is found at all the Data Base might waste a lot of time to discover it.

In the new Mvc4 beta compatible version of the Mvc Controls Toolkit we included tools that face all the problems listed above, namely:

  1. The javascript mvcct.Queryable abstract class allow us to build complex queries with a LinQ like fluent syntax. Moreover, it is able to import sorting, filtering, and paging information from all Mvc Controls Toolkit controls. There are three implementation of this abstract class:
    • The mvcct.oData.Queryable to query remote datasources that implements (also partially) the oData protocol. This is the right tool to use to query WebApi that expose an IQueryable
    • The mvcct.upshotQueryable build queries to be executed by the upsjhot.js library.
    • The localQueryable is able to query data that are already on the client in javascript format. It is useful when we need to work offline
  2. The mvcct.updateManager is able to track the changes of a collection of data, to compute the associated Change Set and to post it to a generic WebApi or standard Mvc Action Method. It is also able to transfer the data to the server with a standard browser submit (it creates dynamically a form that it then submits). Moreover it wait for the result from the server, and process possible errors returned by the server, by dispatching them in the right places of the UI. The developer may also provide two optional callbacks to be called respectively in case of success and in case of error. The error processing can be skipped by setting a property contained the callback arguments.
    Several mvcct.updateManager may cooperate to submit several change sets to the server with an unique post. In this case each mvcct.updateManager prepares the Change Set for the collection it takes care of, and put it in the destination ViewModel. Then, just one of them, the master, posts this ViewModel and wait for the server result . Once the result arrives it notifies all other mvcct.updateManager so that each of them can process its errors, and its optional callbacks.
  3. The addRelated method of the mvcct.updateManager computes automatically all mutual references existing between the items of two related collections by using key and external key information.
  4. The SafeQuery and HttpSafequery implementations of the IQueryable interface work as wrappers to protect any IQueryable from unwished queries. The allowed sorting and filering operations are specified through the CanSort Mvc Controls Toolkit attribute. In case a forbidden operation is detected the SafeQuery throws the adequate subclass of the ForbiddenException, while the HttlpSafeQuery that is specialized for the Web throws an HttpResponseException to set the right Http status code.

But let see how all this works in practice with a simple example. The example I use can be downloaded from the Mvc ControlsToolkit download area here: Mvc4 Client- Filtering -Paging -Sorting-updating. The file AdvancedJSonCommunication contains similar examples that works with standard Mvc3 contollers.

As first step let ‘s run the example to see what happens. We can select two pages in the menu: Index, and IndexEdit.The first one is a query and display only example, while the second one gives also the possibility to update the entities. Let start with the Index page.

ToDoDisplay

There is a pager, we can sort by clicking on the columns, and we can filter data by selecting several filtering options simultaneously and then by clicking Refresh

Let select filter by name, and let choose the Contains operator.

ToDoFiltering

Looking for a string contained in any position of a column is a very inefficient type of search if we have not defined a special index called full text index on that column. So it might be used for a denial of service attack against our website. However, when we  hit refresh we get:

ToDoError

A 403 Http status code: FORBIDDEN! The message explains us that the Contains operation is not among the operation allowed on the colum name.

Let see what happened behind the curtain:

  1. public class ToDoController : ApiController
  2.     {
  3.         // GET /api/todo
  4.         public IQueryable<ToDoView> Get()
  5.         {
  6.             return new HttpSafeQuery<ToDoView>(ToDoViewModel.GetToDoQueryable());
  7.         }

We wrapped our IQueryable within the HttpSafeQuery IQueryable that rejected the Contains clause of our filter.

The data annotations on the ToDoView class are provided through a MetaDataType(to understand why I did ths way, give a look here):

  1. [MetadataType(typeof(MetaToDo))]
  2.     public partial class ToDoView
  3.     {
  4.         public int? id { get; set; }
  5.        
  6.         public string Name { get; set; }
  7.        
  8.         public string Description { set; get; }
  9.  
  10.        
  11.         public DateTime DueDate {set;get;}
  12.  
  13.     }

So Let go to the MetaToDo class:

  1. public class MetaToDo
  2.     {
  3.         
  4.         [Required, CanSort, Display(Name="Name")]
  5.         public object Name { get; set; }
  6.         [Required, Display(ShortName = "Description")]
  7.         public object Description { get; set; }
  8.         [CanSort, Display(ShortName = "Due Date"), Format(DataFormatString="{0:D}")]
  9.         public object DueDate { get; set; }
  10.     }

The Name property is decorated with the CanSortAttribute, but since no argument is passed to specify wich filtering options are allowed, the default settings are taken…and the default setting doesn’t allow the dangerous Contains operator.

Now, let give a look to the way the query is built. We created the mvcct.oDataQueryable  object in the View to take advantage of the @Url.RouteUrl method to conpute the Url of our WebApi:

  1. <script type="text/javascript">
  2.     var query = mvcct.oDataQueryable('@Url.RouteUrl("DefaultApi", new { httproute = "", controller = "ToDo"})', mvcct.$$.and);
  3.     query.importSortingControl('@Html.PrefixedId(m => m.ToDoOrder)');//import default sorting (necessary for paging
  4.  
  5. </script>

The second parameter of the constructor is the logical operator to be used to combine all filter conditions. We might have specified also mvcct.$$.or. There is also a third boolean parameter that we have not used in this example, negate. If true the whole filter is negated.

The first operation we do immediately is importing the initial sorting specified in the control that is bound to the ToDoOrder ViewModel property. It was put there by the action method that took care of creating the Index View:

  1. public const int PageDim=5;//in actual application this should be put in a config file
  2.         public ActionResult Index()
  3.         {
  4.             //define default ordering. It is necessary for paging.
  5.             List<KeyValuePair<LambdaExpression, OrderType>> order = new List<KeyValuePair<LambdaExpression,OrderType>>();
  6.             Expression<Func<ToDoView, DateTime>> defaultOrder = m => m.DueDate;
  7.             order.Add(new KeyValuePair<LambdaExpression, OrderType>(defaultOrder, OrderType.Descending));
  8.  
  9.             ToDoViewModel result = new ToDoViewModel()
  10.                 {
  11.                     DataPage = new ToDoPage {
  12.                         ToDoList=new List<ToDoView>()},
  13.                     ToDoOrder=order
  14.  
  15.                 };
  16.             return this.ClientBlockView(result, "ClientToDoView");
  17.         }

As you can see this action method just creates the substantially empty ViewModel that will be used as page ViewModel by the knockout.js library

The adviced way to use knockout.js with the Mvc Controls Toolkit is through the Client Blocks feature. For more information on the sorting Mvc Controls toolkit controls give a look here.

The remaining code used to build the query is in the WebApiRetrieve.js file:

  1. ClientToDoView.Refresh = function (type, page, sorting, prevPage) {
  2.         if (!type) {
  3.             type = 'filter'; page = 1;
  4.             ClientToDoView.DataPage.CurrPage(1);
  5.             //filter changed, we need reset pager to the first page
  6.         }
  7.         query.setPaging(page, pageSize);
  8.         //sorting changed
  9.         //we don't need to reset pager to the first page, sorting control do it automatically
  10.         if (type == 'sort') query.importSorting(sorting);
  11.         else if (type == 'filter') {
  12.             query.resetFilter()
  13.             .importClauses('ToDoFilter');
  14.         }
  15.         query.execute(function (x) {.......

The Refresh method of the ViewModel is called either when the filter, or the sorting or the page is changed, so the first thing it does is “understanding” what changed. Then it imports into the query the changed information. The pager and the sorting controls triggers an event containing the new page and/or sorting information when something changes, so we need just to register an event to handle sorting and paging:

  1. $('#root').bind('queryChanged', function (e, data) {
  2.         ClientToDoView.Refresh(data.type, data.page, data['sortString'], data['previousPage']);
  3.     });

sortString, and previousPage are put within [‘’] because the sortString property is provided just for the sorting changed event, while the previousPage is provided just for the page changed event, so both properties may be undefined.

There is no event that says us the when filtering condition changes, so the best we can do is binding the refresh method of the ViewModel to the Refresh button through a knockout click binding:

  1. <input id="Button1" type="button" data-bind='click: function(x){x.Refresh();}' value="Refresh" />

The mvcct.Queryable.importClauses imports all filtering conditions by inspecting directly all filtering controls that are bound to the  ToDoFilter ViewModel property. For an introduction to the MvcControls Toolkit Filtering controls give a look here.

The execute method issues the query to the WebApi controller, and receives the results through a callback, whose code is not shown.

Basically the callback inspects the format of the object that is returned to see if the total count of all entities is available (this information may be used to improve the paging experience). WebApi methods doesn’t supplies this information and return just a collection of entities, but full implementation of the oData protocol like the Wcf Data services may return it. Then they add observables and observableArray, as needed by calling the ko.mapping.fromJS function of the knockout mapping plugin:

  1. var newEntities = ko.mapping.fromJS(x.results)();

and put them into the ViewModel:

  1. ClientToDoView.DataPage.ToDoList(newEntities);

The total count of the entiies is updated either with the exact information supplied from the server, if available, or with a smart guess:

  1. ClientToDoView.DataPage.TotalPages(totPages);

The pager is bound to the DataPage.TotalPages ViewModel property, and immediately takes advantage of this new information. In case of errors the pager is reset to its previous page:

  1. ClientToDoView.DataPage.CurrPage(prevPage);

The pager is bound to this ViewModel property, and it updates its state immediately. The ko bindings of the pager are automatically created by the pager Html helper.

In case we would like to use the upshot.js library to query our controller, we can stll import paging, filtering, and sorting infos from the Mvc Controls toolkit controls by using the upshotQueryable in place of the the oDataQueryable, since both objects share the same public interface.

The constructor in this case is: mvcct.upshotQueryable(dataSource, fop, options, negate). Where dataSource is an upshot datasource, fop and negate are completely ignored since upshot allows just the logical and of all filter conditions. Finally the option object contains only the includeTotalCount property whose default value is true;

we can also build our query manually with the help of the whole fluent public interface exposed by all mvcct.Queryable objects:

  • setPaging(page, pageSize); Specifies current page and page size. we already used it in our example.
  • addSort(field, desc, enabled): if enabled is true adds a new orderby clause to the order being built. field is the name (actually a string expression) of the property, and the boolean desc specifies if the order is descending.
  • resetSorting():clears all previously added sorting clauses, so that a completely new sorting can be built.
  • addCondition(operator, value1, value2, enabled): if enabled is true a new filtering condition is added to the filter being built. All filter conditions are combined with the currently active logical operator. The initial logical operator is specified in the constructor of the Queryable. A different logical operator can be specified each time we build a complex condition made of several sub-conditions with the help of the open and close methods (see later), value1 is the property to be constrained by the condition,  value2 the second argument of the comparison , and operator is the condition operator  that is one of:
    • mvcct.$$.eq, mvcct.$$.ne, mvcct.$$.gt, mvcct.$$.ge, mvcct.$$.lt, mvcct.$$.le;
    • mvcct.$$.startswith, mvcct.$$.endswith
    • mvcct.$$.substringofInv; that is the Contains operator.
    • mvcct.$$.substringof; that requires the property value is a substring of the value2 argument
  • resetFilter(): clears all previously added filter clauses, so that a completely new filter can be built.
  • open(logicalOperator, enabled, negate): if enabled is true, it starts a complex condition made of several sub-conditions that will be combined with logicalOperator. If negate is true the whole condition will be negated. Its effect is opening a parenthesys in the filter expression being built.
  • close(enabled): close a previously opened parenthesys. enabled must match the enabled of the matching open otherwise the parenthesys of the resulting expression will be unbalanced.
  • importSorting, importSortingControl, and importClauses are the Mvc Controls Toolkit controls importing methods we have already used in our example. There is also an importPager(pagerId, pageSize) method that imports the paging data contained in the pager whose id is pagerId. we have not used it in the example because we took the all page informations from the event triggered by the pager.
  • get(): returns an object whose nature depends on the implementation of the Queryable: the oDataQueryable returns the complete url to be passed to the server, the localQueryable returns a function that once executed performs all operations specified in the query, and the upshotQueryable returns the upshot DataSource.
  • execute(callback): executes the query and passes the result returned by the server as argument to the callback. The upshotQueryable ignores the callback because the only operation performed is the refresh of the DataSource: this is enough to dispatch the server results in the right place.
  • getState() and setState(x) respectively returns an object that encodes the full state of the Queryable, and set the state of the Queryable by using a previously saved state. They are useful to handle the Back and Forward buttons of the browsers to navigate the history of all query passed to the server.

It is time to understand how to handles data updates with the updateManager class. In our example the creation of the  instance of the updateManager that takes care of our ToDo items is in the IndexEdit view:

  1. ClientToDoView.updater = mvcct.updatesManager(
  2.         '@Url.RouteUrl("DefaultApi", new { httproute = "", controller = "ToDo"})',
  3.          ClientToDoView,
  4.          'DataPage.ToDoList',
  5.          'id', null, null, {isoDate:true, updateCallback: function (e, result, status) { alert("status: " + status); } });

The first argument just computes the Url of our WebApi, the second argument is the javascript variable that contains our page ViewModel, the third argument is a string expression that locates the collection we would like to process. The fourth argument is another string expression that locates the “key” of our items. It doesn’t need to correspond to the actual DB key of the item, but it is just the field that we want our updateManager send to the server when an item is deleted.

The further two arguments that we set to null are respectively, the destination ViewModel and a string expression that locates the place in the destination ViewModel where to put the change set once it has been computed. Since both fields are null there is no destination ViewModel, and the change set will be sent to the server, as it is , without inserting it into another container. The destination ViewModel is useful if we need to send simultaneously several change sets to the server.

The change set is an object with just 3 properties: one containing the array of all modified items, another one containing the array of all newly inserted items, and the last property contains the array of the “keys” of all deleted items. The names of these properties can be gonfigured in the option argument of the constructor of the updateManager (the last argument). The default option object is:

  1. {
  2.     updater: {u: "Modified", i: "Inserted", d: "Deleted"},
  3.     isoDate:false,
  4.     updateCallback: function(e, result, status){},
  5.     updatingCallback: function (changes, modelToPost, expr){return changes;}
  6. }

The first property contains the names of the three fields of the change set object.

The second property specifies if the date format to use when sending the change set to the server is the ISO format or the \/Date(….)\/ format. When sending data to a standard Mvc controller we have to set isoDate to false, while in our case we can’t use the default since WebApi controllers need the ISO format.

The updateCallback is called just before processing possible errors returned by the server, and it receives:

  • e. an object with format:
    { setErrors: true, model: sourceViewModel, expression: sourceExpression, key: keyExpression, success: !result.errors }
    If in our callback we set the setErrors property to false, error processing is not carried out, and we have to process them with our custom logics.
  • result. The result returned by the server, that must  be an object containing an error property with the list of all errors and an optional insertedKeys property with the keys created dynamically when inserting new records in the DB. Both the errors and the keys received from the server are processed automatically. That is, the errors are dispatched in the right places of the UI, and the keys to all newly created items that need them.The result object can contain also further properties that can be processed in a custom way  in the updateCallback.
  • status: the Http status of the server response. In our example we specify a callback that just shows the status with an alert window.

The updatingCallback is invoked just before sending all data to the server. If this callback returns a false, null, or undefined value the update is aborted. The updatingCallback receives three arguments:

  • changes: a boolean that informs us if any change was detected
  • modelToPost: the model that the updateManager is going to post
  • expr: a string expression that locates the just computed changes set within the above model.

Let give a look to our receiving action method:

  1. HttpResponseMessage<ApiServerErrors<int>> Post(Updater<ToDoView, int> model)

The Updater generic class that is defined in the MVCControlsToolkit.Controller namespace is just the server side equivalent of our javascript change set class. It is just an helper class since we can use any class to receive the change set. It is enough to specify the right property names in the updateManager option object.

We can use also a complex object containing several change set objects that matchs our javascript destinationViewModel, as parameter  to deal with several simultaneous collection updates. Moreover, we are not limited to using webApi methods but we can use also standard controller action methods.

The full code of the controller method  is:

  1. public HttpResponseMessage<ApiServerErrors<int>> Post(Updater<ToDoView, int> model)
  2. {
  3.     //uncomment to experiment server side error handling
  4.     //ModelState.AddModelError("Modified[0].Name", "Fake error");
  5.     int[] insertedKeys;
  6.     if (ModelState.IsValid)
  7.     {
  8.         insertedKeys=ToDoViewModel.UpdatePage(model.Inserted, model.Modified, model.Deleted);
  9.         
  10.     }
  11.     else
  12.     {
  13.         insertedKeys = new int[0];
  14.     }
  15.     return new ApiServerErrors<int>(ModelState, insertedKeys).Wrap();
  16. }

If the ModelState is valid we call a business layer method, otherwise we abort business computation and return just the list of errors. It is enough to invoke the constructor of the server side equivalent of our javascript result  object passing it the ModelState, and an array with the keys of all newly created items, to create our result object filled with all errors and/or keys. The call to the Wrap method embeds this object into a response containing an appropriate Http status code. In case we have a complex destinationModel with several change sets, we can use a different overload of the constructor that accepts several arrays of keys, each with a string expressoin that specifies the change set of the destinationViewModel it refers to, Namely:

  1. public ApiServerErrors(ModelStateDictionary origin, ApiKeyInfos<T>[] insertedKeys, string prefix = null)

where:

  1. [DataContract]
  2. public class ApiKeyInfos<T>
  3. {
  4.     [DataMember]
  5.     public string destinationExpression { get; set; }
  6.     [DataMember]
  7.     public T[] keys { get; set; }
  8. }

There is also a not generic version of the ApiServerErrors class that we can use when we don’t need to send keys to the client.

The ApiServerErrors class is contained in the MVCControlsToolkit.Controller namespace together with the analogous ServerErrors class that we can use with standard controllers action methods.

Let uncomment the line that creates a fake error and let see how errors are dispatched to the client:

ToDoEditing

Let modify a couple items, and let add a new one, then let hit submitAllChanges.

since the user input was wrong we get a 400 status code (BAD REQUEST):

error400

Then since the property we added the error is: Modified[0].Name, the error is dispatched to the ValidationMessageFor label next to the name of the first modified item:

ValidationError

Since we had no luck with our changes we can decide to undo everything by hitting the Undo All Changes button: Everything is cleared…changes and errors.

How does Undo and Change Set computation work? How to enable them in our applications?

The first step is to prepare all items that we receive from the server:

  1. var newEntities = ko.mapping.fromJS(x.results)();
  2. if (ClientToDoView['updater']) {//if entities may be modified and sent back to the server, prepare them
  3.     ClientToDoView.updater.prepare(newEntities, true);
  4. }

or that we create on the client:

  1. detailToDo.saveAsNew = function () {
  2.     if (!$('#detailForm').validate().form()) return;
  3.     var item = ko.mapping.fromJS({
  4.         DueDate: this.DueDate(),
  5.         Name: this.Name(),
  6.         Description: this.Description(),
  7.         id: null
  8.     });
  9.     ClientToDoView.updater.prepare(item, true);//newly created entity prepare it
  10.     ClientToDoView.updater.inserted(ClientToDoView.DataPage.ToDoList, item);
  11.     this.reset();
  12. };

Then, we must call the inserted method to insert them in our collection as shown above,  the deleted method to delete them:

  1. detailToDo.remove = function (item) {
  2.     this.resetIfSelected(item);
  3.     ClientToDoView.updater.deleted(ClientToDoView.DataPage.ToDoList, item);
  4. };

and the modified method each time the user manipulate them someway:

  1. detailToDo.save = function () {
  2.     var item = this.DetailOf();
  3.     if (!item) return;
  4.     if (!$('#detailForm').validate().form()) return;
  5.     mvcct.utils.restoreEntity(this, item);
  6.     ClientToDoView.updater.modified(item, true, true);
  7.     this.reset();
  8. };

The second and third argument of the modified method say respectively, to prepare the entity if it is not yet prepared(actually we don’t need it since we know entities have been already prepared), and to do an immediate verification of all changes. Thus all properties of our entity are compared with their old values to verify if an actual change occurred, and only if the entity actually changed it is marked as modified. If the third argument of the modified method is false, our entity is marked as modified without performing any immediate verification; the verification is deferred till the time the updateManager compute the change set.

The mvcct.utils.restoreEntity is an utility method of the Mvc Controls Toolkit  that copies an object into another object having its same structure, properly handlig all ko observables. As a default nested objects are visited and their properties copied but arrays are not. However, if its second optional argument is set to true, references to arrays are copied too.

Preparing a property add it two observable properties: _inserted and _modified, that say us the state of the entity. They are used by the routines that compute the change set, but we can use them also to improve the user experience. In our case we bound them to the enabled status of the undo button:

  1. <input type="button" value="Undo" data-bind='click: function(item){detailToDo.undo(item);}, enable: _inserted() || _modified()'/>

If in the call to the prepare method we pass true as second argument(as we have done), changes tracking is turned on, and the entity will remember its initial state. If changes tracking is off no undo is possible, and an entity is marked modified when we call the modified method without performing any check.

Below how to undo a single entity:

  1. detailToDo.undo = function (item) {
  2.     this.resetIfSelected(item);
  3.     ClientToDoView.updater.reset(item);
  4. };

it is enough to call the reset method of the updateManager. The updateManager has also a resetAll method that restore the initial state of the whole collection:

  1. ClientToDoView.undoAll = function () {
  2.     ClientToDoView.updater.resetAll($('#mainForm'));
  3.  
  4. };

It accepts, as argument, a jQuery object containing a form, that it uses to clear all errors. The state of the errors returned by the server can be also cleared manually each time we need by calling the clearErrors(jForm) method of the updateManager. In our example we do it when we perform a new query to the server:

  1. if (ClientToDoView['updater']) {//clear previous errors, since entities shown on the screen will change
  2.     ClientToDoView.updater.clearErrors($('#mainForm'));
  3. }

we can require manually also the dispatching of errors by calling the refreshErrors(jForm, errorState) where errorState is an object with the same format as the result returned by the server. If errorState is null the last errors returned by the server will be used.

The change set can be submitted to the server either by calling the update(jForm) method:

  1. ClientToDoView.save = function () {
  2.     ClientToDoView.updater.update($('#mainForm'));
  3. };

or by calling the submit(jForm) method, in which case a not-ajax normal browser submit is triggered. The form submitted is dynamically created and contains just the destinationViewModel data.Examples showing the use of the submit method and the use of the updateManager with standard controllers action methods are contained in the file Advanced JSon Communication in the download area of the Mvc Controls Toolkit

Both the update and the submit methods have a second argument: isDependent. It is used when submitting simultaneously multiple change sets to the server into an unique destinationViewModem. When isDependent is set to true the updateManager just compute the change set and insert it in the right place in the destinationViewModel without performing any operation on the server. The operation on the server is performed by an unique updateManager, called with isDependent set to false, that acts as Master. When we call the update method the Master dispatches automatically the results returned by the server to the Slave updateManagers, thus causing they update their, entities, their errors, and call their updateCallBack. The array of all Slaves to notify is passed as third argument of the update method.

The addRelated method of the updateManager can help us in handling multiple related collection on entities:

  1. addRelated(collectionExpression, entities, entitiesExternalExpression, inverseCollectionExpression, overrideKeyExpression)

It computes all mutual references existing between the items of two related collections by using key and external key information:

  • entities is the array(or ko observable array) with the collection we would like to relate with the collection handled by the current instance of the updateManager.
  • collectionExpression is a string expression specifying the array contained in each entity of the collection handled by the current instance of the updateManager,  where the pointers to the related entities will be pushed. If the array doesn’t exists it is created, it it already exists it is not cleared.
  • entitiesExternalexpression is a string expression that locates the external key within each entity of the entities collection.
  • inverseCollectionExpression is a string expression specifying the array contained in each entity of the entities collection where the pointers to the related entities will be pushed. If the array doesn’t exists it is created, if it already exists it is not cleared.
  • overrideKeyexpression, if provided, is used in place of the key defined in the current instance of the updateManager.

In a short time I will write a more detailed blog post on how to handle related collections.

All examples on the js Queryable and on the updateManager are contained in the files Mvc4 Client- Filtering -Paging -Sorting-updating and Advanced JSon Communication in the download area of the Mvc Controls Toolkit

In a short time the team of the jsAction project will give us a very easy way to use the updateManager, by inspecting all controllers and providing automatically the right instances of the updateManager already configured to work with each specific controller. For the moment enjoy the post of my Friend Vincenzo of the jsAction team.

Tags: , , , , , , ,