1. 程式人生 > >js的input檢測(給愛的人發個郵件吧)

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],這個時候出現的提示框內容是

很明顯,上面的提示框不是一個符合使用者需求的提示,我們需要做一些優化:

  • 當用戶輸入含有 @ 符號時,我們選取使用者輸入的@前面的字元來和字尾拼接

設計

同樣有經驗的同學請跳過,零基礎同學和我一起

上面的需求,在我們上一步的結構中,需要在“生成提示框中的提示內容”這個函式中進行調整

我們把需求解讀一下,關鍵在於判斷使用者輸入有沒有 @,那如何判斷字串中是否包含某個字元呢?可以回顧一下字串相關的內容。

在發現有 @ 之後,要做的就是拿到 @ 符號之前的字串內容,來和postfixList進行拼接。

程式碼結構:

function 生成提示框中的提示內容() {
    用來拼接的使用者輸入內容 = 獲取使用者輸入
    if 使用者輸入含有@ {
        用來拼接的使用者輸入內容 = 只使用@之前的字串
    }
    遍歷postfixList {
        把用來拼接的使用者輸入內容和每一個postfix進行結合成為每一個Li
    }
    返回生成的提示內容
}

測試用例

編碼

需求

這下出現的提示好多了,不過使用者如果已經輸入了@1,說明他大概率要輸入163或者126,我們需要讓我們的提示更加符合使用者的期望。滿足以下需求:

  • 當用戶輸入了 @ 及部分字尾時,只從 postfixList 選取符合使用者輸入預期的字尾,我們以字首匹配為要求。
  • 當用戶輸入不滿足任何字首匹配時,則顯示全部提示

設計

這個需求依然需要調整“生成提示框中的提示內容”,如果使用者輸入的字元含有 @,我們需要拿到 @ 之後的字串,來和 postfixList 中每個字串做字首匹配,符合要求的我們才會使用

程式碼結構

function 生成提示框中的提示內容() {
    用來拼接的使用者輸入內容 = 獲取使用者輸入
    if 使用者輸入含有@ {
        用來拼接的使用者輸入內容 = @之前的字串
        用來字首匹配的使用者輸入內容 = @之後的字串
    }    
    遍歷postfixList {
        if 用來字首匹配的使用者輸入內容字首匹配遍歷字串元素
            把用來拼接的使用者輸入內容和這個字串進行結合成為一個Li
    }
    返回生成的提示內容
}

測試用例

新的需求編碼

需求

上面我們只完成了提示,但提示還沒有直接作用到選擇中,我們現在完成以下需求:

  • 使用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,但我這個沒有任何顯示,其他的都按要求完成