# 小專案一: Form Validator | 登入頁面的製作 ###### tags: `JS小專案` ![](https://i.imgur.com/dSyXlTg.png) ## 學習重點_CSS [參考資料_JavaScript初學:DOM常用屬性與方法](https://medium.com/change-or-die/javascript%E5%88%9D%E5%AD%B8-dom%E5%B8%B8%E7%94%A8%E5%B1%AC%E6%80%A7%E8%88%87%E6%96%B9%E6%B3%95-ef851afdb65a) ### 1. 如何連接CSS和Javascript語法 一般來說都會寫在head裡面,但也有撰寫在body內的 ```htmlembedded= <link rel="stylesheet" href="./style.css"> <script src="script.js" defer></script> ``` 以下是寫在head裡面的特性: 1. 可以把jscode跟其他文字分開 2. 如果都是使用外部套件與資源,寫上head會優先讀取這些資源 3. 同樣的script如果都被不同的pages所使用,寫在header看起來會更簡潔 ![](https://i.imgur.com/LLeV0dw.png) ### 2. 撰寫與規劃表單 以下是常用的表單的基本結構? container->form->form-control->label->input->small ```htmlembedded= <div class="container"> <!-- 先設計一個container 把表單的內容都包起來 --> <form id="form" class="form"> <!-- 使用一個表單,並賦予類別,讓欄位和文字都被包在其中 --> <h2>Register With Us</h2> <!-- 主題文字 --> <div class="form-control"> <!-- 表單控制項 --> <label for="username">Username</label> <!-- 使用label來定義欄位標題 --> <input type="text" id="username" placeholder="Enter username"> <!-- 使用input來定義輸入文字的欄位 --> <small>Error message</small> <!-- 萬一出現例外狀況的警告文字 --> </div> <button>Submit</button> <!-- 提交按鈕 --> </form> </div> ``` ### 3. 根元素的運用 根元素在CSS裡面就是宣告`全域變數`的動作 只要制定了根元素,我們就可以在任何地方都使用這個變數 ```css= :root { --success-color: #2ecc71; --error-color: #e74c3c; } ``` 例如: ```css= .form-control.success input { border-color: var(--success-color); /* 如果填寫正確,則邊框效果為綠色 */ } .form-control.error input { border-color: var(--error-color); /* 如果填寫錯誤,邊框效果為紅色 */ } ``` ## 學習重點_Javacript ### 1. 如何把表單欄位與JavaScript連動 這個範例中用到大量的`document.getElementById`,用途在於會回傳所指定id的第一個物件 **原文: Document.getElementById(elementId: string):** HTMLElement, Returns a reference to the first object with the specified value of the ID attribute. ```javascript= const form = document.getElementById('form'); const username = document.getElementById('username'); const email = document.getElementById('email'); const password = document.getElementById('password'); const password2 = document.getElementById('password2'); ``` ### 2. 如何讓出現例外狀況時,表單欄位下面有錯誤訊息(紅字出現) ```javascript= function showError(input, message){ // 設定一個showError的functin,內有兩個引數 const formControl = input.parentElement; // parentElement可以回傳HTML的元素 // 把formControl設定為input的父節點 formControl.className = 'form-control error'; // 將'form-control error'賦給formControl所回傳的Class元素 // 把form-control error設定為formControl的class const small = formControl.querySelector('small'); // querySelector可以抓到特定id的HTNL Node節點 // 把HTML 名為small節點的內容賦值給變數small small.innerText = message; // Node.innerText 是一個代表節點及其後代之「已渲染」(rendered)文字內容的屬性。 // 獲取除了HTML Tag外所包含的內容,此時的innerText取決於function後面輸入的引數 } ``` ### 3. 如何用if else來讓特定的例外狀況觸發特定文字 這邊我們先用檢閱email是否正確,來小小的偷個懶 [參考資料](https://stackoverflow.com/questions/46155/how-to-validate-an-email-address-in-javascript) 首先,把stackflow抓下來的驗證email文字貼在function內,並且賦值給re 只要輸入的文字經過驗證後,結果為true,則可導入輸入成功的狀態,反之則會顯示email is not valid ```javascript= function checkEmail(input) { const re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; // 把Stackflow內的資源抓下來修改 if(re.test(input.value.trim())) { showSuccess(input); // test可以把正規表達式與指定的字符串抓來檢查是否匹配 // 如果把輸入的值拿來與上方驗證,若是為true,代表匹配email的格式 // 則會放入showSuccess的內容中 } else { showError(input, 'Email is not valid'); // 反之,到showError的部分,並且輸入'Email is not valid'的引數' } } ``` ### 4. 如果格式輸入正確,如何導入成功的結果呢? 方才說到,如果輸入格式正確,就會導到另一個結果 這時再針對格式正確的狀態做一個function,讓結果可以正確被導引 ```javascript= function showSuccess(input){ const formControl = input.parentElement; // 把form-control success賦值給formControl的class formControl.className = 'form-control success'; } ``` ### 5. 如果沒填東西怎辦? 一樣,我們需要特別設計一個function來去驗證填寫文字的狀態 不過這次我們需要運用迴圈來處理 ```javascript= function checkRequired(inputArr) { // 運用迴圈,讓每個input都被驗證 inputArr.forEach(function(input){ if(input.value.trim() === '') { // 如果input進去的值經過移除空白仍是處於空值 showError(input, `${getFieldName(input)} is required`); // 則會出現xxx is required的字樣 } else { showSuccess(input); } }); } ``` ### 6. 密碼不一致... 如果使用者前後輸入的密碼不一致,我們也可以使用不等於(`!==`)來處理 ```javascript= function checkPasswordsMatch(input1, input2) { if(input1.value !== input2.value) { showError(input2, 'Password do not match'); } } ``` ## 1. Project HTML ### 步驟1 設定: 先建立一個index.html,輸入!跑出HTML後,把title改成Validate ![](https://i.imgur.com/SYxness.png) ### 步驟2 新增container: 建立一個container,並且加入一個form的區塊以及title ```htmlembedded= <div class="container"> <form id="form" class="form"> <h2>Register With Us</h2> </form> </div> </body> ``` ![](https://i.imgur.com/jAvONpG.png) ### 步驟3 設定表單欄位: 使用Div把各個欄位給設定好,包含username、email、password、confirm password等四個部分 (小訣竅:按壓`shift+滑鼠`可以選擇同樣字元的單字,再用`ctrl+D+方向鍵`來選擇想要改變的單字) ```htmlembedded= <div class="form-control"> <label for="username">Username</label> <input type="text" id="username" placeholder="Enter username"> <small>Error message</small> </div> <div class="form-control"> <label for="email">Email</label> <input type="text" id="email" placeholder="Enter email"> <small>Error message</small> </div> <div class="form-control"> <label for="password">Password</label> <input type="password" id="password" placeholder="Enter password"> <small>Error message</small> </div> <div class="form-control"> <label for="password2">Confirm Password</label> <input type="password" id="password2" placeholder="Enter password again"> <small>Error message</small> </div> ``` ### 步驟4 新增表單按鈕 新增提交按鈕 ```htmlembedded= <button>Submit</button> ``` ![](https://i.imgur.com/61io5SC.png) 此時的成品應該會長這樣子 ![](https://i.imgur.com/73gQylb.png) ## 2. Project CSS ### 步驟5 前往 **Google Fonts** 鑲嵌字型在網頁上 [Google Fonts 網址](https://fonts.google.com/) ![](https://i.imgur.com/PiGMTF9.png) 創建一個style.css,直接把複製的字體貼上去 ```htmlembedded= @import url('https://fonts.googleapis.com/css2?family=Roboto&display=swap'); ``` ![](https://i.imgur.com/aL4aBjq.png) ### 步驟6 設定CSS Link 在原有的HTML中的head區塊,放入CSS的link ```htmlembedded= <link rel="stylesheet" href="./style.css"> ``` ![](https://i.imgur.com/s08KWdx.png) ### 步驟7 CSS內容的初始設定 使用下列的語法,這個元素的內距和邊框將不會增加元素本身的寬度。就不用自己去運算出元素的寬度。 ```css= * { box-sizing:border-box; } ``` ### 步驟8 設定Body內的CSS的內容 [參考資料:align-items](https://w3c.hexschool.com/flexbox/87d66dc4) [MDN Align-items](https://developer.mozilla.org/zh-CN/docs/Web/CSS/align-items) [MDN Justify-contents](https://developer.mozilla.org/zh-CN/docs/Web/CSS/justify-content) [min-height說明](https://www.wibibi.com/info.php?tid=CSS_min-height_%E5%B1%AC%E6%80%A7) [CSS margin](https://www.wibibi.com/info.php?tid=110) ```css= body { background-color: #f9fafb; /* 設定背景顏色 */ font-family: 'Open Sans',sans-serif; /* 設定字體 */ display:flex; /* 使用flexb讓資料自動排列 */ align-items: center; /* 對Y軸中央 */ justify-content: center; /* 在X軸中居中排列 */ min-height: 100vh; /* 設定網頁元素的最低高度限制 */ margin: 0; /* 外邊界距離,俗稱外距 */ } ``` ![](https://i.imgur.com/K4mKMO1.png) ### 步驟9 設定container內的CSS ```Example .container{ /* 用container設定表單整體範圍 */ background-color: #fff; /* 設定背景顏色 */ border-radius: 5px; /* 設定邊界圓角 */ box-shadow: 0 2px 10px rgba(0,0,0,0.3); /* 設定周邊陰影 */ width: 400px; /* 設定寬度 */ } ``` ![](https://i.imgur.com/dprVQXJ.png) ### 步驟10 用form設定表單文字的排列 ```css= .form{ padding: 30px 40px; /* 調整上下內距 */ } ``` ### 步驟11 調整表單的標題文字 ```css= h2 { text-align: center; /* 讓文字置中 */ margin: 0 0 20px; /* 上0px 左、右0px 下20px */ } ``` ### 步驟12 使用表單控制向來調整表單 [參考資料: 甚麼是form-control?](https://matthung0807.blogspot.com/2019/08/html-form-control.html) ``` css= .form-control{ margin-bottom: 10px; /* 外距調整為10px */ padding-bottom: 20px; /* 內距調整為20px */ position: relative; /* 會使其元素「相對地」調整其原本該出現的所在位置 */ } ``` ![](https://i.imgur.com/e1fbi9T.png) ### 步驟13 使用form-control來調整label ```css= .form-control label{ color: #777; /* Label的文字呈現為灰色 */ display: block; /* 要求元素以區塊方式呈現 */ margin-bottom: 5px; /* 底部外距為5px */ } ``` ![](https://i.imgur.com/AqMEftV.png) ### 步驟14 使用form-control來調整input ```css= .form-control input { border: 2px solid #f0f0f0; /* 調整inputbox的邊界為粗框設計 */ border-radius: 4px; /* 讓邊界產生部分圓角 */ display: block; /* 使用區塊型的呈現方式 */ width: 100%; /* 自然地在規定的範圍內延展呈現 */ padding: 10px; /* 內距調整為10px(文字填寫內容與輸入空格的間距) */ font-size: 14px; /* 文字大小為14px */ } ``` ![](https://i.imgur.com/6OfAfG7.png) ### 步驟15 使用form-control input:focus 來設計邊框效果 ```css= .form-control input:focus { outline: 0; /* 去掉原有邊框設計 */ border-color: #777; /* 選取邊框效果改為灰色 */ } ``` ![](https://i.imgur.com/zwBlb3I.png) ### 步驟16 設計填寫正確與錯誤的邊框效果 ```css= .form-control.success input { border-color: #2ecc71; /* 如果填寫正確,則邊框效果為綠色 */ } .form-control.error input { border-color: #e74c3c; /* 如果填寫錯誤,邊框效果為紅色 */ } ``` 示範一下效果,後續可以透過Javascript來呈現 ```htmlembedded= <div class="form-control success"> <label for="username">Username</label> <input type="text" id="username" placeholder="Enter username"> <small>Error message</small> </div> <div class="form-control error"> <label for="email">Email</label> <input type="text" id="email" placeholder="Enter email"> <small>Error message</small> </div> ``` ![](https://i.imgur.com/ZMPKp0Q.png) 設定錯誤訊息的字體顏色 ```javascript= .form-control small{ color: #e74c3c; } ``` 後續可以建立一個根目錄選取器,來建立變數,並且後續可以套用 [參考資料 :root 根目錄選取器 - 叫你阿爸出來講](https://ithelp.ithome.com.tw/articles/10228111) ```javascript= :root { --success-color: #2ecc71; --error-color: #e74c3c; } ``` 套用結果如下: ```javascript= .form-control.success input { border-color: var(--success-color); /* 如果填寫正確,則邊框效果為綠色 */ } .form-control.error input { border-color: var(--error-color); /* 如果填寫錯誤,邊框效果為紅色 */ } .form-control small{ color:var(--error-color); /* 填寫錯誤出現的訊息顏色 */ } ``` ![](https://i.imgur.com/VOvq25m.png) ### 步驟17 進一步設定錯誤訊息 ```javascript= .form-control small{ color:var(--error-color); /* 填寫錯誤出現的訊息顏色 */ position: absolute; /* 修改位置呈現為絕對位置 */ bottom: 0; /* 向下貼齊,與空格保持部分距離 */ left: 0; /* 置左呈現 */ visibility: hidden; /* 預設為隱藏 */ } ``` ![](https://i.imgur.com/wDu1nPg.png) 預設一個機制可以讓error message顯現出來 ```javascript= .form-control.error small { visibility: visible; } ``` ### 步驟18 設定按鈕 ```javascript= .form button { cursor: pointer; /* 會出現手指的符號 */ background-color: #3498db; /* 背景顏色設定 */ border: 2px solid #3498db; /* 邊界顏色與背景色相同 */ border-radius: 4px; /* 部分圓角 */ color: #fff; /* 字體顏色為白色 */ display: block; /* 顯示方式為區塊型呈現 */ font-size: 16px; /* 字體大小為16px */ padding:10px; /* 內距為10px */ margin-top: 20px; /* 與上方空格距離為20px */ width: 100%; /* 按鈕左右延展 */ } ``` ![](https://i.imgur.com/HJ9WI5I.png) ## 3. Adding Simple Validation ### 步驟19 針對表單內各個代表InputBox的元素物件進行賦值 一開始先創建一個js的檔案,命名為script ```javascript= // 首先 指定表單的項目為變數來賦值 // returns an Element object representing the element whose id property matches the specified string. // 回傳特定id所代表的元素物件 const form = document.getElementById('form'); //回傳div內id為form的相關資訊 const username = document.getElementById('username'); //回傳id為username的相關資訊 const email = document.getElementById('email'); //回傳id為email的相關資訊 const password = document.getElementById('password'); //回傳id為password的相關資訊 const password2 = document.getElementById('password2'); //回傳id為password2的相關資訊 ``` **注意** JS Code的擺放位置 1. 放在Body的尾端 ```javascript= <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Validate</title> <link rel="stylesheet" href="./style.css"> // 記得要加 defer <script src="script.js" defer></script> </head> ``` 2. 放在Head裡面 ```javascript= <body> <script src="script.js"></script> </body> ``` ### 步驟20 設定EventListener(submit鍵按下去可能出現的結果) ```javascript= // Event listeners form.addEventListener('submit',function(e) { e.preventDefault(); // 讓所key in的文字可以持續顯示在console內 if (username.value === ''){ showError(username, 'Username is required'); } else { showSuccess(username); } // 設定按下submit後,可能產生的結果 }); ``` #### 甚麼是addEventListener 透過function來向目標物件提供event ```javascript= element.addEventListener(event, function, useCapture) ``` [參考資料](https://www.runoob.com/jsref/met-element-addeventlistener.html) #### 甚麼是preventDefault 作用是停止事件的默認動作,我們會希望透過程式的判斷,希望他暫停執行預設動作,在程式中停止,就可以加入event.preventDefault() ### 步驟21 設定錯誤訊息呈現方式 ```javascript= // Show input error message function showError(input, message){ const formControl = input.parentElement; formControl.className = 'form-control error'; const small = formControl.querySelector('small'); //querySelector可以抓到特定id small.innerText = message; } ``` 不讓錯誤訊息只是error message,透過querySelector的方式可以抓到特定的文字 ![](https://i.imgur.com/r0I3js1.png) ### 步驟22 設定填寫正確的欄位框色 ```javascript= // Show success outline function showSuccess(input){ const formControl = input.parentElement; formControl.className = 'form-control success'; } ``` ![](https://i.imgur.com/Rat5cGW.png) ### 步驟23 把每個欄位的錯誤訊息補充上去 ```javascript= // Event listeners form.addEventListener('submit', function(e) { e.preventDefault(); //如果事件可以取消就取消,但不會影響事件的傳遞 // 作用: 讓所key in的文字可以持續顯示在console內 if (username.value === ''){ showError(username, 'Username is required'); } else { showSuccess(username); } if (email.value === ''){ showError(email, 'Email is required'); } else { showSuccess(email); } if (password.value === ''){ showError(password, 'Password is required'); } else { showSuccess(password); } if (password2.value === ''){ showError(password2, 'Password 2 is required'); } else { showSuccess(password2); } ``` ### 步驟24 如何驗證欄位信箱是否正確(validate email) [資料來源 stack Overflow](https://stackoverflow.com/questions/46155/how-to-validate-an-email-address-in-javascript) 上stack overflow找尋相關的資源並且將其放入function,當填入email時就會產生驗證作用 ```javascript= //Check email is valid function isValidEmail(email) { const re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; return re.test(String(email).toLowerCase()); } ``` 並且修改產生錯誤訊息的條件 ```javascript= if (email.value === ''){ showError(email, 'Email is required'); } else if(!isValidEmail(email.value)) { showError(email, 'Email is not valid'); } else { showSuccess(email); } ``` ![](https://i.imgur.com/7pGfCdo.png) ### 步驟25 用checkRequired替代逐欄設定錯誤訊息 ```javascript= // Check required fields function checkRequired(inputArr) { inputArr.forEach(function(input){ if(input.value.trim() === '') { showError(input, 'is required'); } else { showSuccess(input); } }); } // Event listener裡面有關欄位錯誤描述的內容全部註解,用矩陣來代替 // Event listeners form.addEventListener('submit', function(e) { e.preventDefault(); checkRequired([username, email, password, password2]); }); ``` ![](https://i.imgur.com/Eknv5cu.png) ### 步驟26 讓欄位的id與錯誤訊息結合 使用${}與反引號的方式,讓物件與文字訊息結合 ```javascript= // Check required fields function checkRequired(inputArr) { inputArr.forEach(function(input){ if(input.value.trim() === '') { showError(input, `${input.id} is required`); } else { showSuccess(input); } }); } ``` ![](https://i.imgur.com/3iz8qWS.png) ### 步驟27 將錯誤訊息的第一個字大寫 首先,把input.id的部分改為獲取getFieldName function的值 ```javascript= // Check required fields function checkRequired(inputArr) { inputArr.forEach(function(input){ if(input.value.trim() === '') { showError(input, `${getFieldName(input)} is required`); } else { showSuccess(input); } }); } ``` 新增function,來獲取每個欄位的id並且使用char()和slice()來改寫回傳的數值 ```javascript= // Get fieldname function getFieldName(input){ return input.id.charAt(0).toUpperCase() + input.id.slice(1); } ``` ![](https://i.imgur.com/sSjopj2.png) ### 步驟28 新增密碼長度驗證 在原有的eventlistner內新增checkLength的功能 ``` // Event listeners form.addEventListener('submit', function(e) { e.preventDefault(); //如果事件可以取消就取消,但不會影響事件的傳遞 checkRequired([username, email, password, password2]); checkLength(username, 3, 15); checkLength(password, 6, 25); }); ``` 定義checkLength的功能 ```javascript= // Check input length function checkLength(input, min, max) { if(input.value.length < min) { showError(input,`${getFieldName(input)} must be at least ${min} characters` ); } else if(input.value.length > max) { showError(input, `${getFieldName(input)}must be less than ${max} characters`); } else { showSuccess(input); } } ``` ![](https://i.imgur.com/kG80CzK.png) ### 步驟28 豐富email驗證功能 修改email的錯誤訊息與驗證內容 ```javascript= function checkEmail(input) { const re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; if(re.test(input.value.trim())) { showSuccess(input); } else { showError(input, 'Email is not valid'); } } ``` 新增驗證項目 ```javascript= checkRequired([username, email, password, password2]); checkLength(username, 3, 15); checkLength(password, 6, 25); checkEmail(email); }); ``` ### 步驟29 新增驗證兩組密碼是否相符的功能 新增驗證的function ```javascript= // Check password match function checkPasswordsMatch(input1, input2) { if(input1.value !== input2.value) { showError(input2, 'Password do not match'); } } ``` 把欲驗證的欄位放入陣列內 ```javascript= // Event listeners form.addEventListener('submit', function(e) { e.preventDefault(); //如果事件可以取消就取消,但不會影響事件的傳遞 checkRequired([username, email, password, password2]); checkLength(username, 3, 15); checkLength(password, 6, 25); checkEmail(email); checkPasswordsMatch(password, password2); }); ``` ![](https://i.imgur.com/Y6YpvCx.png)