Dinamicamente preencher um combobox com os valores de um mapa com base no que está selecionado em outro combobox

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

Pergunta

Ok, aqui está um para os gurus Java / JavaScript:

Em meu aplicativo, um dos controladores passa um TreeMap a ele do JSP. Este mapa tem nomes do fabricante de automóveis como chaves e listas de Car objetos como valores. Esses objetos de carro são feijão simples contendo o nome do carro, id, ano de produção etc. Assim, o mapa é algo como isto (isto é apenas um exemplo, para esclarecer as coisas um pouco):

Key: Porsche
Valor: Lista contendo três objetos de carro (por exemplo 911, Carrera, Boxter com seus anos respeitáveis ??de produção e ids)
Key: Fiat
Valor: Lista que contém dois objetos de viaturas (por exemplo, Punto e Uno)
etc ...

Agora, no meu JSP Eu tenho duas caixas de combinação. Um devem receber uma lista de fabricantes de automóveis (chaves do mapa - essa parte eu sei como fazer), eo outro deve mudança dynamicaly para exibir os nomes dos carros quando o usuário seleciona um certo fabricante a partir da primeira caixa de combinação. Assim, por exemplo, o usuário seleciona um "Porsche" na primeira caixa de combinação, eo segundo exibe imediatamente "911, Carrera, Boxter" ...

Depois de passar um par de dias tentando descobrir como fazer isso, eu estou pronto para admitir a derrota. Eu tentei um monte de coisas diferentes, mas cada vez que eu bati em uma parede somewehere ao longo do caminho. Alguém pode sugerir como eu deveria abordar esta? Sim, eu sou um novato JavaScript, se alguém estava pensando ...

EDIT: Eu retagged isso como um código de desafio. Kudos para quem resolve este sem usar qualquer framework JavaScript (como JQuery).

Foi útil?

Solução 2

Bem de qualquer maneira, como eu disse, eu finalmente consegui fazer isso por mim, então aqui está a minha resposta ...

Eu recebo o mapa do meu controlador como este (eu estou usando Spring, não sei como isso funciona com outras estruturas):

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

Estas são as minhas 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>

Eu importado as seguintes classes (alguns nomes têm, é claro, foi alterado):

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

E aqui está o código que faz todo o trabalho 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]);
  }      
}

Outras dicas

Eu adoro um desafio.

No jQuery, apenas javascript simples, testado em Safari.

Eu gostaria de acrescentar as seguintes observações com antecedência:

  • É faily long devido ao erro verificação.
  • Duas peças são gerados; o primeiro nó do script com o Mapa e o conteúdo do manufacterer SELECT
  • funciona na minha máquina (TM) (Safari / OS X)
  • Não há (css) estilo aplicado. Eu tenho mau gosto tão não adianta qualquer maneira.

.

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

Você está usando Struts?

Você vai precisar de alguns truques JavaScript (ou AJAX) para fazer isso.

O que você precisa fazer é, em algum lugar em seu código JavaScript (deixando de lado como você gerá-lo para o minuto):

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

Esta é basicamente uma cópia de sua estrutura de dados do lado do servidor, ou seja, um mapa introduzidos pelo fabricante, cada valor de ter uma variedade de tipos de carro.

Então, em seu evento onchange para os fabricantes, que você precisa para obter a matriz do mapa definido anteriormente, e em seguida, criar uma lista de opções do que isso. (Confira devguru.com - ele tem um monte de informações úteis sobre o padrão de objetos JavaScript).

Dependendo de quão grande é a sua lista de carros está, no entanto, que poderia ser melhor para ir a rota AJAX.

Você precisa criar um novo controlador que olhou para a lista de carros tipos dadas fabricante, e em seguida, encaminhar para um JSP que retornou JSON (ele não tem que ser JSON, mas funciona muito bem para mim).

Em seguida, use uma biblioteca, como jQuery para recuperar a lista de carros em seu evento onchange para a lista de fabricantes. (JQuery é um excelente framework JavaScript saber -. Ele faz desenvolvimento make com JavaScript muito mais fácil A documentação é muito bom).

Espero que alguns de que faz sentido?

Aqui é um trabalho, a resposta cut-and-paste em jsp sem quaisquer bibliotecas de tags ou dependências externas de qualquer natureza. O mapa com modelos é codificado, mas não deverá levantar quaisquer problemas.

separei esta resposta de minha resposta anterior como o JSP adicionado não melhorar a legibilidade. E na 'vida real' Eu não oneraria meu JSP com toda a lógica embutida, mas colocá-lo em um lugar de classe. Ou use tags.

Tudo o que "primeiro" material é para evitar redundância por "" no código gerado. Usando um foreach dosn't dar qualquer conhecimento sobre a quantidade de elementos, para que você verifique para o último. Você também pode pular o manuseio primeiro elemento e tira o último "" depois, diminuindo o comprimento construtor por 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>

Como sobre algo como isso, usando o protótipo? Em primeiro lugar, a sua caixa de seleção de categorias:

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

Em seguida, você saída N diferentes caixas de seleção, uma para cada uma das sub-categorias:

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

A sua função javascript changeCategory desativa todas as seleciona com categorySelect classe, e em seguida, permite apenas o um para o seu categoryID atual.

// 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 você oculta / desativar como este no protótipo, não apenas oculta-lo na página, mas vai manter essa variável de formulário de postagem. Então, mesmo que você tenha seleciona N com o mesmo nome variável de formulário (myFormVar), apenas as activas uma mensagens.

Não que há muito tempo eu pensei em algo semelhante.

Usando jQuery eo TexoTela add-on que não era tão difícil.

Primeiro, você tem uma estrutura de dados como o mapa mencionado acima:

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

O seu HTML deve olhar comparável a:

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

Em seguida, você preenche a primeira combinação com jQuery código como:

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

onde manufacturerSelected é o retorno de chamada registrado no 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
    }
}

Isso deve fazer o truque.

Por favor, note que pode haver erros de sintaxe lá; Eu editei o meu código para refletir seu caso de uso e teve que tira bastante fora muito.

Se isso ajuda eu apreciaria um comentário.

Como um add-on no meu post anterior; Você pode colocar uma tag de script em sua JSP onde você iterar sobre seu mapa. Um exemplo sobre a iteração sobre os mapas podem ser encontrados em Mapas em Struts .

O que você gostaria de alcançar (se você não se preocupam com o envio do formulário) é que eu acho que 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>

Você ainda tem algum superfuous "", que você pode querer evitar, mas acho que isso deve fazer o truque.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top