This project is read-only.

Mvc Control Toolkit does not validate some field in popup

Jun 14, 2012 at 8:01 PM
Edited Jun 14, 2012 at 8:04 PM

Hi guys.

On my Mvc 3 project i`m trying to use Mvc Control Toolkit, but faced with validation problems.

So, i have a page with link that dynamically (by using $.ajax) load View Building. This View hat simple model like here:

 public class Building
    {
        public BuildingModel()
        {
            Sections = new List<BuildingSectionModel>();
        }

        [Required(ErrorMessage = "REQUIRED")]
        public long LocationId { get; set; }
        [Required(ErrorMessage = "REQUIRED")]
        [Display(Name = "BUILDING_NAME")]
        public string Name { get; set; }
        public IList<BuildingSectionModel> Sections { get; set; }
    }
Building view, where i display my Building model looks like here:
@model KnockoutTest.Models.BuildingModel
    <table border="0" cellspacing="0" cellpadding="0" class="entry_table">
        <tr>
            <td width="46%">
                @Html.LabelFor(m => m.Name)
                @Html.TextBoxFor(m => m.Name, new { @class = "field_longest field_required", maxlength = "50" }) @Html.ValidationMessageFor(m => m.Name)
            </td>
        </tr>
    </table>

                @{var h1 = Html._foreach(m => m.Sections, ExternalContainerType.table);}
                @h1._begin()
                <tr><td>
                        <a href="#" data-bind="click: function() { homeModel.Sections.remove($data)                         }">Delete</a>@h1.LabelFor(m=>m.Stories)
                    </td><td>@h1.EditorFor(m =>m, "BuildingSectionItem")</td></tr>
                @h1._end() 

All is ok, but every fields not in collections like Sections - does not display validation. But if i remove loading this view through ajax - all is ok.

Could you help me with this, to understand where is a problem?

Thanks a lot.

Coordinator
Jun 15, 2012 at 10:18 AM

There are 2 problems you need to fix:

1) Since your partial view doesn't contain a form (beginForm) The Mvc engine doesn't add validation attributes to all fields. This can be solved in two ways:

  • Using the Mvc Controls Toolkit. ClentValidationViewResult instead of PartialView. That is you add using MVCControlsToolkit.Controller to your controller file. Then your controller, instead of calling PartialView(...) call this.ClentValidationViewResult(viewName, model); Please do not forget the this...it is necessary since it is an extension method.
  • As an alternative just writh on top of your partila view:

@if (this.ViewContext.FormContext == null) {    this.ViewContext.FormContext = new FormContext();}

Both methods instruct the Mvc Engine to create Html5 Validation attributes in the input fileds.

 

2) Content that reach the client via ajax must be parsed for validation to create the validation rules on the clent. You can do this on the OnSuccess of the ajax call, by using either 

$.validator.unobtrusive.parseExt(selector)
or
Setup_Ajax_ClientValidation(formId, validationType)

Depending on if you substitute completely the content of a form or not. See here fore more details

Jun 15, 2012 at 11:19 AM

Hi.

First of all - thanks for so fast reply, i really appreciate this.

I try your solution, but without any success. So maybe would be better to define my controller and Parent and Child views.

My Home controller you can see below:

        [AcceptViewHint(JsonRequestBehavior.AllowGet)]
        public ViewResult Home()
        {
            var model = new BuildingModel();
            var section = new BuildingSectionModel();
            section.OccupancyCollection.Add(new OccupancyTypeModel());
            model.Sections.Add(section);
            return this.ClientBlockView(model, "homeModel");
        }

        [HttpPost]
        public ActionResult Home(BuildingModel model)
        {

            return this.ClientBlockView(model, "homeModel");
        }

        [AcceptViewHint(JsonRequestBehavior.AllowGet)]
        public ViewResult GetSection()
        {
            var model = BuildingSectionModel.CreateDefaultModel();
            return this.ClientBlockView(model, "homeModel");
        }

As you can see i return not PartialView, but ClientBlockView - to support using knockout within View and allow dynamically add new child element into Sections collection. Is it Ok?

Also the child view - that loaded via ajax here and i wrap it with form:

@model KnockoutTest.Models.BuildingModel
@using MVCControlsToolkit.Controls;
@using MVCControlsToolkit.Controls.Bindings;
@{
    ViewBag.Title = "Home Page";
}

<div id="editPersonContainer">
    @{ Html.BeginForm("Home", "Home", new {id="myForm"}, FormMethod.Post); }
    @Html.ValidationSummary(true)
    @Html.HiddenFor(m => m.LocationId)
    @Html.HiddenFor(m => m.PolicyId)
    
    @Html.LabelFor(m => m.Number)
    @Html.TextBoxFor(m => m.Number, new { @class = "field_short field_required", maxlength = "5" }) @Html.ValidationMessageFor(m => m.Number)

    @Html.LabelFor(m => m.Name)
    @Html.TextBoxFor(m => m.Name, new { @class = "field_longest field_required", maxlength = "50" }) @Html.ValidationMessageFor(m => m.Name)
 
    @Html.LabelFor(m => m.YearBuilt)
    @Html.TextBoxFor(m => m.YearBuilt, new { @class = "field_short", maxlength = "4" }) @Html.ValidationMessageFor(m => m.YearBuilt)

                @{var h1 = Html._foreach(m => m.Sections, ExternalContainerType.table);}
                @h1._begin()
                <tr><td>
                        <a href="#" data-bind="click: function() { homeModel.Sections.remove($data) }">Delete</a>@h1.LabelFor(m=>m.Stories)
                    </td><td>@h1.EditorFor(m =>m, "BuildingSectionItem")</td></tr>
                       @{
        var confirmModel = Html.ClientBindings();
    }
    @confirmModel.HandleServerErrors()
                @h1._end() 

<input type="submit" id="send"/>
<script language='javascript' type='text/javascript'>
    homeModel.AddNewSection = function () {
        $.ajax({
            url: '@MvcHtmlString.Create(Url.Action("GetSection", new { ViewHint = "json" }))',
            dataType: "text",
            success:
                function (textData) {
                    var data = $.parseJSON(textData);
                    if (data != null) {
                        data = ko.mapping.fromJS(data);
                        homeModel.Sections.push(data);
                    }
                }
        });
    };
        homeModel.Submit = function () {this.saveAndSubmit(); };

    $('#send').click(function () { homeModel.Submit(); return false; });
</script>

@{ Html.EndForm(); }

</div>

My main view that load child view via ajax:

@model KnockoutTest.Models.BuildingModel
@{
    ViewBag.Title = "Home Page";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

    <a id="openDialog" href="#">Open</a>
    <span id="personList"></span>
    <script>
        $(document).ready(function () {

            $('#personList').dialog({
                autoOpen: false,
                width: 1000,
                height: 800,
                modal: false,
                open: function (type, data) {
                    $.ajax({
                        url: '@Url.Action("Home", "Home")',
                        success: function (data) {
                            $("#personList").html(data);
                            $.validator.unobtrusive.parseExt('#editPersonContainer');
                        }
                    });
                },
                close: function () {
                    this.dialog("destroy");
                }
            });
            
            $('#openDialog').live('click', function (event) {
                event.preventDefault();
                $('#personList').dialog('open');
            })
        });
    </script>

I tried several variants - no good result. Could you please help maybe i miss something?

Coordinator
Jun 15, 2012 at 6:39 PM
Edited Jun 15, 2012 at 6:40 PM

I dont understand the role of 

 public ViewResult GetSection()
        {
            var model = BuildingSectionModel.CreateDefaultModel();
            return this.ClientBlockView(model, "homeModel");
        }
Moreover, it is not a common pattern to load a knockout view Via ajax. Client Blocks are not designed to be passed to the page through Ajax!Normally you load all Html you need with a normal page (not ajax page). Then if you want to show a dialaog you can create it by using a template that you pre-loaded with the not ajax page.To instantiate dynamically a template you can use the  _with statement.  When the property referred in the _with is populated with a notnull model the template contained within operning and closing of the _with is istantiated with this model and shows up! You can use theafterRender optional argument to specify a funtion to be exected after the rendering...there you can open the jQuery dialog.


When a template is istantiated all input filed are parsed for validation WITH THE RIGHT TIMING(Mvc Controls Toolkit takes care of the timing), 
on the contary when you send a 
client block through ajax it is difficult to ensure that everything is done with the right timing. 
In fact, the new html content must be parsed for ko bindings, for validation, and moreover some 
Mvc Conrols Toolkit complex controls must be initialized..all this must happen in the right order! That is 1) Controls Initilization; 2) knockout parsion
3) validation parsing.
If thge order is not respected...nothing works. Try the following: 
1) delaying execution of the parsing with setTimeout(function(){$.validator.unobtrusive.parseExt('#editPersonContainer');
}, 0);
2) Specify as third argument of this.ClientBlock the id of an Html node that is contained in the content sent via ajax, otherwise 
knockout binding are applied starting from the root of the page...this may create problems is the ajax call is performed several times


Consider that the data that you add with $("#personList").html(data); contains some js that triggers knockout parsing 
on document ready...now since you are into an ajax call this event is triggered immediately that is as soon as the javascript 
is executed...that is probably with the wrong timing. I don't know when $("#personList").html(data) executes the js contained in data..
if it is after data has been appended...ok...but I am not sure...otherwise you have to extract allo js for data and executing it after


The point is that ClientBlocks are not designed to be built passed to the page through ajax. When you use them you are supposed to load all knockout 
staff without ajax.

 

Jun 15, 2012 at 7:39 PM
Edited Jun 15, 2012 at 7:54 PM

Thanks for your assistance.

But, i`m not sure that understand your idea. I download samples from mvc control toolkit page and try to build solution with next goal:

