在JSP/Servlet中填充级联下拉列表

41
假设我有三个下拉列表控件,名称分别为dd1dd2dd3。每个下拉列表的值都来自数据库。dd3的值取决于dd2的值,而dd2的值取决于dd1的值。请问我该如何调用servlet来解决这个问题?
4个回答

49

基本上有三种方法可以实现这一点:

  1. 在第一个下拉菜单的onchange事件期间将表单提交给servlet(您可以使用JavaScript完成此操作),让servlet将第一个下拉菜单中选择的项目作为请求参数获取,让它从数据库中获取第二个下拉菜单的关联值作为Map<String, String>,并将它们存储在请求作用域中。最后让JSP/JSTL显示第二个下拉列表中的值。您可以使用JSTL(只需在/WEB-INF/lib中放置jstl-1.2.jarc:forEach标记进行此操作。您可以在与JSP页面相关联的ServletdoGet()方法中预填充第一个列表。

 <select name="dd1" onchange="submit()">
     <c:forEach items="${dd1options}" var="option">
         <option value="${option.key}" ${param.dd1 == option.key ? 'selected' : ''}>${option.value}</option>
     </c:forEach>
 </select>
 <select name="dd2" onchange="submit()">
     <c:if test="${empty dd2options}">
         <option>Please select parent</option>
     </c:if>
     <c:forEach items="${dd2options}" var="option">
         <option value="${option.key}" ${param.dd2 == option.key ? 'selected' : ''}>${option.value}</option>
     </c:forEach>
 </select>
 <select name="dd3">
     <c:if test="${empty dd3options}">
         <option>Please select parent</option>
     </c:if>
     <c:forEach items="${dd3options}" var="option">
         <option value="${option.key}" ${param.dd3 == option.key ? 'selected' : ''}>${option.value}</option>
     </c:forEach>
 </select>

然而有一个警告,这将提交整个表单并导致“内容闪烁”,可能会对用户体验产生负面影响。您还需要基于请求参数在同一表单中保留其他字段。您还需要在servlet中确定请求是更新下拉菜单(子下拉菜单值为null)还是提交实际表单。

以Javascript对象的形式打印出第二个和第三个下拉列表的所有可能值,并在第一个下拉列表的onchange事件期间使用Javascript函数填充第二个下拉列表的选定项目。不需要提交表单或进行服务器循环。

 <script>
     var dd2options = ${dd2optionsAsJSObject};
     var dd3options = ${dd3optionsAsJSObject};
     function dd1change(dd1) {
         // Fill dd2 options based on selected dd1 value.
         var selected = dd1.options[dd1.selectedIndex].value;
         ...
     }
     function dd2change(dd2) {
         // Fill dd3 options based on selected dd2 value.
         var selected = dd2.options[dd2.selectedIndex].value;
         ...
     }
 </script>

 <select name="dd1" onchange="dd1change(this)">
     <c:forEach items="${dd1options}" var="option">
         <option value="${option.key}" ${param.dd1 == option.key ? 'selected' : ''}>${option.value}</option>
     </c:forEach>
 </select>
 <select name="dd2" onchange="dd2change(this)">
     <option>Please select parent</option>
 </select>
 <select name="dd3">
     <option>Please select parent</option>
 </select>

然而需要注意的是,当有大量的项目时,这可能会变得不必要又昂贵。假设您有每个100个可能项的3个步骤,那么在JS对象中将有100 * 100 * 100 = 1,000,000个项目。HTML页面的长度将超过1MB。

使用Javascript中的XMLHttpRequest,在第一个下拉列表的onchange事件期间异步请求到servlet,让servlet将第一个下拉列表的选择项目作为请求参数获取,让它从数据库中获取第二个下拉列表的关联值,以XML或JSON字符串的形式返回。最后让JavaScript通过HTML DOM树(如前所述的Ajax方式)显示第二个下拉列表中的值。最好的方法是使用jQuery

 <%@ page pageEncoding="UTF-8" %>
 <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
 <!DOCTYPE html>
 <html lang="en">
     <head>
         <title>SO question 2263996</title>
         <script src="http://code.jquery.com/jquery-latest.min.js"></script>
         <script>
             $(document).ready(function() {
                 $('#dd1').change(function() { fillOptions('dd2', this); });
                 $('#dd2').change(function() { fillOptions('dd3', this); });
             });
             function fillOptions(ddId, callingElement) {
                 var dd = $('#' + ddId);
                 $.getJSON('json/options?dd=' + ddId + '&val=' + $(callingElement).val(), function(opts) {
                     $('>option', dd).remove(); // Clean old options first.
                     if (opts) {
                         $.each(opts, function(key, value) {
                             dd.append($('<option/>').val(key).text(value));
                         });
                     } else {
                         dd.append($('<option/>').text("Please select parent"));
                     }
                 });
             }
         </script>
     </head>
     <body>
         <form>
             <select id="dd1" name="dd1">
                 <c:forEach items="${dd1}" var="option">
                     <option value="${option.key}" ${param.dd1 == option.key ? 'selected' : ''}>${option.value}</option>
                 </c:forEach>
             </select>
             <select id="dd2" name="dd2">
                 <option>Please select parent</option>
             </select>
             <select id="dd3" name="dd3">
                 <option>Please select parent</option>
             </select>
         </form>
     </body>
 </html>

其中/json/options后面的Servlet可以如下:

 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
     String dd = request.getParameter("dd"); // ID of child DD to fill options for.
     String val = request.getParameter("val"); // Value of parent DD to find associated child DD options for.
     Map<String, String> options = optionDAO.find(dd, val);
     String json = new Gson().toJson(options);
     response.setContentType("application/json");
     response.setCharacterEncoding("UTF-8");
     response.getWriter().write(json);
 }

