Apps Script用Sheet生成動態網頁(29): 表單input動態候選清單(auto-complete)

我們常用搜尋時下方都會有個建議清單,這種可以稱為建議或候選清單。而在網頁表單上的說法則是自動完成(auto-complete),我們這裡就是要在上傳HTML檔案時,動態從伺服器取得這些建議幫我們列出候選清單,可以大幅增加使用者的使用體驗。

1. 前言

關於HTML5裡頭如何顯示候選清單的功能,請先下列參考這篇問答回覆的範例。

https://stackoverflow.com/questions/33985130/create-a-custom-autocomplete-list-for-an-input-field

裡頭透過指定input標籤的list屬性,並在後方設定datalist標籤與option列表就能讓瀏覽器識別候選清單。

2. 前端

在檔案表單裡面新增兩個參數,並關閉原本表單上的自動完成autocomplete="off"

  1. 監視使用者輸入的onNameChange
  2. 以及關聯候選清單的nameListId

src/client/demo-bootstrap/components/FileForm.jsx

const FileForm = ({
...
onNameChange = null,
nameListId = null,
...
<Form.Control
type="text"
name="the-name"
required="true"
onChange={onNameChange}
autocomplete="off"
list={nameListId}
/>
...
FileForm.propTypes = {
...
onNameChange: PropTypes.func,
nameListId: PropTypes.string,
};

在上傳HTML頁面裡頭我們實作的功能

這裡在處理使用者輸入會時遞延300毫秒才與後端伺服器溝通取得搜尋列表

src/client/demo-bootstrap/pages/UploadHtml.jsx

import React, { useState, useEffect } from 'react';
...
const ID_UPLOAD_NAME_CANDIDATES = 'html-name-candidates';
...
const UploadHtml = () => {
...
const [name, setName] = useState('');
const [nameCandidates, setCandidate] = useState([]);

useEffect(() => {
const timer = setTimeout(() => {
serverFunctions
.searchByNameInUploadedHtml(name)
.then(response => {
setCandidate(response);
})
.catch(error => {
console.error(error);
});
}, 300);
return () => clearTimeout(timer);
}, [name]);
...
<FileForm
titleName="網頁名稱"
onSubmit={onSubmit}
onNameChange={({ target }) => setName(target.value)}
nameListId={
nameCandidates.length > 0 ? ID_UPLOAD_NAME_CANDIDATES : null
}
/>
{nameCandidates.length > 0 && (
<datalist id={ID_UPLOAD_NAME_CANDIDATES}>
{nameCandidates.map((nameSuggestion, idx) => (
<option value={nameSuggestion} key={`nameSuggestion-${idx}`} />
))}
</datalist>
)}
...


3. 後端

新增使用者輸入的搜尋邏輯,這裡透過把目標與我們的資料都toLowerCase來強化搜尋候選清單的功能。

src/server/content.js

export function seachHtmlName(name) {
if (typeof name !== 'string' || name.length < 1) return [];
const target = name.toLowerCase();
try {
const sheet = getContentSheet();
return sheet
.getRange(1, 1 + COLUMN_IDX_OF_NAME, sheet.getLastRow(), 1)
.getValues()
.filter(r => r && r[0] != null && r[0].length > 0)
.map(r => r[0].toLowerCase())
.filter(htmlName => htmlName.indexOf(target) > -1);
} catch (error) {
// we do not record this error
}
return [];
}


新增暴露給用戶端使用的API

src/server/index.js

global.searchByNameInUploadedHtml = contentFunctions.seachHtmlName;


4. 展示成果




留言