turning Create.cshtml page into wizard form

Oct 12, 2011 at 7:52 PM
Edited Oct 12, 2011 at 7:53 PM

Hello,

I have situations where there will be 40+ fields on a create/edit form. I would like to break this up into a wizard type view. I am able to get everything working except for step validation.

I downloaded jquery smartwizard 2.0. Here is my Create.cshtml:

 

<script type="text/javascript">
    $(document).ready(function () {
        // Initialize Smart Wizard        
        $('#wizard').smartWizard({
            enableAllSteps: true, transitionEffect: 'slideleft',
            onLeaveStep: leaveAStepCallback
        });

        function leaveAStepCallback(obj) {
            var step_num = obj.attr('rel');
            return validateSteps(step_num);
        }
    });

    function validateSteps(step) {
        var isStepValid = true;
        // validate step 1
        if (step == 1) {
            if (validateStep1() == false) {
                isStepValid = false;
                $('#wizard').smartWizard('showMessage', '@Mui.step_validation' + step);
                $('#wizard').smartWizard('setError', { stepnum: step, iserror: true });
            } else {
                $('#wizard').smartWizard('setError', { stepnum: step, iserror: false });
            }
        }

        // validate step 2
        if (step == 2) {
            if (validateStep2() == false) {
                isStepValid = false;
                $('#wizard').smartWizard('showMessage', '@Mui.step_validation' + step);
                $('#wizard').smartWizard('setError', { stepnum: step, iserror: true });
            } else {
                $('#wizard').smartWizard('setError', { stepnum: step, iserror: false });
            }
        }

        return isStepValid;
    }

    function validateStep1() {
        var isValid = true;
        // Validate FirstName
        var f = $('#FirstName').val();
        if (!f && f.length <= 0) {
            isValid = false;
        }

        // Validate LastName
        var f = $('#LastName').val();
        if (!f && f.length <= 0) {
            isValid = false;
        }

        return isValid;
    }

    function validateStep2() {
        var isValid = true;
        // Validate DateOfBirth
        var f = $('#DateOfBirth').val();
        if (!f && f.length <= 0) {
            isValid = false;
        }
        return isValid;
    }
</script>

@model ParticipantInput
@using (Html.BeginForm())
{ 
    <div id="wizard" class="swMain">
        <ul>
            <li><a href="#step-1">
                <label class="stepNumber">
                    1</label>
                <span class="stepDesc">general a<br />
                    <small>general demographics</small> </span></a></li>
            <li><a href="#step-2">
                <label class="stepNumber">
                    2</label>
                <span class="stepDesc">general b<br />
                    <small>more general demographics</small> </span></a></li>
            <li><a href="#step-3">
                <label class="stepNumber">
                    3</label>
                <span class="stepDesc">income<br />
                    <small>income info</small> </span></a></li>
            <li><a href="#step-4">
                <label class="stepNumber">
                    4</label>
                <span class="stepDesc">medical<br />
                    <small>medical info</small> </span></a></li>
        </ul>
        <div id="step-1">
            <h2 class="StepTitle">
                general a</h2>
            <!-- step content -->
            <div class="inputarea">
                @Html.EditorFor(o => o.Salutation)
                @Html.EditorFor(o => o.FirstName)
                @Html.EditorFor(o => o.MiddleName)
                @Html.EditorFor(o => o.LastName)
                @if (User.IsInRole("admin"))
                {
                    @Html.EditorFor(o => o.Suffix)
                }
            </div>
        </div>
        <div id="step-2">
            <h2 class="StepTitle">
                general b</h2>
            <!-- step content -->
            <div class="inputarea">
                @Html.EditorFor(o => o.Ssn)
                @Html.EditorFor(o => o.DateOfBirth)
            </div>
        </div>
        <div id="step-3">
            <h2 class="StepTitle">
                income</h2>
            <!-- step content -->
        </div>
        <div id="step-4">
            <h2 class="StepTitle">
                medical</h2>
            <!-- step content -->
        </div>
    </div>
}
<br class="cbt" />
<br />
@Html.ValidationSummary(Mui.Validation)

 

 

My step validation is the sloppy part: 

function validateStep1() {
        var isValid = true;
        // Validate FirstName
        var f = $('#FirstName').val();
        if (!f && f.length <= 0) {
            isValid = false;
        }

        // Validate LastName
        var f = $('#LastName').val();
        if (!f && f.length <= 0) {
            isValid = false;
        }

        return isValid;
    }
here I'm just checking if FirstName and LastName are empty and not letting the user change steps while they are not filled in. 
What I would ideally like to do is somehow check if any of the dataannotations validation/attributes I setup have been violated. Is there any way to do that?
I know about Model.IsValid type syntaxes but I think that will validate the entire model
 (like what happens in a submit or post action) and I don't want to do that, because I may have a single model split up among several wizard pages.

Thanks
Oct 14, 2011 at 12:17 PM

If I manage to enable client validation it will meet my need 99%.

I checked project awesome documentation and tried the following, all to no avail:

webconfig I did   <appSettings>    <add key="ClientValidationEnabled" value="true" />

awesomeconfigurator I did Settings.PopupForm.ClientSideValidation = true;

in _layout.cshtml I added <script src="http://ajax.aspnetcdn.com/ajax/jquery.validate/1.9/jquery.validate.js" type="text/javascript"></script>

in cruds_.ascx I added:

<%=Html.MakePopupForm("create", successFunction: "create", height: h, fullScreen:f, clientSideValidation:true)%>

