Динамическое добавление выпадающих форм и проверка их в ASP.NET MVC - PullRequest
0 голосов
/ 01 февраля 2010

У меня есть форма с различными входами. У меня есть несколько необязательных параметров, которые имеют несколько вариантов. Я хотел бы позволить пользователю выбирать эти необязательные параметры следующим образом:

Сначала пользователь нажимает кнопку «Добавить компонент» в нижней части формы, и над кнопкой появляются два новых раскрывающихся списка. Первый выпадающий список содержит список типов, которые можно выбрать, а второй будет отключен. Когда пользователь выбирает правильный выбор в первом раскрывающемся списке, я хочу заполнить второй раскрывающийся список некоторыми значениями, которые относятся к указанному типу. Пользователь должен иметь возможность продолжить добавление новых Компонентов (пара раскрывающихся списков), пока не будут добавлены все необходимые необязательные Компоненты. В идеале форма не будет опубликована до тех пор, пока не будут заполнены все поля и не будут добавлены нужные компоненты.

У меня такой вопрос: Как мне сделать так, чтобы при отправке формы и при наличии ошибок динамически добавленные поля (Компоненты) оставались на странице и отображали правильные значения ?

Я планировал, чтобы кнопка «Добавить компонент» была Ajax.ActionLink, которая получает частичное представление:

<div id="divComponentHolder"></div>
<%= Ajax.ActionLink("Add a Component", "GetComponentSelector", new AjaxOptions { UpdateTargetId = "divComponentHolder", InsertionMode = InsertionMode.InsertAfter}) %>

Этот частичный вид будет выглядеть примерно так:

   <%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<MVCAndWebFormsTest.Models.ComponentSelectorModel>" %>
   <%= Html.Encode("Type:")%>
   <%= Html.DropDownList("ComponentType", Model.ComponentTypes, "", new {onchange = "updateCompValues(this);"}) %>
   <%= Html.Encode("File/Folder:")%>
   <div id="selectdiv">
       <% Html.RenderPartial("ComponentValueSelector", Model.ComponentValues); %>
   </div> 
   <br/>
   <script type="text/javascript" language="javascript">
        function updateCompValues(obj) {
            $.ajax({
                url: <% Url.Action("GetCompValues") %>,
                async: true,
                type: 'POST',
                data: { type: obj.value },
                dataType: 'text',
                success: function(data) { $("#selectdiv").html(data); },
                error: function() {
                    console.log('Erreur');
                }
            });
        }
   </script>

И часть ComponentValueSelector будет довольно простой:

    <%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<MVCAndWebFormsTest.Controllers.ViewModels.ComponentValueModel>" %>
    <%= Html.DropDownList("CompValue", Model.SelectList) %>

Ответы [ 2 ]

2 голосов
/ 01 февраля 2010

Взгляните на отправку списка в MVC, вот несколько полезных сайтов:

Это полезно для отправки динамического DOM, который вы создаете.

Другой способ вместо вызова ajax для рендеринга частичного представления - всегда можно напрямую добавлять элементы в DOM с помощью jquery. Например, используйте метод jquery clone ($ ('element'). Clone ();), который будет копировать ваши списки, а затем выполнять регулярные выражения для изменения идентификаторов полей ввода, чтобы они имели уникальные идентификаторы / имена.

Когда вы проходите через Список этих «вариантов» на вашем контроллере, вам нужно будет вернуть их обратно в вашу Модель и сделать так, чтобы ваш View просматривал их, чтобы отобразить правильное количество добавленных вариантов.

Вот пример голых костей. Возможно, это не лучшая реализация для вас, или у кого-то еще могут быть лучшие идеи.

View

<% for (int i = 0; i < in Model.Results.Count; i++) { %>
    //render better HTML but you should get the point!
    <%= Html.Hidden("choices[" + i + "].ID", i) %>
    <%= Html.DropDownList("choices[" + i + "].Choice1", ...) %>
    <%= Html.DropDownList("choices[" + i + "].Choice2", ...) %>
<% } %>

- add button

JQuery

$('#addButton').click(function()
{
    //say if your choice drop downs were in a table then take the last
    //row and clone it
    var row = $('table tr:last').clone(true);
    var newId = //work out the new id from how many rows in the table

    //make sure to update the id and name parameters of inputs 
    //of the cloned row
    row.find(':input')
    .attr('id', function()
    {
       return $(this).attr('id').replace(/\[[\d+]\]/g, '[' + newlId + ']');
       //this replaces the cloned [id] with a new id
    })
    .attr('name', function()
    {
       return $(this).attr('name').replace(/\[[\d+]\]/g, '[' + newId + ']');
    });

    row.find(':hidden').val(newId); //update the value of the hidden input

    //alert(row.html()); //debug to check your cloned html is correct!
    //TODO: setup ajax call for 1st drop down list to render 2nd drop down

    $('table tr:last').after(row);//add the row

    return false;
});

Контроллер

public ActionResult YourMethod(IList<YourObject> choices, any other parameters)
{
   YourViewModel model = new YourViewModel();
   model.Results = choices; //where Results is IList<YourObject>

   return View(model);
}
0 голосов
/ 02 февраля 2010

Основываясь на советах Дэвида Лиддла, я нашел другой дизайн, который был немного более элегантным.Он использует больше jQuery и меньше частичных представлений и запросов Ajax.

