aboutsummaryrefslogtreecommitdiff
path: root/files/zh-tw/learn/server-side/express_nodejs/forms/index.html
blob: 008d7ae4e8baa2fea26e73be7f6e8bbb282f4b17 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
---
title: 'Express 教學 6: 使用表單'
slug: Learn/Server-side/Express_Nodejs/forms
translation_of: Learn/Server-side/Express_Nodejs/forms
---
<div>{{LearnSidebar}}</div>

<div>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/Displaying_data", "Learn/Server-side/Express_Nodejs/deployment", "Learn/Server-side/Express_Nodejs")}}</div>

<p class="summary">在此教程中,我們會教你如何使用 Express ,並且結合 Pug 來實現 HTML 表單,並且如何從數據庫中創建、更新、和刪除文檔。</p>

<table class="learn-box standard-table">
 <tbody>
  <tr>
   <th scope="row">前提條件:</th>
   <td>完成前面所有的教程,包括 Express 教程第5章: 展示圖書館數據。</td>
  </tr>
  <tr>
   <th scope="row">目標:</th>
   <td>了解如何編寫表單獲取用戶信息,並且將這些數據更新到數據庫中。</td>
  </tr>
 </tbody>
</table>

<h2 id="概覽">概覽</h2>

<p><a href="/zh-TW/docs/Web/Guide/HTML/Forms">HTML 表單</a>是網頁中由一個、或多個字段/小工具形成的一個組合,它被用來收集用戶的信息,並將信息上傳到服務器。表單作為一種用來收集用戶的機制,非常的靈活,因為有各種合適的輸入框,來接受各種類型的數據——文本框,複選框,單選按鈕,時間選擇器等。表單和服務器交互數據也相對安全,因為它使用<code>POST</code>請求發送數據,保護不受跨站點請求偽造攻擊(cross-site request forgery)的威脅。</p>

<p>但是表單同樣也很複雜!開發者需要編寫給表單編寫 HTML,在服務器上驗證,並且正確去除有害的數據(瀏覽器上也可能需要),對於任何不合法的字段,需要傳給用戶相應的錯誤信息,當數據成功提交後,處理數據,並設法通知用戶提交成功。</p>

<p>此教程將展示上述的操作,如何在 Express 中實現。在此過程中,我們將擴展 LocalLibrary 網站,以允許用戶創建、編輯、和刪除圖書館中的項目。</p>

<div class="note">
<p><strong>注意:</strong> 我們還沒有考慮如何將特定路由,限制為經過身份驗證或授權的用戶,因此在這個時間點,任何用戶都可以對數據庫進行更改。</p>
</div>

<h3 id="HTML_表單">HTML 表單</h3>

<p>首先簡要概述 <a href="/zh-TW/docs/Web/Guide/HTML/Forms">HTML 表單</a>。考慮一個簡單的 HTML 表單,其中包含一個文本字段,用於輸入某些 “團隊” 的名稱,及其相關標籤:</p>

<p><img alt="Simple name field example in HTML form" src="https://mdn.mozillademos.org/files/14117/form_example_name_field.png" style="border-style: solid; border-width: 1px; display: block; height: 44px; margin: 0px auto; width: 399px;"></p>

<p>表單在HTML中,定義為 <code>&lt;form&gt;...&lt;/form&gt;</code>標記內的元素集合,包含至少一個<code>type="submit"</code><code>input</code>輸入元素。</p>

<pre class="brush: html">&lt;form action="/team_name_url/" method="post"&gt;
    &lt;label for="team_name"&gt;Enter name: &lt;/label&gt;
    &lt;input id="team_name" type="text" name="name_field" value="Default name for team."&gt;
    &lt;input type="submit" value="OK"&gt;
&lt;/form&gt;</pre>

