Giter Site home page Giter Site logo

simple-app's People

Contributors

henry1491491 avatar

Watchers

 avatar  avatar

simple-app's Issues

[筆記] 實作一個簡易 To Do App( 將資料存進 MongoDB)

將資料存進 MongoDB

// 第一部分
let express = require("express");
let mongodb = require("mongodb");
let app = express();
let db;

// 第二部分
let connectionString = ''
mongodb.connect(connectionString, {useNewUrlParser: true, useUnifiedTopology: true }, function(err, client) {
  db = client.db()
})

// 第三部分
app.post("/create-item", function(req, res) {

  db.collection("items").insertOne({text: req.body.item}, function() {
 
    res.send("Thanks for submitting the form.")
  })
});

第一部分:
先載入 mongodb 模組

$ npm install mongodb
  • 引入模組
  • 宣告一個變數 db

第二部分:
連接到 mongodb

  • db = client.db():把資料庫內容存進先前我們宣告過的變數 db
  • app.listen(3000) 寫進 mongodb.connect(...) 裡面。如此一來,確保變數 db 可以使用時,再進行 listen 的動作
  • let connectionString = '' 雙引號內請看下方關於 connectionString 這個參數

第三部分:

  • post 請求 create-item
  • 當有 create-item 的請求時,綁定 MongoDB 的 items 這個先前創建的 collection,然後新增一筆資料 {text: req.body.item} 進去剛剛綁定的 items 。
  • 此時資料進去資料庫後,才執行「Thanks for submitting the form.」這個回應

假如把 res.send("Thanks for submitting the form.") 拿出 insertOne() 外面,會因為存進資料庫的時間比較慢,而先出現「Thanks for submitting the form.」這個回應

connectionString

我們現在要來填寫 connectionString。至於為什麼要填寫?我的理解是,連接到 mongodb 資料庫的伺服器,需要我們的密碼及地址,現在我們就來找出這個 connectionString

  • 登入 mongodb
  • 找到 cluster
  • 點擊 connect 這個按鈕
  • 在 whitelist your connection IP address,選 Add a Different Ip Address
  • Ip Address 填 0,0,0,0/0,告訴 Atlas 伺服器接受任何 Ip Address。但這並不危險,因為仍舊需要帳號與密碼
  • create a mongodb user。這裡的 user 並非我們一開始登入的帳號。取一個帳號密碼,並且創建一個mongodb user
  • 選一個 connection method,這邊選擇 connect your application
  • 接下來選擇 driver version,記得選 node.js 就對了
  • 然後複製
    mongodb+srv://todoAppUser:<password>@cluster0-gatlu.mongodb.net/test?retryWrites=true&w=majority
    這一大串東西,把它丟進先前宣告的 connectionString 變數裡面
let connectionString = '丟進這裡'
  • <password> 改成剛剛設定的密碼,test 也改成要連結的 database 名稱

[筆記] 實作一個簡易 To Do App(CRUD 之 update 上 )

更新資料庫內容(前端)

現在要做的事,就是更新資料,我們需要點擊 Edit 按鈕,便能實現一個可以編輯資料的功能出來。一般來說,要做出編輯的欄位可以用 <form>,不過還有一個更快、更方便的方式,就是 window.prompt()

我們先在 html template 的 </body> 上方引入一隻接下來會需要編輯的 js 檔 browser.js,如下

<script src="/browser.js">alert("Hello!")</script>

然後我們在專案的根目錄建立一個 public 資料夾,並在裡面新增一個 browser.js 檔案。另外需記得加上以下的 code 到 server.js 這隻檔案上方

app.use(express.static("public"));

意思是允許 Node 讀取 public 內的檔案

接下來開始寫一個按下 Edit 按鈕的監聽事件在 browser.js 檔案(這裡的 JavaScript 是執行在前端,也就是瀏覽器的 JavaScript)

