Remplissage dynamique d'une liste déroulante avec les valeurs d'une carte en fonction de ce qui est sélectionné dans une autre liste déroulante

StackOverflow https://stackoverflow.com/questions/156852

Question

Ok, en voici un pour les gourous Java / JavaScript:

Dans mon application, l'un des contrôleurs transmet une TreeMap à son JSP. Cette carte contient les noms des constructeurs automobiles en tant que clés et des listes d'objets Car en tant que valeurs. Ces objets de voiture sont de simples haricots contenant le nom de la voiture, son identifiant, l'année de production, etc. La carte ressemble donc à ceci (ceci n’est qu’un exemple, pour clarifier un peu les choses):

Clé: Porsche
Valeur: liste contenant trois objets Car (par exemple, 911, Carrera, Boxter avec leurs années de production respectables et leurs identifiants)
Clé: Fiat
Valeur: liste contenant deux objets Car (par exemple, Punto et Uno)
etc ...

Maintenant, dans mon JSP, j'ai deux combobox. Vous devez recevoir une liste des constructeurs automobiles (clés de la carte - cette partie que je sais faire), et l’autre devrait changer dynamiquement pour afficher les noms des voitures lorsque l’utilisateur sélectionne un certain paramètre. fabricant de la première combobox. Ainsi, par exemple, l’utilisateur sélectionne une " Porsche " dans la première liste déroulante, et la seconde affiche immédiatement "911, Carrera, Boxter" ...

Après avoir passé quelques jours à essayer de savoir comment faire cela, je suis prêt à admettre ma défaite. J'ai essayé beaucoup de choses différentes, mais chaque fois je me suis heurté à un mur quelque part en cours de route. Quelqu'un peut-il suggérer comment je devrais aborder celui-ci? Oui, je suis un débutant en JavaScript, si quelqu'un se demandait ...

EDIT: J'ai repensé cela comme un défi de code. Félicitations à quiconque résoudra ce problème sans utiliser de framework JavaScript (comme JQuery).

Était-ce utile?

La solution 2

Et bien, comme je l'ai dit, j'ai finalement réussi à le faire moi-même, alors voici ma réponse ...

Je reçois la carte de mon contrôleur comme ceci (j'utilise Spring, je ne sais pas comment cela fonctionne avec d'autres frameworks):

<c:set var="manufacturersAndModels" scope="page" value="${MANUFACTURERS_AND_MODELS_MAP}"/>

Ce sont mes combos:

<select id="manufacturersList" name="manufacturersList" onchange="populateModelsCombo(this.options[this.selectedIndex].index);" >
                  <c:forEach var="manufacturersItem" items="<%= manufacturers%>">
                    <option value='<c:out value="${manufacturersItem}" />'><c:out value="${manufacturersItem}" /></option>
                  </c:forEach>
                </select>


select id="modelsList" name="modelsList"
                  <c:forEach var="model" items="<%= models %>" >
                    <option value='<c:out value="${model}" />'><c:out value="${model}" /></option>
                  </c:forEach>
                </select>

J'ai importé les classes suivantes (certains noms ont bien sûr été modifiés):

<%@ page import="org.mycompany.Car,java.util.Map,java.util.TreeMap,java.util.List,java.util.ArrayList,java.util.Set,java.util.Iterator;" %>

Et voici le code qui fait tout le travail difficile:

<script type="text/javascript">
<%  
     Map mansAndModels = new TreeMap();
     mansAndModels = (TreeMap) pageContext.getAttribute("manufacturersAndModels");
     Set manufacturers = mansAndModels.keySet(); //We'll use this one to populate the first combo
     Object[] manufacturersArray = manufacturers.toArray();

     List cars;
     List models = new ArrayList(); //We'll populate the second combo the first time the page is displayed with this list


 //initial second combo population
     cars = (List) mansAndModels.get(manufacturersArray[0]);

     for(Iterator iter = cars.iterator(); iter.hasNext();) {

       Car car = (Car) iter.next();
       models.add(car.getModel());
     }
%>


