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