Oct 28 2011

Handling Big Amounts of Data with Client-Side Templates 2

Category: Asp.net | MVC β€” Francesco @ 23:42

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

Handling Big Amounts of Data with Client-Side Templates

In my last post about Client-Side templates I showed how to show a big amount of products in a View and let the user add some of them to a chart with the help of Client-Side templates. The main issue there, was mainly, the optimization of the View organization.

Here, I will show how the addition of some parameters to the template binding may further improve performance, and how to improve the general look of the View with the help of other kind of bindings such as the CSS binding, and with the help of some extensions of the _D helper of the Mvc Controls Toolkit.

First of all we notice that the productViewTemplate used to show the list of all products doesn’t contain any input filed or any input server control. Moreover, it doesn’t contain any javascript or server control(that rely on javascript), but it just contains “holes” to be filled when the template is instantiated. We can say all this to the template engine so that it can avoid to do high computational cost operations when the template is instantiated. This can be done by simply specifying two optional parameters of our repeater:

new { id = "ProductsContainer" }, fastNoInput:true, fastNoJavaScript:true)

This way the template is not parsed looking for input validation or input bindings, and it is not scanned to collect JavaScript code to be executed. As default the value of this extra parameter is false. There is also an applyClientValidation parameter whose default value is true that can be used when there are input fields but we don’t want validate them on the client side.

As a further improvement we can show the price of each product, …but this is easy ..just adding another property to our model and another _D helper to show it in our template.

What if we want to show some promotional images saying a product has a discount?  Suppose we have 3 possible situations: no discount, 25% discount and 35% discount. We can define an enumeration with this three possible values:

public enum discount { none, d25, d35 };

This way we can take advantage of the possibility to render enumerations with images or strings introduced since the 1.1 RC version of the Mvc Controls Toolkit.

As a first step we have to declare two vectors: the first one containg all image urls, and the second one containing all strings to use as values for the alt attribute of the images:

Html.DeclareStringArray(new string[] { "", Url.Content("~/Content/discount25.png"), Url.Content("~/Content/discount35.png") }, "discountUrls");
Html.DeclareStringArray(new string[] {"", "25%", "35%"}, "discountAlts");

Once we have declared this two arrays and we have given them names, it is enough to render the enumeration property with  the _D helper passing it the array names as parameters. Below the full code of the clint side template definition:

