js的input檢測(給愛的人發個郵件吧)
通過一系列的練習掌握如何分解問題、解決問題,在這個過程中如何設計自己的程式碼結構,如何優化及重構。
課程描述
通過一個小練習綜合運用 HTML、CSS、JavaScript,我們要實現一個郵箱輸入的提示功能。最終示意圖:
部分程式碼參考:
<div class="wrapper">
<input id="email-input" type="text">
<ul id="email-sug-wrapper" class="email-sug"></ul>
</div>
// 郵箱字尾List參考 var postfixList = ['163.com', 'gmail.com', '126.com', 'qq.com', '263.net'];
先來個基礎的
需求
根據下面需求實現如示意圖所示的郵箱輸入提示功能,注意,根據要求只需實現下面功能
- 當用戶沒有任何輸入時,提示框消失
- 當用戶輸入字元後,顯示提示框,並且把使用者輸入的內容自動拼上郵箱字尾進行顯示
- 暫時不用考慮示意圖中的紅色和藍色背景色的邏輯
- 注意使用者輸入中前後空格需要去除
設計
從今天開始,我們希望你在寫程式碼之前開始畫流程圖
請閱讀:流程圖怎麼畫
推薦工具:
對於有經驗的同學,你們可以跳過下面的部分,零基礎的同學請跟著我一起進行設計。
這個任務的總目標是使用者有輸入的時候,進行對應的提示,所以核心流程是:
使用者輸入->提示框進行反饋
那核心流程進一步進行拆解,就包括了:
發現使用者輸入->獲取使用者輸入內容->生成提示框提示內容->進行提示
第一步,發現使用者輸入,可以利用監聽使用者在輸入框的輸入對應的事件,嘗試用keyup, keypress, keydown以及oninput四個事件分別來測試對於使用者輸入的事件監聽,並在事件響應函式中增加console.log('event handle')。並嘗試以下輸入方式,觀察提示框內容變化的情況,以及console的輸出情況:
- 一個字母一個字母的輸入
- 一個字母一個字母輸入,同時加上按回車鍵,ESC鍵,上下左右鍵
- 按住某個字母鍵不動
記住試驗結論,然後就上面4個事件進行搜尋,閱讀相關規範標準及技術文章。根據實驗結果選擇你認為合適的事件監聽方式。
第一步是後面幾步的起點,所以,我們可以把後面幾步各自封裝成一個函式,在第一步裡進行呼叫
第二步,獲取使用者輸入,這個比較簡單,注意考慮trim
第三步,生成提示框提示內容,根據第二步獲得的內容,遍歷postfixList陣列,生成要在email-sug-wrapper的UL中填充的內容。
第四步,根據實際是否有提示內容,控制email-sug-wrapper的顯示/隱藏情況
這樣,這個任務被分解為4個步驟,你要做的就是實現這4個步驟的程式碼細節。
程式碼結構參考如下:
inputDom的輸入監聽 = function() {
獲取使用者輸入,生成提示框中的提示內容,將提示內容新增到email-sug-wrapper中
控制email-sug-wrapper的顯示/隱藏狀態
}
function 獲取使用者輸入() {
拿到input輸入框的輸入內容trim後返回
}
function 生成提示框中的提示內容() {
獲取使用者輸入
遍歷postfixList {
把使用者輸入和每一個postfix進行結合成為每一個Li
}
返回生成的提示內容
}
function 將提示內容新增到email-sug-wrapper中() {
獲取生成提示框中的提示內容
將內容新增到email-sug-wrapper中
}
function 控制email-sug-wrapper的顯示/隱藏狀態() {
if 使用者輸入為空 {
隱藏提示框
} else {
顯示提示框
}
}
function 隱藏提示框() {
做具體隱藏提示框的操作
}
function 顯示提示框() {
做具體顯示提示框的操作
}
測試用例:
測試用例的閱讀方式:描述中間有一個->符號,->前面的內容表示請你這麼操作來測試,->後面的內容表示應該出現的結果
- 輸入框中沒有任何輸入內容->無提示框
- 輸入框中輸入了很多半形或者全形的空格->無提示框
- 輸入框中輸入了"abc"->出現提示框,提示框中的內容為abc開頭,後面跟著@163.com,@gmail.com等一系列的提示
- 輸入框中輸入了" abc "->出現提示框,提示框中的內容為abc開頭,後面跟著@163.com,@gmail.com等一系列的提示
- 輸入框中先輸入"abc",然後再全部刪掉->輸入abc時出現提示框,全部刪除後提示框消失
完成以上測試用例即可,這時候我們有一個最基本的提示功能了,也許你已經發現有一些可以優化的地方,沒關係,我們接下來一起一步一步來優化。
小優化編碼
需求
如果我們輸入的是 [email protected],這個時候出現的提示框內容是
- [email protected]@163.com
- [email protected]@gmail.com
- [email protected]@126.com
……
很明顯,上面的提示框不是一個符合使用者需求的提示,我們需要做一些優化:
- 當用戶輸入含有 @ 符號時,我們選取使用者輸入的@前面的字元來和字尾拼接
設計
同樣有經驗的同學請跳過,零基礎同學和我一起
上面的需求,在我們上一步的結構中,需要在“生成提示框中的提示內容”這個函式中進行調整
我們把需求解讀一下,關鍵在於判斷使用者輸入有沒有 @,那如何判斷字串中是否包含某個字元呢?可以回顧一下字串相關的內容。
在發現有 @ 之後,要做的就是拿到 @ 符號之前的字串內容,來和postfixList進行拼接。
程式碼結構:
function 生成提示框中的提示內容() {
用來拼接的使用者輸入內容 = 獲取使用者輸入
if 使用者輸入含有@ {
用來拼接的使用者輸入內容 = 只使用@之前的字串
}
遍歷postfixList {
把用來拼接的使用者輸入內容和每一個postfix進行結合成為每一個Li
}
返回生成的提示內容
}
測試用例
- 輸入a->出現提示框,提示[email protected],[email protected]……
- 輸入[email protected]>出現提示框,提示[email protected],[email protected]……
- 輸入[email protected]>出現提示框,提示[email protected],[email protected]……
- 輸入[email protected]>出現提示框,提示[email protected],[email protected]……
編碼
需求
這下出現的提示好多了,不過使用者如果已經輸入了@1,說明他大概率要輸入163或者126,我們需要讓我們的提示更加符合使用者的期望。滿足以下需求:
- 當用戶輸入了 @ 及部分字尾時,只從 postfixList 選取符合使用者輸入預期的字尾,我們以字首匹配為要求。
- 當用戶輸入不滿足任何字首匹配時,則顯示全部提示
設計
這個需求依然需要調整“生成提示框中的提示內容”,如果使用者輸入的字元含有 @,我們需要拿到 @ 之後的字串,來和 postfixList 中每個字串做字首匹配,符合要求的我們才會使用
程式碼結構
function 生成提示框中的提示內容() {
用來拼接的使用者輸入內容 = 獲取使用者輸入
if 使用者輸入含有@ {
用來拼接的使用者輸入內容 = @之前的字串
用來字首匹配的使用者輸入內容 = @之後的字串
}
遍歷postfixList {
if 用來字首匹配的使用者輸入內容字首匹配遍歷字串元素
把用來拼接的使用者輸入內容和這個字串進行結合成為一個Li
}
返回生成的提示內容
}
測試用例
- 輸入[email protected]>出現提示框,提示[email protected], [email protected]
- 輸入[email protected]>出現提示框,提示[email protected]
- 輸入[email protected]>出現提示框,提示[email protected]
- 輸入[email protected]>出現提示框,提示[email protected]
- 輸入[email protected]>出現提示框,提示[email protected]
- 輸入[email protected]>出現提示框,提示[email protected]
- 輸入[email protected] (兩個空格)->出現提示框,提示[email protected]
- 輸入[email protected]>出現提示框,出現全部提示
新的需求編碼
需求
上面我們只完成了提示,但提示還沒有直接作用到選擇中,我們現在完成以下需求:
- 使用CSS實現:滑鼠滑過提示框的某一個提示時,這個提示內容背景色變化,表示滑鼠經過了這個DOM節點
- 滑鼠如果點選某個提示,則提示內容進入輸入框,同時提示框消失
- 在上個步驟結束後,在輸入框中任意再輸入字元或刪除字元,則重新開始出現提示框
設計
滑鼠點選,是一個新的使用者輸入,所以我們需要有一個新的事件監聽,那用哪一個DOM節點來監聽這個滑鼠事件呢?想一想前面提到的事件代理,選擇一個合適的事件監聽方式。
當監聽到使用者點選某一個提示的Li後,我們需要做的就是,把Li對應的提示內容,放在輸入框中,同時隱藏提示框
程式碼結構
選擇一個合適的DOM節點.監聽滑鼠點選 = function () {
if 被點選的是不是提示框中的Li節點 {
獲取被點選Li對應的提示內容
將內容放到input輸入框中
隱藏輸入框
}
}
需求
嘗試在輸入框中輸入<b>,看看提示框發生了什麼
閱讀
設計
我們需要在兩個地方進行處理,一個是在生成提示內容那裡,對於特殊字元進行轉義編碼,另一個是在把滑鼠點選的提示框內容轉回輸入框時進行解碼。
加上鍵盤
需求
我們給提示框加上3個按鍵的功能,分別是回車和上下鍵,使得可以通過鍵盤操作進行提示框的選擇
- 當有提示框的時候,預設第一個提示為被選擇狀態,用一個和滑鼠滑過不一樣的背景色來標識
- 當有輸入框的時候,按上鍵,可以向上移動選擇狀態,如果按鍵之前的被選擇提示是第一個,則被選狀態移到最下面一個
- 當有輸入框的時候,按下鍵,可以向下移動選擇狀態,如果按鍵之前的被選擇提示是最後一個,則被選狀態移到第一個
- 當有輸入框時,按回車鍵,則將當前被選中狀態的提示內容,放到輸入框中,並隱藏提示框
- 當沒有輸入框的時候,這3個鍵盤按鍵無響應
- 當用戶輸入發生改變的時候,選擇狀態都重新切回到第一個提示
閱讀
設計
整體任務核心流程為:
監聽鍵盤事件->判斷按鍵->如果是上下鍵則變更選中狀態,如果是回車鍵則進行內容進輸入框操作
監聽鍵盤事件及判斷按鍵:我們需要在鍵盤監聽中,增加對於這3個鍵的特殊處理。回憶一下之前的實驗,keydown,keypress,keyup,oninput,對於這3個鍵的監聽是和之前的合併,還是另外處理,不妨實驗一下。
如果是上下鍵則變更選中狀態:這裡的關鍵在於,我們如何記錄選中狀態,以及如何改變。對於零基礎的同學,在不基於任何框架的情況下,我們介紹兩種基本思路:
- 基於DOM,當初次渲染提示框時,在第一個Li中設定一個選中樣式的CSS,在按上下鍵的時候,我們通過樣式找到設定了特殊樣式的Li,清除掉它的樣式,然後根據上下鍵,判斷要設定狀態的新一個Li是誰,並設定上。按回車的時候,則同樣是通過樣式找到這個Li,拿到它的內容,回填給input
基於DOM的思路一程式碼結構
// 需要修改一下之前的inputDom的輸入監聽
inputDom的輸入監聽 = function() {
// 新增
如果按鍵不是上下及回車重置選中狀態()
獲取使用者輸入,生成提示框中的提示內容,將提示內容新增到email-sug-wrapper中
控制email-sug-wrapper的顯示/隱藏狀態
}
function 重置選中狀態() {
找到當前為選中狀態的Li
if (當前選中狀態Li不是第一個) {
清除掉它的選中狀態
設定第一個Li為選中狀態
}
}
// 監聽特殊3個鍵的鍵盤事件,這個事件可能就是inputDom的輸入監聽,也有可能是另外一個,請自己測試後判斷
監聽特殊3個鍵的鍵盤事件 = function() {
找到當前為選中狀態的Li
清除掉它的選中狀態
if 按的是上鍵 {
if 找到的Li不是第一個 {
將它的前一個Li設為選中
} else {
將最後一個Li設為選中
}
}
if 按的是下鍵 {
if 找到的Li不是最後一個 {
將它的下一個Li設為選中
} else {
將第一個Li設為選中
}
}
if 按的是回車 {
將找到的Li的HTML內容解碼後填到input中
隱藏提示框
}
}
- 基於資料,我們設定一個變數,來儲存當前選擇的index(即當前選中的是第幾行,從0開始計數),當發生上下鍵操作的時候,直接改變index值,然後重新渲染提示框中的所有html內容,根據index設定來操作後的選擇提示樣式,回車的時候,直接根據index來獲取對應的內容
基於資料的程式碼結構
// 增加一個變數,用於儲存當前選中的提示Li的序號
var nowSelectTipIndex = 0;
// 需要修改一下之前的“生成提示框中的提示內容()”
function 生成提示框中的提示內容() {
獲取使用者輸入
遍歷postfixList {
把使用者輸入和每一個postfix進行結合成為每一個Li
}
// 新增
將第nowSelectTipIndex個Li的樣式設定為被選樣式
返回生成的提示內容
}
function 將提示內容新增到email-sug-wrapper中() {
獲取生成提示框中的提示內容
將內容新增到email-sug-wrapper中
}
// 需要修改一下之前的inputDom的輸入監聽
inputDom的輸入監聽 = function() {
// 新增
如果按鍵不是上下及回車重置選中狀態()
獲取使用者輸入,生成提示框中的提示內容,將提示內容新增到email-sug-wrapper中
控制email-sug-wrapper的顯示/隱藏狀態
}
function 重置選中狀態() {
將 nowSelectTipIndex 設為0
}
// 監聽特殊3個鍵的鍵盤事件,這個事件可能就是inputDom的輸入監聽,也有可能是另外一個,請自己測試後判斷
監聽特殊3個鍵的鍵盤事件 = function() {
if 按的是上鍵 {
if nowSelectTipIndex不是第一個 {
nowSelectTipIndex設定為當前提示框的Li的個數 - 1
} else {
nowSelectTipIndex - 1
}
}
if 按的是下鍵 {
if nowSelectTipIndex小於Li的最大索引 {
nowSelectTipIndex設定為 0
} else {
nowSelectTipIndex + 1
}
}
if 按的是回車 {
從當前提示框中選第 nowSelectTipIndex 個Li,將其HTML內容解碼後填到input中
隱藏提示框
}
}
介紹完兩種思路後,請你用兩種思路都實現一遍,我們更加鼓勵基於資料的思路,這樣可以方便我們把使用者介面、互動和資料業務邏輯進行解耦。當然上面的資料思路中,我們可以做得更加徹底一些,哪裡還可以優化呢?這就是留給大家的作業。
優化體驗
需求
當我們進入頁面,或者當我們點選滑鼠進行提示選擇後,輸入框的焦點就不在了,所以請你優化一下使用者體驗:
- 一進入頁面就將焦點放在輸入框中
- 使用者點選滑鼠,進行提示選擇後,焦點依然在輸入框中
- 使用者按ESC鍵的時候,對使用者輸入進行全選
- 對你還能想到的其它使用者體驗的方式進行優化
程式碼
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link href="../style/emailPrac.css" rel="stylesheet">
</head>
<body>
<div class="wrapper">
<label class="label">
<input id="email-input" type="text" placeholder="請輸入郵件地址">
</label>
<ul id="email-sug-wrapper" class="email-sug"></ul>
</div>
<script src="../script/emailPrac.js" rel="stylesheet"></script>
</body>
</html>
var input = document.getElementById('email-input');
var sug = document.getElementById('email-sug-wrapper');//提示框
var nowSelectTipIndex = 0;
// 判斷按鍵
input.oninput = function(event) {
judgeKey(event);
// 獲取使用者輸入,生成提示框中的提示內容,將提示內容新增到
// email-sug-wrapper中
var liText = createTips(xssProtect());
addTips(liText);
// 控制email-sug-wrapper的顯示/隱藏狀態
sugDisplayControl(xssProtect());
};
input.onkeydown = judge;
sug.onclick = function (event) {
var ev = event || window.event;
var target = ev.target || ev.srcElement;
if(target.nodeName.toLowerCase()==='li'&&target.click) {
input.value = decoding(target.innerHTML);
hidden();
}
input.focus();
};
// xss防禦攻擊,編碼轉碼
function xssProtect() {
var text= getInput();
var temp = document.createElement('div');
(temp.textContent !== undefined ) ? (temp.textContent = text) : (temp.innerText = text);
return temp.innerHTML;
}
// xss防禦攻擊,編碼解碼
function decoding(text) {
// 1.首先動態建立一個容器標籤元素,如DIV
var temp = document.createElement("div");
// 2.然後將要轉換的字串設定為這個元素的innerHTML(ie,火狐,google都支援)
temp.innerHTML = text;
// 3.最後返回這個元素的innerText(ie支援)或者textContent(火狐,google支援),
// 即得到經過HTML解碼的字串了。
var output = temp.innerText || temp.textContent;
temp = null;
return output;
}
// 獲取文字中的內容
function getInput() {
// 拿到input輸入框的輸入內容trim後返回
var text = input.value,
myTrim = "";
if(text.length>=1) {
myTrim = text.trim();
}return myTrim;
}
// 生成提示框
function createTips(content) {
var text = [];
// 郵箱字尾List參考
var postfixList = ['163.com', 'gmail.com', '126.com', 'qq.com', '263.net'];
// 遍歷postfixList
if(content!=="")
if (content.indexOf('@')!==-1 ) {
var text1 = content.slice(0, content.indexOf('@')),
text2 = content.slice(content.indexOf('@') + 1);
var j = 0;
for (var i = 0; i < postfixList.length; i++) {
if (postfixList[i].indexOf(text2) !== -1
&& postfixList[i].indexOf(text2) === 0
)
{
text[j] = text1 +'@'+ postfixList[i];
j++;
}
}
}
else {
for(var x=0;x<postfixList.length;x++) {
// 把使用者輸入和每一個postfix進行結合成為每一個Li
text[x] = content + '@'+ postfixList[x];
}
}
// 返回生成的提示內容
return text;
}
// 移除舊的li列表
function removeLi() {
while(sug.hasChildNodes()&&sug.firstChild!=null) {
sug.removeChild(sug.firstChild);
}
}
// 向提示框中新增提示內容
function addTips(liText) {
// 刪除上一次新增的li
removeLi();
// 將內容新增到email-sug-wrapper中
for(var j=0;j<liText.length;j++) {
var li = document.createElement('li');
li.innerHTML = liText[j];
sug.appendChild(li);
}
var aLi = sug.querySelectorAll('li');
if(aLi.length>0)
aLi[nowSelectTipIndex].className = "checked";
}
// 控制提示框是否顯示
function sugDisplayControl(content) {
if (content===""|| content[0]==='@') {
// 隱藏提示框
hidden();
} else {
// 顯示提示框
display();
}
}
// 隱藏提示框
function hidden() {
// 做具體隱藏提示框的操作
sug.style.display = 'none';
}
// 顯示提示框
function display() {
// 做具體顯示提示框的操作
sug.style.display = 'block';
}
// 重置選中狀態
function removeDefault() {
nowSelectTipIndex = 0;
}
// 檢測並重新設定
function judgeKey(event) {
var e = event || window.event || arguments.callee.caller.arguments[0];
if(e.keyCode!==13 && e.keyCode!==38 && e.keyCode!==40) {
removeDefault();
}
}
// 判斷四個特殊按鍵
function judge(event){
var e = event || window.event || arguments.callee.caller.arguments[0];
var aLi = sug.querySelectorAll('li');
if(sug.style.display = 'block') {
if (e && e.keyCode === 27) { // 按 Esc
//要做的事情
input.select();
}
if (e && e.keyCode === 13) { // 按 enter
// 要做的事情
input.value = decoding(aLi[nowSelectTipIndex].innerHTML);
hidden();
}
if (e && e.keyCode === 38) {// up
// 要做的事情
e.preventDefault();
if (nowSelectTipIndex === 0) {
aLi[nowSelectTipIndex].className = "";
nowSelectTipIndex = aLi.length - 1;
aLi[nowSelectTipIndex].className = "checked";
}
else {
aLi[nowSelectTipIndex].className = "";
nowSelectTipIndex -= 1;
aLi[nowSelectTipIndex].className = "checked";
}
}
if (e && e.keyCode === 40) { // down
//要做的事情
if (nowSelectTipIndex === aLi.length - 1) {
aLi[nowSelectTipIndex].className = "";
nowSelectTipIndex = 0;
aLi[nowSelectTipIndex].className = "checked";
}
else {
aLi[nowSelectTipIndex].className = "";
nowSelectTipIndex += 1;
aLi[nowSelectTipIndex].className = 'checked';
}
}
}
}
//input框的聚焦
input.focus();
程式碼又一個功能沒完成,就是當所有後綴都不匹配時,應該顯示提示框的所有li,但我這個沒有任何顯示,其他的都按要求完成