如何在ObservableHQ中创建适配大量选中项的可搜索多选框?
在ObservableHQ中实现紧凑型可搜索多选框
核心思路
通过自定义viewof组件,将大量选中项折叠为数量摘要(如“已选中60项”),仅在需要时展开查看详情,同时集成搜索过滤功能,适配Observable的响应式环境,避免直接操作DOM导致的兼容性问题。
实现代码
viewof selectedItems = { const options = Array.from({length: 100}, (_, i) => `选项${i+1}`); // 模拟100个选项 const container = document.createElement("div"); container.style.maxWidth = "400px"; // 搜索框 const searchInput = document.createElement("input"); searchInput.type = "text"; searchInput.placeholder = "搜索选项..."; searchInput.style.width = "100%"; searchInput.style.padding = "8px"; searchInput.style.boxSizing = "border-box"; container.appendChild(searchInput); // 选中状态摘要 const summary = document.createElement("div"); summary.style.margin = "8px 0"; summary.style.padding = "8px"; summary.style.border = "1px solid #eee"; summary.style.borderRadius = "4px"; summary.style.cursor = "pointer"; let isExpanded = false; // 选项列表容器 const listContainer = document.createElement("div"); listContainer.style.maxHeight = "200px"; listContainer.style.overflowY = "auto"; listContainer.style.display = "none"; container.appendChild(listContainer); // 全选复选框 const selectAll = document.createElement("label"); selectAll.style.display = "block"; selectAll.style.padding = "4px 8px"; selectAll.innerHTML = `<input type="checkbox"> 全选`; listContainer.appendChild(selectAll); // 渲染选项列表 const renderOptions = (filter = "") => { listContainer.innerHTML = ""; listContainer.appendChild(selectAll); const filtered = options.filter(opt => opt.toLowerCase().includes(filter.toLowerCase())); filtered.forEach(opt => { const label = document.createElement("label"); label.style.display = "block"; label.style.padding = "4px 8px"; label.innerHTML = `<input type="checkbox" value="${opt}" ${selected.has(opt) ? "checked" : ""}> ${opt}`; label.querySelector("input").addEventListener("change", updateSelected); listContainer.appendChild(label); }); }; // 选中集合 const selected = new Set(options); // 默认全选 const updateSelected = () => { selected.clear(); // 收集选中项 Array.from(listContainer.querySelectorAll("input[type='checkbox']:not(:first-child):checked")).forEach(el => selected.add(el.value)); // 更新全选状态 selectAll.querySelector("input").checked = selected.size === options.length; // 更新摘要 summary.textContent = selected.size === 0 ? "未选中任何选项" : selected.size > 10 ? `已选中${selected.size}项(点击展开查看)` : `已选中:${Array.from(selected).join(", ")}`; // 触发Observable的更新 container.dispatchEvent(new CustomEvent("input")); }; // 初始更新 updateSelected(); // 搜索事件 searchInput.addEventListener("input", () => { renderOptions(searchInput.value); // 保持选中状态 Array.from(listContainer.querySelectorAll("input[type='checkbox']:not(:first-child)")).forEach(el => { if (selected.has(el.value)) el.checked = true; }); }); // 摘要点击展开/折叠 summary.addEventListener("click", () => { isExpanded = !isExpanded; listContainer.style.display = isExpanded ? "block" : "none"; if (isExpanded) renderOptions(searchInput.value); }); // 全选事件 selectAll.querySelector("input").addEventListener("change", (e) => { const checkboxes = listContainer.querySelectorAll("input[type='checkbox']:not(:first-child)"); checkboxes.forEach(el => el.checked = e.target.checked); updateSelected(); }); // 初始渲染 renderOptions(); container.appendChild(summary); // 返回值 Object.defineProperty(container, "value", {get: () => Array.from(selected)}); return container; }
关键特性说明
- 紧凑显示:当选中项超过10个时,自动折叠为数量摘要,点击可展开查看具体选项
- 搜索过滤:实时过滤选项列表,同时保留已选中状态
- 全选功能:支持一键全选/取消全选,适配过滤后的列表
- Observable友好:通过
viewof和自定义input事件实现响应式更新,避免直接DOM操作的兼容性问题
内容的提问来源于stack exchange,提问作者Jakub.Novotny
相关产品推荐
相关产品推荐