function populateModelsCombo(key) {
  var modelsArray = new Array();

  //Here goes the tricky part, we populate a two-dimensional javascript array with values from the map
<%                          
     for(int i = 0; i < manufacturersArray.length; i++) {

       cars = (List) mansAndModels.get(manufacturersArray[i]);
       Iterator carsIterator = cars.iterator();           
%>
    singleManufacturerModelsArray = new Array();
<%
    for(int j = 0; carsIterator.hasNext(); j++) {

      Car car = (Car) carsIterator.next();

 %>         
    singleManufacturerModelsArray[<%= j%>] = "<%= car.getModel()%>";
 <%
       }
 %>
  modelsArray[<%= i%>] = singleManufacturerModelsArray;
 <%
     }         
 %>   

  var modelsList = document.getElementById("modelsList");

  //Empty the second combo
  while(modelsList.hasChildNodes()) {
    modelsList.removeChild(modelsList.childNodes[0]);
  }

 //Populate the second combo with new values
  for (i = 0; i < modelsArray[key].length; i++) {

    modelsList.options[i] = new Option(modelsArray[key][i], modelsArray[key][i]);
  }      
}

Autres conseils

J'adore les défis.

Pas de jQuery, juste du javascript, testé sur Safari.

Je voudrais ajouter les remarques suivantes à l'avance:

  • Cela échoue longtemps à cause de l'erreur vérification.
  • Deux parties sont générées. le premier noeud de script avec la carte et le contenu du fabricant SÉLECTIONNER
  • Fonctionne sur ma machine (TM) (Safari / OS X)
  • Il n'y a pas (css) style appliqué. J'ai mauvais goût alors de toute façon, il ne sert à rien.

.

<body>
  <script>
  // DYNAMIC
  // Generate in JSP
  // You can put the script tag in the body
  var modelsPerManufacturer = {
    'porsche' : [ 'boxter', '911', 'carrera' ],
    'fiat': [ 'punto', 'uno' ]  
  };
  </script>

  <script>
  // STATIC
  function setSelectOptionsForModels(modelArray) {
    var selectBox = document.myForm.models;

    for (i = selectBox.length - 1; i>= 0; i--) {
    // Bottom-up for less flicker
    selectBox.remove(i);  
    }

    for (i = 0; i< modelArray.length; i++) {
     var text = modelArray[i];
      var opt = new Option(text,text, false, false);
      selectBox.add(opt);
    }  
  }

  function setModels() {
    var index = document.myForm.manufacturer.selectedIndex;
    if (index == -1) {
    return;
    }

    var manufacturerOption = document.myForm.manufacturer.options[index];
    if (!manufacturerOption) {
      // Strange, the form does not have an option with given index.
      return;
    }
    manufacturer = manufacturerOption.value;

    var modelsForManufacturer = modelsPerManufacturer[manufacturer];
    if (!modelsForManufacturer) {
      // This modelsForManufacturer is not in the modelsPerManufacturer map
      return; // or alert
    }   
    setSelectOptionsForModels(modelsForManufacturer);
  }

  function modelSelected() {
    var index = document.myForm.models.selectedIndex;
    if (index == -1) {
      return;
    }
    alert("You selected " + document.myForm.models.options[index].value);
  }
  </script>
  <form name="myForm">
    <select onchange="setModels()" id="manufacturer" size="5">
      <!-- Options generated by the JSP -->
      <!-- value is index of the modelsPerManufacturer map -->
      <option value="porsche">Porsche</option>
      <option value="fiat">Fiat</option>
    </select>

    <select onChange="modelSelected()" id="models" size="5">
      <!-- Filled dynamically by setModels -->
    </select>
  </form>

</body>

Utilisez-vous Struts?

Vous aurez besoin de quelques astuces JavaScript (ou AJAX) pour y parvenir.

Ce que vous devez faire, c'est quelque part dans votre code JavaScript (en laissant de côté la façon dont vous le générez pour la minute):

var map = {
   'porsche': [ 'boxter', '911', 'carrera' ],
   'fiat': ['punto', 'uno']
};

Il s’agit d’une copie de la structure de données côté serveur, c’est-à-dire d’une carte codée par fabricant, chaque valeur comportant un tableau de types de voiture.

