Using Wizard - how do you validate a form when step includes partial view.

1 Answer 139 Views
Wizard
Joshua
Top achievements
Rank 1
Joshua asked on 19 Feb 2024, 09:28 PM

I'm pretty much at the point I give up.  All I want to do is use the Wizard to load partial views for each step, and all I want to do is prevent the next step if the form can't be validated.  I've tried nearly everything for 3 days and I can't seem to find an answer.  I've tried jquery..validate and it just goes no where.

 

I just have a real simple setup, Business.cshtml which contains the wizard


@using Kendo.Mvc.UI
@model Reverberate.BLL.Model.Form.Application.BusinessOnboardingModel

@{
    ViewBag.Title = "Business Onboarding";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<style>
    .wide {
        width: 95%;
    }

    .field-validation-error {
        color: red;
    }
</style>



    <!-- Wizard -->
    @(Html.Kendo().Wizard()
        .Name("wizard")
        .Events(ev => ev.Select("onSelect"))
        .Events(ev => ev.Done("onDone"))
        .LoadOnDemand(true)
        .ReloadOnSelect(false)
        .Steps(s =>
        {
            s.Add<Reverberate.BLL.Model.Form.Application.BusinessOnboardingModel>()
            .Title("Business Profile")
            .ContentUrl(Url.Action("_BusinessProfile", "Onboard"))
            .Buttons(b =>
            {
                b.Next().Text("Next");
            });

            s.Add<Reverberate.BLL.Model.Form.Application.BusinessOnboardingModel>()
            .Title("Business Financial")
            .ContentUrl(Url.Action("_BusinessFinancial", "Onboard", Model))
            .Buttons(b =>
            {
                b.Previous().Text("Back");
                b.Next().Text("Next");
            });
            s.Add<Reverberate.BLL.Model.Form.Application.BusinessOnboardingModel>()
            .Title("Business Address")
            .ContentUrl(Url.Action("_BusinessAddress", "Onboard", Model))
            .Buttons(b =>
            {
                b.Previous().Text("Back");
                b.Done().Text("Submit");
            });
        })
    )

<script type="text/javascript">
    var dataPartial1;
    var dataPartial2;
    var currentStep;

    function onSelect(e) {

       
        var form, contentUrl;

        if (e.step.options.index < currentStep) {
            e.preventDefault();
        }
        else {

            if (e.step.options.index == 1) {
                form = $('#frmPartialBusinessProfilefrmPartialBusinessProfile');
                dataPartial1 = form.serialize();
                contentUrl = '@Url.Action("_BusinessFinancial", "Onboard",Model)';
            }
            else if (e.step.options.index == 2) {
                form = $('#frmPartial2');
                dataLesions = form.serialize();
                contentUrl = '@Url.Action("_BusinessAddress", "Onboard",Model)';
            }
            if (!form.valid()) {
                alert('not valid');
                e.preventDefault();
            }
            else {
                alert('valid');
                e.step.options.contentUrl = contentUrl;
            }
            alert('done');
        }


        currentStep = e.step.options.index;
    }

    function onNextStep(e) {
        if (e.step.options.index == 2) {
            openDoc();
        }
    }

    function onDone(e) {

        var form = $('#frmMain');

        if (form.valid()) {
            form.submit();
        }

    }

    var onAddMainSuccess = function (result) {

        if (result.error) {
            alert(result.error);
        }
    };


</script>
<script src="https://cdn.jsdelivr.net/jquery.validation/1.13.1/jquery.validate.min.js"></script>

 

and a partial view called _BusinessProfile


@using Kendo.Mvc.UI
@model Reverberate.BLL.Model.Form.Application.BusinessOnboardingModel

<form id="frmPartialBusinessProfile" name="frmPartialBusinessProfile">
    <div style="width: 45%; float: left; border: 1px solid black" id="BusinessInfoEntry">
        <h3>Business Profile</h3>
        <fieldset>
            <legend></legend>
            <ol>
                <li>
                    @Html.LabelFor(m => m.BusinessProfileModel.BusinessName)
                    <br />
                    @Html.TextBoxFor(m => m.BusinessProfileModel.BusinessName, new { maxlength = 200, @class = "wide" })
                    @Html.ValidationMessageFor(m => m.BusinessProfileModel.BusinessName)
                </li>
                <li>
                    @Html.LabelFor(m => m.BusinessProfileModel.FederalTaxId)
                    <br />
                    @Html.TextBoxFor(m => m.BusinessProfileModel.FederalTaxId, new { maxlength = 20 })
                    @Html.ValidationMessageFor(m => m.BusinessProfileModel.FederalTaxId)
                </li>
                <li>
                    @Html.LabelFor(m => m.BusinessProfileModel.BusinessStartDate)
                    <br />
                    @Html.TextBoxFor(m => m.BusinessProfileModel.BusinessStartDate, new { @type = "date", @class = "form-control datepicker", @Value = Model == null || Model.BusinessProfileModel.BusinessStartDate == null ? null : Model.BusinessProfileModel.BusinessStartDate.ToString("yyyy-MM-dd") })
                    @Html.ValidationMessageFor(m => m.BusinessProfileModel.BusinessStartDate)
                </li>
                <li>
                    @Html.LabelFor(m => m.BusinessProfileModel.Industry)
                    <br />
                    @Html.TextBoxFor(m => m.BusinessProfileModel.Industry, new { maxlength = 200, @class = "wide" })
                    @Html.ValidationMessageFor(m => m.BusinessProfileModel.Industry)
                </li>
                <li>
                    @Html.LabelFor(m => m.BusinessProfileModel.Sector)
                    <br />
                    @Html.TextBoxFor(m => m.BusinessProfileModel.Sector, new { maxlength = 200, @class = "wide" })
                    @Html.ValidationMessageFor(m => m.BusinessProfileModel.Sector)
                </li>
                <li>
                    @Html.LabelFor(m => m.BusinessProfileModel.StateOfFormation)
                    <br />
                    @Html.DropDownListFor(m => m.BusinessProfileModel.StateOfFormation, new SelectList(ViewBag.States, "Value", "Text"), "Select State", htmlAttributes: new { @class = "form-control", id = "StateOfFormationList" })
                    @Html.ValidationMessageFor(m => m.BusinessProfileModel.StateOfFormation)
                </li>
                <li>
                    @Html.LabelFor(m => m.BusinessProfileModel.EmployeeCount)
                    <br />
                    @Html.TextBoxFor(m => m.BusinessProfileModel.EmployeeCount, new { @type = "number", @class = "wide" })
                    @Html.ValidationMessageFor(m => m.BusinessProfileModel.EmployeeCount)
                </li>
                <li>
                    @Html.LabelFor(m => m.BusinessProfileModel.BusinessStructure)
                    <br />
                    @Html.DropDownListFor(m => m.BusinessProfileModel.BusinessStructure, new SelectList(ViewBag.BusinessTypeList, "Value", "Text"), "Select Business Type", htmlAttributes: new { @class = "form-control", id = "StateOfFormationList" })
                    @Html.ValidationMessageFor(m => m.BusinessProfileModel.BusinessStructure)
                </li>
            </ol>
        </fieldset>
    </div>
</form>

 

{

    public class BusinessOnboardingModel
    {
        public BusinessProfileModel BusinessProfileModel { get; set; }

        public BusinessFinancialModel BusinessFinancialModel { get; set; }
    }
}

 

and here is the BusinessProfileModel

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Reverberate.BLL.Model.Form.Application.BusinessOnboarding
{
    public class BusinessProfileModel
    {
        public BusinessProfileModel() { }

        [Required(ErrorMessage = "The business name is required.")]
        [DisplayName("*Business Name")]
        [StringLength(250)]
        public string BusinessName { get; set; }

        [DisplayName("*Federal Tax Id / SSN")]
        [StringLength(15, ErrorMessage = "Please enter a valid federal tax id or SSN")]
        [Required(ErrorMessage = "Please enter the federal tax id or SSN")]
        public string FederalTaxId { get; set; }

       

        [DisplayName("*Business Start Date")]
        [Required(ErrorMessage = "Please enter the business start date")]
        public DateTime BusinessStartDate { get; set; }

        [Required(ErrorMessage = "The industry is required.")]
        [DisplayName("*Industry")]
        [StringLength(500)]
        public string Industry { get; set; }

        [Required(ErrorMessage = "The sector is required.")]
        [DisplayName("*Sector")]
        [StringLength(500)]
        public string Sector { get; set; }


        [DisplayName("*State of Formation")]
        [Validation.InputValidation.ValidState(ErrorMessage = "This does not appear to be a valid US State.")]
        [Required(ErrorMessage = "The State of Formation is required.")]

        public string StateOfFormation { get; set; }
        

        [DisplayName("*No of Employees")]
        [Validation.InputValidation.ValidState(ErrorMessage = "Number of Employees.")]
        [Required(ErrorMessage = "Please Supply the number of employees.")]
        public int EmployeeCount { get; set; }


        [Validation.InputValidation.ValidBusinessType(ErrorMessage = "Please enter a valid business structure")]
        [Required(ErrorMessage = "The business structure required.")]
        [DisplayName("*Business Structure")]
        public string BusinessStructure { get; set; }
    }
}

Joshua
Top achievements
Rank 1
commented on 20 Feb 2024, 01:47 AM

After an entire day, i figure out the issue, basically Jquery cannot find a form in a form and I have my jquery scripts for validation out of order.
Anton Mironov
Telerik team
commented on 22 Feb 2024, 10:22 AM

Hi Joshua,

Thank you for sharing these details. I am glad to hear that the desired result is now achieved.

Best Regards,
Anton Mironov

1 Answer, 1 is accepted

Sort by
0
Anton Mironov
Telerik team
answered on 22 Feb 2024, 10:21 AM

Hello Joshua,

Thank you for the code snippets and the details provided.

In order to achieve the desired result, I would recommend using the "Select" Event of the Wizard.

Here is an example:

@(Html.Kendo().Wizard()
    .Name("wizard")
    .Events(events => events
        .Select("onSelectStep") 
    )
    .Steps(steps =>
    {
        steps.Add().Label("Step 1").Content(@<text>@Html.Partial("_PartialViewStep1")</text>);
        steps.Add().Label("Step 2").Content(@<text>@Html.Partial("_PartialViewStep2")</text>);
    })
)
In the Event handler, use the form of the current step as a validator. If the validation fails, use the "preventDefault" method.

I hope this information helps. If further assistance is needed - send me a runnable sample project and I will try my best to add the desired functionallity.

Kind Regards,
Anton Mironov
Progress Telerik

Stay tuned by visiting our public roadmap and feedback portal pages. If you're new to the Telerik family, be sure to check out our getting started resources, as well as the only REPL playground for creating, saving, running, and sharing server-side code.

Joshua
Top achievements
Rank 1
commented on 22 Feb 2024, 08:49 PM

Thanks Anton I'll give that a try.    Hopefully someone on your team will see this other question I have.  I'm not entirely sure how to update my model and send it to the next step or at least save it.

 

https://www.telerik.com/forums/asp-net-mvc-wizard-ajax-partial-views-and-how-to-save-the-model

Joshua
Top achievements
Rank 1
commented on 22 Feb 2024, 10:46 PM | edited

Anton - I have updated my question here https://www.telerik.com/forums/asp-net-mvc-wizard-ajax-partial-views-and-how-to-save-the-model and included the sample project.   I realize I may have caused confusion creating two different questions.   


Also in the sample solution I tried your idea above and I'm getting compile issues.Severity Code Description Project File Line Suppression State
Error CS1660 Cannot convert lambda expression to type 'string' because it is not a delegate type 6_Views_Home_Index2.cshtml M:\Code\Development\WizardPartialExample\WizardPartialExample\WizardPartialExample\Views\Home\Index2.cshtml 19 Active`

Anton Mironov
Telerik team
commented on 27 Feb 2024, 01:15 PM

Hi Joshua,

Thank you for sharing the sample project.

I made a debugging session with the sample and found out the following:

  • When the Wizard is first finishing its steps, the validation logic then starts to work when starting from the beginning:

Furthermore, the working scenario above only happens when the executing gets in the if with "(!form.valid())" condition. This is happening only when first you finish all the steps of the Wizard:

    function onSelect(e) {
        var form, contentUrl;
        if (e.step.options.index < currentStep) {
            console.log("test")
        }
        else {
            if (e.step.options.index == 1) {
                form = $('#frmPartialBusinessProfile');
                dataPartial1 = form.serialize();
                contentUrl = '@Url.Action("_BusinessFinancial", "Home",Model)';
            }
            if (!form.valid()) {
                console.log("invalid")
                e.preventDefault();
            }
            else {
                e.step.options.contentUrl = contentUrl;
            }
        }
        currentStep = e.step.options.index;
    }

So, the issue is caused by the custom logic.

Feel free to comment all the custom logic, add a simple validation rule, and debug step by step.

Best Regards,
Anton Mironov

 

 

 

Joshua
Top achievements
Rank 1
commented on 28 Feb 2024, 01:15 AM | edited

Anton, thanks for the follow up, but I'll be honest, I'm not following your answer.   If I simply start the site in debug mode and click submit, I get the form was valid without any input on the first field.  Using your exact script above.

In fact, I can do this multiple times and validation never starts.

However, if I just start the solution in debug mode, and then just reload the page, validation will begin to work.

I don't under why on the first intiial load the page the validation doesn't occur

Anton Mironov
Telerik team
commented on 29 Feb 2024, 03:55 PM

Hi Joshua,

Yes, I totally agree with you. There seems to be a custom logic that is omitting the validation. 

The validation is working after refresh, so feel free to check the custom logic in the sample project.

Let me know if when removing all the custom JavScript the issue persists.

Kind Regards,
Anton Mironov

Joshua
Top achievements
Rank 1
commented on 01 Mar 2024, 12:40 AM | edited

We're not on the same page, what do you mean by custom logic? 

Does Telerik have a standard way to handle loading partial views and validating in the wizard?  I would think that would be a top use case considering you get data annotation validation and  you can have complex partial views, lengthy number of steps, and even dynamic content loaded without stuffing everything inside of the wizard.   I was actually kind of curious why this isn't an example on the site.

There is nothing turning off the validation, so when you say custom logic, I provided the full 100% working example to you, which you have duplicated.  My first thought is the wizard isn't fully loaded or maybe it's the order of the javascript and the resource not being available since when you initially load the page there is a pause loading the wizard, however once you refresh the page all resources are immediately available.

I don't know or understand what is special about the wizard that causes this issue. I know that if I just load the partial view outside of the wizard and use the same javascript with an input button 100% of the time, the validation will work. I know if I visit another page that has a Kendo control and then visit that wizard page (initially), just like in our test, it will 100% work. What I don't understand is why, when no kendo control loads, validation is paused.  The partial views are standard, the model is a standard class with standard data annotations, the jquery valid is the standard jquery validation, and the javascript pretty much stands on it's face.  This situation only happens in the partial view in inside of the wizard.  Does Telerik support partial view validation inside of the wizard?  If not, is that what you mean by custom logic?  With 100% honesty, I can admit I might be overlooking something very dumb, but I don't see it.

That's why I posted it this to the forum.

 

 

Joshua
Top achievements
Rank 1
commented on 01 Mar 2024, 03:22 AM

Okay, I just figured it out.  The reason is jquery.validate.min.js and jquery.validate.unobtrusive.min.js need to be placed on the PartialView _BusinessProfile.cshtml .  The wizard now works as expected however page load time is drastically decreased.

bundles.Add(new ScriptBundle("~/bundles/jquerywizardvalidation").Include(
            "~/Scripts/jquery.validate.min.js",
            "~/Scripts/jquery.validate.unobtrusive.min.js"
            ));

@using Kendo.Mvc.UI
@model WizardPartialExample.Models.WizardComplexModel

@using (Html.BeginForm(null, null, FormMethod.Post, new { @id = "frmPartialBusinessProfile", @name = "frmPartialBusinessProfile"}))
{
    <div style="width: 45%; float: left; border: 1px solid black" id="BusinessInfoEntry">
        <h3>Business Profile</h3>
        <fieldset>
            <legend></legend>
            <ol>
                <li>
                    @Html.LabelFor(m => m.BusinessModel.BusinessName)
                    <br />
                    @Html.TextBoxFor(m => m.BusinessModel.BusinessName, new { maxlength = 200, @class = "wide" })
                    @Html.ValidationMessageFor(m => m.BusinessModel.BusinessName)
                </li>
                <li>
                    @Html.LabelFor(m => m.BusinessModel.BusinessStructure)
                    <br />
                    @Html.TextBoxFor(m => m.BusinessModel.BusinessStructure, new { maxlength = 20 })
                    @Html.ValidationMessageFor(m => m.BusinessModel.BusinessStructure)
                </li>
            </ol>
        </fieldset>
    </div>
}
@Scripts.Render("~/bundles/jquerywizardvalidation")


Tags
Wizard
Asked by
Joshua
Top achievements
Rank 1
Answers by
Anton Mironov
Telerik team
Share this question
or