i need to have opportunity dynamically add element in model collection (by element i understand element in model that also is model - like BuildingSectionModel from my sample) and after user dynamically add new element throw client validation and apply this changes with normal post (submit change).

 public ViewResult GetSection()
        {
            var model = BuildingSectionModel.CreateDefaultModel();
            return this.ClientBlockView(model, "homeModel");
        }

From documentation i understood that this view should return html block for new element in collection with server validation (render all messages like @Html.ValidationMessageFor() ). How would correct to bring server validation to client when user dynamically add new element into model collection property and render validation for it?

I search in documentation but, have no found any example of _with using. Could you please provide link or even better if you have some sample solution that allow better understand _with using.

I really thank you for help.

Coordinator
Jun 15, 2012 at 10:32 PM
Edited Jun 15, 2012 at 10:32 PM

Unlucly at the moment I have not yet prepared any example with _with. But maybe you dont need it.

Your child View appears ok and you make a good use of the ViewHintAttribute. The use of ajax you do in this view is ok too: mainly you receive from the server json data that you add to a collection. You make a good use of the foreach too.

Some problems may come from the fact that you load this child View with ajax.   Client Blocks were not designed to be passed to pages through ajax, so you might experience problems just for this, due to wrong timing of some events. Specifically during ajax call the document ready event doesnt work like in a normal page load, and I rely on it to ensure proper timing.

