Cómo rellenar dinámicamente un cuadro combinado con valores de un mapa según lo que se haya seleccionado en otro cuadro combinado

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

Pregunta

Ok, aquí hay uno para los gurús de Java / JavaScript:

En mi aplicación, uno de los controladores pasa un TreeMap a su JSP. Este mapa tiene nombres de fabricantes de automóviles como claves y listas de objetos de automóviles como valores. Estos objetos de automóvil son frijoles simples que contienen el nombre, id, año de producción, etc. del automóvil. Entonces, el mapa se ve algo como esto (esto es solo un ejemplo, para aclarar un poco las cosas):

Clave: Porsche
Valor: Lista que contiene tres objetos Car (por ejemplo, 911, Carrera, Boxter con sus respetables años de producción e identificaciones)
Clave: Fiat
Valor: Lista que contiene dos objetos Car (por ejemplo, Punto y Uno)
etc ...

Ahora, en mi JSP tengo dos comboboxes. Uno debe recibir una lista de fabricantes de automóviles (claves del mapa, esta parte sé cómo hacerlo), y el otro debe cambiar dinámicamente para mostrar los nombres de los automóviles cuando el usuario selecciona un determinado Fabricante del primer combobox. Así, por ejemplo, el usuario selecciona un " Porsche " en el primer cuadro combinado, y el segundo muestra inmediatamente " 911, Carrera, Boxter " ...

Después de pasar un par de días intentando descubrir cómo hacer esto, estoy listo para admitir la derrota. Probé un montón de cosas diferentes, pero cada vez que golpeo una pared en algún lugar del camino. ¿Alguien puede sugerir cómo debo abordar este? Sí, soy un novato en JavaScript, si alguien se estuviera preguntando ...

EDITAR: He vuelto a etiquetar esto como un desafío de código. Felicitaciones a cualquiera que resuelva este sin utilizar ningún marco de JavaScript (como JQuery).

¿Fue útil?

Solución 2

Bueno de todos modos, como dije, finalmente logré hacerlo solo, así que aquí está mi respuesta ...

Recibo el mapa de mi controlador de esta manera (estoy usando Spring, no sé cómo funciona esto con otros marcos):

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

Estos son mis 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>

Importé las siguientes clases (algunos nombres, por supuesto, se han cambiado):

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

Y aquí está el código que hace todo el trabajo duro:

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

Otros consejos

Me encanta un desafío.

No jQuery, solo javascript, probado en Safari.

Me gustaría agregar las siguientes observaciones por adelantado:

  • Ha fallado mucho tiempo debido al error comprobación.
  • Se generan dos partes; El primer nodo de script con el mapa y los contenidos del fabricante. SELECCIONAR
  • Funciona en mi máquina (TM) (Safari / OS X)
  • No hay (css) Estilismo aplicado. Tengo mal gusto asi que De todos modos, no sirve.

.

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

¿Estás usando Struts?

Necesitará algunos trucos de JavaScript (o AJAX) para lograr esto.

Lo que deberías hacer es, en algún lugar de tu código JavaScript (dejando de lado cómo lo generas por minuto):

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

Esto es básicamente una copia de la estructura de datos del lado del servidor, es decir, un mapa codificado por el fabricante, cada valor tiene una variedad de tipos de automóviles.

Luego, en su evento onchange para los fabricantes, necesitaría obtener la matriz del mapa definido anteriormente, y luego crear una lista de opciones a partir de eso. (Visite devguru.com: contiene mucha información útil sobre los objetos estándar de JavaScript).

Sin embargo, dependiendo de qué tan grande sea tu lista de autos, podría ser mejor ir por la ruta AJAX.

Necesitaría crear un nuevo controlador que buscara la lista de tipos de autos de un fabricante, y luego enviarlo a un JSP que devolviera JSON (no tiene que ser JSON, pero funciona bastante bien para mí).

Luego, use una biblioteca como jQuery para recuperar la lista de autos en su evento onchange para la lista de los fabricantes (jQuery es un marco de JavaScript excelente que debe saber; hace que el desarrollo con JavaScript sea mucho más fácil. La documentación es muy buena).

Espero que algo de eso tenga sentido?

Aquí hay una respuesta de trabajo, cortar y pegar en jsp sin ninguna biblioteca de etiquetas o dependencias externas de ningún tipo. El mapa con modelos está codificado, pero no debería suponer ningún problema.

Separé esta respuesta de mi respuesta anterior ya que el JSP agregado no mejora la legibilidad. Y en la "vida real" no cargaría a mi JSP con toda la lógica incorporada sino que la pondría en una clase en algún lugar. O usa etiquetas.

Todo lo que " primero " es prevenir los superfluos ", " En el código generado. El uso de un foreach no le da ningún conocimiento sobre la cantidad de elementos, por lo que debe verificar el último. También puede omitir el manejo del primer elemento y eliminar el último ", " después, disminuyendo la longitud del constructor en 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>

¿Qué tal algo como esto, usando prototipo? Primero, seleccione su casilla de categorías:

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

Luego, genera N diferentes cuadros de selección, uno para cada una de las subcategorías:

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

Su función de cambio de categoría javascript desactiva todas las selecciones con la categoría categorySelect, y luego habilita solo una para su categoryID actual.

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

Cuando se oculta / deshabilita de esta forma en un prototipo, no solo lo oculta en la página, sino que mantiene esa variable FORM de publicación. Por lo tanto, aunque tenga N selecciones con el mismo nombre de variable FORM (myFormVar), solo el activo se publica.

No hace mucho tiempo pensé en algo similar.

Usar jQuery y el complemento TexoTela no fue tan difícil.

Primero, tienes una estructura de datos como el mapa mencionado anteriormente:

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

Tu HTML debería parecer comparable a:

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

Luego, llena el primer combo con el código jQuery como:

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

donde manufacturerSelected es la devolución de llamada registrada en el 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
    }
}

Esto debería hacer el truco.

Tenga en cuenta que puede haber errores de sintaxis allí; He editado mi código para reflejar su caso de uso y tuve que quitarlo. bastante fuera.

Si esto ayuda, agradecería un comentario.

Como un complemento en mi publicación anterior; Puede poner una etiqueta de secuencia de comandos en su JSP donde itere sobre su mapa. Se puede encontrar un ejemplo de iteración sobre mapas en Maps in Struts .

Lo que te gustaría lograr (si no te importa el envío del formulario) es algo como:

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

Aún tienes algunos " superfluos, " que tal vez desee evitar, pero creo que esto debería hacer el truco.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top