[實例延伸]NodeJs,Express+EJS製作網站 -前端與資料的互動

Sean Yeh
Web Design Zone
Published in
13 min readMar 10, 2020
Photo by Mike Enerio on Unsplash

之前我們已經透過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,檢視看看每個欄位填寫的資料是否出現?

如果每個欄位都出現了,恭喜,您已經成功了。

--

--

Sean Yeh
Web Design Zone

# Taipei, Internet Digital Advertising,透過寫作讓我們回想過去、理解現在並思考未來。並樂於分享,這才是最大贏家。