<div id="ProductsToList">
    <h2>Products List</h2>
    <br /><br /><br />
    @using (Html.BeginForm("Index", "Shopping"))
    {
    
        <div id="automaticLoad" class="ToScroll">
        <div id="automaticLoadContent">
                  @Html.ValidationSummary(false)
                  <table >
                  <thead>
                      <tr>
                      <td><strong>Product</strong></td>
                      <td><strong>Price</strong></td>
                      <td><strong>Discount</strong></td>
                      <td><strong>Select Product</strong></td>
                  
                      </tr>
                  </thead>
                  @{  Html.DeclareStringArray(new string[] { "", Url.Content("~/Content/discount25.png"), Url.Content("~/Content/discount35.png") }, "discountUrls");
                      Html.DeclareStringArray(new string[] { "", "25%", "35%" }, "discountAlts");}
                   @Html.ClientBlockRepeater(m => m.Products,
                       _S.H<ProductViewBase>(
                            @<tr>
                                    <td>
                                    @item._D(m => m.Description)
                                    </td>
                                    <td>
                                    @item._D(m => m.Price)
                                    </td>
                                    <td>@item._D(m => m.Discount, null, "discountAlts", "discountUrls")</td>
                                    <td><a href="#" data-bind="click: function() { productsClientView.buyProduct($data) }">Buy</a> </td>
                             </tr>
                        ),
                        ExternalContainerType.tbody,
                        new { id = "ProductsContainer" })
                  </table>

Once we have the product price we can add to the chart the total cost of each product(number of products*unit price) and the overall price of the purchase.

Each product price can be computed by using a Text binding where we, first perform the needed multiplication and then we format properly the price with the help of the Mvc Controls Toolkit JavaScript formatting function:

function MvcControlsToolkit_FormatDisplay(value, format, dataType, prefix, postfix, nullString)

The first parameter is the value to format, the second one a format string, the third one the data type, then we can add also a prefix and postfix to the string obtained this way, and we can also provide a string to be used when the value is null. In our case the data type is float and we want just 2 decimals, so the ‘n’ format string is ok. Moreover we need to add a ‘$’ postfix since each number is a price:

@{var totalPriceBinding = itemBindings.Text(
                                  m => m.Quantity,
                                  "MvcControlsToolkit_FormatDisplay({0}*{1}, 'n', MvcControlsToolkit_DataType_Float, '', '$', '')",
                                  itemBindings.L<decimal>(m => m.Price)).Get();}
                            <td><span data-bind="@totalPriceBinding"></span></td>

Since the binding has more than one parameter(Quantity and Price) we have to use the additional optional parameters provided by most of bindings. Since they are not strongly typed but they are defined as Expressions (not as Expression<Func<…..) Visual Studio IntelliSense is not available. To overcome this problem we revert to strongly typed Expressions with the help of the itemBindilgs.L method.

In order to display the total price of the purchase we add a table footer and use a text binding that calls the grandTotal() method of the client side ViewModel:

<tfoot>
              <tr>
                <td><strong>Total:</strong></td>
                <td><strong></strong></td>
                <td><strong></strong></td>
                <td><strong><span data-bind="text: MvcControlsToolkit_FormatDisplay(grandTotal(), 'n', MvcControlsToolkit_DataType_Float, '', '$', '')"></span></strong></td>
                <td></td>
              </tr>
              <tr>
              <td colspan="3"></td>
              <td><input type="button" value="Submit" data-bind='click: function(){if(confirmModel.Products().length>0) confirmModel.saveAndSubmit();}'/></td>
              <td></td>
              </tr>
              </tfoot>

The table footer contains also the submit button that is enabled only when there is at least one product added to the chart. The definition of the grandTotal() method is quite immediate:

<script language='javascript' type='text/javascript'>
        confirmModel.grandTotal = ko.dependentObservable(function () {
            var total = 0;
            for (var i = 0; i < this.Products().length; i++) {
                total += this.Products()[i].Quantity() * this.Products()[i].Price();
            }
            return total;
        },
        confirmModel);
    </script>

As you can see the grandTotal member is defined as a dependent observable so that the total is automatically updated when something changes.

Finally, the whole chart might be invisible when empty. This can be easily achieved attaching a CSS binding to the table tag:

var aPurchase = confirmModel.CSS("hide", m => m.Products, "{0}.length==0").Get();

and then:

<table data-bind = "@aPurchase">

That’s All! Download all code of the whole client-template set of tutorials from the Mvc Controls Toolkit download page. The name of the file is RazorClientTemplating.

                                                                           Stay Tuned!

                                                                           Francesco

Tags: , , , ,

Oct 28 2011

Handling Big Amounts of Data with Client-Side Templates

Category: Asp.net | MVC β€” Francesco @ 23:22

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

Handling Big Amounts of Data with Client-Side Templates 2

In my previous post here I introduced the client-side templates of the Mvc Controls Toolkit, and I discussed how they support both the use of server controls and validation.  I pointed out also that they allow the implementation of rich UI interfaces at a low BandWidth cost since the rich UI code is sent in templates and then replicated on the client-side on several data items by exploiting the capability of templates to be instantiated on on whole collection of items.

As already discussed, instantiation of the collections is done substantially, either by substituting “holes” in the template with the actual data contained in each item once for all when the template is transformed into html nodes, or by creating bindings between item properties and template html elements. Bindings grant that modifications of data items performed after the template has been instantiated propagate immediately to the bound html elements, and vice versa that input provided by the user into html elements are propagated immediately to the data items they are bound to. However, bindings have an higher cost in terms of computational resources, especially if the html elements involved in the binding are complex server controls, like for instance a DateTime input.

As a consequence, one should avoid using input fields bound to data items when dealing with big collections of thousands of items. This can be achieved, by allowing the user to select the collection items he would like to operate on, as a first step. Once the item has been selected a new template containing all desired input field is instantiated. Since the user will probably select just a few of all listed items, this way we avoid any performance issue. Moreover, if we group all selected items in a different area of the page we help the user to manage properly all items he decided to operate on without being forced to look for them among thousands of other items.

To show how all this can be implemented we modify adequately the example of my previous post. To download the full code of the example, please,  go to the download area of the Mvc Controls Toolkit  and download the file: RazorClientTemplating.

Now we need two ViewModel versions of the product class, the first one to be used for display only purposes, and the other one to be used once the user has selected  a product and needs to specify the quantity, and delivery date:

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

 

Since now the ProductView class is used only for the products that have been selected by the user, we modified  the RangeAttribute to require a minimum Quantity of 1: if the user changes his mind and he doesn’t want the product anymore he can just delete it from the list of selected products

We need two Client-Side ViewModel. The first one for handling the list of all products with some kind of paging:

public class ToBuyViewModel
    {
        public List<ProductViewBase> Products { get; set; }
        public int LastKey { get; set; }
        public DateTime DefaultDeliveryDate { get; set; }
    }

The LasteKey integer is used as in my previous post to enable the user to load more products pages in the View through Ajax, while DefaultDeliveryDate is the delivery date that is suggested to the user immediately after he selects a product.

The other model handles the selected products and is submitted to the Confirm action method that handles the purchase confirmation:

public class PurchaseConfirmationView
    {
        public List<ProductView> Products { get; set; }
        public bool PurchaseConfirmed { get; set; }
        public bool GoToList { get; set; }
    }

It contains the list of all selected products and two booleans that encode some user choices. Once the user has selected all products the above Client-Side ViewModel is submitted to the Confirm action method that will handle the purchase confirmation process allowing the user either to cancel the purchase and returns to the main purchase page or to confirm the purchase by means of the PurchaseConfirmed and GoToList booleans. Below the Confirm action method:

[HttpPost, System.Web.Mvc.OutputCache(NoStore = true, Duration = 0, VaryByParam = "*")]
        public ActionResult Confirm(PurchaseConfirmationView purchase)
        {
            if (purchase.GoToList)
                return RedirectToAction("Index");
            if (ModelState.IsValid)
            {

                if (purchase.PurchaseConfirmed)
                {
                    //insert here actual purchase locgics
                    return View("PurchaseConfirmed", purchase);
                }
                else
                {
                    purchase.GoToList = false;
                    purchase.PurchaseConfirmed = false;
                    return this.ClientBlockView("PurchaseConfirmation", purchase, "confirmModel");
                }
            }
            else
            {
                purchase.GoToList = false;
                purchase.PurchaseConfirmed = false;
                return this.ClientBlockView("PurchaseConfirmation", purchase, "confirmModel");
            }
        }

As you can see the Confirm Action returns a ClientBlockViewResult, thus defining the whole Confirmation View as a Client Block. Infact the Confirmation View is completely handled on the Client-Side, since it contains just a ClientBlockRepeater showing all products selected by the user.

The PurchaseConfirmationView ViewModel is rendered in the main view in a separate form whose submit target is the Confirm action method, and it it is filled with the new products selected by the user. To handle it we define a different Client Block by means of the Template extension method:

<div id="ProductsToBuy">
    <h2>Products Chosen</h2>
    <br /><br /><br />
    @using (Html.BeginForm("Confirm", "Shopping"))
    {      
        @Html.Template(new PurchaseConfirmationView { Products = new List<ProductView>() },
        _S.H<PurchaseConfirmationView>(ToBuyInnerBlock),
        true,
        "confirmModel",
        "ProductsToBuy")
    }
    
    </div>

The third parameter of the Template extension method is set to true, to declare we are defining a new Client Block, the first parameter is the object we would like to use as Client ViewModel, “confirmModel” is the name of the global javascript variable where to put the Client ViewModel, “ProductsToBuy” is the id of the div acting as Root of he Client Block. Finally, ToBuyInnerBlock is the name of the template containing the whole code of the Client Block; we defined it with a Razor Helper:

@helper ToBuyInnerBlock(HtmlHelper<PurchaseConfirmationView> Html)
    {
       
        @Html.ValidationSummary(false)
        <div class="ToScroll">
                  <table >
                  <thead>
                      <tr>
                      <td><strong>Product</strong></td>
                      <td><strong>Quantity</strong></td>
                      <td><strong>Delivery Date</strong></td>
                      <td><strong>Delete</strong></td>
                      </tr>
                  </thead>
                  @Html.ClientBlockRepeater(m => m.Products,
                    _S.H<ProductView>(
                            @<tr>
                                <td>
                                @{var itemBindings = item.ItemClientViewModel();}
                                @item._D(m => m.Description)
                                </td>
                                <td>
                                @item.TypedTextBoxFor(m => m.Quantity)
                                @item.ValidationMessageFor(m => m.Quantity, "*")
                                </td>
                                <td>
                                @item.DateTimeFor(m => m.DeliveryDate, DateTime.Today,
                                            false).Date()
                                </td>
                                
                                <td><a href="#" data-bind="click: function() { confirmModel.Products.remove($data) }">Delete</a></td>
                         </tr>
                        ),
                        ExternalContainerType.tbody,
                        new { id = "ProductsToBuyContainer" },
                        null,
                        "ProductsToBuyTemplate")
                  
                  </table>
                                    
        </div>
}

We added manually a Click binding to remove a product from the list of products to buy. It uses the remove method of the observableArray class to remove the product from the Products observable array. The argument of the remove method is $data that is substituted with the actual data item that is associated to the template when the template is instantiated. The anonymous object passed as third argument of the repeater just add an Html id attribute to the tbody tag that will be created as container of all instantiated templates, while the last argument of the repeater “ProductsToBuyTemplate” just gives a custom name to the Client Template that will be automatically created by the repeater. In our case this is a necessity, because the standard PrefixedId(m =>m.Products)+”_Template” name that is automatically created by the repeater would have collided with the analogous name of the template for the whole list of products to choose.

In fact the page is divided into two columns, the left column contains all products and the right column the products the user have chosen to buy. The two columns correspond to two different Client Blocks. The root of the Client Block containing the products to buy has been specified in the Template extension method to be the div with id “ProductsToBuy”. The root of the first Client Block that contains all product is specified in the Action method, that returns a ClientBlockViewResult, to be an Html node with id “ProductsToList”:

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

Below the first Client Block:

<div id="ProductsToList">
    <h2>Products List</h2>
    <br /><br /><br />
    @using (Html.BeginForm("Index", "Shopping"))
    {
    
        <div id="automaticLoad" class="ToScroll">
        <div id="automaticLoadContent">
                  @Html.ValidationSummary(false)
                  <table >
                  <thead>
                      <tr>
                      <td><strong>Product</strong></td>
                      <td><strong>Price</strong></td>
                      <td><strong>Select Product</strong></td>
                  
                      </tr>
                  </thead>
                  
                   @Html.ClientBlockRepeater(m => m.Products,
                       _S.H<ProductViewBase>(
                            @<tr>
                                    <td>
                                    @item._D(m => m.Description)
                                    </td>
                                    <td>
                                    @item._D(m => m.Price)
                                    </td>
                                    <td><a href="#" data-bind="click: function() { productsClientView.buyProduct($data) }">Buy</a> </td>
                             </tr>
                        ),
                        ExternalContainerType.tbody,
                        new { id = "ProductsContainer" })
                  </table>

Also here we added manually a Click binding.  In this case the click handler calls the javascript function that adds a new product to the list of products to buy:

<script language='javascript' type='text/javascript'>

                        productsClientView.buyProduct = function (product) {
                            confirmModel.Products.push(
                            {
                                Quantity: ko.observable(1),
                                Code: product.Code,
                                Description: product.Description,
                                Price: product.Price,
                                DeliveryDate: ko.observable(this.DefaultDeliveryDate())
                            }

                        );
                        };
                  </script>

Description and Code are not defined as observables since they will not be bound to any html element with a permanent binding but they will just fill predefined “holes” in the template.Quantity, instead, is defined as an observable and is given the initial value 1, since it will be bound to an input field. DeliveryDate, too, is defined as an observable since it will be bound to a DateTime input. Please notice, that, since it is copied from the DefaultDeliveryField of the first ViewModel, this date is already defined as an observable. However, we are forced to unwrap it from the original observable and place it into a new observable because if we use an unique observable for all dates all dates are forced to have the same value.

The divs named automaticLoad and automaticLoadContent before the repeater handles both the scrolling of the left column and the automating loading of a new page of products that is triggered automatically when the user move the scrollbar to the bottom:

<script language='javascript' type='text/javascript'>
                    $(document).ready(function () {
                        $('#automaticLoad').scroll(function () {
                            if ($('#automaticLoad').scrollTop() >= $('#automaticLoadContent').height() - $('#automaticLoad').height()) {
                                $.getJSON("@MvcHtmlString.Create(nextPageUrl)" + "&startFrom=" + productsClientView.LastKey(), function (data) {
                                    if (data != null && data.Products != null) {
                                        data = ko.mapping.fromJS(data);
                                        for (var i = 0; i < data.Products().length; i++) {
                                            productsClientView.Products.push(data.Products()[i]);
                                        }
                                        productsClientView.LastKey(data.LastKey());
                                    }
                                });
                            }
                        });
                        
                    });
                </script>

That’s all! Download the full code from the download area of the Mvc Controls Toolkit and enjoy! The file is named: RazorClientTemplating

 

                                                                           Francesco

Tags: , , , ,

Oct 28 2011

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

Category: MVC β€” Francesco @ 23:07

Defining MVC Controls 2: Using the DataGrid

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

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

Handling Big Amounts of Data with Client-Side Templates

Handling Big Amounts of Data with Client-Side Templates 2

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

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

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

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

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

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

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

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

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

Our full ViewModel is:

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

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

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

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

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

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

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

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

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

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

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

[AcceptViewHint(JsonRequestBehavior.AllowGet)]

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

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

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

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

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

Below the other form with the secondary ViewModel:

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

 

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

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

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

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

 

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

                                                                    Francesco

Tags: , , , , ,