<%=Html.MakePopupForm("Edit", new[] { "id" }, successFunction: "edit", height: he, fullScreen: f, clientSideValidation: true)%>

 

in my create view I even added

<% Html.EnableClientValidation();  %> before Html.BeginForm()

and 

<script type="text/javascript">    enableClientValidation();</script> after it


What am I missing?

Oct 15, 2011 at 8:13 PM

I made this custom social security validation attribute:

 

 public class SocialSecurityAttribute : ValidationAttribute, IClientValidatable
    {
        public SocialSecurityAttribute()
        {
            ErrorMessageResourceName = "Ssn_valid";
            ErrorMessageResourceType = typeof(Mui);
        }
        
        public override bool IsValid(object value)
        {
            var number = Convert.ToString(value);

            if (String.IsNullOrEmpty(number))
                return true;

            return IsValidSsn(number);
        }

        private bool IsValidSsn(string SsnNumber)
        {
            if (Regex.IsMatch(SsnNumber, @"^(?!000)([0-6]\d{2}|7([0-6]\d|7[012]))([ -]?)(?!00)\d\d\3(?!0000)\d{4}$"))
                return true;
            else
            {
                return false;
            }
        }


        public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
        {
            yield return new ModelClientValidationRule
            {
                ErrorMessage = this.ErrorMessage,
                ValidationType = "ssnvalidation"
            };
        }
    }

in my create.cshtml I have this:
@model ParticipantInput
<script type="text/javascript">
    $(function () {
        jQuery.validator.unobtrusive.adapters.addBool('ssnvalidation');
    });
</script>
@{ Html.EnableClientValidation(); }
@{ Html.EnableUnobtrusiveJavaScript(); }
@using (Html.BeginForm())
{  
    <div class="inputarea">
        @Html.EditorFor(o => o.Salutation)
        @Html.EditorFor(o => o.FirstName)
        @Html.EditorFor(o => o.MiddleName)
        @Html.EditorFor(o => o.LastName)
        @if (User.IsInRole("admin"))
        {
        @Html.EditorFor(o => o.Suffix)
        }
        @Html.EditorFor(o => o.Ssn)
        @Html.EditorFor(o => o.DateOfBirth)
    </div>
}
I have unobtrusive and jquery validate passed in my master page
my popup form has the following code:
function update<%=o %>(data) {

            l<%=o %> = null;
            $("#<%=o %>").html(data);
            <% if(Model.ClientSideValidation){%>
                $.validator.unobtrusive.parse( $("#<%=o %> form"));
            <%} %>
            $("#<%=o %> form").ajaxForm({            
            <% if(Model.ClientSideValidation){%>
                beforeSubmit: function () { return $("#<%=o %> form").validate().valid(); },
            <%} %>
                success: OnSuccess<%=o %>
            }); 
            $("#<%=o %>").dialog('open');
            $("#<%=o %> form input:visible:first").focus();
        }

When I tab out of my social security field I get this error: Microsoft JScript runtime error: 'c.validator.methods[...]' is null or not an object

If I disable client side validation the custom attribute works fine, so I'm not sure what I'm doing wrong.

Coordinator
Oct 17, 2011 at 7:18 AM

your attribute works on server side, when  you disable client side validation, you go instantly to server and do validation there, 

when it's enabled you have to pass the client side validation first

Oct 17, 2011 at 8:03 PM
Edited Oct 17, 2011 at 8:05 PM

Ok, I have client side working (sort of)

This is your ReqAttribute.cs with a client side rule added:

namespace Omu.ACIM.WebUI.Dto
{
    public class ReqAttribute : RequiredAttribute, IClientValidatable
    {
        public ReqAttribute()
        {
            ErrorMessageResourceName = "required";
            ErrorMessageResourceType = typeof(Mui);
        }

        public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
        {
            yield return new ModelClientValidationRule
            {
                ErrorMessage = Mui.req,
                ValidationType = "req"
            };
        }
    }
I call it in my view like so: 
<script type="text/javascript">
    jQuery.validator.unobtrusive.adapters.addBool('req', 'required');
</script>
This works like so:
1) When tabbing through fields on the page, or entering invalid values custom attributes fire on client side.
The issue is that they use unobtrusive validate methods and these don't use the very nice "Err" class you setup, the red-x with the nice tooltips. 
In styles.js you have :

 $('.field-validation-error').each(function () {
                $(this).after('<div class="err" data-msg="' + $(this).html() + '"></div>').remove();
            });

            $(".err").each(function () {
                $(this).tooltip({ bodyHandler: function () { return $(this).data('msg'); } });
            });
Would I have to make additional methods like this in order to use your custom error message styles?
How would you recommend doing this?
Currently I have your error message style only firing on server side validation, and plain text unobtrusive default messages on client side validation.
Wondering what it would take to merge the two methods.
Thank you!
Coordinator
Oct 18, 2011 at 10:28 AM

just don't use client side validation and You'll have the nice err messages.

Oct 18, 2011 at 4:06 PM

i would love to not bother with the extra code(especially in views) to enable client side validation and wizard forms but my user base needs a hand-holding type of experience through larger entry forms.

anyways, the solution for anyone wanting to use the nice err messages (already present in prodinner) is to modify the onError function of the js validation unobtrusive script file with a custom one that 1) removes the default error message 2) replaces it with prodinner nice err message

i can post code if someone is interested

Dec 9, 2011 at 12:28 PM

I would Like to see the code.

Thanks