Popolamento dinamico di una casella combinata con i valori di una mappa in base a ciò che è selezionato in un'altra casella combinata

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

Domanda

Ok, eccone uno per i guru Java / JavaScript:

Nella mia app, uno dei controller passa una TreeMap al suo JSP. Questa mappa ha i nomi dei produttori di auto come chiavi e gli elenchi di oggetti Car come valori. Questi oggetti Car sono semplici fagioli contenenti il ??nome dell'auto, l'id, l'anno di produzione ecc. Quindi, la mappa è simile a questa (questo è solo un esempio, per chiarire un po 'le cose):

Chiave: Porsche
Valore: elenco contenente tre oggetti Car (ad esempio 911, Carrera, Boxter con i loro rispettabili anni di produzione e ID)
Chiave: Fiat
Valore: elenco contenente due oggetti Car (ad esempio Punto e Uno)
ecc ...

Ora, nel mio JSP ho due combobox. Uno dovrebbe ricevere un elenco di case automobilistiche (chiavi dalla mappa - questa parte che so fare), e l'altra dovrebbe cambiare dinamicamente per visualizzare i nomi delle auto quando l'utente seleziona un certo produttore dalla prima casella combinata. Pertanto, ad esempio, l'utente seleziona una "Porsche". nella prima casella combinata e la seconda visualizza immediatamente " 911, Carrera, Boxter " ...

Dopo aver trascorso un paio di giorni cercando di scoprire come fare, sono pronto ad ammettere la sconfitta. Ho provato molte cose diverse ma ogni volta che ho colpito un muro da qualche parte lungo la strada. Qualcuno può suggerire come dovrei avvicinarmi a questo? Sì, sono un novizio di JavaScript, se qualcuno si chiedeva ...

EDIT: l'ho ricodificato come una sfida al codice. Complimenti a chiunque risolva questo problema senza usare alcun framework JavaScript (come JQuery).

È stato utile?

Soluzione 2

Beh, comunque, come ho detto, sono finalmente riuscito a farlo da solo, quindi ecco la mia risposta ...

Ricevo la mappa dal mio controller in questo modo (sto usando Spring, non so come funziona con altri framework):

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

Queste sono le mie combinazioni:

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

Ho importato le seguenti classi (alcuni nomi sono stati, ovviamente, cambiati):

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

Ed ecco il codice che fa tutto il duro lavoro:

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

Altri suggerimenti

Adoro solo una sfida.

No jQuery, solo javascript semplice, testato su Safari.

Vorrei aggiungere in anticipo le seguenti osservazioni:

  • È fallito a lungo a causa dell'errore controllo.
  • Vengono generate due parti; il primo nodo dello script con la mappa e il contenuto del produttore SELEZIONA
  • Funziona su My Machine (TM) (Safari / OS X)
  • Non c'è (css) styling applicato. Ho un cattivo gusto così non serve comunque.

.

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

Stai usando Struts?

Per farlo, avrai bisogno di alcuni trucchi JavaScript (o AJAX).

Quello che dovresti fare è, da qualche parte nel tuo codice JavaScript (a prescindere da come lo generi per il minuto):

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

Questa è sostanzialmente una copia della struttura dei dati sul lato server, ovvero una mappa codificata dal produttore, ogni valore con una matrice di tipi di auto.

Quindi, nel tuo evento onchange per i produttori, dovrai ottenere l'array dalla mappa definita sopra e quindi creare un elenco di opzioni da quello. (Dai un'occhiata a devguru.com - ha molte informazioni utili sugli oggetti JavaScript standard).

Tuttavia, a seconda di quanto è grande il tuo elenco di auto, potrebbe essere meglio seguire la rotta AJAX.

Dovresti creare un nuovo controller che ha cercato l'elenco dei tipi di auto dato a un produttore, e quindi inoltrato a un JSP che ha restituito JSON (non deve essere JSON, ma funziona abbastanza bene per me).

Quindi, utilizza una libreria come jQuery per recuperare l'elenco delle auto nel tuo evento onchange per l'elenco di produttori. (jQuery è un eccellente framework JavaScript da sapere - rende lo sviluppo con JavaScript molto più semplice. La documentazione è molto buona).

Spero che questo abbia senso?

Ecco una risposta funzionante taglia e incolla in jsp senza alcuna libreria di tag o dipendenze esterne di sorta. La mappa con i modelli è hardcoded ma non dovrebbe presentare alcun problema.

Ho separato questa risposta dalla mia risposta precedente poiché il JSP aggiunto non migliora la leggibilità. E nella "vita reale" non caricerei il mio JSP con tutta la logica incorporata, ma lo metterei in una classe da qualche parte. Oppure usa i tag.

Tutto ciò che è "primo" lo scopo è prevenire i superflui "," nel codice generato. L'uso di un dos foreach non ti dà alcuna conoscenza sulla quantità di elementi, quindi controlli per ultimo. Puoi anche saltare la gestione del primo elemento e rimuovere l'ultimo ", " successivamente diminuendo la lunghezza del costruttore di 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>

Che ne dici di qualcosa del genere, usando il prototipo? Innanzitutto, la casella selezionata di categorie:

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

Quindi, vengono emesse N caselle di selezione diverse, una per ciascuna delle sottocategorie:

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

La tua funzione JavaScript di changeCategory disabilita tutte le selezioni con class categorySelect, quindi abilita solo quella per il tuo ID categoria corrente.

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

Quando nascondi / disabiliti in questo modo nel prototipo, non solo lo nasconde nella pagina, ma manterrà la pubblicazione della variabile FORM. Quindi anche se hai N seleziona con lo stesso nome di variabile FORM (myFormVar), solo quello attivo è attivo.

Non molto tempo fa ho pensato a qualcosa di simile.

L'uso di jQuery e del componente aggiuntivo TexoTela non è stato poi così difficile.

Innanzitutto, hai una struttura di dati come la mappa menzionata sopra:

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

Il tuo HTML dovrebbe apparire paragonabile a:

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

Quindi, riempi la prima combo con il codice jQuery come:

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

dove producerSelected è il callback registrato sull'evento 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
    }
}

Questo dovrebbe fare il trucco.

Si noti che potrebbero esserci errori di sintassi; Ho modificato il mio codice per riflettere il tuo caso d'uso e ho dovuto rimuovere abbastanza fuori.

Se questo aiuta, apprezzerei un commento.

Come componente aggiuntivo sul mio post precedente; Puoi inserire un tag di script nel tuo JSP in cui esegui l'iterazione sulla tua mappa. Un esempio di iterazione su mappe è disponibile in Maps in Struts .

Quello che vorresti ottenere (se non ti interessa l'invio del modulo) è che penso a qualcosa del tipo:

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

Hai ancora dei superflui ", " che potresti voler prevenire, ma penso che questo dovrebbe fare il trucco.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top