client template binding to a viewmodel that contains a List and model

Aug 27, 2011 at 11:02 PM

Im new to the control toolkit and have a question.

I am building a page to record recipes. So I have built a viewmodel that looks something like

    public class RecipeViewModel
    {
        public RecipeModel RecipeModel { get; set; }
        public List<IngredientModel> Ingredients { get; set; }
        public int LastKey { get; set; }

    }

I have managed to get the clienttemplate allowing me to add items into my Ingredients List<>

 

        @{var template = Html.ClientTemplate<IngredientModel>(
                    "ingredientViewTemplate",
                    _S.H<IngredientModel>(
            @<tr>
                <td>
                    @{var itemBindings = item.ItemClientViewModel();}
                    @item._D(m => m.IngredientID)
                    @item.TypedTextBoxFor(m => m.IngredientName, new Dictionary<string, object> { { "data-bind", itemBindings.Value(m => m.IngredientName).Get() } })
                </td>
                <td>
                    @item.TypedTextBoxFor(m => m.QuantityUsed, new Dictionary<string, object> { { "data-bind", itemBindings.Value(m => m.QuantityUsed).Get() } })
                </td>
                <td>
                    @item.DropDownListFor((model => model.QuantityTypeId),
                                                   ListHelpers.QuantityTypeItems(), new Dictionary<string, object> { { "data-bind", itemBindings.Value(m => m.QuantityTypeId).Get() } })
                </td>
                <td>
                    @item.TypedTextBoxFor(m => m.IngredientCost, new Dictionary<string, object> { { "data-bind", itemBindings.Value(m => m.IngredientCost).Get() } })
                    @item.HiddenFor(m => m.RowDeleted, new Dictionary<string, object> { { "data-bind", itemBindings.Value(m => m.RowDeleted).Get() }, { "class", "row-deleted" } })
                    <div class="delete-small">
                    </div>
                </td>
            </tr>
                    ));}

 

What do I need to do to enable me to bind to my RecipeModel inside my viewmodel? I have tried doing a simple bind like

@Html.TextBoxFor(...........

However on postback my recipeModel is always null.

Can someone help out?

Coordinator
Aug 28, 2011 at 1:39 PM

Probably you have read the documentation about client side templates here: http://mvccontrolstoolkit.codeplex.com/wikipage?title=Client-Side%20Templates but you missed the documentation about client ViewModel here: http://mvccontrolstoolkit.codeplex.com/wikipage?title=Client-Side%20ViewModel%20%26%20Bindings 

The way to use client side templates is:

1) Define a part of your server side ViewModel as Client Side ViewModel and render it as explained in the link above

2) Define a ClientSide Template (you have done it

3 )Bind your template to an Html node(that will be populated with your list), and specify a property of your Client side ViewModel to use as a source of data.

4) use the submit methode of the client side ViewModel to submit your page, this way your client side ViewModel is serialized in JSOn before the form is submitted.

 

I suggets you read both documentation link carefully. The example used in the second link is contained within the examples of the binary distribution here BinariesWithSimpleExamples.

I have also some tutorials about client side templates:

Using Client Side Templates:

Read them and see the source code of the examples used there  that is available in the download area of this site. The first tutorial is the foundamental one that explains how to use client side templates. However before reading it please read the documentation about clint side viewmodels

Aug 29, 2011 at 5:21 AM

Thanks Frank for your reply. I have read through the documentation and have I think gotten a little closer however still a little confused :(

My goal is to have a single page that allows me to add recipe details e.g. Name & Date made including all ingredients.I created the viewmodel to split them up as a recipe can have many ingredients but only 1 name and 1 create date.

The above source code works perfectly for the ingredients Model inside my VM however I have difficulties trying to understand how I can also have my recipeModel bound.

Your suggestions

1) Define a part of your server side ViewModel as Client Side ViewModel and render it as explained in the link above

This is what I have so far. I am not sure how to get both Ingredients<IngredientModel> & RecipeModel working together

