2010-02-01 3 views
0

J'ai un formulaire avec diverses entrées. J'ai un tas de paramètres optionnels qui ont un certain nombre de choix. Je souhaite autoriser l'utilisateur à sélectionner ces paramètres facultatifs de la façon suivante:Ajouter dynamiquement des listes déroulantes à un formulaire et les valider dans ASP.NET MVC

D'abord, l'utilisateur clique sur le bouton Ajouter un composant en bas du formulaire et deux nouvelles listes déroulantes apparaissent au-dessus du bouton. La première liste déroulante a une liste de types qui peuvent être sélectionnés et le second sera désactivé. Lorsque l'utilisateur sélectionne un choix valide dans la première liste déroulante, je souhaite remplir la deuxième liste déroulante avec des valeurs spécifiques au type spécifié. L'utilisateur devrait pouvoir continuer à ajouter de nouveaux composants (la paire de listes déroulantes) jusqu'à ce que tous les composants facultatifs souhaités soient ajoutés. Idéalement, le formulaire ne serait pas affiché avant que tous les champs aient été remplis et que les composants souhaités aient été ajoutés. Comment puis-je concevoir cela de sorte que lorsque le formulaire est soumis et qu'il y a des erreurs, les champs ajoutés dynamiquement (les composants) resteront sur la page et afficheront les valeurs correctes?

je comptais avoir sur le bouton Ajouter un composant soit une Ajax.ActionLink qui récupère un PartialView:

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

Cette vue partielle ressemblerait à quelque chose comme ceci:

<%@ 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> 

Et le ComponentValueSelector partiel serait assez simple:

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

Répondre

2

Jetez un oeil à la soumission l ist dans MVC, voici quelques sites utiles:

Ceci est utile pour soumettre votre DOM dynamique que vous construisez. Une autre façon, au lieu de faire un appel ajax pour rendre une vue partielle, vous pouvez toujours ajouter directement des éléments au DOM avec jquery. Par exemple, utilisez la méthode jquery clone ($ ('element'). Clone();) qui copiera vos zones de liste, puis regex pour modifier les identifiants des zones de saisie afin qu'ils aient des identifiants/noms uniques. Lorsque vous passez une liste de ces «choix» à votre contrôleur, vous devez alors les replacer dans votre modèle et faire en sorte que votre vue les parcoure pour afficher le nombre correct de choix ajoutés.

Voici un exemple d'os nus. Cela peut ne pas être la meilleure implémentation pour vous-même ou quelqu'un d'autre peut avoir de meilleures idées.

Voir

<% 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; 
}); 

Contrôleur

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

liens grands, Je vous remercie. [En ce qui concerne votre dernier paragraphe] Que voulez-vous dire lorsque vous dites "réglez-les dans votre modèle"? Il semble que je doive ajouter une liste au modèle de mon View, ajouter un paramètre List à ma méthode Action, et suivre la convention de nommage (à partir des liens) dans mes vues partielles, que DefaultModelBinder liera la valeur du des listes déroulantes à la liste? – sdr

+0

J'ai mis à jour ma réponse pour inclure un meilleur exemple. En fait, je l'ai étendu pour que ma validation écrive "[1] date invalide" ou "[4] sélection invalide" dans le récapitulatif de validation en fonction du nombre de lignes dans ma table. Je peux aussi vous montrer que si vous en avez besoin. – David

+0

C'est génial. Sur la base des informations que vous m'avez fournies, j'ai pu créer une solution avec un angle légèrement différent (je l'afficherai comme une autre réponse). J'ai beaucoup appris sur la liaison de modèle de cette discussion. Merci pour votre aide. – sdr

0

Sur la base des conseils de David Liddle, je Foun d un design différent qui était un peu plus élégant. Il utilise plus de jQuery et moins de vues partielles et de requêtes Ajax. Au lieu d'ajouter un tas de DropDownLists, j'ai décidé d'aller avec une table, une paire de listes déroulantes et un bouton "Ajouter". Lorsque l'utilisateur sélectionne une option Type dans la première liste déroulante, ajax est toujours utilisé pour récupérer l'affichage partiel pour remplir la deuxième liste déroulante Valeur. Une fois qu'une option Valeur a été sélectionnée, l'utilisateur clique ensuite sur le bouton Ajouter.

En utilisant jQuery, deux entrées masquées sont ajoutées à la page. La convention de dénomination dans les liens de David est utilisée pour nommer ces éléments (comps [0] .Type et comps [0] .Value). En outre, une nouvelle ligne est ajoutée à la table avec le même type et la même valeur pour un retour visuel à l'utilisateur montrant ce qui a été ajouté.

J'ai également défini une classe Component qui a juste des propriétés Type et Value et ajouté une liste au modèle. Dans la vue, je parcourir cette liste et ajouter tous les composants dans le modèle à la table et comme entrées cachées.

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); 
} 

contrôleur

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; 
    } 
} 
Questions connexes