How to add fields dynamically and link them to viewmodel in Asp.Net

Asked

Viewed 363 times

1

I have a 1:N relationship between Person and Contact. In my view, I can list all the contacts of a customer when it is opened.

inserir a descrição da imagem aqui

I need to implement the "New Contact" button so that when clicked, dynamically add the fields (Contact Type, Contact, Main Contact, Details and Delete Button)... I tried to do with Java Script and got it, but I had problems with the "validations" that didn’t work and the "combobox Contactotpo" that didn’t work... This is because I couldn’t use the Asp.net core Asp-"validation-for" and "Asp-items" tags in the Javascript block...

I made a FOR to receive the database data and the validations and combobox work correctly. The problem is in the JS block that do not work. How do I make it work in Javascript?

@model Retaguarda.Application.ViewModels.Pessoa.PessoaViewModel
@{
    ViewData["Title"] = "_PessoaContato";
}
<div class="form-horizontal">
    <div class="form-group row">
        <div class="col-md-12">
            <div class="col-md-12" id="div-contatos">

                @if (Model.PessoasContatosViewModel != null)
                {
                    @for (int i = 0; i < Model.PessoasContatosViewModel.Count; i++)
                    {
                        <div class="row align-items-center">
                            <div class="col-md-2">
                                @Html.HiddenFor(model => model.PessoasContatosViewModel[i].Id, new { @class = "hid-id" })
                                <label asp-for="PessoasContatosViewModel[i].ContatoTipoId" class="control-label sel-contatoTipo">Tipo de Contato</label>
                                <select asp-for="PessoasContatosViewModel[i].ContatoTipoId" asp-items="Model.ContatosTipos" data-plugin="selectpicker" title="Selecione uma opção" class="form-control show-tick show-menu-arrow sel-contatoTipo"></select>
                                <span asp-validation-for="PessoasContatosViewModel[i].ContatoTipoId" class="text-danger"></span>
                            </div>
                            <div class="col-md-4">
                                <label asp-for="PessoasContatosViewModel[i].Contato" class="control-label txt-contato">Contato</label>
                                <input type="text" asp-for="PessoasContatosViewModel[i].Contato" class="form-control txt-contato" />
                                <span asp-validation-for="PessoasContatosViewModel[i].Contato" class="text-danger"></span>
                            </div>
                            <div class="col-md-2">
                                <label class="control-label">&nbsp;</label>
                                <div class="checkbox-custom checkbox-default">
                                    <input type="checkbox" asp-for="PessoasContatosViewModel[i].ContatoPrincipal" class="ckb-contatoPrincipal" checked autocomplete="off" />
                                    <label asp-for="PessoasContatosViewModel[i].ContatoPrincipal" class=" ckb-contatoPrincipal">Contato Principal</label>
                                </div>
                            </div>
                            <div class="col-md-3">
                                <label asp-for="PessoasContatosViewModel[i].Detalhes" class="control-label txt-detalhes">Detalhes</label>
                                <textarea asp-for="PessoasContatosViewModel[i].Detalhes" class="form-control txt-detalhes"></textarea>
                                <span asp-validation-for="PessoasContatosViewModel[i].Detalhes" class="text-danger"></span>
                            </div>
                            <div class="col-md-1">
                                <button type="button" class="btn btn-icon btn-default btn-outline btn-remover-contato" data-id="@Model.PessoasContatosViewModel[i].Id." style="margin-top: 30px;"><i class="icon wb-trash" aria-hidden="true"></i></button>
                            </div>
                        </div>
                    }


                }

            </div>
        </div>
    </div>
    <div class="form-group row">
        <div class="col-md-12">
            <div class="col-md-2">
                <button id="btn-add-contato" type="button" class="btn btn-icon btn-default btn-outline"><i class="icon wb-plus" aria-hidden="true"></i> Novo Contato</button>
            </div>
        </div>
    </div>
</div>



