Learning JavaScript By Roughly Cloning Coolors

SVRourke
Geek Culture
Published in
7 min readFeb 10, 2021

--

Until very recently I had a very basic level of JavaScript knowledge, I’d solved a few leetcode questions with desirable speed and memory usage, I could set event listeners on DOM elements to do basic things, and I’d even experimented with making requests to APIs. Still, I didn’t feel the confidence I do with python or ruby. Over the past few weeks, I’ve put my nose to the JavaScript grindstone and really gained a better understanding of how the language works. To galvanize my understanding of several concepts I built a very bare-bones knockoff of an app I like Coolors. which is an app that allows you to generate what I would call “supervised random” color palettes by pressing the space-bar.

For the overall structure of this app I went with a rails JSON API and a Vanilla JavaScript/html/CSS front-end.

Back End

For the back-end of the app I created a simple JSON API with rails, all I wanted to be able to do was:

  1. Request a specified number of randomly chosen Colors.
  2. Save a new Palette (a model the HABTM Colors with a title column) to the database.
  3. Request the info for all palettes in the database.

It’s very simple to create an API with rails using the API flag like so

rails new PROJECT_NAME --api

Using the api flag rails will automatically omit superfluous middleware that would normally be included in a regular rails app, make the application controller inherit from ActionController::API rather than BASE omitting any ActionController functionality used by browsers, and tells the generators to skip views. helpers and assets when generating a resource.

Models

My API has 2 models: Color, and Palette. The two models are associated via a has_and_belongs_to_many relationship which is in hindsight unnecessary for the functionality I ended up with but when i was just starting this project I was planning on having a “most popular colors” functionality and having the palettes accessible via the colors would have made that easy to do.

Color:

The color model has columns for storing values that allow the color to be formatted in several formats: “Name”, HEX, RGB, HSL, HSV.

Palette:

The Palette Model has only one column besides timestamps which is the name.

A Note On Source Data:

To seed the database I wrote a web scraper to scrape 971 colors from this Wikipedia page

the scraper crawls all three pages (A-F, G-M, N-Z) and returns an array of hashes with the table’s values for each color.

Wikipedia list of colors

Using that array, The database is populated with Color records. After the colors are recorded to the database a Palette class method is used to create 25 palettes of 5 randomly chosen colors.

def self.newRandom()
palette = Palette.new(name: "palette-#{("a".."z").to_a.concat((0..9).to_a).shuffle.slice(0,4).join("")}")
palette.colors.concat(Color.all.sample(5))
palette.save()
return palette
end

This provides ample data for messing around and using the app.

Endpoints

Since the app only has 3 endpoints I explicitly declared them in routes.rb

Rails.application.routes.draw do
get '/api/colors', to: 'colors#index'
get '/api/palettes', to: 'palettes#index'
post '/api/palettes', to: 'palettes#create'
end
  1. Get /api/colors: The first route is handled by the index action of the colors controller. Sending a GET request to /api/colors returns all colors in JSON format or is a “count” query string is used, returns the specified number of randomly chosen colors in JSON format, this is used for populating a new palette when a user opens the palette creation page and when changing the unlocked colors during palette creation.
  2. Get /api/palettes: The second route is handled by the index action of the palettes controller. This action returns all palettes in the database in JSON format or accepts a “count” query string like the colors route to return the specified number of most recently created palettes. This is used on the palettes view page and to render the three small palettes underneath the palette creation interface.
  3. Post /api/palettes: The final route is handled by the create action of the palettes controller, when a palette is saved by the user the palette name, and the ids of the selected colors is posted to the API using JavaScript. In the create action an instance method is used to create a palette from the JSON data.

CORS

A side note about CORS or cross origin resource sharing. Put simply CORS is a way for a browser to determine if it is safe to allow a client to request a resource. Out of the box the JavaScript front-end would be blocked from making requests to the API. To enable CORS you can add ‘rack-cors’ to the Gemfile and run bundle install, you also have to tell the rails application to use the rack-cors middleware in the application.rb file consult the documentation for more info.

Front-End

With the API handled all that’s left is to consume it with JavaScript.

Responsibilities

By writing out everything I wanted the front-end to do I was able to categorize all the functionality into 4 separate chunks (not including the main routine)

  1. Palette: The Palette class handles tasks like: instantiating and managing the colors in the palette, changing unlocked colors to a new random color, creating the hash to post to the api when saving the palette, adding new colors, adding the plus button to the last color in the palette.
  2. Color: The Color class handles tasks like: accessing the color’s DOM element, changing the DOM element’s background when the color is updated, toggling the lock icon when a color’s locked state is changed, updating the DOM element’s data-attributes.
  3. API: The API object is a hash of functions that handle communications with the API: getting n colors, getting n palettes, and posting the palette hash to the api when a palette is saved
  4. Render: The Render object is also a hash of functions, this time handling the creation of DOM elements: creating the mini palettes displayed below the main palette creation interface, and a helper accepting a tag and an optional array of class names returning a DOM element of the specified tag with the provided class names if any are present.

Main Routine

The entirety of the JavaScript functionality is executed within an event-listener trigger by the DOMContentLoaded event which means the JavaScript waits until the entire page is loaded in the browser before it begins, this prevents the program from trying to access static DOM elements before they are loaded.

Flow

  1. First a new palette object is instantiated and the container div that holds the mini palette elements is saved to a variable for easy acess.
  2. Next the initColors() method is called on the palette object which uses api.getColor() to get 4 colors from the API, and push a new Color instance onto the the Palette instance’s colors array.
  3. The palette.initColors() call is “.then’d” with the renderColors() palette method which clears the DOM palette’s color row and then appends each of the Palette instance’s colors elements to the DOM palette’s color row.
  4. An event-listener is added to the save button DOM element which calls the savePalette method on the palette. This method checks that a custom name has been used, otherwise it uses a browser alert to instruct the user to do so, then it posts a dictionary of the the palette’s name and the palette’s color’s ids to the API. When the request is finished the method calls the render method that refreshes the mini palettes at the bottom of the page to display the most recently saved palette.
  5. An event-listener is added to the document body which is triggered by pressing a keyboard key, if the key is a space-bar and the current focused element is not the palette name input, the palette.shuffle() method is called. The shuffle method iterates through the palette’s colors, if a color is unlocked the updateColor() method is called and provided with a new color hash using the api.getColor() method
  6. Finally the render.palettes() method is called to render the 3 most recently saved palettes below the palette creation interface.

Usage

When the client is opened in the browser the user is greeted with the palette interface, the three most recently created palettes and a link to view every palette in the database.

Pressing the space bar will change the colors of all unlocked columns, pressing the lock icon will lock the column to preserve the desired color in the palette.

Pressing the plus icon will add another color to the palette.

To Save the palette the title must be changed. Follow the view more link to view all of the palettes in the database

Click a palette to view the hex and RGB values of the palette

Notes

I originally had all of my API methods accepting a callback but quickly wound up in callback hell so i switched to using async/await and it improved the readability of my code immensely.

If you enjoyed my writing feel free to check out my website and connect with me on LinkedIn

--

--