So as a first step I would try to avoid loading this partial View through ajax....Maybe I am wrong but I don't see a reason to load yoy childview through ajax: You might include that code with a normal Partial or RenderPartial (It appear you don't need _with..). My question is why are you using ajax  to load that View?

My question now is which kind of validation errors are causing yoiu problems? Validation errors due to Validation attributes like [Required]... This should work automatically if everything has been initilized properly....however as I said your initila use of ajax may create problems. Do you need to show other kind of errors??

Depending of the source of the errors and how the model is sento to the page there are tools to handle it.

We have @confirmModel.HandleServerErrors() to handle errors added MANUALLY to the ModelState. That is say you submit a whole client model to the the server, the server add some errors and then render a new response page that use Client Blocks. Such errors are shown tanks to @confirmModel.HandleServerErrors() .

There is also the updatesManager that handles all stages of a json commuinication (forth and back) with the server. ....

Summing up let me understand well wich kind of validation you need (do an example...so I am sure I understand). Moreover let me undertsand why you need ajax to load the content of your jQuery Dialog.

Coordinator
Jun 18, 2012 at 7:38 PM

I have done some tests:

As I already anticipated. Client Blocks CANNOT BE SEND TO A PAGE WITHA AJAX, they MUST BE DEFINED statically in the page. Moreover, the ClientBlockResult output a whole page with headers and so on, so it CANNOT BE USED like a PartialView but just with a whole view. 