在这里,GsonGoogle Gson,它可以轻松地将完整的Java对象转换为JSON格式,反之亦然。另请参阅如何使用Servlet和Ajax?


厉害的代码BalusC。如果您不介意,我想问一下关于您最后一个方法的几个问题。在您的函数fillOption()中,$.getJSON这一行,看起来像是您向servlet发送了GET请求,但是您在哪里指定了servlet的URL呢?此外,从servlet返回的结果是否存储在“opts”中。那么,“opts”就像一个JSON对象吗?您能更详细地解释一下这一行:$('>option',dd).remove();吗? - Thang Pham
1
@Harry Pham:$.getJSON的文档在这里:http://api.jquery.com/jQuery.getJSON/ URL只是/json/options。同时请查看文本(不仅仅是代码)。您可以选择任何URL。opts确实是由servlet返回的JSON字符串。另请参阅JSON链接以了解更多信息。如果您熟悉Javabeans,那么JSON应该足够熟悉。$('>option', dd).remove()从下拉列表中删除所有先前的选项,否则它将只被追加,追加等等。顺便说一句,如果您喜欢答案,请点赞。我发现您几乎从未投过票。 - BalusC
2
@BalusC:你的 JQuery 示例不会起作用,因为在 fillOptions 函数内部,“this”将指向 Window 对象。只有在传递给 .change 函数的函数内部,“this”才会指向 HTMLSelectElement。我必须将“this”传递给 fillOptions(“fillOptions('dd2', this)”),以便在另一端检索值(“function fillOptions(ddId, callingElement)”和“$(callingElement).val()”)。 - AndrewBourgeois
@AndrewBourgeois 你真是个天才!确实,你的修正让它起作用了。我已经对帖子进行了修正。正在等待批准中... - Christian Vielma
@BalusC 感谢您详尽的回答。它对我帮助很大! - neo108
显示剩余3条评论

4
您可能需要多个servlet来完成此操作。
Servlet 1:从数据库中加载第一个下拉列表的值,在JSP页面上构建下拉列表。当用户选择一个值时,提交到servlet2。
Servlet 2:检索第一个列表中的值,并为第二个列表的值执行数据库搜索。构建第二个列表。当用户选择第二个值时,将其提交到servlet3。
Servlet 3:检索第二个下拉列表中选择的值,并执行数据库搜索以获取最后一个下拉列表的值。
您可能需要考虑使用AJAX使列表的填充对用户无缝地显示。如果您愿意这样做,jQuery有一些非常不错的插件可以使这变得非常容易。
     <form action="servlet2.do">
          <select name="dd1" onchange="Your JavaScript Here">
               <option>....
          </select>
     </form>

您可以编写JavaScript代码,在onchange事件中提交表单。如果使用现有的库,如jQuery,则会简单10倍。


我和我的朋友有同样的想法,感谢合作,但我不知道在dd1失去焦点时如何调用它。 - deven
你可以使用一点 JavaScript 来提交值。我会添加一些。 - Vincent Ramdhanie

4
根据您的问题,您实际上并没有使用Web框架,而是使用servlet来呈现HTML页面。我会友善地说您落后了十年:),人们使用JSP(和像struts这样的Web框架)来完成此类任务。但是,话虽如此,以下是具体步骤:
  1. 在表单中创建一个隐藏字段,并将值设置为“1”,“2”或“3”,具体取决于要填充的下拉列表;
  2. 在您的servlet中,捕获该值(request.getParamter()),然后使用'case'/if/else语句返回适当的下拉列表值。
我再次强调,只需使用Web框架,或至少使用普通的jsp即可完成此操作。

3

这是一个非常棒的简单解决方案。我喜欢JQuery代码非常小,也很感谢提供GSON API的链接。所有的例子都使得实现变得非常容易。

在构建JSON服务器URL时遇到了一个问题,需要引用父级SELECT(例如$(this).val()),需要指定:selected属性。我稍微修改了脚本以包含建议的更新。感谢最初的代码。

<script>
$(document).ready(function() 
{
    $('#dd1').change(function() { fillOptions('dd1', 'dd2'); });
    $('#dd2').change(function() { fillOptions('dd2', 'dd3'); });
});

function fillOptions(parentId, ddId) 
{
    var dd = $('#' + ddId);
    var jsonURL = 'json/options?dd=' + ddId + '&val=' + $('#' + parentId + ' :selected').val();
    $.getJSON(jsonURL, function(opts) 
    {
        $('>option', dd).remove(); // Clean old options first.
        if (opts) 
        {
            $.each(opts, function(key, value) 
            {
                dd.append($('<option/>').val(key).text(value));
            });
        } 
        else 
        {
            dd.append($('<option/>').text("Please select parent"));
        }
    });
}
</script>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接