頁面包含兩個下拉選單:**第一級「所屬行業」**、**第二級「所屬明細行業」**。選擇第一級後,即時從資料庫查詢對應明細並填充第二級,無需重新整理頁面。
webapp/
├── search.jsp # 主頁面 + AJAX 觸發邏輯
├── css/
│ ├── main.js # JavaScript: AJAX 請求、DOM 更新
│ └── bytetostr.vbs # VBScript: 位元組陣列轉字串 (解決 responseBody 中文亂碼)
// main.js
function findObject(fName, initValue) {
// fName: 目標容器 ID (如 'hy_dm', 'hymx_dm')
// initValue: 父層選中值 (第一級傳空字串,第二級傳第一級代碼)
var xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
// 同步請求 (async=false) 簡化流程,生產建議改非同步 + Promise
xmlhttp.open("POST", "search.jsp?findObject=" + fName + "&initValue=" + encodeURIComponent(initValue), false);
xmlhttp.send();
// responseBody 為位元組陣列,需 VBScript 轉換
var html = bytes2BSTR(xmlhttp.responseBody);
document.getElementById(fName).innerHTML = html;
}
// 頁面載入時初始化兩級選單
window.onload = function() {
findObject("hy_dm", ""); // 第一級:無父值
findObject("hymx_dm", ""); // 第二級:初始空
};
' bytetostr.vbs (VBScript)
Function bytes2BSTR(vIn)
Dim i, strReturn, ThisCharCode, NextCharCode
strReturn = ""
For i = 1 To LenB(vIn)
ThisCharCode = AscB(MidB(vIn, i, 1))
If ThisCharCode < &H80 Then
strReturn = strReturn & Chr(ThisCharCode)
Else
NextCharCode = AscB(MidB(vIn, i + 1, 1))
strReturn = strReturn & Chr(CLng(ThisCharCode) * &H100 + CInt(NextCharCode))
i = i + 1
End If
Next
bytes2BSTR = strReturn
End Function
原理:`xmlhttp.responseBody` 回傳原始位元組,IE 中直接用 `responseText` 可能因編碼錯誤顯示亂碼。VBScript `AscB/MidB/Chr` 逐位元組處理雙位元組字元 (Big5/GB2312/UTF-16LE) 組合為字串。
⚠️ **現代替代方案**:設定伺服器回應標頭 `Content-Type: text/html; charset=UTF-8`,前端使用 `fetch`/`XMLHttpRequest` + `responseText`,或 `responseType = "text"`,完全避免 VBScript 依賴 (僅 IE 支援)。
<%@ page contentType="text/html; charset=UTF-8" language="java"
import="java.sql.*, javax.sql.DataSource, javax.naming.*" %>
<%
// 1. 取得參數
String findObject = request.getParameter("findObject");
String initValue = request.getParameter("initValue");
if (findObject == null) { return; }
// 2. 取得資料庫連線 (建議使用 JNDI DataSource / 連線池)
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
StringBuilder html = new StringBuilder();
try {
// DataSource 範例 (需在 context.xml / server.xml 配置)
Context initCtx = new InitialContext();
DataSource ds = (DataSource) initCtx.lookup("java:comp/env/jdbc/MyDB");
conn = ds.getConnection();
// 3. 依參數決定查詢邏輯
String sql;
if ("hy_dm".equals(findObject)) {
// 第一級:查詢頂層行業 (代碼前 7 碼分組)
sql = "SELECT hy_dm, hy_mc FROM dm_hy GROUP BY LEFT(hy_dm, 7)";
html.append("<select name='hy_dm' onchange=\"findObject('hymx_dm', this.value)\">");
} else if ("hymx_dm".equals(findObject)) {
// 第二級:查詢對應明細行業
if (initValue == null || initValue.isEmpty()) {
sql = "SELECT hy_dm, hy_mc FROM dm_hy";
} else {
sql = "SELECT hy_dm, hy_mc FROM dm_hy WHERE hy_dm LIKE ?";
}
html.append("<select name='hymx_dm'>");
} else {
return; // 未知參數
}
pstmt = conn.prepareStatement(sql);
if ("hymx_dm".equals(findObject) && initValue != null && !initValue.isEmpty()) {
pstmt.setString(1, initValue + "%");
}
rs = pstmt.executeQuery();
while (rs.next()) {
String dm = rs.getString("hy_dm");
String mc = rs.getString("hy_mc");
html.append("<option value='").append(dm).append("'>")
.append(mc).append("</option>");
}
html.append("</select>");
} catch (Exception e) {
e.printStackTrace();
html.append("<select><option>載入失敗</option></select>");
} finally {
// 4. 資源關閉
try { if (rs != null) rs.close(); } catch (Exception ignore) {}
try { if (pstmt != null) pstmt.close(); } catch (Exception ignore) {}
try { if (conn != null) conn.close(); } catch (Exception ignore) {}
}
// 5. 輸出 HTML 片段
out.print(html.toString());
%>
<%@ page contentType="text/html; charset=UTF-8" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>二級下拉聯動示範</title>
<script language="vbscript" src="css/bytetostr.vbs"></script>
<script language="javascript" src="css/main.js"></script>
</head>
<body>
<div>所屬行業:</div>
<div id="hy_dm"></div> <!-- 第一級選單容器 -->
<div>所屬明細行業:</div>
<div id="hymx_dm"></div> <!-- 第二級選單容器 -->
</body>
</html>
CREATE TABLE dm_hy (
hy_dm VARCHAR(20) NOT NULL PRIMARY KEY, -- 如 'A01', 'A0101', 'A010101'
hy_mc VARCHAR(100) NOT NULL -- 如 '農業', '農作物種植', '稻米種植'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 範例資料
INSERT INTO dm_hy VALUES
('A01', '農業'),
('A0101', '農作物種植'),
('A010101', '稻米種植'),
('A010102', '小麥種植'),
('A02', '林業'),
('A0201', '造林'),
('A020101', '人工造林');
| 痛點 | 現代方案 |
|---|---|
| ActiveXObject / VBScript 僅 IE 支援 | 原生 fetch() + response.json(),後端回傳 JSON |
| 同步阻塞請求 (async=false) | 非同步 await fetch() + Loading 狀態 |
| 後端輸出 HTML 片段 (XSS 風險、耦合) | 後端回傳 JSON 陣列 `[{value:'A01', label:'農業'},...]`,前端動態建立 ` |
| JSP 混合 Java 程式碼 | Spring MVC / Spring Boot @RestController + Thymeleaf / 純前端框架 (Vue/React) |
| 手動關閉資源 | Try-with-resources / HikariCP 連線池 / @Transactional |