Client Block complete example

Suppose our server-side ViewModel has a property named ClientKeywords whose type is the class:

 

public class ClientViewModel
    {
        public List<string> Keywords { get; set; }
        public List<string> SelectedKeywords { get; set; }
        [StringLength(10, ErrorMessage="The maximum length of field {0} is 10")]
        [Display(Name="Item to add", Prompt="Add new keyword")]
        public string ItemToAdd { get; set; }
        public ClientViewModel()
        {
            
            SelectedKeywords = new List<string>();
            ItemToAdd = "";
        }

       
    }

 

If we want to use the above class as client-side ViewModel by defining a Client Block in our View we need just:

 

<%: Html.TemplateFor(m => ClientViewModel, "ClientViewModelExample", null, true, "keywordsHandling", "ClientViewModelContainer", true) %>

 

Where ClientViewModelExample is a partial View strongly typed with ClientViewModel that we will use as template keywordsHandling is the name of the Javascript global variable that will contain our Client View Model. and ClientViewModelContainer the id of our Client Block Root. This is enough for our main View...let go to the ClientViewModelExample partial View.

As a first step we empower our ViewModel with some Javascript methods. Methods can be added to the ViewModel with the help of the method of the IBindingsBuilder<M> interface:  

 

IBindingsBuilder<M> AddMethod(string name, string javaScriptCode);

 

However to call it we need an IBindingsBuilder<ClientViewModel> interface. We can get it from the helper of our  ClientViewModelExample Partial View: 

 

var bindings = Html.ClientBindings();

 

Now we can define a couple of methods of our Client ViewModel we will need later. Summing up we have:

 

<% var bindings = Html.ClientBindings();
            bindings
                .AddMethod("addNewItem",
                @"
                    function () {"+
                        bindings.VerifyFieldsValid (m => m.ItemToAdd) + @"
                        if (this.ItemToAdd() == null || this.ItemToAdd().length == 0) return;
                        this.Keywords.push(this.ItemToAdd());
                        this.SelectedKeywords.push(this.ItemToAdd());
                        this.ItemToAdd('');  
                    }
                    ")
                .AddMethod("removeSelected",
                @"
                    function () {
                        this.Keywords.removeAll(this.SelectedKeywords());
                        this.SelectedKeywords([]); // Reset selection to an empty set
                    }

                    ");
            %>

 

The first method just adds the string contained in the ItemToAdd  to the Keywords collection that on the client-side has become an array, and than it clearsItemToAdd, that,  this way acts as a buffer to insert new elements in Keywords. Once we have  bound ItemToAdd to the value of a TextBox and Keywords to the options of a ListBox,  the addNewItem method causes the content of the TextBox to be added to the ListBox, and the TextBox to be cleared. Analogously the removeSelected method removes the items contained in the SelectedKeywords list from the ones contained in the Keywords list, so when we bind the SelectedKeywords to the selected items of our ListBox the call to the removeSelected method causes all the selected items to be removed from the ListBox.

Now we just need a ListBox whose list Items are bound to the KeyWords property, and whose selected items are bound to the SelectedItems property of our ViewModel....very easy to do:

 

<div>
            
            <%: Html.DropDownListFor(m => m.SelectedKeywords, new {style="min-width:100px"}, Html.CreateChoiceList(
                m => m.Keywords,
                m => m,
                m => m))%>
            </div>

 

To insert a new KeyWord we can use a TypedTextBox, so we can easily add a watermatk. We have to bind it to the ItemToAdd property of our Client ViewModel. Let add also a validation helper:

 

<%:
                
               Html.TypedTextBoxFor(
                    m => m.ItemToAdd, 
                    "watermark")
                %>
                <%:
                    Html.ValidationMessageFor(m => m.ItemToAdd, "*")
                %>

 

The watermark prompt has been provided through Data Annotations.

Now we need just two buttons that call the addNewItem and removeSelected methods. We can bind the wo buttons to their click handler by adding them manually two click bindings:

 

<%
                var addButtonBindings = bindings
                    .Click(m => m, "addNewItem")
                    .Get();
                var removeButtonBinding = bindings
                    .Click(m => m, "removeSelected")
                    .Enable(m => m.SelectedKeywords, "{0}.length > 0").Get();
                var sumbitButtonBindings = bindings.Click(m => m, "saveAndSubmit").Get();
                 %>
                <input type="button" value="Add" data-bind='<%: addButtonBindings %>'/>
                <input type="button" value="Remove Selected" data-bind='<%: removeButtonBinding %>'/>
                <input type="button" value="Submit" data-bind='<%: sumbitButtonBindings %>'/>

 

As you can see there is also a third button that is bound to a saveAndSubmit method. This is a predefined method that is automatically added to all Client ViewModel, and do simply what follows:

1) validate the form it is in, if validation fails, return otherwise go on.

2) serialize the Client ViewModel into an predefined hidden filed

3) submit the form.

 

That's all!......

Not really....There is still something strange..to take care of.  It is very unlikely that our server-side ViewModel include an ItemToAdd and a SelectedKeywords properties because they have nothing todo with the original keyword list, but they have been introduced just because we need them for the specific way we decided to let the user edit the keywords list. The ViewModel just defines What to display while ItemToAdd and SelectedKeywords depend   on How we have decided to display it.

Therefore, in order to keep separation of concerns between the Controller and the View we cannot add the above properties to our ViewModel, but we have to add  just a keyword List that we may call ClientKeywords to our ViewModel. The problem can be easily solved by defining a in-line transformation as explained in the In-Line Transformations section.

The In line trasformation need to be applied in the Main View that nows become:

 

<div id="ClientViewModelContainer">
            <%
           var clientKeywordsHelper =
                  Html.TransformedHelper(
                      m => m.ClientKeywords,
                      new ClientViewModel());
            %>
           <%: clientKeywordsHelper.TemplateFor(m => m, "ClientViewModelExample", null, true, "keywordsHandling", "ClientViewModelContainer", true) %>

 

For the above to work properly our ClientViewModel must implement the IDisplayModel interface:

 

public class ClientViewModel:IDisplayModel
    {
        public List<string> Keywords { get; set; }
        public List<string> SelectedKeywords { get; set; }
        [StringLength(10, ErrorMessage="The maximum length of field {0} is 10")]
        [Display(Name="Item to add", Prompt="Add new keyword")]
        public string ItemToAdd { get; set; }
        public ClientViewModel()
        {
            
            SelectedKeywords = new List<string>();
            ItemToAdd = "";
        }

        public object ExportToModel(Type TargetType, params object[] context)
        {
            return Keywords;
        }

        public void ImportFromModel(object model, params object[] context)
        {
            Keywords = model as List<string>;
            if (Keywords == null)
                Keywords = new List<string>();
        }
    }

Last edited Jun 25, 2012 at 10:49 PM by frankabbruzzese, version 5

Comments

No comments yet.