Вместо того чтобы добавлять группу DropDownLists, я решил использовать таблицу, пару выпадающих списков и кнопку «Добавить».Когда пользователь выбирает параметр «Тип» в первом раскрывающемся списке, по-прежнему используется ajax для получения частичного представления для заполнения второго раскрывающегося списка «Значение».После выбора параметра «Значение» пользователь нажимает кнопку «Добавить».

Используя jQuery, на страницу добавляются два скрытых ввода.Соглашение об именах в ссылках от Дэвида используется для именования этих элементов (comps [0] .Type и comps [0] .Value).Кроме того, в таблицу добавляется новая строка с такими же типом и значением для визуальной обратной связи с пользователем, показывающей, что было добавлено.

Я также определил класс Component, который просто имеет свойства Type и Value, и добавил List в модель.В представлении я перебираю этот список и добавляю все компоненты модели в таблицу и в качестве скрытых входных данных.

IndexView

<table id="componentTable">
    <tr>
        <th>Type</th>
        <th>Deploy From</th>
    </tr>
    <% foreach (Component comp in Model.comps) { %>
        <tr>
            <td><%= Html.Encode(comp.Type) %></td>
            <td><%= Html.Encode(comp.Value) %></td>
        </tr> 
    <% } %>
</table>

<div id="hiddenComponentFields">
<% var index = 0;%>
<% foreach (Component comp in Model.comps) { %>
    <input type="hidden" name="comps[<%= Html.Encode(index) %>].Type" value="<%= Html.Encode(comp.Type) %>" />
    <input type="hidden" name="comps[<%= Html.Encode(index) %>].Value" value="<%= Html.Encode(comp.value) %>" />
    <% index++; %>
<% } %>
</div>

<%= Html.DropDownList("ComponentTypeDropDown", Model.ComponentTypes, "", new { onchange = "updateCompValues();"}) %>
<span id="CompValueContainer">
    <% Html.RenderPartial("ComponentValueSelector", new ComponentValueModel()); %>
</span>

<span class="button" id="addComponentButton" onclick="AddComponentButtonClicked()">Add the File</span>

<span id="componentStatus"></span>

ComponentValueSelector PartialView

<%@ Control Language="C#" Inherits="ViewUserControl<ComponentValueModel>" %>

<% if(Model.SelectList == null) { %>
    <select id="CompValue" name="CompValue" disabled="true">
        <option></option>
    </select>
<% } else { %>
    <%= Html.DropDownList("CompValue", Model.SelectList, "") %>
<% } %>

jQuery

function updateCompValues() {
    $.ajax({
        url: '<%= Url.Action("GetComponentValues") %>',
        async: true,
        type: 'POST',
        data: { type: $("#CompValue").value },
        dataType: 'text',
        success: function(data) { $("#CompValueContainer").html(data); enable($("#CompValue")) },
        error: function() {
            console.log('Erreur');
        }
    });
}

function AddComponentButtonClicked() {
    UpdateCompStatus("info", "Updating...");
    var type = $("#ComponentTypeDropDown").val();
    var value = $("#CompValue").val();
    if (type == "" || value == "") {  // No values selected
        UpdateCompStatus("warning", "* Please select both a type and a value");
        return;  // Don't add the component
    }
    AddComponent(type, value);
}

function AddComponent(type, setting_1) {
    // Add hidden fields
    var newIndex = GetLastCompsIndex() + 1;
    var toAdd = '<input type="hidden" name="comps[' + newIndex + '].Type" value="' + type + '" />' +
                '<input type="hidden" name="comps[' + newIndex + '].Setting_1" value="' + setting_1 + '" />';
    $("#hiddenComponentFields").append(toAdd);

    // Add to page
    // Note: there will always be one row of headers so the selector should always work.
    $('#componentTable tr:last').after('<tr><td>'+type+'</td><td>'+setting_1+'</td>remove</tr>');
}

function GetLastCompsIndex() {
    // TODO
    alert("GetLastCompsIndex unimplemented!");
    // haven't figured this out yet but something like 
    // $("#hiddenComponentFields input:hidden" :last).useRegExToExtractIndexFromName(); :)
}

function UpdateCompStatus(level, message) {
    var statusSpan = $("#componentStatus");
    // TODO Change the class to reflect level (warning, info, error?, success?)
    // statusSpan.addClassName(...)
    statusSpan.html(message);
}

Контроллер

public ActionResult Index() { 
    SelectList compTypes = repos.GetAllComponentTypesAsSelectList();
    return View(new IndexViewModel(compTypes));
}

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Index(Component[] comps, other params...) {
    foreach(Component comp in comps) {
        // Do something with comp.Type and comp.Value
    }
    return RedirectToAction(...);
}

public ActionResult GetComponentValues(string type) {
    ComponentValueModel valueModel = new ComponentValueModel();
    valueModel.SelectList = repos.GetAllComponentValuesForTypeAsSelectList(type);
    return PartialView("ComponentValueSelector", valueModel);
}

IndexViewModel

public class IndexViewModel {
    public List<Component> comps { get; set; }
    public SelectList ComponentTypes { get; set; }

    public IndexViewModel(SelectList types) {
        comps = new List<Component>();
        ComponentTypes = types;
    }
}
...