iT邦幫忙

第 12 屆 iT 邦幫忙鐵人賽

DAY 11
0
Modern Web

用 JavaScript 打造全端產品的入門學習筆記系列 第 11

活用 URL 及表單資料打造多種功能——全端實作體驗 IV

URL & Form

全端開發者必懂的「產品搭建程序」中提過:產品依序由地基、結構、建設逐步打造。當伺服器架設完畢後,下一個關鍵階段就是路由設計,而路由設計的第一步就是取得請求(resquest)。本筆記將彙整如何取得請求的資訊及怎麼應用。

本篇筆記將解決以下問題:

  • 如何應用 URL 的路徑,打造細節頁面?
  • 如何取得 URL 的參數,設計搜尋功能?
  • 如何運用表單處理非公開資料?

誰適合閱讀:

  • 學會架設伺服器,打算深入路由操作者

 

應用 URL 的路徑,打造細節頁面

動態路由示意圖

電影清單動態路由示意圖 from Alpha Camp's material

很多網站內都含有許多類似資訊,例如電商網站的商品、電影/餐廳/書籍清單的項目等。若這麼大量的資訊,每一項都需要對應一條路由和一個局部樣版頁面,不只耗費資源、程式碼冗長並難以管理。身為動不動就想自動化的開發者,一定有更快的方式來解決,那就是——「透過 URL 路徑設計來打造動態路由」。

什麼是動態路由 :params

有別於寫死的靜態路由(例如:清單頁就使用 /list/、編輯的頁面使用 /edit 等),動態路由能在路徑內容為合法的字串時,導引到指定的頁面。

例如製作電影清單時,使用 /movies/1/movies/2/movies/3 等各自對應特定電影時,我們無需一一建立路由,僅需以一個動態路由 /movies/:id 就能搞定!

使用方式也很直觀,以電影清單為例分為以下四個步驟:

1. 產生對應到動態路由的路徑

render each movie