@{ var clientModel = Html.SAClientViewModel("ingredientsClientView", Model, "noSubmit"); var tBinding = clientModel.Template("ingredientViewTemplate", m => m.Ingredients).Get(); <<<Somehow am I meant to insert my RecipeModel>>>>> } <script language='javascript' type='text/javascript'> ingredientsClientView.submitProductsChosen = function () { @MvcHtmlString.Create(clientModel.VerifyFormValid()) confirmModel.Ingredients = new Array(); for (var i = 0; i < this.Ingredients().length; i++) { var ing = this.Ingredients()[i].IngredientName(); if (ing != null && ing != 0) { confirmModel.Ingredients.push(this.Ingredients()[i]); } } if(confirmModel.Ingredients.length>0) confirmModel.saveAndSubmit(); };

 

2) Define a ClientSide Template (you have done it

This is my clienttemplate but bound to my IngredientModel, I have tried to change the binding to my RecipeViewModel and changing my my binding to be something similar to @item.TypedTextBoxFor(m => m.Ingredients[0].IngredientName,  however it fails to run.

  @{var template = Html.ClientTemplate<IngredientModel>(
                    "ingredientViewTemplate",
                    _S.H<IngredientModel>(
            @<tr>
                <td>
                    @{var itemBindings = item.ItemClientViewModel();}
                    @item._D(m => m.IngredientID)
                    @item.TypedTextBoxFor(m => m.IngredientName, new Dictionary<stringobject> { { "data-bind", itemBindings.Value(m => m.IngredientName).Get() } })
                </td>
                <td>
                    @item.TypedTextBoxFor(m => m.QuantityUsed, new Dictionary<stringobject> { { "data-bind", itemBindings.Value(m => m.QuantityUsed).Get() } })
                </td>
                <td>
                    @item.DropDownListFor((model => model.QuantityTypeId),
                                                   ListHelpers.QuantityTypeItems(), new Dictionary<stringobject> { { "data-bind", itemBindings.Value(m => m.QuantityTypeId).Get() } })
                </td>
                <td>
                    @item.TypedTextBoxFor(m => m.IngredientCost, new Dictionary<stringobject> { { "data-bind", itemBindings.Value(m => m.IngredientCost).Get() } })
                    @item.HiddenFor(m => m.RowDeleted, new Dictionary<stringobject> { { "data-bind", itemBindings.Value(m => m.RowDeleted).Get() }, { "class""row-deleted" } })
                    <div class="delete-small">
                    </div>
                </td>
            </tr>
                    ));}

 

3 )Bind your template to an Html node(that will be populated with your list), and specify a property of your Client side ViewModel to use as a source of data.

This part is sorted for my IngredientsModel however not my RecipeModel due to the clientemplate been bound to my IngredientModel not the RecipeViewModel.

4) use the submit methode of the client side ViewModel to submit your page, this way your client side ViewModel is serialized in JSOn before the form is submitted.

I used the example code and when the form has been submitted the ingredients are been returned correctly however the recipemodel isn't.

 

Am I getting close or am I going in the wrong direction?

 

Thank you for your help!

Coordinator
Aug 30, 2011 at 1:34 PM

Not sur what you want to do but I suppose you ant to implement Nested templates, that is something like this:

  1. Recepee1
    1. Ingredient1
    2. Ingredient2
  2. Recipee2
  3. Recipee3
  4. .........

I can explain how to implenent it but nested templates have not a good performance, so if the list is cery long they shoul be avoided. Moreover the nested nlist may confuse the user...A better approach should be a Master Detail approach, where you select a Recipee, the recipeed details appear in a detail area where the user can adit their ingredients. When the user finish editing it hit a save detail button and the detail is saved in the main model containing all recipee.

 

In order to implement nested lists you have to undertand well the condept of template binding. A template binding is a binding that connect a list of your view model to an html node. Whenever you add or remove nodes in your list, new html nodes representing them are added to the father html node bound to the list. The template specify how each node need to be represented. In turn the templates use bindings that connect the property of a generic element of the list to html elements.

The model you use in a template is not an ACTAUAL JS OBJECT but just a representative of the generic element of the list. In order to implement nested templates you have to define an Ingredient template and a Rexipee template. Now the Recipee template is used in the binding between the main list and the table where you want the recipee will be added. Now in the Recipee template you must define a template binding that connect the list of Ingredients of the generic Recipee object, to an html element that will contain all ingredients. This last template binding uses the Ingredient template.

This way the nested list of recipees and ingredinets will be updated. To gain a better understanding of binding and template bindings, please study also the knockout library docmentation: http://knockoutjs.com/documentation/introduction.html