viernes, 10 de marzo de 2017

Ventana popup en ASP.NET MVC. Formularios con envío AJAX a Controller Web Api y validación en servidor.

Introducción:

¡Cuánto tiempo sin postear un caso!

Y es que el servicio de urgencias últimamente está más apacible desde que nos hemos trasladado a los nuevos proyectos, que son más de desarrollo y menos de técnica de sistemas.

Por eso hoy, más que arreglar síntomas de enfermos, nos dedicaremos a dar a luz un formulario en popup para una aplicación ASP.NET MVC que es abierto con jquery y envía los datos a un controlador web api con AJAX, para después mostrar una validación que viene del servidor si hubo problemas o mostrar otro popup indicando que se ha guardado todo con éxito si fue todo bien.

Aunque lo explicado aquí es muy ad hoc, siempre podrá el lector entresacar algo, o al menos alguna idea útil, aunque suponemos que este ejemplo es perfectamente aplicable a cualquier desarrollo que se haga con esta tecnología.

Nos disculparemos desde Doctor SharePoint diciendo que aquí somos así, ponemos casos que nos suceden, no somos un blog de divulgación ni de pruebas piloto, por ni mencionar que este caso ni siquiera es de SharePoint :-o.

Materia:

Comencemos por JavaScript, son dos archivos, uno para inicializar el popup o los popups que necesitemos mostrar desde una vista y otro que contiene el código que muestra el popup (nuestra caja negra)

Son necesarios los siguientes elementos en la vista origen o hay que tener en cuenta los siguientes aspectos:

1. Un div para la caja de diálogo con un id.
2. Un input de tipo button con un id. Podría valer con otro control, pero no está garantizado que funcione.
3. Un anchor con un id, debe llevar un href apuntando a la url de la vista de formulario que quermos mostrar.
4. Un div para mostrar una caja de resultado con un Id, el mensaje de éxito debe ir ya dentro.
5. Hay que inicializar las variables de configuración y llamar al método crearPopUp como se indica más abajo en el código JavaScript con los Id's comentados antes.
6. En un array hay que poner los nombres de los campos que se pretende pasar al servidor, sus nombres han de ser iguales a los del modelo de la vista popup.
7. En caso de querer poner más de un popup en una vista, todos los elementos deben ser únicos para cada popup.

Ejemplo de código que hay que poner en la vista desde la que se abrirá el popup, explicado antes:

<div id="dialog" style="overflow:hidden"></div>
        <input type="button" id="btnPopUp" value="Nueva entidad" class="btn btn-default" />
        <a id="urlPopUp" href="@Url.Action("CreatePopUp", "Entidades", null)" style="display:none"></a>
        <div id="dialogAlert" style="display:none">
            <p>
                Se ha enviado con éxito
            </p>
        </div>

Necesitamos que sea así pues nos obliga la estructura de multiidioma con recursos literales, que aquí no se muestran, si no necesitamos utilizar recursos de servidor aún podríamos minimizarlo más, pero no es el caso.

Inicialización de las variables en el código js para cada formulario:

//Id del div para encajar popup.
var dialogNameId = "dialog";

//Id del div para mostrar un diálogo de confirmación.
var dialogAlertId = "dialogAlert";

//Id del anchor que contiene la url a la vista que queremos mostrar.
var viewUrlAnchorId = "urlPopUp";

//Id del botón que generará la acción de mostrar el popup.
var popUpActionTriggerId = "btnPopUp"

//Url relativa hacia el controlador web api que ejecutará la validación y las inserciones o lecturas cuando pulsemos en el submit dentro del popup.
var controllerUrl = "/api/Controlador/CreatePopUp";

//Array con los nombres de los campos, deben ser iguales al modelo de la vista que se muestra en el //popup. 
//Estas columnas serán montadas como un string de JSON para enviar al controlador web api
var modelColumns = ["UserName", "NIF", "Email", "CodigoEntidad"];

crearPopUp(dialogNameId, dialogAlertId, viewUrlAnchorId, popUpActionTriggerId, controllerUrl, modelColumns);

Segundo archivo, el que muestra el popup, este código no debe tocarse, tan sólo hay que inicializar lo anterior como se ha especificado y llamar a esta función:

