简体   繁体   中英

ASP.NET MVC with JQuery, JQuery Validate & bootstrap, validation inconveniences

According to title, my project has a main page Index to show a table of employees data provided from a Data layer (DLL using Nhibernate). Table has typical add new employee button and edit/remove links. My _Layout references all necessary bundles (CSS & JS) but for make clear I'll show links for reference:

 <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width" /> <title>Empleados</title> <link href="/Content/bootstrap.css" rel="stylesheet"/> <link href="/Content/bootstrap-datetimepicker.css" rel="stylesheet"/> <link href="/Content/site.css" rel="stylesheet"/> <script src="/Scripts/modernizr-2.5.3.js"></script> <link href="/Content/themes/base/core.css" rel="stylesheet"/> <link href="/Content/themes/base/resizable.css" rel="stylesheet"/> <link href="/Content/themes/base/selectable.css" rel="stylesheet"/> <link href="/Content/themes/base/accordion.css" rel="stylesheet"/> <link href="/Content/themes/base/autocomplete.css" rel="stylesheet"/> <link href="/Content/themes/base/button.css" rel="stylesheet"/> <link href="/Content/themes/base/dialog.css" rel="stylesheet"/> <link href="/Content/themes/base/slider.css" rel="stylesheet"/> <link href="/Content/themes/base/tabs.css" rel="stylesheet"/> <link href="/Content/themes/base/datepicker.css" rel="stylesheet"/> <link href="/Content/themes/base/progressbar.css" rel="stylesheet"/> <link href="/Content/themes/base/theme.css" rel="stylesheet"/> <script src="/Scripts/jquery-1.9.1.js"></script> <script src="/Scripts/jquery-ui-1.11.4.js"></script> <script src="/Scripts/jquery.validate-vsdoc.js"></script> <script src="/Scripts/jquery.validate.js"></script> <script src="/Scripts/jquery.validate.min.js"></script> <script src="/Scripts/jquery.validate.unobtrusive.js"></script> <script src="/Scripts/jquery.validate.unobtrusive.min.js"></script> <script src="/Scripts/jquery.unobtrusive-ajax.js"></script> <script src="/Scripts/jquery.unobtrusive-ajax.min.js"></script> <script src="/Scripts/jquery.unobtrusive-ajax.min.js.map"></script> <script src="/Scripts/moment-with-locales.js"></script> <script src="/Scripts/bootstrap.js"></script> <script src="/Scripts/bootstrap-datetimepicker.js"></script> <script src="/Scripts/bootbox.js"></script> <script src="/Scripts/site.js"></script> </head>

_Layout with bundles:

 <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width" /> <title>@ViewBag.Title</title> @Styles.Render("~/Content/css") @Scripts.Render("~/bundles/modernizr") @Styles.Render("~/Content/themes/base/css") @Scripts.Render("~/bundles/jquery") @Scripts.Render("~/bundles/jqueryui") @Scripts.Render("~/bundles/jqueryval") @Scripts.Render("~/bundles/bootstrap") <script src="@Url.Content("~/Scripts/site.js")"></script> </head> <body> @RenderBody() @RenderSection("scripts", required: false) </body> </html>

