Skip to main content

Using jquery templates for adding form element dynamically in ASP.NET MVC

Hello friends. Today I wanna tell about some obstucles with validation which I've found when I had working with form.
At first I've created two models for binding

using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using DataAnnotationsExtensions;

namespace DynamicForm.Models
{
    public class FormViewModel
    {
        public int? Id { get; set; }

        [Required]
        [StringLength(25, ErrorMessage = "Max firstname length is 25 symbols.")]
        [DisplayName("First name")]
        public string FirstName { get; set; }

        [Required]
        [StringLength(25, ErrorMessage = "Max lastname length is 25 symbols.")]
        [DisplayName("Last name")]
        public string LastName { get; set; }

        [Required]
        [Email(ErrorMessage = "Provide correct email address, please.")]
        [DisplayName("Email")]
        public string Email { get; set; }

        [Range(16, 150, ErrorMessage = "Age should be between 16 and 150.")]
        [DisplayName("Age")]
        public int? Age { get; set; }

        public IList Discounts { get; set; }
    }
}

and DiscountCode model

using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using DataAnnotationsExtensions;

namespace DynamicForm.Models
{
    public class DiscountCode
    {
        [Required]
        [DisplayName("Code name")]
        [StringLength(10, ErrorMessage = "Max name length is 10 symbols.")]
        public string Code { get; set; }

        [Required]
        [DisplayName("Code discount")]
        [Integer(ErrorMessage = "The field Percent should be a positive non-decimal number")]
        [Range(1,60, ErrorMessage = "The field Percent should be between 1 and 60.")]
        public int Percent { get; set; }
    }
}

As you know HTML helpers for rendering inputs generate elements with additional data attributes (data-val, data-val-* and so on)
for unobtrusive client-side validation. I've created template without any data atributes at all. When data has been sent to server in action it will be validated if there is some problems action return back partial view with form to client, but now all inputs which had been added dynamically will be rendered with all data attributes which are needed for unobtrusive validation.

@{
    ViewBag.Title = "Dynamic form";
    Layout = "~/Views/Shared/Layout.cshtml";
}

<h2>Test dynamic form</h2>
<button id="showForm">Show form</button>
<div id="dialog" style="display: none;" title="Dynamic loaded form">
    <div id="formContainer">
        @{ Html.RenderAction("GetForm"); }
    </div>
    <div>
        <button id="AddInput">add element dynamically</button>
    </div>