我們先試著理解這段 code。其中 e 為事件監聽的參數,代表事件 event,這個參數隨便怎麼取都可以,但我們還是習慣 event 或是 e。

先知道一下以下 code 的意思為符合 class名稱為 「edit-me」的事件。

e.target.classList.contains("edit-me")

用 console.log(e) 就會發現有個 target 屬性

接著就可以開始來寫 Edit 事件了!

document.addEventListener("click", function(e) {
  if (e.target.classList.contains("edit-me")) {
    let userInput = prompt("Enter your desired new text");
    //假如點擊事件觸發時,有 class 名為 edit-me 的 dom 元素,則將 prompt 輸入的內容存進 userInput 變數
    axios.post("/update-item", { text: userInput }).then(function() {
      // do something interesting here in the next video
      }).catch(function() {
        console.log("Please try again later.");
      });
  }
});

為了確定 prompt 可以正常輸入內容,可以加上一段 console.log(userInput) 檢查看看。切記此時 console 是 web browser 的 console。。

接下來我們要用到 axios 這個方法。需要另外安裝 axios 這個套件。但這邊為了方便起見,直接引入 axios 的 cdn

為什麼要用 axios?

  • 簡潔、乾淨
  • 這樣可以直接將前端 userInput 內的資料直接傳到後端

axios.post("/update-item", { text: userInput }) 這邊使用到了 promise,後續也會更常接觸到 promise,這邊大概知道就好。我們大概知道 promise 跟執行時間順序有關。當使用到 promise 則 axios 後面會有 .then(),而這確保了一件事:當 axios 內的內容執行完,才會去執行 .then() 裡面的內容

而緊接著 .then() 後面的函式 .catch() 則是當執行前面函式有問題時,會執行的函式

然後此時此刻會發現一件事,既然前端 post 到 "/update-item" 這裏,那後端也要對接才對,因此我們來確定一下後端接收到資料

app.post("/update-item", function(req, res) {
  console.log(req.body.text)
})

然後需要使用這個 express 的中間層軟體

app.use(express.json());

可能會新增⋯⋯

  • promise
  • axios

[筆記] 實作一個簡易 To Do App( Express 開發 Server)

用 Express 開發 Server

建立一個資料夾,取名 todoapp,名稱要怎麼取都可以,好記就好。

創一個檔案 server.js。名稱也是隨便取即可,但在這邊因為我們要寫一個 server,所以這隻檔案這樣取會比較容易記得。

打開編輯器(我使用的編輯器是 VSCode),打開 VSCode 後直接將檔案拖曳過來

在 todoapp 資料夾進行初始化

$ npm init -y

如果不用 -y 會需要一直按 Enter,輸入一些設定

這時會看到 pacjage.json 的設定檔

安裝 express 套件

$ npm install express

此時會多出

  • package-lock.json 檔案,不太需要動到它
  • node_modules 資料夾(可以在裡面確定有沒有剛剛載入的 Express )

打開 server.js,寫上以下 code

// 第一部分
let express = require("express") 
let app = express()
// 第二部分
app.get('/',function(req, res) {
  res.send(`這是一段文字`)
})
// 第三部分
app.listen(3000) // 將 port 設定到 3000

command line 執行

$ node server

第一部分:先是引入剛才使用 terminal 載入的 Express 模組
第二部分:使用 get 方法。當有 / 的 request 時,server 會 response 「這是一段文字」在瀏覽器
第三部分:網址輸入 http://localhost:3000 看到剛剛執行的結果

[筆記] 實作一個簡易 To Do App(CRUD 之 read )

讀取資料庫資料

先前記得我們寫過一段 code,當收到 req 請求在 / 時,會顯示 home page 的頁面

app.get("/",function(req, res) {
  res.send(`this is html template`)
})

但現在我們不要這麼做,我們要在取得 data 後,才去回應這個請求

因此這段我們會寫成