<p>雖然這裡,我們只包含一個(文本)字段,用於輸入團隊名稱,但表單可能包含任意數量的其他輸入元素,及其相關標籤。字段的 <code>type</code> 屬性,定義將顯示哪種窗口小部件。該字段的名稱<code>name</code><code>id</code> ,用於標識JavaScript/CSS/HTML 中的字段,而 <code>value</code>定義字段首次顯示時的初始值。匹配團隊標籤使用 <code style="font-style: normal; font-weight: normal;">label </code>標籤,指定(請參閱上面的“輸入名稱” "Enter name"),其中 <code style="font-style: normal; font-weight: normal;">for</code> 字段,包含 <code style="font-style: normal; font-weight: normal;">input </code>相關輸入的 <code style="font-style: normal; font-weight: normal;">id</code>值。</p>

<p>提交輸入(<code>submit</code>)將顯示為按鈕(默認情況下) - 用戶可以按此按鈕,將其他輸入元素包含的數據,上傳到服務器(在本例中,只有 <code>team_name</code>)。表單屬性,定義用於發送數據的HTTP <code>method</code>方法,和服務器上數據的目標(<code>action</code>):</p>

<ul>
 <li><code>action</code>: 提交表單時,要發送數據以進行處理的資源/ URL。如果未設置(或設置為空字符串),則表單將提交回當前頁面 URL。</li>
 <li><code>method</code>: 用於發送數據的 HTTP 方法:<code>POST</code><code>GET</code><ul>
   <li><code>POST</code> 方法。如果數據將導致服務器數據庫的更改,則始終應該使用 <code>POST</code>方法,因為這更加可以抵抗跨站點偽造請求攻擊。</li>
   <li><code>GET</code> 方法只應用於不更改用戶數據的表單(例如,搜索表單)。當您希望能夠為URL添加書籤或共享時,建議使用此選項。</li>
  </ul>
 </li>
</ul>

<h3 id="表單處理流程">表單處理流程</h3>

<p>表單處理使用的技術,與我們學習過、用來顯示有關模型的信息的所有技術,是相同的:路由將我們的請求發送到控制器函數,該函數執行所需的任何數據庫操作,包括從模型中讀取數據,然後生成並返回 HTML 頁面。使事情變得更複雜的是,服務器還需要能夠處理用戶提供的數據,並在出現任何問題時,重新顯示帶有錯誤信息的表單。</p>

<p>下面顯示了處理表單請求的流程圖,從包含表單的頁面請求開始(以綠色顯示):</p>

<p><img alt="" src="https://mdn.mozillademos.org/files/14478/Web%20server%20form%20handling.png" style="height: 649px; width: 800px;"></p>

<p>如上圖所示,構成處理代碼所需要做的主要是:  </p>

<ol>
 <li>在用戶第一次請求時顯示默認表單。
  <ul>
   <li>表單可能包含空白字段(例如,如果您正在創建新記錄),或者可能預先填充了初始值(例如,如果您要更改記錄,或者俱有有用的默認初始值)。</li>
  </ul>
 </li>
 <li>接收用戶提交的數據,通常是在HTTP <code>POST</code>請求中。</li>
 <li>驗證並清理數據。</li>
 <li>如果任何數據無效,請重新顯示表單 - 這次使用用戶填寫的任何值,和問題字段的錯誤消息。</li>
 <li>如果所有數據都有效,請執行所需的操作(例如,將數據保存在數據庫中,發送通知電子郵件,返回搜索結果,上傳文件等)</li>
 <li>完成所有操作之後,將用戶重定向到另一個頁面。</li>
</ol>

<p>表格處理代碼,通常使用 <code>GET</code>路由,以實現表單的初始顯示,以及 <code>POST</code>路由到同一路徑,以處理表單數據的驗證和處理。這是將在本教程中使用的方法!</p>

<p>Express 本身不提供表單處理操作的任何特定支持,但它可以使用中間件,以處理表單中的 <code>POST</code><code>GET</code>參數,並驗證/清理它們的值。</p>

<h3 id="驗證和清理">驗證和清理</h3>

<p>在儲存表單中的數據之前,必須對其進行驗證和清理:</p>