In the 2.2 release of the toolkit I will define also a ClientBlockPartialResult that will work like a Partial View. However ALSO IN THIS CASE YOU CANNOT USE IT WITH AJAX BUT JUST WITH Action and RenderAction.

About examples with the _with operatore, it is just a wrapper around the with knockout operator. Look in the knockout docs more infos on how to use it, and then use back the _width that allow the use of the full power of Razor. Docs on the with operator is here:  http://knockoutjs.com/documentation/with-binding.html

Jun 19, 2012 at 1:11 PM

Hi, sorry for delay without answering.

Let me one more time clarify goal:

I need in popup (via ajax its necessary) load vsome view that should show and allow edit model. This model will have a List<OtherModel> - that is collection of another child model. User can add or remove dynamically nested model on different levels.

WIthout returning this.ClientBlockView(model, "homeModel"); on view i handle ArgumentNullException - Value cannot be null.Parameter name: bindings.

And one more terribly - if i add some error into model on post, collection of child will null, so user should reenter all values.

I upload simple solution just to be on the same page, to better understand - you can find it here

Thanks in advance for your help.

BTW. Do you have some faster way to contact, like skype?

My skype: ciget_software, please contact me if you will have a time.

Coordinator
Jun 19, 2012 at 2:01 PM

"I need in popup (via ajax its necessary) load vsome view that should show and allow edit model. "

As I already said you cannot use ClientBlocks with ajax, and in general it is difficult to initilaize knockout for html loaded via ajax from timing problems.

What you do normally with knockout is you load all html and templates when you load the page statically, then ajax calls are handled by by simply exchanging json NOT HTML with the server. The new data are loaded in an already existing ViewModel and cause the appearence of the page to change because they cause the instantiation of other templates or similar.

 

Say your popup has a model M that is bound to some Html Via knockout bindings. What I suggest to do is what follows:

1) Use ClientBlockView to output a whole page statically. This way the Viewmodel is trandferred to the client. Let call it VM.

2) M is contained in a property of VM and it is bound to the popup template with a _with construct. Initially M is null so the template contained withing the _with construct is not istantiated and nothing is shown.

3) You receive M in json format from the server, and put it into the property of M. Now M is no more null _with "triggers" and shows the template bound to the newly arrived M.

4) Use the afterRender optional argument of the _with to call the show command of the jQuery dialog as soon as the template is instantiated.

If you do this way you get "for free" client side validation..if you need other validation please explain what do you want to validated...There are several routine to handle validation errors returned by the servers...they should cover all possibilities. Validation takes place when you send some data modified by the user to the server. if the submission to the server is a normal submit you may use something like @confirmModel.HandleServerErrors() to handle server errors. On the other end if what you submit to the server is your collection modified by the user in json format  I suggest to use the updatesManager that do all job of computing a change sets and submitting it to the server, and then , of returning to the client both possible server errors (that are immediately shown) and  the keys of the newly inserted records.

Is this approach acceptable for you?

About a skype talk...please tell me your timezone. I am in Rome-Amsterdam timeline

Jun 19, 2012 at 2:18 PM

i'm from Ukraine, Kiev UTC +3

Coordinator
Jun 21, 2012 at 8:02 AM

Sorry for the delay I am very busey (I am closing a project in this days). I will contact you in the weekend on skype. Say on Saturday?

Jun 21, 2012 at 5:03 PM

Ok. I think we can do this at 2PM or any time that would be suitable for you