function crearPopUp(dialogNameId, dialogAlertId, viewUrlAnchorId, popUpActionTriggerId, controllerUrl, modelColumns, dialogTitle) {
    var dialog = $("#" + dialogNameId);
    var dialogAlert = $('#' + dialogAlert);
    var url;
//evento del botón que acciona el popup
    $("#" + popUpActionTriggerId).click(function (e) {
        url = $('#' + viewUrlAnchorId).attr('href');
        e.preventDefault();
        dialog.dialog('open');
    });
    dialog.dialog({
        autoOpen: false,
        width: 700,
        resizable: false,
        title: $("#" + popUpActionTriggerId).val(),
        modal: true,
        dialogClass: 'ui-dialog-osx',
        open: function (event, ui) {
            $(this).load(url);
            $('button[type="button"]').each(function () {
                $(this).addClass('btn btn-default');
            });
        },
        close: function (event, ui) {
            //$(this).find('form')[0].reset();
        },
        buttons: {
            OK: function () {
//llamada con ajax para controlar que el controlador no nos redirija a otra vista.
                $.ajax({
                    type: "POST",
                    url: document.location.origin + controllerUrl,
                    data: initializeModel(),// JSON.stringify(model),
                    contentType: "application/json",
                    success: function (result) {
//cerramos el popup y mostramos un popup de envío exitoso
                        dialog.dialog("close");
                        dialogAlert.dialog('open');
                    },
                    error: function (result) {
//recojemos el ModelState con los errores de validación
                        for (var name in result.responseJSON.ModelState) {
                            var value = result.responseJSON.ModelState[name];
                            $('#' + name).removeClass('form-control text-box single-line');
                            $('#' + name).addClass('input-validation-error form-control text-box single-line');
                            $('span[data-valmsg-for="' + name + '"]').removeClass('field-validation-valid text-danger');
                            $('span[data-valmsg-for="' + name + '"]').addClass('field-validation-error text-danger');
                            $('span[data-valmsg-for="' + name + '"]').text(value);
                        }
                    }
                }).done(function (res) {
                    console.log('res', res);
                });
            },
            Cancel: function () {
                dialog.dialog("close");
            }
        }
    });
    dialogAlert.dialog({
        modal: true,
        autoOpen: false,
        open: function () {
            $(".ui-dialog-title").html("Alert"); //título de la ventana de popup éxito
        },
        buttons: {
            Ok: function () {
                dialog.dialog("close");
            }
        }
    });
}

//montaje del JSON a partir del array de columnas
function initializeModel() {
    var str = "{";
    for (i = 0; i < modelColumns.length; i++) {
        str += '"' + modelColumns[i] + '":"' + $('#' + modelColumns[i]).val() + '",';
    }
    str = str.substring(0, str.length - 1);
    str += "}";
    return str;
}

Vista del popup, NÓTESE que no lleva botón submit, el js ya se encarga de poner los botones:

@model Entity
 using (Html.BeginForm())
    {
        @Html.AntiForgeryToken()
        <div class="form-horizontal">
            <hr />
            @Html.ValidationSummary(true, "", new { @class = "text-danger" })
            <div class="form-group">
                <div class="control-label col-md-2">
                    <b>Nombre</b>
                </div>
                <div class="col-md-10">
                    @Html.EditorFor(model => model.UserName, new { htmlAttributes = new { @class = "form-control" } })
                    @Html.ValidationMessage("UserName", new { @class = "text-danger" })
                </div>
            </div>
            <div class="form-group">
                <div class="control-label col-md-2">
                    <b>NIF</b>
                </div>
                <div class="col-md-10">
                    @Html.EditorFor(model => model.NIF, new { htmlAttributes = new { @class = "form-control" } })
                    @Html.ValidationMessage("NIF", new { @class = "text-danger" })
                </div>
            </div>
            <div class="form-group">
                <div class="control-label col-md-2">
                    <b>Email</b>
                </div>
                <div class="col-md-10">
                    @Html.EditorFor(model => model.Email, new { htmlAttributes = new { @class = "form-control" } })
                    @Html.ValidationMessage("Email", new { @class = "text-danger" })
                </div>
            </div>
            <div class="form-group">
                <div class="control-label col-md-2">
                    <b>Código Entidad</b>
                </div>
                <div class="col-md-10">
                    @Html.EditorFor(model => model.CodigoEntidad, new { htmlAttributes = new { @class = "form-control" } })
                    @Html.ValidationMessage("CodigoEntidad", new { @class = "text-danger" })
                </div>
            </div>
        </div>
    }


Vista del controlador Web Api EntidadesController:

 [HttpPost] //es un post
 [ValidateModel] //validación de servidor, uso de ModelState
public HttpResponseMessage CreatePopUp([FromBody] Entity entity)
        {
            ValidarMandatario(entity); //código para validar
            if (ModelState.IsValid)
            {
                //guardar datos o hacer otras cosas y devolver response http OK
                        return new HttpResponseMessage(HttpStatusCode.OK);
            }
//devolver http rseponse error con el ModelState, que será parseado a JSON
            return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
        }

No hay comentarios: