Хорошо, вот один для гуру Java / JavaScript:
В моем приложении один из контроллеров передает TreeMap своему JSP. На этой карте названия производителей автомобилей указаны в качестве ключей, а объекты Lists of Car - в качестве значений. Эти объекты Car представляют собой простые бобы, содержащие имя автомобиля, идентификатор, год выпуска и т. д. Итак, карта выглядит примерно так (это просто пример, чтобы немного прояснить ситуацию):
Ключ: Porsche
Значение: список, содержащий три объекта "Автомобиль" (например, 911, Carrera, Boxter с их приличными годами производства и идентификаторами)
Ключ: Fiat
Значение: список, содержащий два объекта "Автомобиль" (например, Punto и Uno)
так далее...
Теперь в моем JSP есть два комбинированных списка. Один должен получить список производителей автомобилей (ключи с карты - эту часть я знаю, как это сделать), а другой должен динамическое изменение для отображения названий автомобилей, когда пользователь выбирает определенного производителя из первого поля со списком. Так, например, пользователь выбирает «Porsche» в первом выпадающем списке, а второй сразу отображает «911, Carrera, Boxter» ...
Потратив пару дней на то, чтобы понять, как это сделать, я готов признать поражение. Я пробовал много разных вещей, но каждый раз на пути где-то ударялся о стену. Кто-нибудь может подсказать, как мне подойти к этому?
Да, я новичок в JavaScript, если кому-то интересно ...
Обновлено: Я пометил это как вызов кода. Престижность всем, кто решает эту проблему без использования какой-либо инфраструктуры JavaScript (например, JQuery).




