同事報案,在「以 MVVM 清單實作資料編輯介面」的經典應用場景(Knockout版範例、Angular版範例)遇見怪事。新増一筆資料後,將焦點移至 <input type="text"> 輸入欄位,若按下 Enter 資料會莫名消失,按一次消失一筆…
程式用了 jQuery、Bootstrape、Knockout、KendoUI,加上一堆自訂程式庫,無法斷定是誰造成,只好抽絲剝繭,以能重現問題為原則,將掛載的程式庫及 DOM 元素一一拆除。歷經一番功夫,最後竟發現是個 HTML 基本觀念,某自以為資深的網頁設計老鳥,乖乖上了一課。
用一個超精簡範例重現問題:Live Demo
將焦點停在 Input A,按下 Enter 鍵會觸發 Button A 的 onclick 事件;但同樣狀況則不會發生在 Input B 與 Button B 上,關鍵在於 Input A 與 Button A 被包在 <form></form> 之中,而 Input B / Button B 沒有:
<!DOCTYPEhtml>
<html>
<head>
<metacharset="utf-8">
<title>Enter on form</title>
</head>
<body>
<form>
<fieldset>
<legend>Inside Form</legend>
<inputtype="text"value="Input A"/>
<buttononclick="alert('Button A Clicked');return false;">Button A</button>
</fieldset>
</form>
<fieldset>
<legend>Outside Form</legend>
<inputtype="text"value="Input B"/>
<buttononclick="alert('Button B Clicked')">Button B</button>
</fieldset>
</body>
</html>
追究原因,「在輸入欄位按 Enter 送出表單」幾乎是所有瀏覽器的預設行為(感覺是網頁設計基本常識,但先前以寫 AJAX 跟 SPA 為主,碰 Form 的經驗不夠多,沒遇過還真就沒學到),Enter 送出表單可以理解,但瀏覽器送出前還幫忙按下 <form> 裡的第一顆按鈕倒是出乎意料。(在以上範例,Button A onclick alert 完要 return false,不然會觸發表單送出行為)再回頭觀察一開始的展示,按 Enter 被刪除的永遠是第一筆,由此可證。
2017-6-29補充: 貼文後不少網友提醒我 <button> 在各瀏覽器的預設 type 可能不同,而我這也才發現對 Chrome 與 IE 而言,<button> type 預設為 submit,是按 Enter 會連帶觸發按鈕的原因,改為 type="button" 後不再被 Enter 觸發點擊,但在這個案例中要防止表單被送出。感謝大家的回饋~(再上一課)
至於解決方案,用 prevent enter submit form 可以 Google 到一大票詢問與討論,常見做法是攔截 document 或 input 的 keypress 事件,在遇到 Enter 鍵時取消動作。但本案例倒不用這麼麻煩,該段程式以 AJAX 方式運作,用不到 <form> ,是因為寫在 ASP.NET WebForm 才被包在 <form> 中,只需將該段 HTML 移至 <form> 之外,問題即刻消失。
撇開這次遇到的特殊狀況,回到「按 Enter 會送出 Form 」行為上,其實存在爭議。Stackoverflow 上有一堆人詢問如何取消,卻也有人強力主張不該改掉(The Enter Key should Submit Forms, Stop Suppressing it),我則喜歡 StackExchange 的這篇觀點,作者以 Facebook 提供 Press Enter to send 選項為例(過去式 ,現已改版),認為交由使用者決定才是最佳選擇,附議!