In this small project we will create a bookshelf that we can interact with.
This project will help with learning AJAX and DOM manipulation.
We will also introduce two patterns for structuring our code.
- Open the
index.html
file in a web browser. - Open the console and look at the console error. It tells us that the file
bookshelf.js
cannot be found. - Create a file called
bookshelf.js
and refresh the webpage the error should go away.
- In our
bookshelf.js
file create a button using thecreateElement
method. - Set the button's id attribute to
fetch-books-btn
using thesetAttribute
method. - Create a text node with the text
Fetch Books
using thecreateTextNode
method. - Append the text node to the button using the
appendChild
method. - Add an event listener that will call a method called fetchBooks when the button is clicked using the
addEventListener
method. - Finally append the button to the document body.
Refresh the webpage to see a reference error fetchBooks
is undefined.
In the next stage we will define the fetchBooks
function.
- Create a new function named
fetchBooks
. - Inside the
fetchBooks
function add a console log statement. - Refresh the webpage and click the
Fetch Books
button. You should see the statement appear in the console. - If it everything has worked delete the console log statement else try and debug what has gone wrong, retracing the steps if needed.
In the next stage we will fetch the data for the books.
Add the following code within the fetchBooks
function.
const booksJSON = 'https://raw.githubusercontent.com/codeyourfuture/bookshelf-project/master/books.json'
fetch(booksJSON)
.then(response => response.json())
.then(books => {
/* TODO: create the bookshelf */
console.log(books)
})
This is the fetch
method. We are using it to fetch the books data. It executes asynchronously, meaning that we have to use a promise chain to interact with the data the fetch statement returns.
Refresh the webpage and click the Fetch Books
button. You should see the books data in the console.
Create a new function named processBooks
that takes the books JSON as a parameter.
Replace the inline function that contains the TODO
comment with the processBooks
function.
Your fetch statement should look like the following.
fetch(booksJSON)
.then(response => response.json())
.then(processBooks)
Write your solution inside the processBooks
function. The function should create an unordered list containing list items with the author and titles of the books. Each list item should also have an id attribute set to the book id.
When you click the Fetch Books
button it should add the following HTML to the webpage.
<ul>
<li id="1">The Catcher in the Rye by J. D. Salinger</li>
<li id="2">To Kill a Mockingbird by Harper Lee</li>
<li id="3">The Grapes of Wrath by John Steinbeck</li>
<li id="4">The Great Gatsby by F Scott Fitzgerald</li>
<li id="5">Moby-Dick by Herman Melville</li>
</ul>
Use the forEach
method to iterate over the books array and create the list items.
When you click the Fetch Books
button it continues to add more and more lists of books. Let us remove this button once we have created the first list.
- Add a
then
statement to the promise chain and have it call a function calledremoveBtn
. - Create the function
removeBtn
. - Inside this function find the button element using the
getElementById
method. - Remove the event listener using the
removeEventListener
method. - Finally remove the element using the
remove
method.
Refresh the webpage and click the Fetch Books
button. The button should disappear once the bookshelf is displayed.
If you are using Internet Explorer you may discover that the remove
method does not exist. Try using the removeChild
method on the button's parentNode
.
To organise our bookshelf we are going to need to move the books around.
- Add ⬆ and ⬇ buttons to the book list items.
The HTML should look like the following.
<li id="1">
<button>⬆</button>
<button>⬇</button>
- The Catcher in the Rye by J. D. Salinger
</li>
- Create a function named
moveUp
that accepts a parameter calledid
and contains a console log statement that saysmove up
followed by theid
. - Create a similar function named
moveDown
.
We are going to wire up those two functions so that when the ⬆ and ⬇ buttons are clicked they get triggered. However we don't want to attach event listeners to every button (that's crazy!) so we are going to use a common ancestor (a parent node) to handle the event for us. This is pattern is called event delegation and it is very useful for simplifying event handling.
- Add an event listener to the unordered list node using the
addEventListener
method that will call an inline function when aclick
event is triggered.
The inline function will receive the event as a parameter.
- Inside our inline function look at the
event.target.textContent
to determine whether to callmoveUp
ormoveDown
. We will also need to know which book to move so passevent.target.parentElement.id
to the choosen method.
Refresh the webpage, click the Fetch Books
button. We should see our bookshelf complete with buttons to move the books up and down the list.
- Implement the
moveUp
andmoveDown
buttons with what you have learnt from the lesson. You will need to use theinsertBefore
method.
In the console type fetchBooks
. It will execute our function. So far we have written our code directly into the global scope. This is not a good thing, it can cause lots of unforseen problems.
Let us encapsulate our code in a module which does not pollute the global scope.
- Wrap all of the code into an immediately-invoked function expression (IIFE) and assign it to a new property on the window called
bookshelf
.
window.bookshelf = (function() {
/* Our code goes here */
}())
- Refresh the webpage. Everything should still work but
fetchBooks
is no longer available as a method in the console.
Let us regain control over how the module is intialised.
- Wrap the code responsible for creating the button into a function named
createBtn
.
If we refresh our webpage at this point nothing will appear. We are going to need to call the new createBtn
. We will reveal this function to the global scope so that it can be called outside of our module.
- At the bottom of the file but inside the IIFE return an object containing an
init
property that references ourcreateBtn
function.
window.bookshelf = (function() {
/* our code */
return {
init: createBtn
}
}())
This pattern is called the revealing module pattern and it is very useful for encapsulating data and functionality.
So that we can still test all the functions easily, ensure that they are revealed also.
return {
init: createBtn,
removeBtn,
fetchBooks,
moveUp,
moveDown,
processBooks
}
- Inside the HTML file below the
script
tag add another script tag that calls ourinit
method on thebookshelf
.
<script>bookshelf.init()</script>
Refresh the webpage. Everything should still work but we have regained control over how the module is initialised.