Dominique Clarke a4c53e0cea added clean index.html and styles.css | 6 lat temu | |
---|---|---|
README.md | 6 lat temu | |
index.html | 6 lat temu | |
styles.css | 6 lat temu |
index.html
and styles.css
, providing you with the basic html structure of the project and some basic styles.python -m SimpleHTTPServer 8000
. This will expose the project on localhost:8000
. Navigate there in your browser to view your project.index.js
.<head>
of your index.html
file, using the <script>
tag.
<script>
tag, type
and async
.type
attribute, assign it a value of module
. This denotes that the file should be treated as a JavaScript module. Normally, JavaScript files are executed immediately once they are downloaded, even if the HTML hasn't finished parsing yet. We'll explore the benefits of JavaScirpt modules throughout this lab, but one benefit is that the executive of modules
is deferred
until after the HTML is Parsed.
Read more about JavaScript modulesasync
attribute, assign it a value of true
. Typically, when an HTML file hits a <script>
tag, it stops parsing the HTML, downloads the JavaScript, and then executes the JavaScript. async="true"
overrides this behavior, instead allowing the HTML to be parsed alongside downloading the JavaScript. Once the JavaScript is finished downloading, the HTML parsing is paused and the script executes. Read more about async and deferindex.html
file, declare a new variable called currentUser
and assign it your YouAreEll username (You should have made one in the previous YouAreEll lab).window
object. The addEventListener
method takes two parameters, the type of event you're listening for (examples include "load", "click", "keydown", etc), and a function reference, known as a [callback](), representing the function you want to invoke when the event occurs. Wraping code in a "load" event listener attached to the window
object will insure that your code is only ran once the page has loaded.
```javascript
let userId = "dominiqueclarke";window.addEventListener("load", function () {
});
* Our goal is to add some text to the ``<h2`` element, nested within the ``header`` element containing the ``id`` of ``greeting``. In order to do so, we need to grab this element off the [``document``](https://developer.mozilla.org/en-US/docs/Web/API/Document) object
* Use the [``getElementById``](https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementById) method to grab the element containing the id ``greeting``. This will return to you an object of type [``element``](https://developer.mozilla.org/en-US/docs/Web/API/Element), allowing you to use any of the methods or access any of the properites available on the element interface.
* Assign the [``innerHTML``](https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML) property the [template string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals) ``` `Welcome ${userId}` ```
```javascript
let userId = "dominiqueclarke";
window.addEventListener("load", function () {
document.getElementById("greeting").innerHTML = `Welcome ${userId}!`;
});
message-serivce.js
. This file will contain a JavaScript class called MessageService
, responsible for making HTTP requests to fetch and update data from the YouAreEll RESTful API.
```javascript
class MessageService {
}
* Configure your ``MessageService`` as a module.
* In JavaScript, the word "modules" refers to small units of independent, reusable code. They are the foundation of many JavaScript design patterns and are critically necessary when building any non-trivial JavaScript-based application. The closest analog in the Java language are Java Classes. However, JavaScript modules export a value, rather than define a type. In practice, most JavaScript modules export an object literal, a function, or a constructor. Modules that export a string containing an HTML template or a CSS stylesheet are also common.
* The [``export``](https://developer.mozilla.org/en-US/docs/web/javascript/reference/statements/export) statement is used when creating JavaScript modules to export functions, objects, classes or primitive values from the module so they can be used by other programs with the import statement.
* ``export`` your ``MessageService`` as the ``default``.
```javascript
export default class MessageService {
}
index.js
file using the import
statement. This creates a global variable containing the exported value from the imported module.
```javascript
import MessageService from "./message-service.js";let userId = "dominiqueclarke";
window.addEventListener("load", function () {
document.getElementById("greeting").innerHTML = `Welcome ${userId}!`;
});
* Create a new ``MessageService`` object by using the ``new`` keyword to invoke the ``MessageService`` constructor.
```javascript
import MessageService from "./message-service.js";
let userId = "dominiqueclarke";
const messageService = new MessageService();
window.addEventListener("load", function () {
document.getElementById("greeting").innerHTML = `Welcome ${userId}!`;
});
message-service.js
, create a method called getAllMessages
, which takes 0 parametersXMLHTTPRequest
(XHR) object and assign it to a variable called request
. XMLHttpRequest (XHR) objects interact with servers through HTTP
requests. You can retrieve data from a URL without having to do a full page refresh. XMLHttpRequest is used heavily in Ajax programming.open
method on the request
object, passing the type of HTTP
request you'd like to make and the request endpoint as the first two arguments. To get all the global messages, use the /messages/
endpoint. Refer back to the original YouAreEll lab for documentation on the API if necessary.Use the send
method to send the request. This method takes an optional parameter of the request body
when necessary.
export default class MessageService {
getAllMessages() {
let request = new XMLHttpRequest();
request.open("GET", "http://zipcode.rocks:8085/messages");
request.send();
}
}
We've configured and sent the request, but what happens when we receive the request back? We can define a function to be used once the response is received using the onload
property of the request
object.
getAllMessages() {
let request = new XMLHttpRequest();
// Setup our listener to process compeleted requests
request.onload = function() {
// do something
};
request.open("GET", `http://zipcode.rocks:8085/messages`);
request.send();
}
If the status is greater than or equal to 200 and less than 300, than we have a successful response. Else, we have an error. Create an if/else statement to handle the response or error.
The response is stored in the responseText
property of the request
object as an array of JSON objects. To convert it into an array of JavaScript objects, use JSON.parse(request.responseText)
.
getAllMessages() {
let request = new XMLHttpRequest();
// Setup our listener to process compeleted requests
request.onload = function() {
if (request.status >= 200 && request.status < 300) {
console.log(JSON.parse(request.responseText)); // 'This is the returned text.'
} else {
console.log('Error: ' + request.status); // An error occurred during the request.
}
};
request.open("GET", "http://zipcode.rocks:8085/messages");
request.send();
}
Test the function by navigating back to index.js
and invoking the function.
```javascript
import MessageService from "./message-service.js";
let userId = "dominiqueclarke"; const messageService = new MessageService(userId);
window.addEventListener("load", function () {
document.getElementById("greeting").innerHTML = `Welcome ${userId}!`;
messageService.getAllMessages();
});
* Refresh your browser. Right click on the page and select ``inspect``. When the dev tools container pops up, click the ``console`` tab. Once the response is returned, you should see the returned array of messages printed to the console.
### Part 4.0 - Promise based AJAX requests
* Our current ``getAllMessages`` method has some issues. XMLHTTPRequests are processed asynchronously using callbacks. Callbacks cannot contain a return value. This makes it difficult to pass back a value to ``index.js`` where this ``messageService.getAllMessages()`` is being called. Fortunately, we can alieviate this issue using [``promises``](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise).
* A [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) is an object representing a contract to preform some task asynchronous (often, an ``HTTP`` request), providing a value (often, an ``HTTP`` response) when the task is complete.
* Promises allow us to continue running syncronous code while waiting for for the execution of the promised task.
* Promises allow us to specify a function that should be run once the task is complete using the ``then`` method.
* Promises are tricky. Familiarize yourself with Promises with [this tutorial](https://www.sitepoint.com/overview-javascript-promises/)
* Wrap your ``request.onload`` function in a ``new`` ``Promise``;
```javascript
getAllMessages() {
const request = new XMLHttpRequest();
new Promise(function (resolve, reject) {
// Setup our listener to process compeleted requests
request.onload = function () {
// Process the response
if (request.status >= 200 && request.status < 300) {
console.log(JSON.parse(request.responseText)); // 'This is the returned text.'
} else {
console.log('Error: ' + request.status); // An error occurred during the request.
}
};
request.open("GET", "http://zipcode.rocks:8085/messages");
request.send();
});
}
If the request is successful, resolve
the promise
passing in the threads
object``
getAllMessages() {
const request = new XMLHttpRequest();
new Promise(function (resolve, reject) {
// Setup our listener to process compeleted requests
request.onload = function () {
// Process the response
if (request.status >= 200 && request.status < 300) {
const threads = JSON.parse(request.responseText); // 'This is the returned text.'
resolve(threads);
} else {
console.log('Error: ' + request.status); // An error occurred during the request.
}
};
request.open("GET", "http://zipcode.rocks:8085/messages");
request.send();
});
}
If the request returns an error, reject
the promise
passing in the threads
object``
getAllMessages() {
const request = new XMLHttpRequest();
new Promise(function (resolve, reject) {
// Setup our listener to process compeleted requests
request.onload = function () {
// Process the response
if (request.status >= 200 && request.status < 300) {
const threads = JSON.parse(request.responseText); // 'This is the returned text.'
resolve(threads);
} else {
reject({
status: request.status,
statusText: request.statusText
});
}
};
request.open("GET", "http://zipcode.rocks:8085/messages");
request.send();
});
}
Specify the function you'd like executed when the promise is resolved by using the then
method.
then
method is part of the Promise
interface. It takes up to two parameters: a callback
function for the success case and a callback function for the failure case of the Promise
.Promise
is successful, the first parameter (the success callback), is executed. If the Promise
results in an error, the second parameter (the failure callback), is excuted.new Promise(function (resolve, reject) {
// Setup our listener to process compeleted requests
request.onload = function () {
// Process the response
if (request.status >= 200 && request.status < 300) {
// If successful
const threads = JSON.parse(request.responseText);
resolve(threads);
} else {
reject({
status: request.status,
statusText: request.statusText
});
}
};
request.open("GET", "http://zipcode.rocks:8085/messages");
request.send();
}).then(successCallback, errorCallback);
function successCallback() {
console.log("Promise is successful!");
}
function errorCallback() {
console.log("An error occurred");
} } ```
When the callbacks are executed, the receive a special parameter. The success callback receives the value passed to the resolve
method, while the failure callback receives the value passed to the reject
method.
getAllMessages() {
const request = new XMLHttpRequest();
new Promise(function (resolve, reject) {
// Setup our listener to process compeleted requests
request.onload = function () {
// Process the response
if (request.status >= 200 && request.status < 300) {
// If successful
const threads = JSON.parse(request.responseText);
// this data is passed to the success callback
resolve(threads);
} else {
// this data is passed to the failure callback
reject({
status: request.status,
statusText: request.statusText
});
}
};
request.open("GET", "http://zipcode.rocks:8085/messages");
request.send();
}).then(successCallback, errorCallback);
function successCallback(response) {
// This data comes from the resolve method
console.log(response);
}
function errorCallback(response) {
// This data comes from the reject method
console.log(response);
}
}
getAllMessages
method, we can consume the Promise
within our index.js
file, allowing for separation of concerns.then
method, successCallback
declaration and errorCallback
declaration from getAllMessages
.return
the Promise from the getAllMessages
method. This will allow us to call the then
method, passing in the appropriate success and failure callbacks, elsewhere.
getAllMessages() {
const request = new XMLHttpRequest();
return new Promise(function (resolve, reject) {
// Setup our listener to process compeleted requests
request.onload = function () {
// Process the response
if (request.status >= 200 && request.status < 300) {
// If successful
const threads = JSON.parse(request.responseText);
// this data is passed to the success callback
resolve(threads);
} else {
// this data is passed to the failure callback
reject({
status: request.status,
statusText: request.statusText
});
}
};
request.open("GET", "http://zipcode.rocks:8085/messages");
request.send();
})
}
Navigate back to your index.js
file. getAllMessages
now returns a Promise
. We can now use the then
method to specify a callback
function to be executed in case of success or failure of that Promise
. Call .then
on messageService.getAllMessages
, reimplementing the original code.
```javascript
messageService.getAllMessages()
.then(successCallback, errorCallback);
function successCallback(response) {
// This data comes from the resolve method
console.log(response);
}
function errorCallback(response) {
// This data comes from the reject method
console.log(response);
}
### Part 6.0 - Populating the DOM
* Now that we have our messages, let's add them to our page visually. Using the DOM interface, we can create and add HTML elements to our page.
* We'll populate our messages inside the unordered list``<ul id="message-list">``.
* Create a new function in ``index.js`` called ``populateMessages``. ``populateMessages`` should take one parameter, a list of messages.
```javascript
import MessageService from "./message-service.js";
let userId = "dominiqueclarke";
const messageService = new MessageService(userId);
window.addEventListener("load", function () {
document.getElementById("greeting").innerHTML = `Welcome ${userId}!`;
messageService.getAllMessages()
.then(successCallback, errorCallback);
function successCallback(response) {
// This data comes from the resolve method
console.log(response);
}
function errorCallback(response) {
// This data comes from the reject method
console.log(response);
}
});
function populateMessages(messages) {
}
DOM
, we need to create new nodes
. A node
is an interface is an interface from which a number of DOM
API object types inherit, including document
, element
and more. A node
represents a piece of the DOM
tree.forEach
loop, loop through each message in the array of messages
.<li>
element
to hold the sender username and the message content and assign it to messageListItem
.
createElement
method on the document
object, passing in the element tag name as a string. This will return a new HTML element
that you can later append to the DOM
. Remember, elements
are a type of node
.<h3>
element for the sender username and assign it to const userIdHeading
.<p>
element for the message content and assign it to const messageParagraph
.
javascript
function populateThread(messages) {
messages.forEach(message => {
const messageListItem = document.createElement("LI");
const userIdHeading = document.createElement("h3");
const messageParagraph = document.createElement("p");
})
}
<h3>
element and our <p>
element will contain text.
text node
. You can create a text node
using the createTextNode
method on the document
object, passing in the text you wish to include in the node. This will return a new text node
that you can later append to an element
.text node
using the fromid
property on the message
object and assign it to const userIdContent
.text node
using the message
property on the message
object and assign it to const messageContent
.
javascript
function populateThread(messages) {
messages.forEach(message => {
const messageListItem = document.createElement("LI");
const userIdHeading = document.createElement("h3");
const messageParagraph = document.createElement("p");
const messageContent = document.createTextNode(message.message);
const userIdContent = document.createTextNode(message.fromid);
})
}
appendChild
] method. The Node.appendChild()
method adds a node to the end of the list of children of a specified parent node. appendChild
returns the modified node
object, allowing you to perform method chaining.messageContent
node
to your messageParagraph
node
using the appendChild
method.userIdContent
node
to your userIdHeading
node
using the appendChild
method.userIdHeading
node
and your messageParagraph
node
to your messageListItem
node, using the appendChild
method and method chaining.
javascript
function populateThread(messages) {
messages.forEach(message => {
const messageListItem = document.createElement("LI");
const userIdHeading = document.createElement("h3");
const messageParagraph = document.createElement("p");
const messageContent = document.createTextNode(message.message);
const userIdContent = document.createTextNode(message.fromid);
userIdHeading.appendChild(userIdContent);
messageParagraph.appendChild(messageContent);
messageListItem
.appendChild(userIdHeading)
.appendChild(messageParagraph);
})
}
DOM
node
for each message that includes an <li>
containing a <h3>
element
for the message.fromId
and an <p>
element
for the message.message
.node
, we need to add it to an existing HTML element
on our page. Review the index.html
file and find <ul id="message-list">
. We want to add all of our new individual <li>
elements to this <ul>
. To grab this element
using javascript, we can use the getElementById
method on the document
object, passing in the element's id
as a string.appendChild
method, append the messageListItem
node
to the element
returned using document.getElementById("message-list")
. This will add a new <li>
representing each message to our <ul id="message-list">
element.
javascript
function populateThread(messages) {
messages.forEach(message => {
const messageListItem = document.createElement("LI");
const userIdHeading = document.createElement("h3");
const messageParagraph = document.createElement("p");
const messageContent = document.createTextNode(message.message);
const userIdContent = document.createTextNode(message.fromid);
userIdHeading.appendChild(userIdContent);
messageParagraph.appendChild(messageContent);
messageListItem
.appendChild(userIdHeading)
.appendChild(messageParagraph);
document.getElementById("message-list").appendChild(messageListItem);
})
}
Now that we've created our message, let's invoke the function from our successCallback
method, passing in the array of messages
returned from our HTTP request.
window.addEventListener("load", function () {
document.getElementById("greeting").innerHTML = `Welcome ${userId}!`;
messageService.getAllMessages()
.then(successCallback, errorCallback);
function successCallback(response) {
// This data comes from the resolve method
populateMessages(response);
}
function errorCallback(response) {
// This data comes from the reject method
console.log(response);
}
});
Refresh your page to review the results and check for any errors
message-service.js
file. Add a new method called createNewMessage
. It should take one parameter, the new message
object.XMLHTTPRequest
. The set up is the same as our getAllMessages
method, except for calling the request.open
and request.send
methods.POST
method. In the request.open
method, pass in "POST"
as the first parameter, and the Post endpoint as the second parameter. The endpoint to send a new message is /ids/:mygithubid/messages/
. Refer back to the original YouAreEll lab for documentation on the API if necessary.For HTTP
methods where a request body
is necessary, pass the request body as a parameter to the request.send
method. To send our message
object as the request
body, first convert it from a JavaScript object to a JSON object using the JSON.stringify
method.
createNewMessage(message) {
const request = new XMLHttpRequest();
return new Promise(function (resolve, reject) {
// Setup our listener to process compeleted requests
request.onload = function () {
// Process the response
if (request.status >= 200 && request.status < 300) {
// If successful
resolve(JSON.parse(request.responseText));
} else {
reject({
status: request.status,
statusText: request.statusText
});
}
};
request.open("POST", `http://zipcode.rocks:8085/ids/${message.fromid}/messages`);
request.send(JSON.stringify(message));
});
}
Navigate to your index.js
file. Notice that in our index.html
file, we have a form
. This form exists to create and send new messages. In order to set up the form to listen to input from the user and respond propertly to the user hitting the submit button, we need to set up an eventListener
for our form.
Create a new function in index.js
called createFormListener
. This method takes 0 parameters.
```javascript
function createFormListener() {
}
* Grab the ``form`` ``element`` using ``document.getElementById`` passing in the ``id`` of the ``form``.
* Set the onsubmit property of the ``form`` to a function reference. This function takes one parameter, ``event``. This function will fire when the form is submitted.
* To prevent the default form action from occuring, use the ``preventDefault`` method on the ``event`` object.
```javascript
function createFormListener() {
const form = document.getElementById("new-message-form");
form.onsubmit = function (event) {
// stop the regular form submission
event.preventDefault();
}
};
index.html
and find the form
element. Notice that the form
contains two form elements, textarea
and button
. textarea
has an attribute
of name
set to the property message
. When form elements are given a name
attribute
, it adds information about that element the form
object as a property.data
with two properties, fromid
and message
. fromid
should be assigned the value of userid
, and message should be assigned the value of form.message.value
(the value of the textarea with attribute name="message"
).createNewMessage
method on the messageService
object, passing in the data
object. The createNewMessage
method returns a Promise
, so specify your success and failure callbacks
using the then
method.In your successCallback
method, invoke the populateMessages
function createFormListener() {
const form = document.getElementById("new-message-form");
form.onsubmit = function (event) {
// stop the regular form submission
event.preventDefault();
const data = {
fromid: userId,
message: form.message.value
};
messageService.createNewMessage(data)
.then(successCallback, errorCallback);
function successCallback(response) {
// This data comes from the resolve method
console.log(response);
}
function errorCallback(response) {
// This data comes from the reject method
console.log(response);
}
}
};
Just like we added our array of messages from before, we now need to add our new message to our list of messages.
Navigate to your index.js
file. Add a method called addMessageToThread
. The method should take on parameter, a single message
.
Like before, we need to create a bunch of individual nodes and combine them together in order to create a full <li>
element containing a message.
function addMessageToThread(message) {
const messageListItem = document.createElement("LI");
const userIdHeading = document.createElement("h3");
const messageParagraph = document.createElement("p");
const messageContent = document.createTextNode(message.message);
const userIdContent = document.createTextNode(message.fromid);
userIdHeading.appendChild(userIdContent);
messageParagraph.appendChild(messageContent);
messageListItem
.appendChild(userIdHeading)
.appendChild(messageParagraph);
messageList.appendChild(messageListItem);
}
Does this code look familiar? Before we move forward, let's go back and refactor our populateThread
method to use this addMessageToThread
method.
function populateMessages(messages) {
messages.forEach(message => {
addMessageToThread(message);
})
}
Navigate back to your createFormListener
method. In the successCallback
, invoke the addMessageToThread
method, passing in the response, instead of logging the response.
function createFormListener() {
const form = document.getElementById("new-message-form");
form.onsubmit = function (event) {
// stop the regular form submission
event.preventDefault();
const data = {
fromid: userId,
message: form.message.value
};
messageService.createNewMessage(data)
.then(successCallback, errorCallback);
function successCallback(response) {
// This data comes from the resolve method
addMessageToThread(response);
}
function errorCallback(response) {
// This data comes from the reject method
console.log(response);
}
}
};