app.get("/",function(req, res) {
  db.collection("items").find().toArray(function(err, items) {
    console.log(items)
    res.send(`this is html template`)
  })
})
  • db.collection("items"):熟悉的一段 code,使用 mongodb 的一個 collection 方法綁定 items 這個 collection
  • find():抓出 items 的資料。這邊裡面沒有參數,表示抓出整坨資料。之後會學到如何抓取「特定」資料
  • toArray():這個方法是方便我們看資料,將 database 的資料轉換成 javascript 的陣列,當然第二個參數 items 就是我們要轉換的資料
  • 我們用 console.log() 看一下 items 內容
    注意這裡的 console 是 node 的,不是 browser 的 console

接下來 terminal 看一下這個 items 長怎麼樣

大概是這樣⋯⋯

[ { _id: 5dab39b6e20b3c3d2cd74a44, text: '成為一位合格工程師' },
  { _id: 5dab39c2e20b3c3d2cd74a45, text: '去倒垃圾' }]

將 html template 裡面的其中一個 <li>...</li> 複製下來備用,然後刪除所有 <li> 標籤

接下來看到 template 裡 <ul> 標籤的部分,試看看 map() 這個方法怎麼用。

<ul class="list-group pb-5">
  ${items.map(function() {
    return "Hello"
  })}
</ul>

假如你有 N 個待辦事項在 collection 裡,我們就會看到出現 N 個 Hello,Hello。但是 Hello 之間的 , 看起來很拙,我們不想要每個 Hello 之間都有一個 ,,此時有個方法 join()

 ${items.map(function() {
    return "Hello"
  }).join()}

我們在 map() 後面加上 .join() 這個方法。目的是要去掉這個預設的 ,。當 .join()裡面沒有放參數時,依然會顯示預設的 ,,假如我們希望 Hello 之間不要有 ,,我們可以改成 join("") 空字串表示不要有任何東西。當然如果希望呈現 Hello/Hello/Hello,則可以改成 join("/")

接下來我們將先前複製的 <li>...</li> 內容貼在 return````

items.map(function(item) {
    return "Hello"
  })

並且在 function() 參數加上自定義的參數 item,表示 map 過後的的單筆資料,這樣才可以用在
<li>...</li> 內的標題。我們把標題改成 ${item.text}

如果忘記 items 跟 text 是什麼,可以回顧上一篇

目前資料都可以正確顯示在 html 上了,不過還有個問題,當我們送出資料後,畫面會停留在 「Thanks for submitting the form.」,假如要看到我們剛剛新增的內容,又得手動更改網址到 "/",非常麻煩,所以接下來要來處理 redirect 重新導向的部分

程式碼寫成 res.redirect("/")

app.post("/create-item", function(req, res) {
  db.collection("items").insertOne({ text: req.body.item }, function() {
    res.redirect("/");
  });
});

這樣,新增完資料,畫面便會重新導向回 home page

之後可能會補上⋯⋯

  • map() 方法

[筆記] 實作一個簡易 To Do App(create 之不需要重跑頁面)

新增項目之不需要重跑頁面

到 server.js 檔案找到 html template,有三個 tags 需要加上 id

  • <form id="create-form"></form>
  • form 裡面的 <input id="create-field" ...>
  • <ul id="item-list" ...></ul>
// 第三部分
function itemTemplate() {
  return `
  <li class="list-group-item list-group-item-action d-flex align-items-center justify-content-between">
    <span class="item-text">${item.text}</span>
    <div>
      <button data-id="${item._id}" class="edit-me btn btn-secondary btn-sm mr-1">Edit</button>
      <button data-id="${item._id}" class="delete-me btn btn-danger btn-sm">Delete</button>
    </div>
  </li>`
}

// Create Feature
// 第一部份
let createField = document.getElementById("create-field")
// 第二部份
document.getElementById("create-form").addEventListener("submit", function (e) {
  e.preventDefault()
  axios.post("/create-item", { text: createField.value }).then(function () {
    // Create the html for a new item.
    document.getElementById("item-list").insertAdjacentHTML("beforeend", itemTemplate())
  }).catch(function () {
    console.log("Please try again later.");
  });
})