<ul>
 <li>驗證檢查輸入的值,適用於每個字段(範圍,格式等),並且已為所有必填字段提供了值。</li>
 <li>數據中的字符,可能用於將惡意內容送到服務器,為其進行清理刪除/替換。</li>
</ul>

<p>在本教程中,我們將使用流行的 <a href="https://www.npmjs.com/package/express-validator">express-validator </a>模塊,來執行表單數據的驗證和清理。</p>

<h4 id="安裝">安裝</h4>

<p>通過在項目的根目錄中,運行以下命令,來安裝模塊。</p>

<pre class="brush: bash">npm install express-validator
</pre>

<h4 id="使用_express-validator">使用 express-validator</h4>

<div class="note">
<p><strong>注意:</strong> Github上的 <a href="https://github.com/ctavan/express-validator#express-validator">express-validator</a> 指南,提供了API的良好概述。我們建議您閱讀該內容,以了解其所有功能(包括創建自定義驗證程序)。下面我們只介紹一個對 LocalLibrary 有用的子集。</p>

<p> </p>

<p> </p>
</div>

<p>要在我們的控制器中使用驗證器,我們必須從 'e<strong>xpress-validator/check</strong>'和'<strong>express-validator/filter</strong>'模塊中,導入我們想要使用的函數,如下所示:</p>

<pre class="brush: js">const { body,validationResult } = require('express-validator/check');
const { sanitizeBody } = require('express-validator/filter');
</pre>

<p>有許多可用的功能,允許您一次檢查和清理請求參數,正文,標頭,cookie 等數據,或所有數據。對於本教程,我們主要使用 <code>body</code><code>sanitizeBody</code>,和 <code>validationResult</code>(如上面 required 導入的 )。</p>

<p>功能定義如下:</p>

<ul>
 <li><code><a href="https://github.com/ctavan/express-validator#bodyfields-message">body(fields[, message])</a></code>: 指定請求本文中的一組字段(<code>POST</code>參數)以及可選的錯誤消息,如果測試失敗,則可以顯示該字段。驗證標準以菊花鏈形式連接到 <code>body()</code>方法。例如,下面的第一個檢查測試,“name”字段不為空,如果不是,則設置錯誤消息“Empty name”。第二個測試,檢查age字段是否為有效日期,並使用<code>optional()</code>指定null和空字符串不會驗證失敗。

  <pre class="brush: js">body('name', 'Empty name').isLength({ min: 1 }),
body('age', 'Invalid age').optional({ checkFalsy: true }).isISO8601(),
</pre>
  您還可以用菊花鍊式連接不同的驗證器,並添加前面驗證器為真時顯示的消息。

  <pre class="brush: js">body('name').isLength({ min: 1 }).trim().withMessage('Name empty.')
    .isAlpha().withMessage('Name must be alphabet letters.'),
</pre>

  <div class="note">
  <p><strong>注意:</strong> 您還可以添加內聯清理器,如<code>trim()</code>,如上所示。但是,此處應用清理器,僅適用於驗證步驟。如果要對最終輸出進行消毒,則需要使用單獨的清理器方法,如下所示。</p>
  </div>
 </li>
 <li><code><a href="https://github.com/ctavan/express-validator#sanitizebodyfields">sanitizeBody(fields)</a></code>: 指定一個正文要清理的字段。然後將清理操作,以菊花鏈形式連接到此方法。例如,下面的 <code>escape()</code>清理操作,會從名稱變量中,刪除可能在 JavaScript 跨站點腳本攻擊中使用的 HTML 字符。
  <pre class="brush: js">sanitizeBody('name').trim().escape(),