Ensuite, dans votre événement onchange pour les fabricants, vous devez extraire le tableau de la carte définie ci-dessus, puis créer une liste d'options. (Consultez devguru.com - il contient de nombreuses informations utiles sur les objets JavaScript standard).

En fonction de la taille de votre liste de voitures, il serait peut-être préférable de choisir l'itinéraire AJAX.

Vous devez créer un nouveau contrôleur qui consulte la liste des types de voitures avec un constructeur, puis l'envoyer à un JSP qui renvoie JSON (il n'est pas nécessaire que ce soit JSON, mais cela fonctionne assez bien pour moi).

Ensuite, utilisez une bibliothèque telle que jQuery pour récupérer la liste des voitures dans votre événement onchange pour la liste des fabricants. (jQuery est un excellent framework JavaScript à connaître - cela facilite beaucoup le développement avec JavaScript. La documentation est très bonne).

J'espère que cela a du sens?

Voici une réponse fonctionnelle en couper-coller dans jsp, sans aucune bibliothèque de balises ou dépendance externe. La carte avec les modèles est codée en dur mais ne devrait poser aucun problème.

J'ai séparé cette réponse de ma réponse précédente, le JSP ajouté n'améliorant pas la lisibilité. Et dans la «vraie vie», je n’encombrerais pas mon JSP de toute la logique intégrée, mais le mettrais dans une classe quelque part. Ou utilisez des tags.

Tout cela "premier" des trucs sont pour empêcher les superfluos "," dans le code généré. Utiliser un foreach ne vous donne aucune information sur la quantité d'éléments, vous devez donc vérifier en dernier. Vous pouvez également ignorer le traitement du premier élément et supprimer le dernier "," ensuite, en diminuant la longueur du constructeur de 1.

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

<%@page import="java.util.Map"%>
<%@page import="java.util.TreeMap"%>
<%@page import="java.util.Arrays"%>
<%@page import="java.util.Collection"%>
<%@page import="java.util.List"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Challenge</title>
</head>
<body onload="setModels()">
<% // You would get your map some other way.
    Map<String,List<String>> map = new TreeMap<String,List<String>>();
    map.put("porsche", Arrays.asList(new String[]{"911", "Carrera"}));
    map.put("mercedes", Arrays.asList(new String[]{"foo", "bar"}));
%>

<%! // You may wish to put this in a class
  public String modelsToJavascriptList(Collection<String> items) {
    StringBuilder builder = new StringBuilder();
    builder.append('[');
    boolean first = true;
    for (String item : items) {
        if (!first) {
          builder.append(',');
        } else {
          first = false;
        }
        builder.append('\'').append(item).append('\'');
    }
    builder.append(']');
    return builder.toString();
  }

  public String mfMapToString(Map<String,List<String>> mfmap) {
    StringBuilder builder = new StringBuilder();
    builder.append('{');
    boolean first = true;
    for (String mf : mfmap.keySet()) {
      if (!first) {
          builder.append(',');
      } else {
          first = false;
      }
      builder.append('\'').append(mf).append('\'');
      builder.append(" : ");
      builder.append( modelsToJavascriptList(mfmap.get(mf)) );
    }
    builder.append("};");
    return builder.toString();
  }
%>

<script>
var modelsPerManufacturer =<%= mfMapToString(map) %>
  function setSelectOptionsForModels(modelArray) {
    var selectBox = document.myForm.models;

    for (i = selectBox.length - 1; i>= 0; i--) {
    // Bottom-up for less flicker
    selectBox.remove(i);
    }

    for (i = 0; i< modelArray.length; i++) {
     var text = modelArray[i];
      var opt = new Option(text,text, false, false);
      selectBox.add(opt);
    }
  }

  function setModels() {
    var index = document.myForm.manufacturer.selectedIndex;
    if (index == -1) {
    return;
    }

    var manufacturerOption = document.myForm.manufacturer.options[index];
    if (!manufacturerOption) {
      // Strange, the form does not have an option with given index.
      return;
    }
    manufacturer = manufacturerOption.value;

    var modelsForManufacturer = modelsPerManufacturer[manufacturer];
    if (!modelsForManufacturer) {
      // This modelsForManufacturer is not in the modelsPerManufacturer map
      return; // or alert
    }
    setSelectOptionsForModels(modelsForManufacturer);
  }

  function modelSelected() {
    var index = document.myForm.models.selectedIndex;
    if (index == -1) {
      return;
    }
    alert("You selected " + document.myForm.models.options[index].value);
  }
  </script>
  <form name="myForm">
    <select onchange="setModels()" id="manufacturer" size="5">
      <% boolean first = true;
         for (String mf : map.keySet()) { %>
          <option value="<%= mf %>" <%= first ? "SELECTED" : "" %>><%= mf %></option>
      <%   first = false;
         } %>
    </select>

    <select onChange="modelSelected()" id="models" size="5">
      <!-- Filled dynamically by setModels -->
    </select>
  </form>

