幹話產生器

透過 AC 課程作業,利用 node.js 環境加上 express 和 handlerbars 等工具進行實作,主要紀錄邏輯想法。

安裝

mkdir trash_talk
cd trash_talk
npm init -y
npm i express express-handlerbars body-parser

設定資料

修改 package.json

"description": "新增描述",
"main": "app.js",
"scripts": {
"start": "node app.js",
"dev": "nodemon app.js",
}

全部需要新增的資料,app.jstrashTalk.jscharacter.json/views/layouts/mian.handlebars/views/index.handlebars,css、JS 檔案/public/javascripts/<引入資料>/public/stylesheets/<引入資料>

邏輯講述

我的作法是經由 character.json 創造一個資料庫存放腳色及幹話數據,再藉由匯入讀取資訊。

例:

{
"腳色":[
{
"name": "學生",
"name_en": "student",
"image": "./public/img/student.jpg",
"trash": ["0", "1", "2", "3", "4", "5"]
},
.
.
.
.
],
"共用句":[
"一",
"二",
"三",
"四",
"五"
]
}

在網頁上我們可以選擇腳色,目前一次只能選擇一位,選擇資後按下 產生幹話 ,在旁邊的顯示區塊便會隨機組合句子:

  • 身為一個學生0應該吧!
  • 身為一個學生3應該吧!

腳色職業選定後,會對應到 trash 然後隨機選擇陣列中的一個值(value),再將它放到句子中應該去的位子,而共用的句子也是同樣的道理。

這邊必須使用到兩次的同樣方式的隨機選取陣列,設定一個 function 以方便使用

function randomIndex(arr) {
const index = Math.floor(Math.random() * arr.length)
return arr[index]
}

再來要設置一個產生 trash talk 的 function
先建立一個簡單虛擬碼

function generateTrashTalk(option) {
//使用 if/eles 比對 req.body 傳入的職業
if (option.job === '職業1') {
return `身為一個${職業1},${randomIndex(trash)}應該${randomIndex(共用句)}!`
}
if (option.job === '職業2') {
return `身為一個${職業1},${randomIndex(trash)}應該${randomIndex(共用句)}!`
}
if (option.job === '職業3') {
return `身為一個${職業3},${randomIndex(trash)}應該${randomIndex(共用句)}!`
}
}

考慮到可能在預設的狀況下也有人,不選擇按產生幹話

function generateTrashTalk(job){
//如果什麼都沒按
if (job === 0) {
return `沒有選擇人物!`
}
}

如此便簡單設置好邏輯再來就是將 trash_talk.js 設置匯出的 function 名稱

// exports function
module.exports = generateTrashTalk

邏輯補充

在阿全同學作業中的邏輯判斷是這樣,判斷 req.body.job 是否為 true ,也就是裡面有沒有值,如果是真,那就回傳想要的句子,如果是 false 那就回傳請選擇人物,如此一來code就完全的簡化了。

而在另一位同學駱駱作業,助教給了一個建議,可以使用 find() 方法尋找相對應的值,而 find 是 array.prototype.find 的方法,適用在尋找 array 中的 value。

MDN給了這樣的解釋是,它會尋找出在陣列中第一個符合條件的值,並且回傳 value。

而我做成的作業在尋找進 character 之後,還是一個 hash ,但是又進一步的尋找 job_en,相當只比對全部人的 job_en 而且只回傳比對到的第一個,所以在沒有多筆從重複的資料才可以使用,之後就可以這樣寫:

function generateTrashTalk(job){
// 回傳符合條件的第一個值為 man
cosnt man = characters.chatcater.find(person => person.job_en === job)

(job) ? `身為一個${職業3},${randomIndex(trash)}應該${randomIndex(共用句)}!` : `沒有選擇人物!`
}

app.js 設定

架設伺服器配置

// 引入 express module 以及自定義檔案
const express = require('express')
const app = express()
const port = 3000
const trashTalk = require('./trsh_talk.js')
const character = require('./character.json')

// 引入套件
const exphbs = require('express-handlebars')
const bodyParser = require('body-parser')

// 由於有使用 handlebars 所以需引入 render engine
app.engine('handlebars', exphbs({ defaultLayout: 'main' }))
app.set('view_engine', 'handlebars')

// 設定靜態網頁使用的檔案 public 的檔案,可以防止自訂CSS出錯
app.use(express.static('public'))

// 處理 exquest 和 response
app.get('/', (request, response) => {
response.render('index')
})
app.post('/', (request, response) => {
// 經由 body-parser 解析資料
const option = request.body
const sentence = trashTalk(option)
response.render('index', {option:option , sentence:sentence, character:character})
})

// 啟動和監聽 server
app.listen(port, () => {
console.log(`Express server is on http://lacolhost:${port}`)
})

所以大概的設定是這樣。

補充作業回饋

在作業回饋中,助教有說到,由於在 req.body 中只有一個值,也只要取得那個值,而我的值是設定 job,所以把 option 改成 job:

const job = request.body.job
const sentence = trashTalk(job)

如此一來更容易了解,在帶入 trashTalk.js 檔案的時候也更簡潔明瞭。

關於 index.handlebars

這次的實作中,遇到了一個意想不到的問題,關於迭代的模板,又得要做 if 相等的判斷該怎麼辦,目前我的作法是使用 handlebars 中的自定義 helpers

在模板中因為使用到了 <input type="radio">,而且又使用到了迭代(Iteration),導致使用一般原本套件的 if 是不足以應付的,在原本 google 得到 helper 去判斷 checked 問題以為可以一勞永逸,沒想到怎麼跑都跑不出來。

stack Overflow

設置一個 helper,自己定義一個 function

const Handlebars = require('handlebars')
Handlebars.registerHelper('ifEquals', (value1, value2) => (value1 === value2) ? 'checked' : '')

並且在迭代的template中加入

{{ifEquals option.job this.job}}

這時候就遇到了麻煩了,由於我是使用

{{#each}}{{/each}}

在跑作用域的時候,option 也就是app.js中 req.body 的值,這個變數在index.handlebars 中根本無法取得,在傳入 function 中的會變成 undefine 是個空的資訊。

為什麼會這樣呢?主要原因出在作用域(scope)時,在我的實作中,使用了 each 每一層的 this 不同,下一圈的時候 this 改變了,而 option 則是存在父層,得跳出迴圈去尋找父層(parent context)資訊,藉由詢問 Mike 才得以解決這個問題,尋找到的 stack overflow資料

關鍵字為express handlebars each two argument

可以看到使用了 ../ 這個往上層移動,最後在 template 中想要放置的位置改成

{{ifEquals ../option.job this.job}}

如果是使用兩層 ../../,就可以再往上一層尋找了。

重要資料

成品

github