Javascript event listeners are an important component in the user experience with a webpage. Event listeners allow to dynamically display lots of information and provide rich functionality to a webpage without page reload. In this article I would like to discuss my experience with Javascript event listeners while working on my Javascript project, StoQuotes, as part of Flatiron school software engineering 4th milestone project. [Note: this is one of my Medium articles transferred to DevBlog; you can find the original article here for comparison purposes]
The project required to use Rails as an API for the backend (the server side) implementing RESTful routes and the MVC framework, and object-oriented Javascript for the frontend (the client side). In addition, all functionality needed to occur within a single page using AJAX calls (that is, no page reload). For this project, I decided to extend my previous Ruby command line interface (CLI) project, QuotesApp, and make it live in a webpage using Javascript, while adding additional functionalities. In QuotesApp, a user can select to get a random quote, a quote from a list of ten random authors, or a quote from a list of five categories (you can find an article on QuotesApp here, and the link to the repo here). For my javascript project, I included all features from QuotesApp and added the functionality for a user to search for their desired author, and the ability to write a story, edit a story, or delete a story for any given quote; hence the name for the app, StoQuotes.
Developing this project was very exciting to me as I was going to transform my very first app, QuotesApp, accessible only in the command line, and make it available in a webpage. Let us take a look at a snapshot of the welcome interface for each of the applications:
Figure 1. QuotesApp welcome interface
Figure 2. StoQuotes welcome interface
Along with event listeners, I would also like to discuss how my approach to the code changed as I was guided by the project workflow. I wanted a new set of ten authors to be displayed each time the Authors tab was clicked. So, at first, I added an event listener to the Authors tab, with a callback function that triggered a get request to the /authors
endpoint of my API:
// In index.js:
const authorsTab = document.getElementById('nav-authors-tab');
authorsTab.addEventListener('click', () => {
authorService.getAuthors();
})
// In authorService.js:
getAuthors(){
fetch(`${this.endpoint}/authors`)
.then (resp => resp.json())
.then(authors => {
for (const author of authors){
const a = new Author(author)
a.addToDom();
}
})
}
Then I configured my API to render just ten random authors rather than all authors:
# In authors_controller.rb in API:
def index
authors = Author.all.sample(10)`
end
However, at a later stage, I realized that I would eventually need all authors– when the Search Author tab was clicked. With such a setting, I would have an API call for each click on the Authors tab and the Search Author tab, respectively. I was not happy with this setting and came up with a solution: load all the authors upon initialization of the app, and manipulate my javascriptAuthor.all
array to get the desired number of authors I wanted to display depending on the scenario I was working on. Below is the code that shows this new approach:
# In authors_controller.rb in API:
def index
authors = Author.all
end
// In index.js:
authorService.loadAuthors()
// In authorService.js:
loadAuthors(){
fetch(`${this.endpoint}/authors`)
.then(resp => resp.json())
.then(authors => {
for(const author of authors){
const a = new Author(author)
}
})
}
getAuthors()
method from the AuthorService class to retrieve ten random authors from the Author.all array generated in author.js (no API calls):// In index.js:
const authorsTab = document.getElementById('nav-authors-tab')
authorsTab.addEventListener('click', () => {
authorService.getAuthors();
})
// In authorService.js:
getAuthors(){
// Empty the authors container
Author.authorsContainer.innerHTML = ""
// Make a copy of the Author.all array, shuffle the array, get 10 authors, and sort by author name
const authors =
shuffleArray(Author.all.slice(0)).slice(0,10).sort((a,b) => {
if (a.name > b.name){
return 1;
}
if (a.name < b.name){
return -1
}
return 0;
});
// Add each author to the DOM
authors.forEach(author => author.addToDom())
}
The result? Only one API call is made to the /authors
endpoint; after that, authors are retrieved from the javascript Author class. Below are two pictures that partially show the functionality of the three previous code snippets:
Figure 3. Clicking the Authors tab one time renders ten random authors.
Figure 4. Clicking the Authors tab another time renders a new set of ten random authors.
Another important component of my project consisted in populating a datalist with all the authors from my API. A datalist is a HTML element that can be linked to an input field and provides a list of available options as the user types in the input field. The displayed options are appended to the datalist element. Find an example below:
<input type="text" list="author-name">
<datalist id="author-name">
</datalist>
The datalist is linked to the input field via its id attribute, matching the input’s list attribute. The datalist takes option elements:
<input type="text" list="author-name">
<datalist id="author-name>
<option value="Isaac Newton">
<option value="Charles Dickens">
</datalist>
The above datalist contains two options (two authors); I needed to populate the datalist for my project with all the authors from my API: more than 700. Obviously, populating the datalist needed to be done dynamically. After a big fight with nonworking static methods, I arrived at a solution via object instantiation. Bear with me on the next steps to end this article:
// In index.js:
authorService.loadAuthors()
// In authorService.js:
loadAuthors(){
fetch(`${this.endpoint}/authors`)
.then(resp => resp.json())
.then(authors => {
for(const author of authors){
const a = new Author(author)
a.addToDatalist()
}
})
}
class Author {
static all = []
static authorsContainer = document.getElementById('authors-container')
static datalist = document.getElementById('author-name')
constructor({id, name}) {
this.id = id,
this.name = name
// 'this.element' is used for rendering authors
this.element = document.createElement('li')
this.element.dataset.id = this.id
this.element.id = `author-${this.id}`
this.element.addEventListener('click', this.handleClick)
// 'this.option' is used for author-name datalist
this.option = document.createElement('option')
this.option.value = this.name
Author.all.push(this)
}
addToDatalist(){
Author.datalist.appendChild(this.option)
}
// more code
You can find a link to StoQuotes frontend repo here, and its backend repo here.