sanitizeBody('date').toDate(),</pre>
 </li>
 <li><code><a href="https://github.com/ctavan/express-validator#validationresultreq">validationResult(req)</a></code>: 運行驗證,以 <code>validation </code>驗證結果對象的形式,提供錯誤。這是在單獨的回調中調用的,如下所示:
  <pre class="brush: js">(req, res, next) =&gt; {
    // Extract the validation errors from a request.
    const errors = validationResult(req);

    if (!errors.isEmpty()) {
        // There are errors. Render form again with sanitized values/errors messages.
        // Error messages can be returned in an array using `errors.array()`.
        }
    else {
        // Data from form is valid.
    }
}</pre>
  我們使用驗證結果的<code>isEmpty()</code>方法,來檢查是否存在錯誤,並使用其 <code>array()</code> 方法,來獲取錯誤消息集合。有關更多信息,請參閱驗證結果的 <a href="https://github.com/ctavan/express-validator#validation-result-api">Validation Result API</a></li>
</ul>

<p>驗證和清理鏈,是應該傳遞給 Express 路由處理程序的中間件(我們通過控制器,間接地執行此操作)。中間件運行時,每個驗證器/清理程序都按指定的順序運行。</p>

<p>當我們實現下面的LocalLibrary表單時,我們將介紹一些真實的例子。</p>

<h3 id="表單設計">表單設計</h3>

<p>圖書館中的許多模型都是相關/依賴的 - 例如,一本書需要一個作者,也可能有一個或多個種類。這提出了一個問題,即我們應該如何處理用戶希望的情況:</p>

<ul>
 <li>在其相關對象尚不存在時,創建對象(例如,尚未定義作者對象的書)。<br>
     </li>
 <li>刪除另一個對象仍在使用的對象(例如,刪除仍有書本 <code>Book </code>使用的種類 <code>Genre</code>)。</li>
</ul>

<p>在這個項目,我們為了簡化實作,將聲明表單只能:</p>

<ul>
 <li>使用已存在的對象創建對象(因此用戶在嘗試創建任何 <code>Book</code>對象之前,必須創建任何所需的 <code>Author</code><code>Genre</code>實例)。</li>
 <li>如果對象未被其他對象引用,則刪除該對象(例如,在刪除所有關聯的 <code>BookInstance</code>對象之前,您將無法刪除該書)。</li>
</ul>

<div class="note">
<p><strong>注意:</strong> 更“牢固”的實現,可能允許您在創建新對象時,創建依賴對象,並隨時刪除任何對象(例如,通過刪除依賴對象,或從數據庫中,刪除對已刪除對象的引用) 。</p>
</div>

<h3 id="路由">路由</h3>

<p>為了實現我們的表單處理代碼,我們需要兩個具有相同 URL 模式的路由。</p>

<p>第一個(<code>GET</code>)路由,用於顯示用於創建對象的新空表單。第二個路由(<code>POST</code>),用於驗證用戶輸入的數據,然後保存信息,並重定向到詳細信息頁面(如果數據有效),或重新顯示有錯誤的表單(如果數據無效)。</p>

<p>我們已經在 <strong>/routes/catalog.js</strong>(在之前的教程中)為我們所有模型的創建頁面,創建了路徑。例如,種類路由如下所示:</p>

<pre class="brush: js">// GET request for creating a Genre. NOTE This must come before route that displays Genre (uses id).
router.get('/genre/create', genre_controller.genre_create_get);

// POST request for creating Genre.
router.post('/genre/create', genre_controller.genre_create_post);
</pre>

<h2 id="Express_表單子文件">Express 表單子文件</h2>

<p>以下子文件,將帶我們完成向示例應用程序添加所需表單的過程。在進入下一個文件之前,您需要依次閱讀並解決每個問題。</p>

<ol>
 <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms/Create_genre_form">創建種類表單</a> — 定義我們的頁面以創建種類對象 <code>Genre </code></li>
 <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms/Create_author_form">創建作者表單</a> — 定義用於創建作者對象 <code>Author </code>的頁面。</li>
 <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms/Create_book_form">創建書本表單</a> — 定義頁面/表單以創建書本對象 <code>Book</code></li>
 <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms/Create_BookInstance_form">創建書本實例表單</a> — 定義頁面/表單以創建書本實例對象 <code>BookInstance</code></li>
 <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms/Delete_author_form">刪除作者表單 </a>— 定義要刪除作者對象 <code>Author </code>的頁面。</li>
 <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms/Update_Book_form">更新書本表單</a> — 定義頁面以更新書本對象 <code>Book</code></li>
