Luis Martinez
Aug 12, 2021

JavaScript Event Listeners

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]

Event Listeners and API Calls

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:

  • First, reconfigure the backend to send all authors:
# In authors_controller.rb in API:
def index
  authors = Author.all
end
  • Then, load (instantiate) the authors in the frontend (one API call):
// 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)
    }
  })
}
  • Last, refactor the 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.

Object Instantiation and Event Listeners 

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:

  • Fire an instance method upon initialization of the app:
// In index.js:
authorService.loadAuthors() 
  • The instance method instantiates all authors on the frontend:
// 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()
    }
  })
}
  • Create an option HTML element for each author object upon instantiation:
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
  • Finally, each author is appended to the datalist via the addToDatalist method.


You can find a link to StoQuotes frontend repo here, and its backend repo here.