views:

1195

answers:

6

Hi,

I am making an Ajax call and adding content to a form inside a MVC2 app. I need to update the Client Validation Metadata with the validation for my new content.

 <script type="text/javascript"> 
//<![CDATA[
if (!window.mvcClientValidationMetadata) { window.mvcClientValidationMetadata = []; }
window.mvcClientValidationMetadata.push({"Fields":[{"
...
</script>

Is there a way to generate this metadata for a partial view ?

Thanks in advance.

A: 

Finally found it. After content is loaded in dynamically you will need to register the new form.

Since I am using Facebox, I added it to the facebox code, however you can add it wherever you need, or in a callback if your modal or whatever you are loading into has an afterLoaded event.

I wrapped them in a try/catch just in case i ever use facebox without the validation stuff.

Just run these two lines AFTER your content has been loaded:

try {
        Sys.Application.remove_load(arguments.callee);
        Sys.Mvc.FormContext._Application_Load();
} catch (err) {/* MVC Clientside framework is likely not loaded*/ }
Chad Ruppert
That works if you update the whole form.I only add new content to the form from rendering a partial view. How can I generate the validation metadata for a partial view?
you still need to use the <% Html.EnableClientValidation(); %> in your partial. I just got this working yesterday. To verify that you have validation working, drop it in your main page NOT via ajax. verify that validation occurs. If it does, you are set up fine. I load my entire partial editor via a jQuery $.get() request, and dump it into a facebox. No full page load. When I stated after your content has been loaded, i mean after the content you loaded via ajax call.
Chad Ruppert
I tried that but the partial html doesn't get the metadata.I'll start over with a nice clean demo.
are you sure the metadata didn't come through? You reviewed the response within firebug or something to be sure? It comes through every time on my stuff. Expand your question with your class, partial, and page please, and I can help further.
Chad Ruppert
A: 

I made some progress but I am not quite happy.

Problem #1: The client validation metadata is not generated unless you have a Html.BeginForm() in your partial. Which in my case is false because I do not update the entire form, I update portions of it.

Solution for Problem #1: Add a form in the partial view, let MVC generate the client validation MetaData and remove the form tags with a action filter. Let's call this Hack #1.

public class RemoveFormFilterAttribute : ActionFilterAttribute
{       

    private static readonly MethodInfo SwitchWriterMethod = typeof(HttpResponse).GetMethod("SwitchWriter", BindingFlags.Instance | BindingFlags.NonPublic);

    private TextWriter _OriginalWriter;

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        _OriginalWriter = (TextWriter)SwitchWriterMethod.Invoke(HttpContext.Current.Response, new object[] {new HtmlTextWriter(new StringWriter())});
    }

    public override void OnResultExecuted(ResultExecutedContext filterContext)
    {
        if (_OriginalWriter != null)
        {
            HtmlTextWriter lTextWriter =(HtmlTextWriter) SwitchWriterMethod.Invoke(HttpContext.Current.Response, new object[] {_OriginalWriter});

            string lOriginalHTML = lTextWriter.InnerWriter.ToString();

            string lNewHTML =  RemoveFormTags(lOriginalHTML);

            filterContext.HttpContext.Response.Write(lNewHTML);
        }
    }

Problem #2: The initial client validation metadata for the page is gone by the time I have the metaData for the new content.

Solution for Problem #2: Store the initial metadata (hard copy) and update it with the new fieds, than call the methods you mentioned to let MVC know new stuff arrived. Let's call this Hack #2.

<script type="text/javascript">

    var pageMvcClientValidationMetadata;

    $(document).ready(function() {

        $("input[name='PaymentTypeName']").change(PaymentTypeChanged);

        //create a back-up of the ValidationMetadata
        pageMvcClientValidationMetadata = JSON.parse(JSON.stringify(window.mvcClientValidationMetadata));
    });

    function PaymentTypeChanged() {

        var selectedPaymentType = $("input[name='PaymentTypeName']:checked").val();

        $.ajax(
            {
                url: 'PersonalData/GetPaymentTypeHtml?&paymentType=' + selectedPaymentType,
                type: "GET",
                cache: false,
                success: GetPaymentTypeHtml_Success
            });
    }

    function GetPaymentTypeHtml_Success(result) {

        $('#divPaymentTypeDetails').html(result);

        UpdateValidationMetaData();
    }

    function UpdateValidationMetaData() {

        //update the ValidationMetadata
        for (i = 0; i < window.mvcClientValidationMetadata[0].Fields.length; i++) {
            pageMvcClientValidationMetadata[0].Fields.push(window.mvcClientValidationMetadata[0].Fields[i]);
        }

        //restore the ValidationMetadata
        window.mvcClientValidationMetadata = JSON.parse(JSON.stringify(pageMvcClientValidationMetadata));

        //Notify the Validation Framework that new Metadata exists
        Sys.Application.remove_load(arguments.callee);          
        Sys.Mvc.FormContext._Application_Load();            
    }

Now. Any improvements would be appreciated.

Hack #1: How can I generate the client validation metadata without having an actual form ?

HAck #2: How can I appent to the page validation metadata ?

The last 2 calls seem to work only the first time.I didn't manage to find out why. It could be because I don't resend the whole form. I don't know...I'm giving up on MicrosoftAjax.I can't believe I'm the only one trying this scenario. Who updates a whole form ? That is there left, the menu ?
+1  A: 

Finally got it to work.

The answer is simple: don't waist time with MicrosoftMvcValidation.js. It is generated with Script# which makes it difficult to extend.

Switch to xVal and jQuery Validation. It doesn't need a form to generate the client validation metadata. Also in order to load validation for a AJAX request all you have to do is to call the following after you have the new Html:

lForm.find("#placeholder").empty();                     
lForm.valid();
lForm.find("#placeholder").html(responseHtml);   

That does it. First you remove the old content. Than re-run validation to get rid of potentially obsolete validation errors. Than add the new content. Works like a cham.

Also jQuery Validation makes it really easy to enable or disable validation for a certain field (conditional validation).

"..you have to do is to call the following after you have the new Html:" Can you explain where and how i should do that? Thanks.
AhmetC
+2  A: 

Hi Radu,

I was banging my head against a wall for a few days on this too and was going to go down the route of removing the form tag, but have just got it working in a slightly less hacky way if you are still interested. My scenario was similar in that I have a form with a collection of elements to validate initially, but users can dynamically add new rows via ajax.

I'll break it down so hopefully it'll be easier to see what is going on. Looking at the MVC source code, the form and validation works roughly as so:

Html.BeginForm() outputs the opening form tag then creates and returns a new instance of MvcForm, which doesn't outwardly do much except make the scope of the form easier to manage for you. It does however create a new FormContext and stores this within ViewContext.FormContext. It is this FormContext that tracks the client validation.

The last thing Html.BeginForm() does is set the FormId property of the new FormContext, using the id of the form tag. This is required so the client script can match up forms and validation rules.

Html.EndForm() disposes the MvcForm. This Dispose method outputs the form closing tag and then calls ViewContext.OutputClientValidation() which is resposible for outputting the javascript. Lastly it removes the current FormContext and sets it back to the parent FormContext or null if there isn't one.

So to not output the form tag we somehow need to take some of the FormContext management out of the MvcForm constructor/destructor.

So within my Partial View I did the following:

At the top I check if the ViewContext.FormContext has a value. If so we we are in the initial load so no need to mess around. If not, it is an ajax call, so I enable client validation, create a new MvcForm directly (not with BeginForm) - this causes a FormContext to be created - and set the FormContext.FormId to the same as my parent page

At the end of the view, I check if I have a form instance and if so, call ViewContext.OutputClientValidation() and reset the ViewContext.FormContext to null. I do not Dispose() the MvcForm as this would output the closing tag and MvcForm does not contain disposable objects.

The skeleton of the view looks as so:

<%
MvcForm dummyForm = null;
if (this.ViewContext.FormContext == null)
{
    Html.EnableClientValidation();
    dummyForm = new MvcForm(this.ViewContext);
    this.ViewContext.FormContext.FormId = "mainform";
}
%>

// standard partial view markup goes here

<%
if (dummyForm != null)
{
    this.ViewContext.OutputClientValidation();
    this.ViewContext.FormContext = null;
}
%>

You could quite easily wrap this up into an extension method

Phil

Phil
So I'm going down this path, trying to get jquery.validate to work with MicrosoftMvcJQueryValidation WITHOUT relaying on a BeginForm() call from either Html or Ajax. I, too, saw that in the mvc assembly, when a BeginForm is closed, a call to OutputClientValidation() is made to output the client validation information. However, a script tag is still NOT outputted when I make the method call. I'm looking into it further.
kdawg
Phil, you were right the whole time. I just had to follow through with the creation of a FormContext for the ViewContext, like you have above. Thanks!
kdawg
A: 

Here is my solution to make any dynamically loaded JavaScript (including MVC2 Client Validation) seamlessly work:

http://adammcraventech.wordpress.com/2010/06/11/asp-net-mvc2-ajax-executing-dynamically-loaded-javascript/

adammcraven
A: 

I have the same problem and resolve using the Future files, and in MicrosoftMvcJQueryValidation.js I change the and of file, this:

$(document).ready(function () {
    var allFormOptions = window.mvcClientValidationMetadata;
    if (allFormOptions) {
        while (allFormOptions.length > 0) {
            var thisFormOptions = allFormOptions.pop();
            __MVC_EnableClientValidation(thisFormOptions);
        }
    }
});

for:

function chargeValidation() {
    var allFormOptions = window.mvcClientValidationMetadata;
    if (allFormOptions) {
        while (allFormOptions.length > 0) {
            var thisFormOptions = allFormOptions.pop();
            __MVC_EnableClientValidation(thisFormOptions);
        }
    }
}

and in content after close form using I call the 'chargeValidation()', this resolve for me the problem I have using $.get(action) containing a form validation.

I hope to help you!

ggrocco