第一部份:綁定 input 上的 id 存進變數 createField
第二部份:

  • 用修飾符 e.preventDefault() 取消默認行為
  • { text: createField.value},將 text 的內容改為先前存進的變數之值(也就是 input 的 值)
  • 綁定 item-list (
      的 id ),新增一段新的 html template,這裡用到 insertAdjacentHTML 這個方法,看裡面帶入的兩個參數就知道怎麼用了。
    • 新增 template 這段是因為原先 browser 端僅負責告知 server 要儲存資料進資料庫,並重新導向頁面。但現在我們要直接就在畫面上新增資料上去
    • 第一個參數是位置,而位置有四種,可參考這篇文章。
    • 第二個參數就是要新增的內容,這裡用一個可包裝 html template 的 函式
      第三部分:將想要新增的 html template 放進一個自訂的函式名為 itemTemplate()

    目前為止,要解決的問題是 itemTemplate() 內的 ${item._id}。在 browser 端這邊新增了項目,但怎樣才能馬上能夠編輯及刪除項目?所以我們需要拿到正確的 id,以便於操作資料庫。截至目前為止,在
    itemTemplate() 內的 id 應該是取不到的,所以我們要去 server.js 中修改一下。

    app.post("/create-item", function (req, res) {
      db.collection("items").insertOne({ text: req.body.text }, function (err, info) {
        res.json(info.ops[0]) // 當寫進資料庫後,傳送一筆新增的資料。問題是傳到哪裡?
      });
    });

    res.json(info.ops[0]) 這邊會傳送資料到 browser 端,否則在 browser 那邊剛新增出來的項目並沒有辦法做編輯及刪除的動作。所以在 browser 端,也要個能夠接收 server 傳過去的新增項目。因此我們在 insertAdjacentHTML() 裡面的第二參數,也就是我們自訂的函式 itemTemplate 內再帶入一個參數 response.data,如下:

    axios.post("/create-item", { text: createField.value }).then(function (response) {
        // Create the html for a new item.
        console.log(response.data)
        document.getElementById("item-list").insertAdjacentHTML("beforeend", itemTemplate(response.data))
      }).catch(function () {
        console.log("Please try again later.");
      });

    並在我們自訂的 function 帶進 item 參數,使得 <li> 樣板內的 item 變數可以使用

    function itemTemplate(item) {
      return `
      <li class="list-group-item list-group-item-action d-flex align-items-center justify-content-between">
        <span class="item-text">${item.text}</span>
        <div>
          <button data-id="${item._id}" class="edit-me btn btn-secondary btn-sm mr-1">Edit</button>
          <button data-id="${item._id}" class="delete-me btn btn-danger btn-sm">Delete</button>
        </div>
      </li>`
    }

    現在資料可以新增、可以編輯及刪除,但當我們新增完資料,畫面上的 input 欄位內的資料並沒有清空。所以我們在來修改一下 browser 端。在 then function 裡面加上以下兩段 code

    createField.value = "" // 清空 input 內資料
    createField.focus() // 新增完資料後,input 框會呈現 focus 的狀態

[筆記] 實作一個簡易 To Do App(CRUD 之 delete)

刪除資料庫資料

在 browser.js 裡面的監聽事件,我們同樣新增一段 if statement,將原先 Update 的那段整個複製過來,如果忘記了參考這篇

 // Delete Feature
  if (e.target.classList.contains("delete-me")) {
    if (confirm("Do you really want to delete this item permanently?")) {
      axios.post("/delete-item", { id: e.target.getAttribute("data-id") }).then(function () {
        e.target.parentElement.parentElement.querySelector(".item-text").innerHTML = userInput
      }).catch(function () {
        console.log("Please try again later.");
      });
    }
  }

將所有 edit-item 的部分改成 delete-item

回到 server.js 我們要更改 html 部分的 delete button,同樣的加上 data-id 這個屬性