用 Handlebars 做出保留原始資料的好用表單 中,稍微提到過 haldlebars 提供的一個好用方法: {{#each}} {{/each}}。透過這了方式,利用大量格式相似的資料,產生版型一致的網頁元件,並且在按鈕或連結中鑲嵌特定的路徑,使其帶有該資料的 id 或編碼。

<!-- movie list -->
<div class="row" id="data-panel">
  {{#each movies}}
  <a href="/movies/{{this.id}}">
      {{this.title}}
  </a>
  {{/each}}
</div>

2. 設定動態路由

動態路由

只要在路徑設定中加入 :pararms 即可,其中 pararms 可任意命名,例如 idnametitle 等。

app.get('/movies/:id', (req, res) => {
  // ...
})

3. 取得動態路由內的資訊

params in console

params in console from Alpha Camp's material

既然資訊是從 req 送來的,就可以用 console.log(req) 嘗試把請求資訊印出來看看。在當中就能找到 req.params 的資訊。

app.get('/movies/:id', (req, res) => {
  const id = req.params.id
  // ...
})

4. 渲染特定資訊

取出後與資料庫或資料表中的資訊比對後,即可用來渲染前端頁面。

app.get('/movies/:id', (req, res) => {
  const id = req.params.id
  const movie = movieList.results.find(movie => movie.id.toString() === id)
  res.render('show', { movie: movie })
})

 

取得 URL 的參數,設計搜尋功能

搜尋功能示意圖

電影清單搜尋功能示意圖 from Alpha Camp's material

當網頁上的資料量開始變多,我們會想使用搜尋來找到特定資訊。如果觀察任意網站上的站內搜尋,可能會在送出搜尋後看到網址產生如下的變化:

https://example.com/search?keyword=macbook

什麼是參數 query

網址最後的參數 ?keyword=macbook 就是所謂的 query string,這同時方便使用者看見自己搜尋的內容,並且讓我們可以截取 URL 上的搜尋字串。

使用方法可以分為以下四個步驟:

1. 產生 URL 上的 query

通常我們使用 HTML 的 <form> 表單搭配 text <input> 及 submit <button> 來設計搜尋框。

<!-- index.handlebars -->
<form action="/search">
  <div class="input-group mb-3">
    <input type="text" name="keyword" class="form-control" placeholder="Enter movie name to search..." aria-label="Movie Name..." aria-describedby="search-button">
    <div class="input-group-append">
      <button class="btn btn-outline-secondary" type="submit" id="search-button">Search</button>
    </div>
  </div>
</form>
  • <form action="/search"> 可一目瞭然路徑用於搜尋
  • <input name="keyword"> 協助我們知道這個參數是搜尋的關鍵字

2. 取得 URL 上的 query

query in console

query in console from Alpha Camp's material

試著把整個 req 印出來,可以找到 query 就藏在其中。所以可以透過 req.query 擷取 URL 上的 query

// app.js
app.get('/search', (req, res) => {
  console.log('req.query', req.query)
  // ...
})

3. 處理 query 並搜尋符合的資料

再次以電影清單為例,來深入搜尋的處理邏輯:

// app.js
const movieList = require('./movies.json')
// ...

app.get('/search', (req, res) => {
  const keyword = req.query.keyword.trim() // 取得 query 中的 keyword 字串,並去除頭尾空白
  const movies = movieList.results.filter(movie => movie.title.toLowerCase().includes(keyword.toLowerCase()))
  res.render('index', { movies: movies })
})

搜尋功能的關鍵邏輯是這行:

const movies = movieList.results.filter(movie => movie.title.toLowerCase().includes(keyword.toLowerCase()))

可以拆解成這樣讀:

const movies = movieList.results.filter(movie => {
  movie.title.toLowerCase().includes(
    keyword.toLowerCase()
  )
})

4. 保留關鍵字以優化使用者體驗

可以從 query 把取得的 keyword 透過 handlebars 再傳回前端介面中。

// app.js
const movieList = require('./movies.json')
// ...

app.get('/search', (req, res) => {
  const keyword = req.query.keyword.trim()
  // ...
  res.render('index', { movies: movies, keyword: keyword }) // 把 keyword 回傳到 index.handlebars 中
})

並以 value="{{keyword}}" 來呈現。

<!-- index.handlebars -->
<form action="/search">
  <div class="input-group mb-3">
    <input type="text" name="keyword" class="form-control" placeholder="Enter movie name to search..." aria-label="Movie Name..."
      value="{{keyword}}" aria-describedby="search-button">
    <div class="input-group-append">
      <button class="btn btn-outline-secondary" type="submit" id="search-button">Search</button>
    </div>
  </div>
</form>

 

運用表單處理非公開資料

前面我們看到透過 <form method="GET"> 能把請求資訊呈現在 URL 上;但並非所有的資訊我們都想赤裸裸地放在網址上,所以我們能透過表單的另一個方法 method="POST" 來隱藏資訊。

GETPOST 的差異?

GETPOST 最大的差異就是,資訊是呈現在 URL 上或放在 request body 中。可以用郵寄來理解這個概念:

  • GET 像寄明信片,所以資訊一覽無遺。
  • POST 似寄平信,除了地址,其他資訊是包裹在信封內。

特別注意的是,從資安角度來談,兩者的安全程度差不多低。因為不管哪個方法,資訊都可被擷取,所以通常會使用「加密」事先處理機敏資料(如:帳號、密碼等)。

但通常我們無法直接取得 POST 過來的 request body,所以需要分兩步驟,並透過額外的套件 body-parser 來協助:

1. 引用並設定 body-parser

$ npm install body-parser

透過 Terminal 安裝完畢後需要進行以下設定:

// app.js
// Include packages and define server related variables
const express = require('express')
const exphbs = require('express-handlebars')
const bodyParser = require('body-parser')
// ...

// setting template engine
// ...

// setting body-parser
app.use(bodyParser.urlencoded({ extended: true }))

// setting routes
  • 與其他套件相同,以 require() 引入
  • app.use() 的方式插入在其他路由之前,目的是讓進來的所有 request 優先經過 body-parser 處理。

2. 設定路由取得 request body

接著就能 以 POST / 的路由設定及 req.body 來取得表單資訊了!

// app.js
// ...

// setting routes
app.post('/', (req, res) => {
  console.log('req.body', req.body)
  res.render('index')
})

 


閱讀更多

Infinite Gamer
關於本系列更多內容及導讀,請閱讀作者於 Medium 個人專欄 【無限賽局玩家 Infinite Gamer | Publication – 】 上的文章 《用 JavaScript 打造全端產品的入門學習筆記》系列指南


上一篇
引入外部檔案以快速搭建美觀網頁——全端實作體驗 III
下一篇
以演算法實踐商業邏輯——全端實作體驗 V
系列文
用 JavaScript 打造全端產品的入門學習筆記30

尚未有邦友留言

立即登入留言