<script>
    $(function () {
        var qtdContatos = 0;

        $("#btn-add-contato").click(function (e) {
            e.preventDefault();

            var blocoContato = '<div class="row align-items-center">' +
                '  <div class="col-md-2" >' +
                '     <label name="PessoaContatoViewModel[' + qtdContatos + '].ContatoTipoId" class="control-label sel-contatoTipo">Tipo de Contato</label>' +
                '    <select name="PessoaContatoViewModel[' + qtdContatos + '].ContatoTipoId" data-plugin="selectpicker" title="Selecione uma opção" class="form-control show-tick show-menu-arrow sel-contatoTipo"></select>' +
                ' </div >' +
                '<div class="col-md-4">' +
                ' <label name="PessoaContatoViewModel[' + qtdContatos + '].Contato" class="control-label txt-contato">Contato</label>' +
                '  <input type="text" name="PessoaContatoViewModel[' + qtdContatos + '].Contato" class="form-control txt-contato" />' +
                ' </div>' +
                ' <div class="col-md-2">' +
                ' <label class="control-label">&nbsp;</label>' +
                ' <div class="checkbox-custom checkbox-default">' +
                ' <input type="checkbox" name="PessoaContatoViewModel[' + qtdContatos + '].ContatoPrincipal" class="ckb-contatoPrincipal" checked autocomplete="off" />' +
                '<label for="PessoaContatoViewModel[' + qtdContatos + '].ContatoPrincipal class="ckb-contatoPrincipal">Contato Principal</label>' +
                '</div>' +
                ' </div>' +
                ' <div class="col-md-3">' +
                '<label name="PessoaContatoViewModel[' + qtdContatos + '].Detalhes" class="control-label txt-detalhes">Detalhes</label>' +
                '<textarea name="PessoaContatoViewModel[' + qtdContatos + '].Detalhes" class="form-control txt-detalhes"></textarea>' +
                '</div>' +
                '<div class="col-md-1">' +

                '<button type="button" class="btn btn-icon btn-default btn-outline btn-remover-contato" style="margin-top: 30px;"><i class="icon wb-trash" aria-hidden="true"></i></button>' +
                ' </div>' +
                ' </div>';

            $("#div-contatos").append(blocoContato);
            qtdContatos++;
        });

        $("#div-contatos").on("click", ".btn-remover-contato", function (e) {
            e.preventDefault();

            $(this).parent().parent().remove();

            qtdContatos--;

            $("#div-contatos .row").each(function (indice, elemento) {
                $(elemento).find(".sel-contatoTipo").attr("name", "PessoaContatoViewModel[" + indice + "].ContatoTipoId");
                $(elemento).find(".txt-contato").attr("name", "PessoaContatoViewModel[" + indice + "].Contato");
                $(elemento).find(".ckb-contatoPrincipal").attr("name", "PessoaContatoViewModel[" + indice + "].ContatoPrincipal");
                $(elemento).find(".txt-detalhes").attr("name", "PessoaContatoViewModel[" + indice + "].Detalhes");
            });

        });

    });
</script>


@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

Controller:

[HttpGet]
[Authorize(Policy = "CanWritePessoaData")]
[Route("pessoa-gerenciar/editar-pessoa/{id:int}")]
public IActionResult Edit(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var pessoaViewModel = _pessoaAppService.GetJoinById(id.Value);

    if (pessoaViewModel == null)
    {
        return NotFound();
    }       

    pessoaViewModel.PessoaGenericoViewModel.Filiais = PreencherFiliais();
    pessoaViewModel.ContatosTipos = PreencherContatodosTipos();
    return View(pessoaViewModel);

}

Viewmodels:

public class PessoaViewModel
{
    [Key]
    public int Id { get; set; }

    [DisplayName("Natureza")]
    [Required(ErrorMessage = "Escolha uma Natureza")]
    public PessoaNatureza PessoaNatureza { get; set; }
    [DisplayName("Natureza")]
    public string PessoaNaturezaDescricao { get; set; }
    [DisplayName("Naturezas")]
    public IEnumerable<SelectListItem> PessoasNaturezas { get; set; }

    public PessoaGenericoViewModel PessoaGenericoViewModel { get; set; }
    public IEnumerable<PessoaGenericoViewModel> PessoasGenericosViewModel { get; set; }

    public IEnumerable<SelectListItem> ContatosTipos { get; set; }
    public List<PessoaContatoViewModel> PessoasContatosViewModel { get; set; }

    public PessoaFisicaViewModel PessoaFisicaViewModel { get; set; }
    public PessoaJuridicaViewModel PessoaJuridicaViewModel { get; set; }

    public PessoaViewModel()
    {
        PessoasNaturezas = ExtensaoDeEnumerador.EnumParaSelectListGenerico<PessoaNatureza>("U", PessoaNatureza.ToString()).OrderBy(x => x.Text);
        PessoaFisicaViewModel = null;
        PessoaJuridicaViewModel = null;
    }
}


public class PessoaContatoViewModel
{
    [Key]
    public int Id { get; set; }

    public int PessoaId { get; set; }

    [DisplayName("Tipo de Contato")]
    [Required(ErrorMessage = "Escolha um Tipo de Contato")]
    public int ContatoTipoId { get; set; }
    [DisplayName("Tipo de Contato")]
    public string ContatoTipoDescricao { get; set; }
    [DisplayName("Tipos de Contato")]
    public IEnumerable<SelectListItem> ContatosTipos { get; set; }

    [DisplayName("Contato")]
    [Required(ErrorMessage = "O campo Contato é obrigatório")]
    [MaxLength(100, ErrorMessage = "O campo {0} deve ter no máximo {1} caracteres")]
    public string Contato { get; set; }