Site.js is just a Scripts I use to configurate styles and to execute ajax submit of forms, as follows:

 $(function () { /* Define mecanismo de validación de fechas y sobreescribe el mecanismo por defecto definido. Sólo en el caso de las fechas, existe un problema de formatos debido a problemas en la cultura configurada. Esta definición es también útil con páginas que realizan validaciones con base en DataAnnotations definidos en el modelo y que ClientValidationEnabled=true y UnobtrusiveJavaScriptEnabled=true. Microsoft usa los recursos de validación propios de jquery-validate.js a través del uso de atributos data-*. Cuando se ingresa la fecha, Microsoft ejecuta la validación "date" propia de JQuery.validator y genera siempre un error del tipo: "El campo <nombrecampo> debe ser una fecha". Generando con esto que el ModelState.IsValid = false. Si se quitan los DataAnnotations que validan, la fecha se quedará sin ningún tipo de validación y tampoco es posible validar mediante javascript debido a que se ejecuta obligatoriamente el form action definido y por consiguiente, todas las validaciones necesarias se ejecutan, mostrando los errores. Debido a lo anterior, es necesario reescribir la validación date para hacerla personalizada y obligar a que cuando se genere el error descrito arriba, Microsoft marca el campo con la clase input-validation-error y si existe Html.ValidationMessageFor, coloca el texto del error en el span creado y que se asocia al campo mediante el atributo data-valmsg-for='nombrecampo' y agrega el error en el ModelState. Al eliminar el texto del error y la clase que marca el campo con error, la validación del campo dependerá del código definido aquí abajo. Observar como se quita la validación estandar de fechas usada por Microsoft en _Layout.cshtml. Este código es útil para formularios cuya validación se base en DataAnnotations. Las validaciones a nivel de javascript personalizadas ya manejan sus propios mecanismos de validación y este código no es necesario (Ver Create2.cshtml).*/ $.validator.addMethod( 'date', function (value, element, params) { if (this.optional(element)) { return true; }; var result = false; try { $.datepicker.parseDate('dd/mm/yy', value); var name = element.name; $(element).closest('form').find("span[data-valmsg-for='" + element.name + "']").first().empty(); $(element).removeClass('input-validation-error'); result = true; } catch (err) { bootbox.alert('Se presentó error date validator: ' + err.message); } return result; }, '' ); // cualquier resumen de validación (validation summary) se encapsula con alert y alert-danger $('.validation-summary-errors').each(function () { $(this).addClass('alert'); $(this).addClass('alert-danger'); }); // Verifica cada form-group para ver si hay errores $('form').each(function () { $(this).find('div.form-group').each(function () { if ($(this).find('span.field-validation-error').length > 0) { $(this).addClass('has-error'); } }); }); }); function configuracion() { var formData = $('form'); formData.submit(function () { if ($(this).valid()) { // Actualiza los campos de validación al hacer submit en la forma $(this).find('div.form-group').each(function () { if ($(this).find('span.field-validation-error').length == 0) { $(this).removeClass('has-error'); $(this).addClass('has-success'); } }); bootbox.confirm('Está seguro?', function (result) { if (result === true) { $.ajax({ url: formData.attr('action'), type: formData.attr('method'), data: formData.serialize() }) .success(function (result) { window.location.href = "/Home/Index"; }) .error(function (request, status, error) { bootbox.dialog({ title: 'Error', message: '<div class="row">' + ' <div class="col-md-12">' + ' <textarea rows="50" cols="50" style="overflow-y: scroll; height: 100px; width: 100%; resize: none;">' + 'Se presentó error: ' + request.responseText + ' </textarea>' + ' </div>' + '</div>', buttons: { success: { label: 'Aceptar', className: 'btn btn-error' } } }); }); } }); } else { // Actualiza los campos de validación si no hay datos validos $(this).find('div.form-group').each(function () { if ($(this).find('span.field-validation-error').length > 0) { $(this).removeClass('has-success'); $(this).addClass('has-error'); } }); $('.validation-summary-errors').each(function () { if ($(this).hasClass('alert-danger') == false) { $(this).addClass('alert'); $(this).addClass('alert-danger'); } }); } return false; }); $('.date').each(function () { $(this).datetimepicker({ locale: 'es', format: 'DD/MM/YYYY', minDate: new Date('01/01/1920'), maxDate: new Date() }); $(this).tooltip({ placement: 'top', trigger: 'focus', title: 'dd/mm/yyyy' }); // Esto solo aplica cuando hay validaciones a nivel de DataAnnotations para quitar la validación // estandar de fechas: $(this).find('input').first().removeAttr('data-val-date'); }); // Seleccionar por defecto el primer item de los ComboBox y si existe opción por defecto --- Seleccione --- // entonces deshabilitar la posibilidad de que el usuario pueda seleccionarlo después de que haya seleccionado // otra opción. $('select').each(function () { var element = $(this).find('option').first(); element.attr('selected', true); if (element.html() === "--- Seleccione ---") element.attr('disabled', true); }); $('.form-group input').addClass('form-control'); $('.form-group select').addClass('form-control'); } var page = function () { alert("page"); //Actualiza el validador $.validator.setDefaults({ highlight: function (element) { $(element).closest('.form-group').addClass('has-error'); $(element).closest('.form-group').removeClass('has-success'); }, unhighlight: function (element) { $(element).closest('.form-group').removeClass('has-error'); $(element).closest('.form-group').addClass('has-success'); } }); }();

Index.cshtml is:

 @using Datos.Entities @model IEnumerable<Empleado> @{ ViewBag.Title = "Empleados"; } <div class="container"> <div class="panel panel-primary"> <div class="panel-heading"><h2>Empleados</h2></div> <div class="panel-body"> <p> @Html.ActionLink("Crear Empleado", "Create", null, new { @class = "btn btn-info glyphicon-plus modal-link" }) </p> <table class="table table-striped table-bordered table-hover table-condensed"> <thead> <tr class="info"> <th> @Html.DisplayNameFor(model => model.Nombres) </th> <th> @Html.DisplayNameFor(model => model.Apellidos) </th> <th> @Html.DisplayNameFor(model => model.FechaNacimiento) </th> <th> @Html.DisplayNameFor(model => model.TipoDocumento) </th> <th> @Html.DisplayNameFor(model => model.NumeroDocumento) </th> <th></th> <th></th> </tr> </thead> <tbody> @foreach (var item in Model) { <tr> <td> @Html.DisplayFor(modelItem => item.Nombres) </td> <td> @Html.DisplayFor(modelItem => item.Apellidos) </td> <td> @Html.DisplayFor(modelItem => item.FechaNacimiento) </td> <td> @Html.DisplayFor(modelItem => item.TipoDocumentoDTO.Descripcion) </td> <td> @Html.DisplayFor(modelItem => item.NumeroDocumento) </td> <td> @Html.ActionLink(" Editar", "Edit", new { id = item.Id }, new { @class = "btn glyphicon glyphicon-edit modal-link" }) </td> <td> @Html.ActionLink(" Eliminar", "Delete", new { id = item.Id }, new { @class = "btn glyphicon glyphicon-trash delete-link" }) </td> </tr> } </tbody> </table> </div> </div> </div> <div id="modal-container" class="modal fade" tabindex="-1" role="dialog"> </div> <script type="text/javascript"> $(function () { var container = $('#modal-container'); container.on('show.bs.modal', function () { configuracion(); }); var dialog = $('.modal-dialog'); $('.modal-link').click(function (e) { e.preventDefault(); var url = $(this).attr('href'); var request = $.get(url); request.success(function (result) { container.html(result); // backdrop: modo de operación del dialogo cuando se da click fuera de sus limites para evitar/permitir cerrar. // keyboard: modo de operación del dialogo cuando se presiona ESC para evitar/permitir cerrar. container.modal({ backdrop: 'static', keyboard: false }); dialog.draggable({ handle: '.modal-header' }); }); request.fail(function (jqXHR, textStatus, errorThrown) { bootbox.alert(textStatus + ' : ' + errorThrown); }); return false; }); $('.delete-link').click(function (e) { e.preventDefault(); var link = $(this); bootbox.confirm('Está seguro?', function (result) { if (result === true) { var url = link.attr('href'); $.ajax({ url: url, type: 'DELETE', success: function () { window.location.href = '/Home/Index'; }, error: function (jqXHR, textStatus, errorThrown) { bootbox.alert(textStatus + ' : ' + errorThrown); } }); } }); }); }); </script>

Create.cshtml is:

 @using Datos.Entities @model Empleado <div class="modal-dialog modal-md"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button> <h4 class="modal-title">Empleado</h4> </div> <div class="modal-body"> @using (Ajax.BeginForm("Create", "Home", new AjaxOptions() { HttpMethod = "post", OnSuccess = "configuracion" }, new { @class = "form-horizontal", id = "frmData", role = "form" })) { @Html.AntiForgeryToken() @Html.HiddenFor(model => model.Id) <div class="form-group"> @Html.LabelFor(model => model.Nombres, new { @class = "col-md-4 control-label" }) <div class="col-md-8"> @Html.EditorFor(model => model.Nombres) @Html.ValidationMessageFor(model => model.Nombres) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.Apellidos, new { @class = "col-md-4 control-label" }) <div class="col-md-8"> @Html.EditorFor(model => model.Apellidos) @Html.ValidationMessageFor(model => model.Apellidos) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.FechaNacimiento, new { @class = "col-md-4 control-label" }) <div class="col-md-8"> <div class="input-group date"> @Html.TextBoxFor(model => model.FechaNacimiento) <span class="input-group-addon"> <span class="glyphicon glyphicon-calendar"></span> </span> </div> @Html.ValidationMessageFor(model => model.FechaNacimiento) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.TipoDocumento, new { @class = "col-md-4 control-label" }) <div class="col-md-8"> @Html.DropDownListFor(model => model.TipoDocumento, new SelectList(ViewBag.TiposDocumento, "Id", "Descripcion", 0), "--- Seleccione ---") @Html.ValidationMessageFor(model => model.TipoDocumento) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.NumeroDocumento, new { @class = "col-md-4 control-label" }) <div class="col-md-8"> @Html.EditorFor(model => model.NumeroDocumento) @Html.ValidationMessageFor(model => model.NumeroDocumento) </div> </div> <div class="modal-footer"> <button type="button" class="btn btn-default" data-dismiss="modal" aria-hidden="true">Cerrar</button> <button type="submit" class="btn btn-primary" id="submit">Grabar</button> </div> } </div> </div> </div>

And last, Model Empleado is:

public class Empleado
{
    [DefaultValue(0)]
    public virtual int Id { get; set; }

    [Display(Name="Nombres", Prompt="Nombres")]
    [Required]
    public virtual string Nombres { get; set; }

    [Display(Name="Apellidos", Prompt="Apellidos")]
    [Required]
    public virtual string Apellidos { get; set; }

    [Display(Name="Fecha Nacimiento")]
    [DisplayFormat(ApplyFormatInEditMode=true,DataFormatString="{0:dd/MM/yyyy}")]
    [Required]
    public virtual DateTime FechaNacimiento { get; set; }

    [Display(Name = "Tipo de Documento")]
    [Required]
    [Range(1, 3, ErrorMessage = "El tipo de documento no es válido")]
    [DefaultValue(0)]
    public virtual int TipoDocumento { get; set; }

    [Display(Name = "Número de Documento")]
    [Required]
    public virtual string NumeroDocumento { get; set; }

    public virtual TipoDocumento TipoDocumentoDTO { get; set; }

    public Empleado()
    {
        FechaNacimiento = DateTime.Now;
        TipoDocumentoDTO = new TipoDocumento();
    }
}

Web.config has:

<appSettings>
    <add key="ClientValidationEnabled" value="true"/> 
    <add key="UnobtrusiveJavaScriptEnabled" value="true"/> 
</appSettings>

Basically index is shown with data, when I click on Crear Empleado link, a bootstrap modal dialog is loaded into modal-container by a call to the Controller returning a PartialView (Create.cshtml) and shown. Everything is working fine, boostrap styles works perfect, but when I click on Save (Grabar), form.submit is executed (scripts in site.js):

function configuracion() {
        var formData = $('form');

        formData.submit(function () {
            if ($(this).valid()) {   

DataAnnotations validations doesn't work $(this).valid() are always true and not errors occurs!. I tried doing this:

 <div id="modal-container" class="modal fade" tabindex="-1" role="dialog"> Html.Partial("~\\views\\home\\create.cshtml", new Empleado()) </div>

In this way it seems data validations work but I need to work with ajax.submit. Please need help!!!

It happens because you are calling a view via ajax. The unobtrusive validation parse the page when it loads but not include the elements when you call forms after content loaded at first time.

You need to call the $.validator.unobtrusive.parse method exposed in the validator object.

when you call to the partial view is completed and you show the modal, just call

$.validator.unobtrusive.parse("#modal-container");

after that your validations and data annotations will work correctly.

If you need to parse all your forms just do this:

$.validator.unobtrusive.parse("form");

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM