@@ -11,3 +11,333 @@ Hello everyone, Zua here, today I will be explaining how to build a full-stack w
11
11
## Explanation (Intermissions)
12
12
13
13
1 . Now that the backend is complete, let's review what we just created. Our ` express ` API webserver listens for requests from clients. For testing purposes, our client right now is ` postman ` . The Postman client will initiate a request to the API webserver we just created. The API will then send us back a response based on what the user initially requested. Hopefully now, it's all starting to come together. Now, let's build a user-friendly interface that allows us to interact with our new API.
14
+
15
+ ___
16
+
17
+ ## Procedure
18
+
19
+ ### Frontend:
20
+
21
+ * Begin Design UI - ` index.html `
22
+ ``` html
23
+ <!doctype html>
24
+ <html lang =" en" >
25
+ <head >
26
+ <meta charset =" utf-8" >
27
+ <meta http-equiv =" X-UA-Compatible" content =" IE=edge" >
28
+ <meta name =" viewport" content =" width=device-width, initial-scale=1" >
29
+ <title >Messenger App</title >
30
+ <style >
31
+ #footer {
32
+ position : fixed ;
33
+ left : 0 ;
34
+ bottom : 0 ;
35
+ width : 100% ;
36
+ }
37
+ </style >
38
+ <!-- Bootstrap CSS Stylesheet-->
39
+ <
link href =
" https://cdn.jsdelivr.net/npm/[email protected] /dist/css/bootstrap.min.css" rel =
" stylesheet"
40
+ integrity =" sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin =" anonymous" >
41
+ <!-- Bootstrap Javascript-->
42
+ <
script src =
" https://cdn.jsdelivr.net/npm/[email protected] /dist/js/bootstrap.bundle.min.js"
43
+ integrity =" sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p"
44
+ crossorigin =" anonymous" ></script >
45
+ <!-- Axios HTTP Library -->
46
+ <script src =" https://unpkg.com/axios/dist/axios.min.js" ></script >
47
+ </head >
48
+
49
+ <body >
50
+ <div class =" container" >
51
+ <!-- Title header -->
52
+ <h1 >Messenger App</h1 >
53
+ <hr />
54
+ <!-- Bootstrap Accordion https://getbootstrap.com/docs/5.0/components/accordion START -->
55
+ <div class =" accordion" id =" accordionExample" style =" margin-bottom : 25px " >
56
+ <div class =" accordion-item" >
57
+ <h2 class =" accordion-header" id =" headingOne" >
58
+ <button class =" accordion-button" type =" button" data-bs-toggle =" collapse"
59
+ data-bs-target =" #collapseOne" aria-expanded =" true" aria-controls =" collapseOne" >
60
+ Authors
61
+ </button >
62
+ </h2 >
63
+ <div id =" collapseOne" class =" accordion-collapse collapse show" aria-labelledby =" headingOne"
64
+ data-bs-parent =" #accordionExample" >
65
+ <div id =" authors" class =" accordion-body" >
66
+ </div >
67
+ </div >
68
+ </div >
69
+ </div >
70
+ <!-- Bootstrap Accordion END -->
71
+ <!-- Card container (Houses all cards/messages) START -->
72
+ <div class =" row" id =" cards" style =" margin-bottom : 50px " >
73
+ </div >
74
+ <!-- Footer (sticks to the bottom of the screen) -->
75
+ <div id =" footer" >
76
+ <button style =" width : 100% " class =" btn btn-primary" >Create</button >
77
+ </div >
78
+
79
+ </div >
80
+ <!-- External custom scripts -->
81
+ <script src =" API.js" ></script >
82
+ <script src =" utils.js" ></script >
83
+ <script src =" actions.js" ></script >
84
+ <script >
85
+ // More code will be here
86
+ </script >
87
+ <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
88
+ <script src =" https://code.jquery.com/jquery-1.12.4.min.js"
89
+ integrity =" sha384-nvAa0+6Qg9clwYCGGPpDQLVpLNn0fRaROjHqs13t4Ggj3Ez50XnGQqc/r8MhnRDZ"
90
+ crossorigin =" anonymous" ></script >
91
+ </body >
92
+ </html >
93
+ ```
94
+ ____
95
+
96
+ * Write API Wrapper - ` API.js `
97
+ ``` js
98
+ var API = { // this object acts as our API Wrapper. It works standalone! (with the exception of the axios lib)
99
+ api: axios .create ({ baseURL: " http://localhost:5000/api" }),
100
+ getMessage : function (id ) {
101
+ return this .api .get (` /message/${ id} ` )
102
+ .then (response => response .data )
103
+ },
104
+ getAllMessages : function () {
105
+ return this .api .get (" /message/" )
106
+ .then (response => response .data )
107
+ },
108
+ editMessage : function (author , content , id ) {
109
+ return this .api .put (` /message/${ id} ?author=${ author} ` ,
110
+ new URLSearchParams ({ content }))
111
+ .then (response => response .data )
112
+ },
113
+ deleteMessage : function (author , id ) {
114
+ return this .api .delete (` /message/${ id} ?author=${ author} ` )
115
+ .then (response => response .data )
116
+ },
117
+ createMessage : function (author , content ) {
118
+ return this .api .post (` /message/create?author=${ author} ` ,
119
+ new URLSearchParams ({ content }))
120
+ .then (response => response .data )
121
+ },
122
+ getAllAuthors : function () {
123
+ return this .api .get (` /author/` )
124
+ .then (response => response .data )
125
+ },
126
+ getMessagesByAuthor : function (author ) {
127
+ return this .api .get (` /author/${ author} ` )
128
+ .then (response => response .data )
129
+ },
130
+ };
131
+ ```
132
+
133
+ ___
134
+
135
+ * Start utility helper script - ` utils.js `
136
+ ``` js
137
+ var utils = {
138
+ /**
139
+ * @description convert an object to a meaningful output that users can read
140
+ * @param {Object} o - array of prompts to ask the user
141
+ * @returns {String} easy to understand string output
142
+ */
143
+ objToString : (o ) => {
144
+ let final = ' '
145
+ for (const [key , value ] of Object .entries (o)) {
146
+ final += ` ${ key} : ${ value} \n `
147
+ }
148
+ return final
149
+ },
150
+ /**
151
+ * @description prompt the user with multiple prompts
152
+ * @param {Array} prompts - array of prompts to ask the user
153
+ * @returns {Array} list of the user's responses
154
+ */
155
+ promptUser : (prompts ) => prompts .map (p => prompt (p))
156
+ };
157
+ ```
158
+
159
+ ___
160
+
161
+ * Write actions script - ` actions.js `
162
+
163
+ ``` js
164
+ // actions get executed based on user intractability
165
+ var actions = {
166
+ createMessage : function () { // gets executed when the create message button gets pressed
167
+ const inputs = utils .promptUser ([" Name: " , " Message: " ])
168
+ if (inputs .every (e => e)) { // checks if all fields were filled in with some sort of content
169
+ API .createMessage (... inputs).then (response => {
170
+ alert (" Created message successfully" )
171
+ this .refreshMessages ()
172
+ }).catch (alert)
173
+ } else {
174
+ alert (" Please enter the fields correctly" )
175
+ }
176
+ },
177
+ refreshMessages : function (author ) { // updates ui with all current messages & authors (if no author provided)
178
+ if (! author) {
179
+ API .getAllAuthors ().then ((response ) => {
180
+ document .querySelector (" #authors" ).innerHTML =
181
+ " Filter by: <button class='btn btn-primary' onclick='actions.refreshMessages()'>Everyone</button> "
182
+ response .map (author => {
183
+ document .querySelector (" #authors" ).innerHTML +=
184
+ ` <button class="btn btn-secondary" onclick='actions.refreshMessages("${ author} ")'>${ author} </button> `
185
+ })
186
+ }).catch (alert)
187
+ }
188
+ API [author ? " getMessagesByAuthor" : " getAllMessages" ](author).then (response => {
189
+ document .querySelector (" #cards" ).innerHTML = " " // reset before appending
190
+ response .forEach (msg => {
191
+ document .querySelector (" #cards" ).innerHTML += utils .card (msg)
192
+ })
193
+ }).catch (alert)
194
+ },
195
+ moreInfo : function (id ) {
196
+ API .getMessage (id).then ((data ) => { alert (utils .objToString (data)) })
197
+ .catch (alert)
198
+ },
199
+ editMessage : function (id ) {
200
+ API .editMessage (... utils .promptUser ([" Name: " , " Edited message: " ]), id) // spread syntax
201
+ .then ((response ) => {
202
+ this .refreshMessages ()
203
+ alert (" Edited message." )
204
+ }).catch (alert)
205
+ },
206
+ deleteMessage : function (id ) {
207
+ API .deleteMessage (... utils .promptUser ([" Name: " ]), id)
208
+ .then ((response ) => {
209
+ this .refreshMessages ()
210
+ alert (" Deleted message." )
211
+ }).catch (alert)
212
+ }
213
+ };
214
+ ```
215
+
216
+ ___
217
+
218
+ * Add render card function - ` utils.js `
219
+ ``` diff
220
+ var utils = {
221
+ /**
222
+ * @description convert an object to a meaningful output that users can read
223
+ * @param {Object} o - array of prompts to ask the user
224
+ * @returns {String} easy to understand string output
225
+ */
226
+ objToString: (o) => {
227
+ let final = ''
228
+ for (const [key, value] of Object.entries(o)) {
229
+ final += `${key}: ${value}\n`
230
+ }
231
+ return final
232
+ },
233
+ /**
234
+ * @description prompt the user with multiple prompts
235
+ * @param {Array} prompts - array of prompts to ask the user
236
+ * @returns {Array} list of the user's responses
237
+ */
238
+ promptUser: (prompts) => prompts.map(p => prompt(p)),
239
+ + card: ({author, content, timestamp, edited, id}) => {
240
+ + return `
241
+ + <div class="col">
242
+ + <div class="card">
243
+ + <div class="card-body">
244
+ + <h3 class="card-title"><b>${author}</b></h5>
245
+ + <p class="card-text fs-2">${content}</p>
246
+ + <p class="text-secondary fs-6">${timestamp} ${edited ? "<i>(edited)</i>" : ''}</p>
247
+ + <button onclick="actions.moreInfo('${id}')" class="btn btn-info">More Info</button>
248
+ + <button onclick="actions.editMessage('${id}')" class="btn btn-warning">
249
+ + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-pencil-square" viewBox="0 0 16 16">
250
+ + <path d="M15.502 1.94a.5.5 0 0 1 0 .706L14.459 3.69l-2-2L13.502.646a.5.5 0 0 1 .707 0l1.293 1.293zm-1.75 2.456-2-2L4.939 9.21a.5.5 0 0 0-.121.196l-.805 2.414a.25.25 0 0 0 .316.316l2.414-.805a.5.5 0 0 0 .196-.12l6.813-6.814z"/>
251
+ + <path fill-rule="evenodd" d="M1 13.5A1.5 1.5 0 0 0 2.5 15h11a1.5 1.5 0 0 0 1.5-1.5v-6a.5.5 0 0 0-1 0v6a.5.5 0 0 1-.5.5h-11a.5.5 0 0 1-.5-.5v-11a.5.5 0 0 1 .5-.5H9a.5.5 0 0 0 0-1H2.5A1.5 1.5 0 0 0 1 2.5v11z"/>
252
+ + </svg>
253
+ + </button>
254
+ + <button onclick="actions.deleteMessage('${id}')" class="btn btn-danger">
255
+ + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-trash-fill" viewBox="0 0 16 16">
256
+ + <path d="M2.5 1a1 1 0 0 0-1 1v1a1 1 0 0 0 1 1H3v9a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V4h.5a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1H10a1 1 0 0 0-1-1H7a1 1 0 0 0-1 1H2.5zm3 4a.5.5 0 0 1 .5.5v7a.5.5 0 0 1-1 0v-7a.5.5 0 0 1 .5-.5zM8 5a.5.5 0 0 1 .5.5v7a.5.5 0 0 1-1 0v-7A.5.5 0 0 1 8 5zm3 .5v7a.5.5 0 0 1-1 0v-7a.5.5 0 0 1 1 0z"/>
257
+ + </svg>
258
+ + </button>
259
+ + </div>
260
+ + </div>
261
+ + <br/>
262
+ + </div>
263
+ + `
264
+ + }
265
+ };
266
+ ```
267
+
268
+ ___
269
+
270
+ * Add onload code - ` index.html `
271
+ ``` diff
272
+ <!doctype html>
273
+ <html lang="en">
274
+ <head>
275
+ <meta charset="utf-8">
276
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
277
+ <meta name="viewport" content="width=device-width, initial-scale=1">
278
+ <title>Messenger App</title>
279
+ <style>
280
+ #footer {
281
+ position: fixed;
282
+ left: 0;
283
+ bottom: 0;
284
+ width: 100%;
285
+ }
286
+ </style>
287
+ <!-- Bootstrap CSS Stylesheet-->
288
+ <link href="https://cdn.jsdelivr.net/npm/
[email protected] /dist/css/bootstrap.min.css" rel="stylesheet"
289
+ integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
290
+ <!-- Bootstrap Javascript-->
291
+ <script src="https://cdn.jsdelivr.net/npm/
[email protected] /dist/js/bootstrap.bundle.min.js"
292
+ integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p"
293
+ crossorigin="anonymous"></script>
294
+ <!-- Axios HTTP Library -->
295
+ <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
296
+ </head>
297
+
298
+ <body>
299
+ <div class="container">
300
+ <!-- Title header -->
301
+ <h1>Messenger App</h1>
302
+ <hr />
303
+ <!-- Bootstrap Accordion https://getbootstrap.com/docs/5.0/components/accordion START -->
304
+ <div class="accordion" id="accordionExample" style="margin-bottom: 25px">
305
+ <div class="accordion-item">
306
+ <h2 class="accordion-header" id="headingOne">
307
+ <button class="accordion-button" type="button" data-bs-toggle="collapse"
308
+ data-bs-target="#collapseOne" aria-expanded="true" aria-controls="collapseOne">
309
+ Authors
310
+ </button>
311
+ </h2>
312
+ <div id="collapseOne" class="accordion-collapse collapse show" aria-labelledby="headingOne"
313
+ data-bs-parent="#accordionExample">
314
+ <div id="authors" class="accordion-body">
315
+ </div>
316
+ </div>
317
+ </div>
318
+ </div>
319
+ <!-- Bootstrap Accordion END -->
320
+ <!-- Card container (Houses all cards/messages) START -->
321
+ <div class="row" id="cards" style="margin-bottom: 50px">
322
+ </div>
323
+ <!-- Footer (sticks to the bottom of the screen) -->
324
+ <div id="footer">
325
+ <button style="width: 100%" class="btn btn-primary">Create</button>
326
+ </div>
327
+
328
+ </div>
329
+ <!-- External custom scripts -->
330
+ <script src="API.js"></script>
331
+ <script src="utils.js"></script>
332
+ <script src="actions.js"></script>
333
+ <script>
334
+ - // More code will be here
335
+ + actions.refreshMessages()
336
+ </script>
337
+ <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
338
+ <script src="https://code.jquery.com/jquery-1.12.4.min.js"
339
+ integrity="sha384-nvAa0+6Qg9clwYCGGPpDQLVpLNn0fRaROjHqs13t4Ggj3Ez50XnGQqc/r8MhnRDZ"
340
+ crossorigin="anonymous"></script>
341
+ </body>
342
+ </html>
343
+ ```
0 commit comments