    [DisplayName("Detalhes")]
    [MaxLength(150, ErrorMessage = "O campo {0} deve ter no máximo {1} caracteres")]
    public string Detalhes { get; set; }

    [DisplayName("Contato Principal")]
    public bool ContatoPrincipal { get; set; }
}

inserir a descrição da imagem aqui

  • Include your viewmodel and controller. Ps. You can only do it with javascript... but it takes more work. The validations didn’t work because you’re not creating all the necessary elements. But I think taking advantage of the feature of a View Component can be solved in a simpler way

  • Thank you @Leandro Angelo. I updated the Post. Just to reinforce, my viewmodel is already with the validated fields using dataannotations and the unibtrusive javascript feature is already installed... I just need to do what already works in the FOR block, using javascript... :)

  • Inside Personal Property is the Personal Property...

1 answer

2


Add neat html just like you’re doing, you won’t have important things from your model, like Dataannotations for example. A "mixed" solution would be to create a Action return a new line from your model and through a call Ajax to this action, take the content properly rendered, and add.

To do this, transform the block you want to add dynamically into a Partialview:

Contactopartial:

<div class="row align-items-center">
    <div class="col-md-2">
        @Html.HiddenFor(model => model.PessoasContatosViewModel[@ViewBag.indice].Id, new { @class = "hid-id" })
        <label asp-for="PessoasContatosViewModel[@ViewBag.indice].ContatoTipoId" class="control-label sel-contatoTipo">Tipo de Contato</label>
        <select asp-for="PessoasContatosViewModel[@ViewBag.indice].ContatoTipoId" asp-items="Model.ContatosTipos" data-plugin="selectpicker" title="Selecione uma opção" class="form-control show-tick show-menu-arrow sel-contatoTipo"></select>
        <span asp-validation-for="PessoasContatosViewModel[@ViewBag.indice].ContatoTipoId" class="text-danger"></span>
    </div>
    <div class="col-md-4">
        <label asp-for="PessoasContatosViewModel[@ViewBag.indice].Contato" class="control-label txt-contato">Contato</label>
        <input type="text" asp-for="PessoasContatosViewModel[@ViewBag.indice].Contato" class="form-control txt-contato" />
        <span asp-validation-for="PessoasContatosViewModel[@ViewBag.indice].Contato" class="text-danger"></span>
    </div>
    <div class="col-md-2">
        <label class="control-label">&nbsp;</label>
        <div class="checkbox-custom checkbox-default">
            <input type="checkbox" asp-for="PessoasContatosViewModel[@ViewBag.indice].ContatoPrincipal" class="ckb-contatoPrincipal" checked autocomplete="off" />
            <label asp-for="PessoasContatosViewModel[@ViewBag.indice].ContatoPrincipal" class=" ckb-contatoPrincipal">Contato Principal</label>
        </div>
    </div>
    <div class="col-md-3">
        <label asp-for="PessoasContatosViewModel[@ViewBag.indice].Detalhes" class="control-label txt-detalhes">Detalhes</label>
        <textarea asp-for="PessoasContatosViewModel[@ViewBag.indice].Detalhes" class="form-control txt-detalhes"></textarea>
        <span asp-validation-for="PessoasContatosViewModel[@ViewBag.indice].Detalhes" class="text-danger"></span>
    </div>
    <div class="col-md-1">
        <button type="button" class="btn btn-icon btn-default btn-outline btn-remover-contato" data-id="@Model.PessoasContatosViewModel[@ViewBag.indice].Id." style="margin-top: 30px;"><i class="icon wb-trash" aria-hidden="true"></i></button>
    </div>
</div>

Create a Action that comes back this Partial:

public PartialViewResult NovoContato(int indice)
{
     ViewBag.indice= indice;
     return PartialView("ContatoPartial", new PessoasContatosViewModel());
}

Change javascript to make a call Ajax to trigger the Action, and add the content to the page:

var itemIndex = $("#container input.iHidden").length; 
        var novoIndice = qtdContatos +1;      //suponho q já tenha calculado
        e.preventDefault();
        $.get("@Url.Action("NovoContato", "NomeDaController")/"+novoIndice ,function(data){
            $("#div-contatos").append(data);
        });    

With this, the new line will be rendered by ASP.Net, and the call ajax allows no need for a Post page.

  • Thank you @Ricardo Pontual. Her help was very useful to create Rows without the use of JS, but in addition I had to make the partialview using the Personal Modelcontact and I’m having difficulties to create indexes in it because it does not contain list only the standard Fields. I created another post with the problem... in case you know how to solve it... https://answall.com/questions/327732/howto create an index-relation to a field

  • I’ll take a look, the night I take it easy I’ll take a look, I’ve done it a few times :)

Browser other questions tagged

You are not signed in. Login or sign up in order to post.