-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathindex.html
754 lines (552 loc) · 26.2 KB
/
index.html
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
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>ChatApp Tutorial</title>
<!-- Bootstrap -->
<link href="dist/css/bootstrap.css" rel="stylesheet">
<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->
</head>
<body>
<!-- <div class = "sidepanel"> -->
<div class="nav_menu">
<div id="repo" class="step top" href="#">Intro</div>
<div id="github" class="step inner" href="#">Git & GitHub</div>
<div id="server" class="step inner" href="#">Server</div>
<div id="client" class="step inner" href="#">Client</div>
<div id="testing" class="step inner" href="#">Testing</div>
<div id="styling" class="step inner" href="#">Styling</div>
<div id="hosting" class="step bottom" href="#">Hosting</div>
</div>
<div id="links">
<div id="gitLink" class="link"><a href="http://rogerdudler.github.io/git-guide/">An simple guide to Git</a></div>
<div id="githubLink" class="link"><a href="https://help.github.com/articles/set-up-git/">Getting Started with GitHub</a></div>
<div id="serverLink" class="link"> <a href=" http://www.developerfusion.com/article/143158/an-introduction-to-websockets/">Intro to WebSockets</a></div>
<div id="serverLink2" class="link"><a href="https://www.websocket.org/aboutwebsocket.html">The WebSocket Authority</a></div>
<div id="serverLink3" class="link"><a href="http://cjihrig.com/blog/creating-your-own-node-js-websocket-echo-server/">Echo WebSocket servers</a></div>
<div id="clientLink" class="link"><a href="http://ahoj.io/nodejs-and-websocket-simple-chat-tutorial">Some other guy's tutorial with JSON</a></div>
<div id="clientLink2" class="link"><a href="https://developer.mozilla.org/en-US/docs/WebSockets/Writing_WebSocket_client_applications">MDN's take on Clients with JSON</a></div>
</div>
<xmp theme="cerulean" style="display:none;">
<a class="anchors" id="one"></a>
#Chat App Tutorial
---
<h2 id = "1">Build your own chat app!</h2>
---
If you'd like to create the next SnapChat, proceed.
If you'd like to be the creator of a new and improved SnapChat, read on.
You still there? Awesome. Thanks for checking out our tutorial!
Today you'll learn how to create your first functional, peer to peer, chat app. For many beginner JavaScript developers, building one of these has become somewhat of a rite of passage so congratulations, welcome to the club. Chat app projects are a good place to test your budding Javascript skills because it's framework comprise of complex yet accessible parts.
Here, we'll show you the basic framework. And from the framework, we'll go on to teach you how to style, test, and host your site! Sounds like a lot right? Don't panic, youre in good hand. By the end of our tutorial, you'll be able to build features that go above and beyond the SnapChat-standard.
We'll teach you what a server and client does and of course give you instruction on the code to make the server and client connect with each other.
By the end you will have learned: how to create a server and client, how to get them communicating with each other, how to style the app, how to test it, how to host it, and finally, how to impress the internet by pushing your code to Github.
###WebSockets
---
A WebSocket is a technology, based on the WS protocol, that makes it possible to establish a continuous full-duplex connection stream between a client and a server. A typical WebSocket client would be a user's browser, but the protocol is platform independent. The WebSocket API is available to JavaScript code whose scope is either a DOM Window object or any object implementing WorkerUtil.
The WebSockets API was originally part of the HTML5 standard, but it has been split off into a separate W3C standard. The WebSockets protocol is an IETF standard described in RFC 6455.
The WebSockets API has full browser support in Chrome 14, Firefox 6, IE 10 (desktop and mobile), Opera 12.1 (desktop and moblie), Safari 6.0 (desktop and mobile), Android 4.4, Chrome Mobile, and Firefox Mobile. Some older browsers have partial support or can be supported using a Flash based fallback.
This simple example creates a new WebSocket, connecting to the server at:
```
var WebSocket = require("ws");
var ws = new WebSocket("ws://localhost:3000");
ws.on("open", function () {
console.log("Connected to server.");
});
```
</br>
---
#Git & Github
---
<h2 id = "2">Creating a Git Repository</h2>
---
The git init command creates a new Git repository. It can be used to convert an existing, unversioned project to a Git repository or initialize a new empty repository. Most of the other Git commands are not available outside of an initialized repository, so this is usually the first command you’ll run in a new project.
The git clone command copies an existing Git repository. This is sort of like svn checkout, except the “working copy” is a full-fledged Git repository—it has its own history, manages its own files, and is a completely isolated environment from the original repository.
The git config command lets you configure your Git installation (or an individual repository) from the command line. This command can define everything from user info to preferences to the behavior of a repository. Several common configuration options are listed below.
Linktos or Asides or Syntax Bubble:
Executing git init creates a .git subdirectory in the project root, which contains all of the necessary metadata for the repo, and makes it possible to start recording revisions of the project..
$ git clone <repository link>
Clone the repository located at <repo> onto the local machine. The original repository can be located on the local filesystem or on a remote machine accessible via HTTP or SSH.
To update your local repository to the newest commit, execute git pull in your working directory to fetch and merge remote changes.
</br>
### Version control with Github
---
Assuming you've already created a folder inside your text editor. Now its time to create a github repo and push to github.
1. Navigate to the working directory
```
$ cd tutorialChatApp
```
2. Check status & proceed
```
$ git status
```
3. Add any changes - note `add .` will add all but you can also specify files
```
$ git add .
```
4. Commit your changes - whatever you write here will help in tracking changes
```
$ git commit -m "First commit"
```
Your changes are now in your local working copy.
5. Push up to git via https (not SSH)- granted you have no troubles with your log in. Make sure
```
$ git push origin master
```
</br>
---
#Back end
---
<h2 id = "3">Setting up the server</h2>
---
##Ws
A websocket server can receive and send data among multiple clients, and is necessary to host your chat app. The purpose of this server is to relay messages, This code will require several crucial lines of code, but additional features can be added to make your chat app more robust.
1. First, the ws module must be required.
```
var WebSocket = require("ws").Server;
var chatServer = new WebSocket({port:3000});
```
Note that the Server method is necessary when requiring ws for the purpose of creating a server. A port number of your choice should be declared, but it must consistent across future code in order for the server and clients to interact.
2. An empty array should be created to track clients connecting to the server. This will allow the server to send information to multiple clients at once.
```
var users = [];
```
3. Event listeners are added to the server in order to define how it handles connections and messages.
```
chatServer.on("connection" , function(ws){
console.log("connection");
users.push(ws);
}
```
The above code adds every connecting client to the users array, and console logs a simple message that will help you monitor /test functionality. Nested within the above function, you must also add event listeners to the clients, which are defined above by the ws parameter.
```
ws.on("message" , function(msg){
console.log(msg);
users.forEach(function(usr){usr.send(msg)});
})
```
4. Every time there is a message event, the above code will log that message on the server console, and send the message to every user in the users array, including the original sender.
5. This next step is virtually essential. In the event a client disconnects, any attempt the server makes to send a message will cause it to crash. We must add another event listener to the client that will remove it from the users array on a close event.
```
ws.on("close" , function(){
var clientIndex = users.indexOf(ws);
users.splice(clientIndex,1);
console.log("a client has disconnected");
})
```
Every time a client disconnects, the above code finds the index of that client with the users array, and then deletes their information from the array. This code in its current state is completely usable for the purpose of serving a simple chat app.
```
var WebSocket = require("ws").Server;
var chatServ = new WebSocket({port:3000});
var users = [];
chatServ.on("connection" , function(ws){
console.log("connection");
users.push(ws);
ws.on("message" , function(msg){
console.log(msg);
users.forEach(function(usr){
usr.send(msg)});
})
ws.on("close" , function(){
var client = users.indexOf(ws);
users.splice(client,1);
console.log("a client has disconnected");
})
})
```
Other possible features you might want to consider adding to your app are a persistent message history, private messages between users, and word filtering functions. Incorporating JSON into your code will allow you to package more information in every message beyond simple strings.
<br>
---
#Front end
---
<h2 id = "4">Client files</h2>
---
####HTML file
Let's make a barebones HTML file that'll allow us to use the client for the chat app.
<html lang="en">
<head>
<meta charset="UTF-8">
<!-- link to style sheet - we'll get to his later! -->
<link rel="stylesheet" type="text/css" href="tutorialStyle.css">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Chat</title>
</head>
<body>
<!-- In the beginning there was a div for the header -->
<div class="header">
<h2>A Tutorial Chat App</h2>
</div>
<!-- But! Here's a div that binds them all! -->
<div class="container">
<!-- and in the darkness... presents the message... -->
<div id="inset">
<!-- but first this div collects your name! -->
<div id = "fixed">
<p id="enter"> Please enter your screen name:</p></br>
<input type="text" id="inputName" placeholder="Name"></input>
</div>
<ul id="msgHolder"></ul>
</div>
</div>
<!-- This div holds the input box -->
<div class="bottom">
<!-- <div class="bottom"> -->
<input placeholder="Type here" id="inputMsg"></input>
<!-- </div> -->
</div>
<script type="text/javascript" src="tutorialclient.js"></script>
</body>
</html>
---
###Client JS file
---
In order for the client to connect to the server we'll want to set up some variables.
1. Establish a new Websocket client. Remember to be explicit in naming your variables!
```
var client = new WebSocket( "ws://localhost:3000" );
```
2. Let's say that our client is going to log in and have a name. But prior to connecting the name will be a string that is empty.
```
var userName = '';
var connected = true;
```
3. Let's setup event listeners for the client to determine what's happening.
```
client.addEventListener( 'open', function ( evt ) {
} );
client.addEventListener( 'message', function ( message ) {
} )
client.addEventListener( 'close', function ( evt ) {
} );
```
4. Now to get more detailed...
Upon connecting ('open') we want the client to set their name. In order to do this, let's have them send a message that changes their `userName` value. We'll utilize DOM manipulation and a keyboard event listener to do that.
```
// upon open
client.addEventListener( 'open', function () {
// grab inputTwo
var inputTwo = document.getElementById( 'inputTwo' )
// event listener on inputTwo
inputTwo.addEventListener( 'keyup', function ( e ) {
// if enter is pressed and the box is NOT empty
if ( e.keyCode === 13 && inputOne.value.trim() != '' ) {
// set the userName value...
var fixedDiv = document.querySelector( '#fixed' )
userName = inputOne.value;
// and grab the fixedDiv and set it's style to make it disappear!
fixedDiv.style.display = 'none';
}
} );
} );
```
5. Now that we've set our client's name upon connect, let's tackle what happens when we recieve a message.
```
// upon recieving a message
client.addEventListener( 'message', function ( message ) {
//create an li element
var li = document.createElement( "li" );
// change it's innerText value to the recieved message
li.innerText = input.value;
// set it's id to 'msg'
li.setAttribute( 'id', 'msg' )
// grab the ul element
var msgHolder = document.querySelector( "ul#msgHolder" );
// and append it to include the new li
msgHolder.appendChild( li );
// then have it display BEFORE the newest element
var before = ul.firstChild;
ul.insertBefore( li, before )
} );
```
6. While we're not doing anything complex yet, you can certainly have events occur upon close. Let's make a couple edits to display this.
We'll add a boolean to the file
```
var userName = '';
var connected = false; //set to false by default
```
and have it return true upon connect
```
client.addEventListener( 'open', function () {
connected = true;
```
and have it return back to false upon close!
```
client.addEventListener( 'close', function () {
connected = false;
```
While not necessary in our tutorial app, just note that it's possible to have events occur on the client side but you can't send anything to the server upon close.
7. BUT WAIT! How on earth do we send messages?!!
Well.. there's an app for that...
But seriously...
We've already done it upon connection so let's make another event listener.
```
// grab the inputMsg element
var inputMsg = document.getElementById( 'inputMsg' )
inputMsg.addEventListener( 'keyup', function ( e ) {
// upon pressing enter
if ( e.keyCode === 13 ) {
// grab the text in the input box and call it newMessage
var newMessage = input.value;
// with the .send method let's format and send our name & message
client.send( userName + ': ' + newMessage );
// finally, let's clear the input box
input.value = ''; //clear the input area
}
} );
```
And now you have your javascript file for your client!
<br>
---
<h1 id = 5>Local Testing</h1>
---
It's important to local test your site as much as possible but mainly after you get it set up and have finished each feature. make sure to put console.log() at different places in your code so that you can debug and know where your code is getting to and where it might be breaking.
1. Navigate in terminal to folder
Open up a terminal window and navigate into the folder that holds your Chat App
```
$ cd tutorialChatApp
```
2. Open your index.html file
To see your Chat App on the client side you will need to open your html file locally to show you what if would look like and if your chat features work.
While your in your folder, type in the command to open the index.html file.
```
$ open index.html
```
3. OOPs! An error occurred!
Before we can actually use our Chat App, we will need to run our Node.js file as well as understand what to look for with errors.
If on mac: press CMD + ALT + I on your keyboard. This should bring up the developer tools at the bottom.
Navigate to the console by clicking on the console tap in the top nav bar.
![image](pictures/picOne.png)
This console area will show if you what errors occur and on what line they occur in what file (client or server side). Also you can see how many errors have occurred total from any window in the developers tools by looking in the top right hand corner of the window.
![image](pictures/picTwo.png)
4. Run tutorialServer.js file
The first thing we need to do and should of done to test locally was run our Node.js file and start up our server side javascript.
Start by navigating into the folder with the Node.js file and then type in the command:
“node file-name”
Then press enter. you should see the cursor go the far left hand side on the next line. It is now listening for a client connection. If you ever see an error in the developer tools: “web socket console is closing or is already closed”, probably need to come check this terminal to see if there was an error thrown.
If there was an error thrown the server will shutdown and it will tell you what line and the error to look for. debug this and then try again.
5. Refresh HTML file
Now that the server is running, go refresh your index.html page in the browser
6. Check for console errors and debug!
Now open the developer tools and check for console errors.
If there are errors find where they are fix them and save the file.
Then try again.
---
<h2 id = "6"> CSS & Styling the app</h2>
---
### Got 99 problems? FLEXBOX!
---
We'll reference some of our HTML tags here so please feel free to review or refer to the [html code][@3]
####Divs Divs Divs - All about dem divs
In order for us to have a responsive (and what other way is there to be?!) design let's start with some givens.
1. Flexbox [ex: vertical centering made easy!](http://philipwalton.github.io/solved-by-flexbox/demos/vertical-centering/)
This alone will make your CSS life easier
2. Use percentages and vh instead of fixed pixels when able to in order to have a relative design. [provide links on the side to responsive design]
With the following snippet in your header you're establishing that the width of the content will be the device width, and with percentages you should be able to have easy responsive design.
<meta name="viewport" content="width=device-width, initial-scale=1">
3. Framework your design, whether by hand, inDesign, photoshop or just with divs alone before you start so you have a path.
[Flexbox Cheatsheet](http://www.sketchingwithcss.com/samplechapter/cheatsheet.html)
[Learn Layout](http://learnlayout.com/)
[Using vw and vh measurements](http://demosthenes.info/blog/660/Using-vw-and-vh-Measurements-In-Modern-Site-Design)
[Using ems](http://www.garethredfern.com/articles/responsive-web-design-using-ems)
4. A good tip to remember while working with divs is to give them all different colored borders so you can tell where they are exactly and help you position them as well. This is espescially convenient when planning to make your site responsive. For example here are 2 divs that we'll be working on:
```
div.container {
border: thin solid black;
height: 80%;
width: 80%;
margin: auto;
}
.bottom {
border: thin solid orange;
height: 40px;
width: 80%;
margin: auto;
}
```
---
####Creating the CSS file
___
1. Please note that while we refer to the divs as `div.whatever` it's only to point it out to you. Let's set up all our divs now so we can view the frame:
```
div.container {
border: thin solid black;
height: 80%; /* set to 80% of the body */
width: 80%; /* set to 80% of the window */
display: flex;
margin: auto;
}
div#fixed {
border: thin solid black;
min-height: 10vh; /* vh is viewport height - this says 10% of vh*/
margin: auto;
text-align: center;
padding: 2%;
overflow: auto;
}
div#inset {
border: thin solid red;
height: 100%;
width: 100%;
min-width: 30vh;
overflow: auto;
align-self: center;
display: flex;
flex-direction: column-reverse;
}
div.bottom {
display: flex;
border: thin solid orange;
height: 40px;
width: 80%;
display: flex;
margin: auto;
font-size: 1.25em;
}
```
2. Working from top down, we'll style the header:
```
.header {
font-size: 100%;
/*set the font-size to 100% (note the %)*/
text-align: center;
/*set the text-align value to center*/
width: 50%;
/*we'll establish the width at 50%*/
margin-left: 25%;
/*as we've set the width to 50% we can set the margin-left at 25% thereby centering the element*/
}
```
3. We are setting the container div to have `display: flex;` in order to easily vertical center the fixed div that'll appear upon login.
```
div.container {
height: 80%;
width: 80%;
display: flex; /* Hey Alex... FLEXBOX! */
margin: auto;
}
div#fixed {
min-height: 10vh;
margin: auto; /*setting the display of the parent to flex lets you vertical center with just this line!*/
text-align: center;
padding: 2%;
overflow: auto;
}
p#enter {
font-size: 80%;
line-height: 10%;
vertical-align: top;
}
```
Note that we've removed the borders now and have styled the components of the div that will disappear upon recieving client name as well.
4. Continuing with our responsive design let's edit the `div#inset` and the `div.bottom`.
```
#inset {
background-color: #1a1a1a;
color: #2cbda9;
height: 100%;
width: 100%;
min-width: 30vh;
overflow: auto;
align-self: center;
display: flex;
flex-direction: column-reverse;
}
.bottom {
display: flex;
background-color: #333;
height: 40px;
width: 80%;
display: flex;
margin: auto;
font-size: 1.25em;
}
```
5. And finish off with the input elements and text...
```
input {
border-style: none;
background-color: #333;
}
#inputName {
color: #ff323b;
font-size: 1em;
text-align: center;
}
#inputMsg {
flex: 2;
/*FLEXBOX! This flex setting allows us to span the input to the entire div*/
font-size: 1em;
padding-left: 1%;
/*When adjusting the blank space, remember to stay responsive!*/
background-color: #333;
color: #ff323b;
}
li#msg {
margin-right: 5%;
list-style-type: none;
}
```
###Voila!
![image](chatApp.png)
---
<h1 id = "7"> Hosting your chat app online</h1>
---
### Push files to Github
---
Before we can set up a the hosting side of things we need to make sure out github is up to date!
As you remember in steps one and two we set up the git repo and connected it to our github repo.
Lets push our files up to that repo:
1. first initiate push by typing in the terminal:
```
$ git add .
```
2. Then asign a commit log by typing in the terminal:
```
$ git commit -m “first commit”
```
3. then push it by typing in the terminal:
```
$ git push master origin
```
Your files are now ready on Github!
### Hosting through your server
---
In order to host your files on the internet publicly we'll need to set up your server. Let's SSH into the server.
1. Open up a new terminal window.in the terminal type:
```
$ ssh root@yourserveraddress
```
Have your login information handy as you'll need to enter it here
2. Clone the site's Github repo
* Now that we are in the server we need to clone our repo over so that we have all the files.
* Go to your github page and find the directory address and copy it.
* In the Server Terminal type:
```
$ git clone directoryAddress
```
3. Once it's cloned, type in `ls` in the command line and press enter. This should show you if it is in the directory. Move (`cd`) into the folder.
4. Run your tutorialServer.js file
* Now that we have our file all set up we need to start our server, run your node file just like you did in step 6. if you need help or reference, here is a link back the step.
* Now open up a new terminal and ssh into your server again using #2 in this step.
5. Http-server
* Once you have your second terminal open and your ssh’d in, its time to start up your server.
* In the terminal type in:
`$ http-server -p80`
This should start the server and allow you to go to the server address and see your chat app up and running. The -p80 at the end allows you to not have to have a subdomain and make a clean and tidy server address. If any errors occur you can see them on the terminal where you are running the node file and also in the client side browser in the developer tools.
6. Shutting down the server
* When you are done or need to fix a bug, press CONTROL + C at anytime to shut the server down. You will need to shut the node file down as well.
</xmp>
<script src="v/0.2/strapdown.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="jquery-scrollto.js"></script>
<!-- Include all compiled plugins (below), or include individual files as needed -->
<script src="dist/js/bootstrap.min.js"></script>
<script src="scroll.js"></script>
</body>
</html>