Вы используете Struts?
Для этого вам понадобится уловка JavaScript (или AJAX).
Что вам нужно сделать, так это где-то в вашем коде JavaScript (не говоря уже о том, как вы его генерируете на минуту):
var map = {
'porsche': [ 'boxter', '911', 'carrera' ],
'fiat': ['punto', 'uno']
};
По сути, это копия вашей серверной структуры данных, то есть карта с ключом производителя, каждое значение имеет массив типов автомобилей.
Затем, в событии onchange для производителей, вам нужно будет получить массив из карты, определенной выше, а затем создать из него список параметров. (Посетите devguru.com - там много полезной информации о стандартных объектах JavaScript).
Однако, в зависимости от того, насколько велик ваш список автомобилей, лучше всего выбрать AJAX-маршрут.
Вам нужно будет создать новый контроллер, который будет искать список типов автомобилей, указанный производителем, а затем перейти к JSP, который вернет JSON (это не обязательно должен быть JSON, но для меня он работает довольно хорошо).
Затем используйте библиотеку, такую как jQuery, чтобы получить список автомобилей в вашем событии onchange для списка производителей. (jQuery - отличный JavaScript-фреймворк, который нужно знать - он значительно упрощает разработку с помощью JavaScript. Документация очень хороша).
Надеюсь, в этом есть смысл?
Как насчет того, чтобы использовать прототип? Во-первых, ваше поле выбора категорий:
<SELECT onchange = "changeCategory(this.options[this.selectedIndex].value); return false;">
<OPTION value = "#categoryID#">#category#</OPTION>
...
Затем вы выводите N разных полей выбора, по одному для каждой подкатегории:
<SELECT name = "myFormVar" class = "categorySelect">
...
Ваша функция javascript changeCategory отключает все выборки с помощью класса categorySelect, а затем включает только один для вашего текущего идентификатора категории.
// 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();
}
Когда вы скрываете / отключаете это в прототипе, он не только скрывает его на странице, но и предотвращает публикацию этой переменной FORM. Таким образом, даже если у вас есть N выборок с тем же именем переменной FORM (myFormVar), только активная публикация.
Если я правильно понимаю, каждый раз, когда производитель автомобилей добавляется или удаляется с карты, мне придется добавлять или удалять соответствующий тег выбора в JSP. Поскольку я получаю эту карту из веб-службы поставщика, у меня нет возможности узнать, сколько элементов может иметь карта.
И я до сих пор не знаю, как узнать названия машин. Имя автомобиля представляет собой String в объекте Car, который хранится в List, который хранится как значение в Map. Уф ... поговорим о сложном ...
Не так давно я думал о чем-то подобном.
Используя jQuery и надстройку TexoTela, это было не так уж и сложно.
Во-первых, у вас есть структура данных, подобная упомянутой выше карте:
var map = {
'porsche': [ 'boxter', '911', 'carrera' ],
'fiat': ['punto', 'uno']
};
Ваш HTML должен выглядеть примерно так:
<select size = "4" id = "manufacturers">
</select>
<select size = "4" id = "models">
</select>
Затем вы заполняете первую комбинацию кодом jQuery, например:
$(document).ready(
function() {
$("#bronsysteem").change( manufacturerSelected() );
} );
);
где ManufacturerSelected - обратный вызов, зарегистрированный в событии 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
}
}
Это должно помочь.
Обратите внимание, что там могут быть синтаксические ошибки; Я отредактировал свой код, чтобы отразить ваш вариант использования, и мне пришлось удалить довольно много вышло.
Если это поможет, я был бы признателен за комментарий.
Прежде всего, спасибо за ответ, extraneon. Я воспользуюсь им, если другого выхода не найду. Однако здесь есть две проблемы. Во-первых, я делаю это не для своего личного проекта, а как часть более крупного корпоративного приложения для компании, в которой я сейчас работаю. Из-за этого,
Возможно, мне не разрешат использовать JQuery. Они немного раздражительны, когда дело доходит до внедрения новых фреймворков в приложение. Но, если я не смогу заставить его работать по-другому, мне просто нужно будет попросить моего начальника убедить бизнесменов, что это единственный способ сделать это. Однако более серьезная проблема - получить карту
работать в JavaScript. Я уже упоминал, что получаю его из веб-службы, и мой контроллер передает его в JSP, поэтому я не могу просто жестко закодировать его в JavaScript, я должен иметь возможность сопоставить свою карту с объектом JavaScript, чтобы я мог манипулируйте им там. Я все еще работаю над своим собственным решением, поэтому
если где-нибудь доберусь, выложу решение здесь. Еще раз всем, спасибо за все ответы и извините за длинный комментарий.
В качестве дополнения к моему предыдущему сообщению; Вы можете поместить тег скрипта в свой JSP, где вы перебираете карту. Пример перебора карт можно найти в Карты в Struts.
Чего вы хотели бы достичь (если вас не волнует отправка формы), я думаю, что-то вроде:
<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>
У вас все еще есть лишнее "," которое вы могли бы предотвратить, но я думаю, что это должно помочь.
Я просто люблю вызовы.
Никакого jQuery, только простой javascript, протестированный в Safari.
Заранее хочу добавить следующие замечания:
.
<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>
Очень мило, extraneon! Однако карта все еще жестко запрограммирована, поэтому проблема все еще существует ... :-) Честно говоря, мне удалось решить проблему, хотя решение не очень хорошее. Часть заполнения выполняется примерно так же, как и вы. Я еще не опубликовал его, потому что у меня много дел, но я опубликую
это как можно скорее, наверное, послезавтра. Хотя ваше решение не является законченным, я даю вам голос только за усилия, которые вы вложили в него :-) Подсказка: мое решение включает использование скриптлета.
В ПОРЯДКЕ. Я должен был написать JSP и динамически генерировать modelsPerManufacturer и SELECT производителя. не используя ничего, кроме входной карты. Я обновлю ответ сегодня вечером (сейчас 7 утра).
Вот рабочий, вырезанный и вставленный ответ в jsp без каких-либо библиотек тегов или каких-либо внешних зависимостей. Карта с моделями жестко запрограммирована, но не должна вызывать никаких проблем.
Я отделил этот ответ от своего предыдущего ответа, поскольку добавленный JSP не улучшает читаемость. И в «реальной жизни» я бы не стал обременять свой JSP всей встроенной логикой, а поместил бы его где-нибудь в классе. Или используйте теги.
Все, что «в первую очередь» нужно для предотвращения "избыточного потока" в сгенерированном коде. Использование foreach не дает вам никаких сведений о количестве элементов, поэтому вы проверяете последнее. Вы также можете пропустить обработку первого элемента и удалить последний "," впоследствии, уменьшив длину компоновщика на 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>
В любом случае, как я уже сказал, мне наконец удалось сделать это самому, так что вот мой ответ ...
Я получаю карту от своего контроллера следующим образом (я использую Spring, не знаю, как это работает с другими фреймворками):
<c:set var = "manufacturersAndModels" scope = "page" value = "${MANUFACTURERS_AND_MODELS_MAP}"/>
Это мои комбо:
<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>
Я импортировал следующие классы (некоторые названия, конечно, были изменены):
<%@ page import = "org.mycompany.Car,java.util.Map,java.util.TreeMap,java.util.List,java.util.ArrayList,java.util.Set,java.util.Iterator;" %>
А вот код, который выполняет всю тяжелую работу:
<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]);
}
}
Спасибо за ответ, Фил. На самом деле я использую Spring. Хотя ваш ответ хорош, я все же хотел бы услышать другие мнения :-)