</body>
</html>

Et si quelque chose comme ça, utilisant un prototype? Tout d’abord, votre sélection de catégories:

<SELECT onchange="changeCategory(this.options[this.selectedIndex].value); return false;">
   <OPTION value="#categoryID#">#category#</OPTION>
   ...

Ensuite, vous créez N zones de sélection différentes, une pour chacune des sous-catégories:

<SELECT name="myFormVar" class="categorySelect">
...                                        

Votre fonction javascript changeCategory désactive toutes les sélections avec la classe categorySelect, puis n'active que celle de votre categoryID actuel.

// Hide all category select boxes except the new one
function changeCategory(categoryID) {

   $("select.categorySelect").each(function (select) {
      select.hide();
      select.disable();
   });

   $(categoryID).show();
   $(categoryID).enable();
}

Lorsque vous masquez / désactivez ce prototype dans un prototype, non seulement il est masqué sur la page, mais il empêche la variable FORM de la publier. Ainsi, même si vous avez N sélections avec le même nom de variable FORM (myFormVar), seule la variable active est publiée.

Il n'y a pas si longtemps, j'ai pensé à quelque chose de similaire.

Utiliser jQuery et l'add-on TexoTela n'a pas été aussi difficile.

Tout d'abord, vous avez une structure de données similaire à la carte mentionnée ci-dessus:

var map = {
   'porsche': [ 'boxter', '911', 'carrera' ],
   'fiat': ['punto', 'uno']
}; 

Votre code HTML devrait ressembler à:

<select size="4" id="manufacturers">
</select>
<select size="4" id="models">
</select>

Ensuite, vous remplissez le premier combo avec du code jQuery tel que:

$(document).ready(
  function() {
    $("#bronsysteem").change( manufacturerSelected() );
  } );
);

où manufacturerSelected est le rappel enregistré sur l'événement onChange

function manufacturerSelected() {
  newSelection = $("#manufacturers").selectedValues();
  if (newSelection.length != 1) {
    alert("Expected a selection!");
    return; 
  }
  newSelection = newSelection[0];
  fillModels(newSelection);     
}

function fillModels(manufacterer) {
    var models = map[manufacturer];

    $("models").removeOption(/./); // Empty combo

    for(modelId in models) {
       model = models[modelId];
       $("models").addOption(model,model); // Value, Text
    }
}

Cela devrait faire l'affaire.

Veuillez noter qu'il peut y avoir des erreurs de syntaxe; J'ai modifié mon code pour refléter votre cas d'utilisation et j'ai dû me déshabiller pas mal de choses.

Si cela vous aide, j'apprécierais un commentaire.

En complément de mon précédent message; Vous pouvez insérer une balise de script dans votre JSP afin de pouvoir effectuer une itération sur votre carte. Vous trouverez un exemple d'itération sur des cartes dans Cartes dans Struts .

Ce que vous voudriez réaliser (si vous ne vous souciez pas de la soumission du formulaire), je pense quelque chose du genre:

<script>
  var map = {
  <logic:iterate id="entry" name="myForm" property="myMap">
     '<bean:write name=" user" property="key"/>' : [
     <logic:iterate id="model" name="entry" property="value">
       '<bean:write name=" model" property="name"/>' ,
     </logic:iterate>
     ] ,
 </logic:iterate>
  };
</script>

Vous avez encore des informations superficielles " " que vous voudrez peut-être empêcher, mais je pense que cela devrait faire l'affaire.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top