前端 JavaScript 觸發 `jsf.ajax.request()` 後,JSF 生命週期執行至 **Invoke Application** 階段,調用綁定在輸入框 `valueChangeListener` 和列表框 `valueChangeListener` 上的監聽器方法。本文完整呈現 `AutocompleteListener` 代碼,逐行解析核心邏輯:完成項過濾、列表框顯示/隱藏控制、選中值回寫、多實例隔離機制。
package com.corejsf;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.enterprise.context.SessionScoped;
import javax.faces.component.UIInput;
import javax.faces.component.UISelectItems;
import javax.faces.component.UISelectOne;
import javax.faces.context.FacesContext;
import javax.faces.event.ValueChangeEvent;
import javax.inject.Named;
@Named
@SessionScoped
public class AutocompleteListener implements Serializable {
private static final String COMPLETION_ITEMS_ATTR = "corejsf.completionItems";
// 1. 輸入框 valueChangeListener -> 觸發完成項過濾與列表顯示
public void valueChanged(ValueChangeEvent e) {
UIInput input = (UIInput) e.getSource(); // 觸發事件的輸入框
UISelectOne listbox = (UISelectOne) input.findComponent("listbox"); // 同組件內的列表框
if (listbox != null) {
UISelectItems items = (UISelectItems) listbox.getChildren().get(0); // 獲取 f:selectItems
Map attrs = listbox.getAttributes(); // 列表框屬性映射 (含 style、size)
// 核心:根據輸入值過濾完成項
List newItems = getNewItems((String) input.getValue(),
getCompletionItems(listbox, items, attrs));
// 更新列表框數據模型
items.setValue(newItems.toArray());
// 動態控制列表框顯示/隱藏、位置、大小
setListboxStyle(newItems.size(), attrs);
}
}
// 2. 列表框 valueChangeListener -> 用戶選中項,回寫輸入框並隱藏列表
public void completionItemSelected(ValueChangeEvent e) {
UISelectOne listbox = (UISelectOne) e.getSource();
UIInput input = (UIInput) listbox.findComponent("input"); // 對應的輸入框
if (input != null) {
input.setValue(listbox.getValue()); // 將選中值寫入輸入框 (觸發 model 更新)
}
// 隱藏列表框
Map attrs = listbox.getAttributes();
attrs.put("style", "display: none");
}
// 3. 過濾邏輯:前綴匹配 (忽略大小寫)
private List getNewItems(String inputValue, String[] completionItems) {
List newItems = new ArrayList<>();
if (inputValue == null || inputValue.isEmpty()) return newItems;
for (String item : completionItems) {
if (item.length() >= inputValue.length()) {
String prefix = item.substring(0, inputValue.length());
if (prefix.equalsIgnoreCase(inputValue)) {
newItems.add(item);
}
}
}
return newItems;
}
// 4. 列表框樣式動態控制:位置、顯示/隱藏、行數
private void setListboxStyle(int rows, Map attrs) {
if (rows > 0) {
// 從請求參數獲取 JS 傳來的 x,y 座標 (見 autoComplete.js)
Map reqParams = FacesContext.getCurrentInstance()
.getExternalContext().getRequestParameterMap();
attrs.put("style", "display: inline; position: absolute; left: "
+ reqParams.get("x") + "px; top: " + reqParams.get("y") + "px");
attrs.put("size", rows == 1 ? 2 : rows); // 至少顯示 2 行避免單行過窄
} else {
attrs.put("style", "display: none");
}
}
// 5. 獲取/緩存原始完成項數組 (支援多實例隔離)
private String[] getCompletionItems(UISelectOne listbox,
UISelectItems items, Map attrs) {
String[] completionItems = (String[]) attrs.get(COMPLETION_ITEMS_ATTR);
if (completionItems == null) {
completionItems = (String[]) items.getValue(); // 首次從 f:selectItems 獲取
attrs.put(COMPLETION_ITEMS_ATTR, completionItems); // 緩存到列表框屬性
}
return completionItems;
}
}
用戶輸入 (keyup)
-> JS: jsf.ajax.request(input, event, {render: listboxId, x, y})
-> JSF 執行生命週期 (執行 input 的 valueChangeListener)
-> AutocompleteListener.valueChanged()
1. 讀取 input.getValue() (新輸入文本)
2. 從 listbox 屬性獲取/緩存原始完成項數組
3. 前綴過濾 -> newItems
4. items.setValue(newItems.toArray()) 更新列表數據模型
5. setListboxStyle() 計算 style (x,y,display:inline) 和 size
-> JSF Render Response: 僅渲染 listbox (render=clientId)
-> 瀏覽器局部更新: 列表框顯示過濾後項目,定位在輸入框下方
每個 `
用戶點擊列表項
-> JSF 觸發 listbox 的 ValueChangeEvent
-> AutocompleteListener.completionItemSelected()
1. listbox.getValue() 獲取選中項
2. input.setValue(...) 寫入輸入框 (觸發 model 更新 #{user.country})
3. attrs.put("style", "display: none") 隱藏列表
| 擴展點 | 建議實現 |
|---|---|
| 匹配模式 | 策略模式:`MatchStrategy` 介面 (前綴/模糊/正則/拼音),屬性注入。 |
| 數據源 | 注入 `CompletionProvider` 介面 (內存列表/JPA/REST/Redis),支援分頁/懶加載。 |
| 大小寫/語言環境 | 使用 `Collator.getInstance(locale)` 進行本地化比較。 |
| 無障礙 (ARIA) | 監聽器輸出 `aria-activedescendant`、`aria-expanded` 屬性到 listbox。 |
| 客戶端緩存 | JS 端維護 LRU 緩存,減少 Ajax 請求 (需失效策略)。 |
`AutocompleteListener` 展示了 JSF 2 **組件行為封裝在 CDI Bean 中** 的優雅模式:無需繼承 `UIComponent`、無需 `Renderer`、無需 `.taglib.xml`。通過標準 `ValueChangeEvent`、組件樹導航、屬性映射存儲狀態,實現了完整的自動完成交互邏輯,且天然支援多實例隔離。下篇將解析前端 JavaScript 協同邏輯 (`autoComplete.js`)。