接著在 browser axios.post() 的部分,修改 .then() 裡面的 function,也就是當資料從前端送到後端後,要做的事(刪除資料)

改成

.then(function () {
  e.target.parentElement.parentElement.remove()})

以上是 browser 的部分,在後端也需要處理資料庫的部分

app.post('/delete-item', function (req, res) {
  db.collection('items').deleteOne({ _id: new mongodb.ObjectId(req.body.id) }, function () {
    res.send("Success")
  })
})

[筆記] 實作一個簡易 To Do App(CRUD 之 update 下 )

更新資料庫內容(後端)

現在開始寫後端的更新資料,code 如下

app.post("/update-item", function(req, res) {
  db.collection("items").findOneAndUpdate(a, { $set: { text: req.body.text } }, function() {
    res.send("Success");
  });
  res.send("Success");
});

關於這個 $set,它是 MongoDB 上一個更新資料的用法。當使用 $set 時,後面接著的物件就是我們要替換的內容。假如一筆資料有許多物件,我們也可以挑選其中幾個我們需要替換的物件。多個物件ㄧ樣用 , 作分隔,詳細參考 MongoDB 官網

截至目前為止,稍微整理一下。遇到

a.b().c(d, function() {
  //當 a.b() 執行,c() 裡面的匿名函式才會開始執行
})

通常遇到的就是,處理完資料庫的部分,才執行 res.send()

回到正題,現在我們要更新資料,需要有 id 來做對應,我們跳到 html template 找到 <ul> 標籤內的 edit button,加上 data-id,這個 id 可以隨意取名,你也可以叫 data-pizza

改完後變成

<button data-id="${item._id}" class="edit-me btn btn-secondary btn-sm mr-1">Edit</button>

現在我們改寫一下前端 browser.js 的 axios,也要傳送 id 到後端,以便於後端去做該項資料的修改

截至目前為止,為什麼這邊需要用到 id?先說為什麼新增不需要用到 id。因為新增時,我們可以隨意增加「新」的資料這筆新的資料可以重複也可以不重複,都是獨一無二的資料,因此不需要特別去給予 id。那麼通常需要用到 id,就是表示該筆資料會「再」次被碰到時。當我們要做 edit,我們是要重複去提取舊有的 item 去作更新,那麼究竟是哪個 item,這時就需要 id。你說,那麼為什麼不直接去找到該 item 的標題就好了?很簡單,因為該 item 可能會出現重複的項目,這時你不知道要新增哪一個

因此在 browser.js 這隻檔案的 axios.post() 的部分變成

axios.post("/update-item", { text: userInput, id: e.target.getAttribute("data-id")})

id: 這段字面上意思: e.target 取得 data-id 的屬性

回到先前 browser.js 有一段內容

document.addEventListener("click", function (e) {
  if (e.target.classList.contains("edit-me")) {
    let userInput = prompt("Enter your desired new text");
    axios.post("/update-item", { text: userInput, id: e.target.getAttribute("data-id") }).then(function () {
      e.target.parentElement.parentElement.querySelector(".item-text").innerHTML = userInput
    }).catch(function () {
      console.log("Please try again later.");
    });
  }
});

增加以下這段:
e.target.parentElement.parentElement.querySelector(".item-text").innerHTML = userInput

接下來我們優化 prompt 的部分,我們要做出的是,當我們點擊 edit 後,原先資料會直接顯示在輸入框內,這樣一來,我們只要更改部分內容即可,不需要重新打字進去

prompt() 裡,可以加入第二個參數,而第二個參數要放的就是我們要直接顯示在輸入框的原內容

prompt("Enter your desired new text", e.target.parentElement.parentElement.querySelector(".item-text").innerHTML)

接下來的問題就是,當我們不想更改內容,按取消時,原內容變成空白的!

因此我們在 axios 的部分加上條件判斷

[筆記] 實作一個簡易 To Do App(將 App 放到網路上 )

