Learning Objectives
- 說明表單由 form 與各種輸入欄位組成,並讀取文字欄、密碼欄與多行文字欄的 value
- 用單選按鈕與下拉選單做單選項目,讀出使用者選了哪一個
- 用核取方塊與多選下拉選單做多選項目,讀出所有勾選的項目
- 處理 input、change、submit 事件,用 preventDefault 攔下送出,並用 FormData 一次取得所有欄位
表單:收集使用者的輸入
登入、報名、問卷、購物結帳,都是表單。HTML 表單由一個 <form> 加上許多輸入欄位組成:
<form action="/signup" method="post">
<label for="user">帳號</label>
<input type="text" id="user" name="user">
<button type="submit">送出</button>
</form>
action:資料要送到哪個網址;method:送出的方式,get(預設,資料接在網址的查詢字串)或post(資料放在請求內容裡)- 每個欄位的
name是送出時的欄位名稱;沒有name的欄位不會被送出 <label for="user">對應id="user"的欄位:點標籤文字就等於點那個欄位,螢幕閱讀器也會唸出標籤
JavaScript 在表單上要做的事,就是讀出使用者填了什麼,並在送出之前檢查、整理(下一課的正規表示法會用來檢查格式)。
文字輸入欄
| 欄位 | HTML |
|---|---|
| 單行文字 | <input type="text"> |
| 搜尋欄位 | <input type="search"> |
| 密碼(輸入時顯示成圓點) | <input type="password"> |
| 多行文字 | <textarea></textarea> |
不管哪一種,讀取與設定內容都用 value,讀到的一律是字串(即使使用者輸入的是數字):
const user = document.getElementById('user');
console.log(user.value); // 目前的內容
user.value = ''; // 清空
要在使用者打字的同時做事(即時預覽、字數統計),監聽 input 事件:每改一個字就發生一次。
change 事件則要等內容改變而且離開欄位才發生。
單選項目:單選按鈕與下拉選單
單選按鈕(radio button):name 相同的一組按鈕裡只能選一個;checked 屬性設定預設選項。
<label><input type="radio" name="shirt" value="S"> S</label>
<label><input type="radio" name="shirt" value="M" checked> M</label>
讀出選了哪一個:
const picked = document.querySelector('input[name="shirt"]:checked'); // 一個都沒選時是 null
console.log(picked ? picked.value : '還沒選');
console.log(form.elements.shirt.value); // 也可以從表單的 elements 用 name 取得;沒選時是空字串
下拉選單(dropdown list):<select> 裡放好幾個 <option>,selected 屬性設定預設選項。
<select id="city" name="city">
<option value="tp">臺北</option>
<option value="kh" selected>高雄</option>
</select>
| 讀取 | 結果 |
|---|---|
city.value |
選到的 <option> 的 value(沒寫 value 時就是選項文字) |
city.selectedIndex |
選到第幾個(從 0 開始) |
city.options[city.selectedIndex].text |
選到的選項顯示的文字,例如「高雄」 |
city.value = 'tp' |
用程式改成選臺北 |
選項改變時發生 change 事件。選項少(2~5 個)時用單選按鈕,一眼就看得到所有選項;選項多時用下拉選單比較省空間。
想一想:文字欄與單選項目
使用者在 <input id="age"> 輸入 20,document.getElementById('age').value 是什麼?
一組單選按鈕要能「只選一個」,必須符合什麼條件?
下拉選單裡選到的是 <option value="kh">高雄</option>,select.value 與 select.options[select.selectedIndex].text 分別是什麼?
一組單選按鈕都沒有被選取時,document.querySelector('input[name="shirt"]:checked') 會得到什麼?
多選項目:核取方塊與多選下拉選單
核取方塊(checkbox):可以勾選零個、一個或好幾個,適合購物清單、待辦事項、多重選擇題。
<label><input type="checkbox" name="item" value="雞蛋" checked> 雞蛋</label>
<label><input type="checkbox" name="item" value="牛奶"> 牛奶</label>
- 有沒有勾選看
checked(true/false);value不管有沒有勾都是同一個值 - 取得所有勾選的項目:
document.querySelectorAll('input[name="item"]:checked'),再轉成陣列取出value - 用程式勾選:
box.checked = true
多選下拉選單:在 <select> 加上 multiple 屬性就能選好幾個,size 設定一次顯示幾個選項。
使用者要按住 Ctrl(Windows)或 command(Mac)才能多選,很多人不知道,所以一般建議用核取方塊。
讀取時,select.value 只會得到第一個選項,要用 select.selectedOptions 取得全部選到的 <option>:
const picked = Array.from(multi.selectedOptions).map(o => o.value);
送出表單與 FormData
按下表單裡的 <button type="submit">(或在文字欄按 Enter)會發生 submit 事件,瀏覽器預設會把資料送到 action 並載入新頁面。
用 JavaScript 處理表單時,第一件事通常是攔下這個預設動作:
form.addEventListener('submit', (e) => {
e.preventDefault(); // 不要離開這一頁
const fd = new FormData(form); // 一次取得表單裡所有有 name 的欄位
console.log(fd.get('name')); // 單一個值
console.log(fd.getAll('topic')); // 同名的多個值(核取方塊)→ 陣列
});
監聽 form 的 submit,而不是按鈕的 click:使用者在文字欄按 Enter 也會送出表單,只聽按鈕的 click 會漏掉。
FormData 收集欄位的規則:
- 只收有
name的欄位;停用(disabled)的欄位不收 - 單選按鈕只收選到的那一個;核取方塊只收有勾選的,沒勾的不會出現
for (const [key, value] of fd)可以逐一取得;Object.fromEntries(fd)可以轉成物件,但同名的多個值只會留下最後一個,有核取方塊時要另外用getAll
之後學到 fetch 時,FormData 可以直接當作請求的內容送到伺服器。
想一想:表單事件與 FormData
要在使用者每打一個字時就更新字數統計,應該監聽哪一個事件?
下面的程式會印出什麼?
const fd = new FormData();
fd.append('topic', 'js');
fd.append('topic', 'css');
console.log(fd.get('topic'), fd.getAll('topic'));
下面的程式會印出什麼?
const fd = new FormData();
fd.append('name', 'Amy');
fd.append('topic', 'js');
fd.append('topic', 'css');
console.log(Object.fromEntries(fd));
表單裡有一個核取方塊 <input type="checkbox" name="agree">,使用者沒有勾選就送出,new FormData(form).get('agree') 是什麼?
重點精華回顧
- 表單由
<form>與輸入欄位組成;action是送出的網址、method是get(預設)或post;欄位要有name才會被送出,<label for>對應欄位的id。 - 文字欄(
text、search、password、textarea)用value讀寫,讀到的一律是字串。 - 單選:單選按鈕用相同的
name分成一組,用:checked找出選到的那個;下拉選單用select.value,顯示文字用options[selectedIndex].text。 - 多選:核取方塊看
checked,用querySelectorAll('…:checked')取得所有勾選項目;多選下拉選單用selectedOptions。 input每次改變就發生,change改變並離開欄位(或選項改變)才發生;表單送出監聽form的submit,用e.preventDefault()攔下。new FormData(form)一次取得所有欄位:get取第一個值、getAll取全部;沒勾選的核取方塊不會出現。