</ol>

<h2 id="挑戰自我">挑戰自我</h2>

<p>實現 <code>Book</code>, <code>BookInstance</code>, 和 <code>Genre</code>模型的刪除頁面,用跟我們的作者刪除頁面相同的方式,將它們與關聯的詳細信息頁面,鏈接起來。頁面應遵循相同的設計方法:</p>

<ul>
 <li>如果有來自其他對象的、對於對象的引用,則應顯示註釋,列出這些對象,並說明在刪除列出的對象之前,無法刪除此記錄。</li>
 <li>如果沒有對該對象的其他引用,則視圖應提示刪除它。如果用戶按下“刪除”按鈕 <strong>Delete</strong>,則應刪除該記錄。</li>
</ul>

<p>一些提示:</p>

<ul>
 <li>刪除種類 <code>Genre</code>就像刪除作者<code>Author</code>一樣,因為兩個對像都是 <code>Book</code>的依賴項(因此在這兩種情況下,只有在刪除相關書本時,才能刪除對象)。</li>
 <li>刪除書本 <code>Book</code>也很相似,但您需要檢查是否沒有關聯的書本實例 <code>BookInstances</code></li>
 <li>刪除書本實例 <code>BookInstances</code>是最簡單的,因為沒有依賴對象。在這種情況下,您只需找到相關記錄並將其刪除即可。</li>
</ul>

<p>實現 <code>BookInstance</code>, <code>Author</code>, 和 <code>Genre</code>模型的更新頁面,以與我們的書本更新頁面相同的方式,將它們與關聯的詳細信息頁面,鏈接起來。</p>

<p>一些提示:</p>

<ul>
 <li>我們剛剛實施的圖書更新頁面是最難的!相同的模式可用於其他對象的更新頁面。</li>
 <li>作者<code>Author</code>的死亡日期和出生日期字段,以及書本實例<code>BookInstance</code> 的 due_date 字段,是輸入到表單上日期輸入字段的錯誤格式(它需要 “YYYY-MM-DD” 形式的數據)。解決此問題的最簡單方法,是為適當格式化的日期,定義新的虛擬屬性,然後在關聯的視圖模板中,使用此字段。</li>
 <li>如果您遇到困難,此處示例中的更新頁面有一些示例<a href="https://github.com/mdn/express-locallibrary-tutorial">的連結。</a></li>
</ul>

<h2 id="總結">總結</h2>

<p>Express, node, 與NPM上面的第三方套件,提供你需要的每樣東西 ,可用於新增表單到你的網站上。在本文中,您學習如何使用 Pug 創建表單,使用 express-validator 驗證和清理輸入,以及添加,刪除和修改數據庫中的記錄。</p>

<p>你現在應該了解如何新增基本表單,以及表單處理代碼到你的 node 網站!</p>

<h2 id="請參閱">請參閱</h2>

<ul>
 <li><a href="https://www.npmjs.com/package/express-validator">express-validator</a> (npm docs).</li>
</ul>

<p>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/Displaying_data", "Learn/Server-side/Express_Nodejs/deployment", "Learn/Server-side/Express_Nodejs")}}</p>

<h2 id="本教程連結">本教程連結</h2>

<ul>
 <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Introduction">Express/Node introduction</a></li>
 <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/development_environment">Setting up a Node (Express) development environment</a></li>
 <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">Express Tutorial: The Local Library website</a></li>
 <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/skeleton_website">Express Tutorial Part 2: Creating a skeleton website</a></li>
 <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/mongoose">Express Tutorial Part 3: Using a Database (with Mongoose)</a></li>
 <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/routes">Express Tutorial Part 4: Routes and controllers</a></li>
 <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express Tutorial Part 5: Displaying library data</a></li>
 <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms">Express Tutorial Part 6: Working with forms</a></li>
 <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/deployment">Express Tutorial Part 7: Deploying to production</a></li>
</ul>

<p> </p>