</div>
@section scripts
{
    <script>
        (function ($) {
            $.fn.clearErrors = function () {
                $(this).each(function () {
                    $(this).find(".field-validation-error").empty();
                    $(this).trigger('reset.unobtrusiveValidation');
                });
            };

            $.fn.resetForm = function () {
                $(this).find('#Discounts > .row').remove();
                $(this).find('input').val('');
                $(this).find('.form-group').removeClass("has-success").removeClass("has-error");
            };

            $.fn.enableValidation = function () {
                $(this).removeData("validator").removeData("unobtrusiveValidation");
                $.validator.unobtrusive.parse($(this));
            };
            $.validator.setDefaults({
                highlight: function (element) {
                    $(element).closest(".form-group").removeClass("has-success").addClass("has-error");
                },
                unhighlight: function (element) {
                    $(element).closest(".form-group").removeClass("has-error").addClass('has-success');
                }
            });
            $.ajaxSetup({ cache: false });
            var dialog,
                dialogParams = {
                    dialogClass: "no-close",
                    minWidth: 800,
                    autoOpen: false,
                    modal: true
                };
            function View(params) {

                function initDialog() {
                    dialog = $(params.dialogSelector).dialog($.extend(dialogParams, {
                        open: function (event, ui) {
                            $('form').clearErrors();
                        },
                        buttons: {
                            "Close": function () {
                                dialog.dialog('close');
                            },
                            "Save": function () {
                                if ($('form').valid()) {
                                    $.ajax({
                                        url: $('form').attr('action'),
                                        type: "POST",
                                        data: $('form').serialize(),
                                        success: function (result) {
                                            if (result.success) {
                                                $('form').resetForm();
                                                $(dialog).dialog('close');
                                            } else {
                                                $("#formContainer").html(result);
                                                $('form').enableValidation();
                                            }
                                        }
                                    });
                                }
                            }
                        }
                    }));
                }

                function showDialog() {
                    initDialog();
                    dialog.dialog('open');
                }

                function bindEvents() {
                    $(params.showFormButtonSelector).on('click', function () {
                        showDialog();
                    });

                    $(params.dialogSelector).on('click', '#AddInput', function () {
                        var $discountsContainer = $("#Discounts"),
                             lastIndex = 0,
                            data, html;
                        if ($discountsContainer.find('.row:last').length > 0) {
                            //provide correct index for non-sequential binding
                            lastIndex = parseInt($discountsContainer.find('.row:last > input[name="Discounts.Index"]').val(), 10) + 1;
                        }
                        data = { index: lastIndex };
                        html = $.templates("#discountRow").render(data);
                        $(html).appendTo($discountsContainer);
                        $('form').enableValidation();
                    });

                    $(document).on('click', '.removeDiscountRow', function (e) {
                        $(e.target).parents('.row').remove();
                    });
                }

                return {
                    bindEvents: bindEvents
                }
            }

            $(function () {
                var view = new View({
                    dialogSelector: "#dialog",
                    showFormButtonSelector: "#showForm"
                });
                view.bindEvents();
            });
        })(jQuery);
    </script>
}
<script id="discountRow" type="text/x-jsrender">
    <div class="row">
        <input type="hidden" name="Discounts.Index" value="{{: index}}">
        <div class="col-md-4 form-group">
            <div class="input-group">
                <label class="control-label" for="Discounts_{{: index}}__Code">Code name</label>
                <input class="form-control" id="Discounts_{{: index}}__Code" name="Discounts[{{: index}}].Code" type="text" />
            </div>
        </div>
        <div class="col-md-6 form-group">
            <div class="input-group">
                <label class="control-label" for="Discounts_{{: index}}__Percent">Code discount</label>
                <input class="form-control" id="Discounts_{{: index}}__Percent" name="Discounts[{{: index}}].Percent" type="text" />
            </div>
        </div>
        <div class="col-md-2 form-group">
            <div class="input-group">
                <button type="button" class="btn btn-primary removeDiscountRow">Remove</button>
            </div>
        </div>
    </div>
</script>

<style>
    .removeDiscountRow {
        margin-top: 25px;
    }
</style>
I've written simple Controller for this example
using System.Web.Mvc;
using DynamicForm.Models;

namespace DynamicForm.Controllers
{
    public class IndexController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }

        [HttpGet]
        public ActionResult GetForm()
        {
            return PartialView("_Form", new FormViewModel());
        }

        [HttpPost]
        public ActionResult SaveForm(FormViewModel formviewModel)
        {
            if (!ModelState.IsValid)
            {
                return PartialView("_Form", formviewModel);
            }
            return Json(new { success = true });
        }
    }
}
The form you can see below
viewModel under debugger
All code you can find at github: GitHub

Comments

Popular posts from this blog

Університет нафти і газу

Всім привіт. Я поступив в національний технічний університет Нафти і Газу. Поступив на 2 курс, хоча мав б бути на третьому. Я дякую, за те, що поступив на другий курс на держ. форму. Але не все так просто. Потрібно перезаразувати години предметів, які вчили в універі на 1 і частково на 2 курсі, для того щоб без проблем перейти на 3 курс. На рахунок програмування, майже нічого нового немає. Хіба що будем вчити Java на на предметі "технологія розробки ПЗ". Ось і все, що я хотів написати. Всім удачі!

Some notes about transportation problem

Hello guys. After work I remembered my studying at university. My first thoughts is about solving Monge–Kantorovich transportation problem using a modification of simplex method known as Method of Potentials. Transportation theory investigates methods for optimal allocation resources among consumers and transportation them with minimum cost. For example, suppose we have some factories which provide materials and shops which consume it. (To be continued)

Docker multi-stage build in Action

Hi everyone, today I want to show how to create the small docker image for deploying the application. I've an application which consists of a backend(go application) and angular based front-end application. So let's create the Dockerfile and step by step fill it with commands. I started with frontend Next I added the golang builder For avoiding, the certificate signed by unknown authority error during HTTP request I add trusted certificates (details more here Stackoverflow issue ) Finally, copy all items to the end image I keep my container on the quay.io so everyone can see logs and pull the container docker pull quay.io/vdzundza/shoper All Dockerfile listing here Github Gist Thanks everyone !