將 App 放到網路上

檢查電腦是否有 git,可以在終端機輸入

$ git --version

假如有安裝,接著在命令列輸入

$ git config --global user.name "henry"

然後再次輸入

$ git config --global user.email "[email protected]"

現在我們要使用 heroku (一個免費的 hoisting 公司推出的服務)。先到官網註冊後,登入進去。

點選 create new app 後,創建一個名稱(不能跟其他人的重複,否則無效)

進去後,往下滑找到 Deploy using heroku git 這個選項,然後下載一個東西(讓我們方便在 terminal 就可以操作 heroku 的套件)。找到 Heroku CLI 這個連結,開啟新分頁。我們要下載的是 heroku 的 cli ( command line interface )

繼續往下滑,找到一個 download and install,依照自己電腦的作業系統下載

完成後回到 terminal

$ heroku login 

然後就會跳出一段訊息就你點選任何一個鍵
heroku: Press any key to open up the browser to login or q to exit:

然後就會跳出一個登入頁面

此時我們準備將檔案上傳到 heroku,但在這之前,我們要創建一隻檔案,檔名叫 Procfile 在根目錄,然後打上以下 code

web: node server.js

然後在 server.js 的 let db 下方加入環境變數及一段判斷句。

let port = process.env.PORT
if (port == null || port == "") {
  port = 3000
}

告訴 server 要監聽的 port ,然後 app.listen 內的 3000 改為 port

mongodb.connect(
  connectionString,
  { useNewUrlParser: true, useUnifiedTopology: true },
  function (err, client) {
    db = client.db();
    app.listen(port);
  }
);

存檔,到command line 輸入

$ git init

因為我們並不需要將所有檔案上傳到 heroku,像是 node_module,因此我們在根目錄再新增一隻檔案 .gitignore,在檔案裡加上 node_module

然後接下來執行 git(記得是在 server.js 這隻檔案)

$ git add -A
$ git commit -m 'our first commt'
$ heroku git:remote -a todoappforhenrytest

然後等待他跑完,就可以到 heroku 官網(登入進去後),找到右上角的 open app

跑出來的頁面的網址,就是其他人可以看到我們做好的 App 的網址

[筆記] 實作一個簡易 To Do App(about Security )

關於資安

這篇是要實作如何避免駭客的一些基本攻擊,首現我們先在 server.js 檔案自訂一段函式

function passwordProtected(req, res, next) {
  console.log("Our custom function just ran!")
  next()
}

這邊多出一個參數 next,稍後會介紹到

然後在 app.get 這邊也新增剛剛自定義的函式作為第二個參數

