[實例延伸]NodeJs,Express+EJS製作網站 -前端與資料的互動
之前我們已經透過Express的route完成了每一個頁面的靜態展示網頁。接下來我們想要討論的是關於網頁的互動。不能與使用者進行互動的網站,就如一個告示板一樣。我們不只希望網站可以有這樣的功能,還希望他可以在在某種程度上與使用者進行一來一往的對話。
然而,當使用者與網頁頁面互動時,伺服器要使用什麼方式才可以取用到使用者輸入的資料?又如,我們預先有一長串的資料,這一長串的資料可能來自於其他的來源處,又該如何呈現在網頁上?
取得表單資訊
由於Express在4.x版以後,就將中介軟體從中分離出來了,只留下靜態檔案處理的static 中介軟體。
static於前一篇提過:app.use(express.static(‘public’)) 。因此,在Express4.0以後若有使用到中介軟體時,就需要透過單獨安裝才可以呼叫使用。
body-parser套件
body-parser 也是一個中介軟體。它的功能在於將輸入到body的請求(request)解析出來,讓之後的處理事件可以取用這些請求。處理方式透過req.body這個屬性。
1.安裝套件 body-parser
透過npm install安裝套件。npm的使用方式可以參考這一篇。
$ npm install body-parser --save
2.載入body-parser到專案
let bodyParser = require('body-parser')
3.使用body-parser套件
我們可以透過 app.use() 來使用中介軟體。body-parser對body內容通常會採用四種不同的處理方法,分別是:
- 處理json資料、
- Buffer流資料、
- 文字資料、
- UTF-8的編碼的資料。
以下說明第一種與第四種用法:
a.如果是處理 json資料時,要透過下面方式使用:
app.use(bodyParser.json())
b.處理UTF-8的編碼的資料時,則需要透過下面方式使用:
app.use(bodyParser.urlencoded({extended:false}))
這樣子,就可以讓表單submit時順利抓出 input中,特定 [name] 的資料。
測試:如何取得表單資訊?
我們先實做看看:試著建立一個表單與按鈕,當按下按鈕時,我們希望使用者輸入的資訊,可以呈現在console裡面。接下來就一步一步來試試吧。
增加一個search路由
為了不影響現在已經做好的頁面,我們在index.js增加一個獨立的路由,並且命名為search。當這個路由被使用時,隨即 render對應到search.ejs檔案。程式碼表現如下:
app.get('/search', function (req, res) { res.render('search')})
建立表單
在view裡面增加一個頁面,名字為search.ejs
,也就是上面render對應到的檔案。由於我們希望未來header.ejs
裡面的<form>
表單可以正常運作,遂先把header.ejs
裡面的<form>
拷貝過來做測試。測試成功之後,再把實作的結果放回原來header的位置上。
在拷貝到search的<form>
裡面加入兩個屬性:
- 一個是
action=”/searchResult”
- 另一個是
method=”POST”
- 並且在
<input>
裡面加上name屬性:searchText,結果如下:
<form action="/searchResult" method="POST" class="form-inline my-2 my-lg-0"> <input name="searchText" value="" class="form-control mr-sm-2" type="text" placeholder="搜尋..." aria-label="Search"> <button class="btn btn-secondary my-2 my-sm-0" type="submit">搜尋</button></form>
增加一個路由 searchResult
在index.js增加一個route searchResult。這次我們使用POST而不是GET來取得表單的資訊。用 req.body 把取得的資訊顯示在console中。
app.post('/searchResult', function (req, res) { //接收資料 console.log(req.body)})
如果在表單的<input>欄位上輸入Hello,出現的結果是:
{ searchText: 'Hello' }
我們可以在細化程式碼,只抓取body下面的searchText,也就是下面的結構:
req.body.[input欄位的名稱]
表現在程式碼時,如下:
app.post('/searchResult', function (req, res) { //接收資料 console.log(req.body.searchText)})
執行後,出現在在console中的結果應該就剩下:Hello 了。
到目前為止,只要輸入字串,就會在console區出現答案,但是整個流程尚未完成,目前你可能會發現瀏覽器似乎仍不停的在擷取某資料,尚未停息下來。這是因為我們還需要把網址轉向至特定的位置。我們試著使用 redirect() 把它導向search.ejs。
res.redirect('search')
再執行一次,瀏覽器不會再不停的轉動了。這樣子才完成整個流程。
下面是完整測試的程式碼:
app.post('/searchResult', function (req, res) { //接收資料 console.log(req.body.searchText) res.redirect('search')})
實際部署
上面的實驗確定是成功可行的之後,接著就要把它移植到正式的地方。
拷貝search裡面<from>到</form>之間的程式碼。打開header.ejs並且尋找到,<form>的位置,並且覆蓋它。
在index.js。把原來的res.redirect(‘search’) 導向res.redirect(‘/’)首頁。程式修改後的結果:
app.post('/searchResult', function (req, res) { //接收資料 console.log(req.body.searchText) res.redirect('/')})
如果首頁右上角的搜尋輸入框可以使用的話,就表示移植成功了。
其他的輸入標籤
剛剛已經成功地擷取到<input>標籤裡面的值。你可能會問『其他的標籤也是一樣嗎?』學習程式就是要抱著研究的精神,我們可以來試試看。
如果曾經學習過HTML網頁的人應該會知道,在諸多HTML標籤中,有一個被稱為<form>標籤。在<form>標籤這個框架裡面,可以放置其他的標籤。一般來說,<form>標籤是為了讓使用者輸入資料而創設的HTML表單。常見的表單裡面包含了input元素、select、textarea等標籤。依照標籤上加入不同的type屬性,input元素可以呈現為文字框、單選、複選、提交(submit)等型態。
文字框<input type="text">
單選<input type="radio">
複選<input type="checkbox">
提交(submit)<input type="submit">
還記得我們上一篇曾經完成了一個靜態的聯絡我們頁面(如下圖),該頁面上有這各種input輸入標籤。我們只需要在這個頁面上增加其他的標籤就可以來進行測試。
下面是原來的頁面加上性別欄位(使用單選radio button ),三個住址聯絡方式欄位(前兩個使用下拉選單 select,第三個仍然使用input),興趣欄位(使用複選checkbox button )以及提交(submit)按鈕。看起來差不多就是一個註冊會員的表單了(當然有的註冊會員表單會設計得比下面表單複雜很多)。
Form表單調整
仿照前面的search測試,首先在<form>標籤加上action與method兩個屬性。
<form action="contactResult" method="POST">
確定每個欄位都有name屬性。
input等欄位都要加上name屬性,例如:
<input name="firstname" type="text" class="form-control" placeholder="請輸入名字">
單選的name屬性兩個都要一樣。如下,不論選項是男是女,name屬性只有一個,叫做gender。
<input type="radio" id="gender1" name="gender" class="custom-control-input" value="男"><input type="radio" id="gender2" name="gender" class="custom-control-input" value="女">
你可能會發現,上面的input標籤裡面分別有兩個不同的 id(gender1與gender2)。
這兩個id分別對應到下面這兩個label的for屬性(for=”gender1” 與 for=”gender2”)。這樣可以讓使用者即使按下網頁上面的文字(男、女)也可以選擇到radio button,不必非按下radio button才能選擇。
<label class="custom-control-label" for="gender1">男</label><label class="custom-control-label" for="gender2">女</label>
複選的checkbox跟單選的概念一樣,name屬性的名稱要一樣。id的概念也一樣。
<input type="checkbox" class="custom-control-input" name="hobby" id="hobby1" value="唱歌"><input type="checkbox" class="custom-control-input" name="hobby" id="hobby2" value="跳舞"><input type="checkbox" class="custom-control-input" name="hobby" id="hobby3" value="電影">
下拉選單也要加上name屬性,如下:
<select name="city" class="custom-select"><option selected>縣市</option><option value="基隆縣">基隆縣</option><option value="台北市">台北市</option><option value="新北市">新北市</option><option value="台中市">台中市</option><option value="台南市">台南市</option><option value="高雄市">高雄市</option></select>
增加路由
首先,我們直接拷貝前面測試的路由searchResult,並且重新改名字為contactResult。兩個名稱的命名方式類似,有助於記憶與辨識。
app.post('/contactResult', function (req, res) { //接收資料 console.log(req.body) res.redirect('/contactResult')})
注意:這個名字必須與表單中action屬性的名稱一樣。而且app與form都是採用POST的方式。
<form action="contactResult" method="POST">
這樣子還沒有完畢,還要增加一個contactResult頁面,就是送出資料後想要導向的頁面。為了簡便處理,直接複製contact頁面的路由,並且修改一下參數的數值。結果如下。
app.get('/contactResult', function (req, res) { res.render('contactResult', { 'title': 'Contact Us', 'subtitle': '聯絡我們', 'description': `謝謝您完成表單的填寫。我們會儘快與您聯絡`, 'jumbo': true,})//資料表單})
在contactResult.ejs
裡面只放了一行引入layout.ejs版型,這樣子就可以把上面的參數全部顯示上面的參數全部顯示出來了。
<% layout('layout') %>
測試運行
想像自己是個一般使用者,沿著網站向下逐欄輸入各個欄位的資料。(目前只測試輸入與顯示,暫時不理會資料輸入時的錯誤檢查問題)
填寫完資料後,按下送出按鈕。網站將帶您導向contactResult頁面,這個頁面作為感謝頁面。
再把自己切換回工程師模式,檢查VS Code裡面終端機的console,檢視看看每個欄位填寫的資料是否出現?
如果每個欄位都出現了,恭喜,您已經成功了。