app.get("/", passwordProtected, function (req, res) {
  db.collection("items").find().toArray(function (err, items) {
      res.send(`<!DOCTYPE html> . . .

這邊的意思是,當有 get 請求,會先執行 passwordProtected 這個函式,然後才執行下一個匿名函式

然後當執行到 passwordProtected 這裡時,剛剛介紹到的 next() 就派上用場了,它會告知此函式可以繼續執行下一個函式了

接下來,我們開始來寫 passwordProtected 內的內容

function passwordProtected(req, res, next) {
  res.set('WWW-Authenticate', 'Basic realm="Simple Todo App"')
  console.log(req.headers.authorization)  
  if (req.headers.authorization == "Placeholder") {
    next()
  } else {
    res.status(401).send("Authentication required")
  }
}

當我們重整頁面,會出現一個要你輸入帳號密碼的驗證框,我們試著隨意輸入東西進去。然後會在 command line 看到一串東西,如:

Basic MTIzMTIzOjEyMzEyMw==

那其實我們可以在帳號密碼欄位輸入我們自己要設定的帳號密碼,一樣按下登入,重 command line 找到這串內容,並將它複製後替換到 req.headers.authorization == "Placeholder" 的 Placeholder 這個地方

此時會想到其他的 route 也需要設置帳密,但假如一個一個在參數加上 passwordProtected 會顯得很麻煩。因此現在來做一個可以讓所有 url 都適用的一勞永逸方法。

首先先將 app.get("/",function...) 這邊的 passwordProtected 拿掉,新增以下這段

app.use(passwordProtected)

然後這個方法便會適用於所有 url

但,駭客此時還有其它種攻擊方式。比如說在 input 欄位注入一段 a 連結

<a href="# onclick="() => {alert('這是一段攻擊'})">click me</a>

然後就會產一個新項目 click me,並且點擊後確實會產生 alert 訊息

因此我們要避免這樣的情況,我們要有一個功能可以在 input 內容過濾掉一些不必要的元素。我們不需要自己寫出這樣的功能,有個套件可以下載。到 command line 執行

$ npm install sanitize-html

然後一樣在 server.js 最上方引入這個套件

let sanitizeHTML = require("sanitize-html")

接著我們修改一下 app.post 的 create-item 的地方

app.post("/create-item", function (req, res) {
  let safeText = sanitizeHTML(req.body.text, {allowedTags: [], allowedAttributes: {}})
  db.collection("items").insertOne({ text: safeText }, function (err, info) {
    res.json(info.ops[0])
  });
});

在 db.collection . . . 上方新增一個變數 safeText,這是用來過濾輸入內容用的。我們要過濾的項目寫在 sanitizeHTML 內的第二個參數。第一個參數是要處理的內容,第二個則是我們允許的內容。通通設為不允許(方法就是 allowedTags 跟 allowedAttributes 的後面分別是空的 []{}

然後同樣修改編輯的部分。

此時我們用通樣的方式駭看看,在 input 欄位輸入剛才那段 a 連結。然後再到資料庫去看,只剩下 id 跟 text 為 click me

[筆記] 實作一個簡易 To Do App(CSR )

瀏覽器端渲染

這篇介紹如何由 SSR(Server Side Render) 轉成 CSR(Client Side Render)。

目前為止的 <ul> 標籤內的動態資料及 html template 是由 server 端傳輸到 browser 端的,但現在我們要讓server 端僅傳輸原始資料到 browser 端。也就是我們要將產生 html template 的職責轉交給 browser

現在我們來讓原始資料可以傳到 browser ,在 server.js 內的 html template 找到兩行 <script> 標籤,然後在上面新增一段 <script>

<script>
  let item = ${JSON.stringify(items)}
</script>

<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="/browser.js">alert("Hello!")</script>

此時,為了確定是否有將資料傳到 browser,我們打開頁面的「檢視原始碼」,會看到

browser 已經有這段資料

所以現在我們來將 server.js 內的 <ul> 標籤內的內容通通刪除,並到 browser.js ,在
// Create Feature 上方增加一段 // Initial Page Load Render

// Initial Page Load Render
let ourHTML = "HELLO"
document.getElementById("item-list").insertAdjacentHTML("beforeend", ourHTML)

重整畫面,會看到沒有任何項目只有 HELLO。

所以現在就是想辦法讓項目可以呈現在畫面上

let ourHTML = "HELLO" 改成

let ourHTML = items.map(function (item) {
  return itemTemplate(item)
}).join('')

創建一個變數將原始資料 items 經過處理,成為新陣列,返回 html template(itemTemplate(item)),並且透過 join 方法將 ourHTML 變為字串。記得先前說明過 join 方法如果沒有加上' ' 參數,則預設會在每個字串中間加上 , 分隔號

到此我們回顧一下 items 原始資料的傳遞順序:

  1. 最一開始使在 server.js 內的 html template 內出現。我們透過 get 方法,在取得資料庫資料後,將 items 轉為陣列,並且帶入 res.end 出來的 html template 內,供其使用。
  2. 因此原先在 server.js 內的 html template 內的 <ul> 標籤就可以使用 items 這個資料
  3. 後來因為將 <ul> 標籤 移至 browser 內,這段 items 原始資料就改為在 server.js 內 response 的 html template 中引入一段 <script> 的方式,透過將其字串化供 browser rending 使用
<script>
  let items = ${JSON.stringify(items)}
</script>
  1. 在 browser.js 中,透過 map 方法,將它帶進 itemTemplate(item) 函式內,透過前端的新增動作,直接帶進原始 items

[筆記] 實作一個簡易 To Do App(CRUD 之 create )

新增項目到待辦事項

接著開始寫新增待辦事項的功能

什麼是 CRUD

  • C:create 創造
  • R:read 讀取
  • U:update 更新
  • D:delete 刪除

後面筆記會陸續碰到這四個步驟

複習一下上一篇,server 如何處理請求(request)跟回應(response)

let express = require("express")
let app = express()

app.get('/',function(req, res) {
  res.send(`這是一段文字`)
})

app.listen(3000)

現在開始做出新增的功能

回顧先前,當網址出現 / 時,代表訪問首頁的請求。那麼假如有個新增的請求,我們可以取名為/create-item,此時code 如下

// 第一部分
let express = require("express")
let app = express()
// 第二部分
app.use(express.urlencoded({ extended: false }));
// 第三部分:
app.get('/', function(req, res) {
  res.send(`
  <!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Simple To-Do App</title>
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/css/bootstrap.min.css" integrity="sha384-GJzZqFGwb1QTTN6wy59ffF1BuGJpLSa9DkKMp0DgiMDm4iYMj70gZWKYbI706tWS" crossorigin="anonymous">
</head>
<body>
  <div class="container">
    <h1 class="display-4 text-center py-1">To-Do App</h1>
    
    <div class="jumbotron p-3 shadow-sm">
      <form>
        <div class="d-flex align-items-center">
          <input autofocus autocomplete="off" class="form-control mr-3" type="text" style="flex: 1;">
          <button class="btn btn-primary">Add New Item</button>
        </div>
      </form>
    </div>
    
    <ul class="list-group pb-5">
      <li class="list-group-item list-group-item-action d-flex align-items-center justify-content-between">
        <span class="item-text">Fake example item #1</span>
        <div>
          <button class="edit-me btn btn-secondary btn-sm mr-1">Edit</button>
          <button class="delete-me btn btn-danger btn-sm">Delete</button>
        </div>
      </li>
      <li class="list-group-item list-group-item-action d-flex align-items-center justify-content-between">
        <span class="item-text">Fake example item #2</span>
        <div>
          <button class="edit-me btn btn-secondary btn-sm mr-1">Edit</button>
          <button class="delete-me btn btn-danger btn-sm">Delete</button>
        </div>
      </li>
      <li class="list-group-item list-group-item-action d-flex align-items-center justify-content-between">
        <span class="item-text">Fake example item #3</span>
        <div>
          <button class="edit-me btn btn-secondary btn-sm mr-1">Edit</button>
          <button class="delete-me btn btn-danger btn-sm">Delete</button>
        </div>
      </li>
    </ul>
    
  </div>
  
</body>
</html>
  `)
})
// 第四部分:
app.post("/create-item", function(req, res) {
  console.log(req.body.item);
  res.send("Thanks for submitting the form.");
});
// 第五部分:
app.listen(3000);

第一部分:不變
第二部分:這段與 req.body.item 有關!就是個 boilerplate code(不太需要更動的樣板程式碼)
第三部分:增加了 html 內容。要從講師的 GitHub 複製 html template 過來。連結

的地方,加上 action 及 method

<form action="/create-item" method="POST" >
  <div class="d-flex align-items-center">
    <input autofocus autocomplete="off" class="form-control mr-3" type="text" style="flex: 1;">
    <button class="btn btn-primary">Add New Item</button>
  </div>
</form>

第四部分:這邊多了一種請求方式 post,並透過 console.log() 看看 req.body.item 是什麼?然後最後回應 「Thanks for submitting the form.」
第五部分:不變

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.