DEV Community

Cover image for Javascript and Rails Single Page Application ("SPA")
Brittany
Brittany

Posted on • Edited on

Javascript and Rails Single Page Application ("SPA")

GUESS WHAT!?

I built a single page application using vanilla Javascript("JS") as the frontend and Ruby on Rails("Rails") as the backend.

Github Link Youtube Demo Live Site - Coming Soon

Introduction

I just want to say that this project was NOT easy, in fact this project was one of the hardest I had, in my opinion. With Rails, everything seemed so simple and organized, but then Javascript came and showed me that there are no RULES ๐Ÿ˜ฉ๐Ÿ˜‚! Learning Javascript is no easy task and I have reasons to believe that no one ever truly knows Javascript or all that it is capable of. I constantly found myself going to different platforms to learn more and although it was hard at times, I enjoyed every minute of it. I learned about Javascript variables, functions, data structures, hoisting, closures, class syntax, and much much more.

I recommend this course if you are learning Javascript, it helped me a lot. Javascript: Understanding The Weird Parts

Planning

First I had to come up with an idea for my project. I had to plan out my database. I created the following flow chart on Draw.io:

Alt Text

I knew that I wanted my application to have many users and that each user would have many workouts. However, I wanted to be able to have multiple workouts under the same date and my instructor recommended a join table for that, he is a smart man. Next step was to put my plans into action.

Using Rails as an API

The main difference between my previous Ruby on Rails application and this one is that I was only using it as an API. That meant that I had to run the following code to get my Ruby on Rails API up and running:

$ rails new my_api --api
Enter fullscreen mode Exit fullscreen mode

Once I got rails api up, I added a few gems that I knew I needed into my Gemfile. The first one was gem 'rack-cors', which is a a gem that allows web applications to make cross domain AJAX calls. More information on how rack-cors works and how to implement it can be found here.

Once my gems were all set up, I created my migrations, created seeded data, and set up my RESTful routes. Another difference between a project built completely using Ruby on Rails versus when you are using it as your server is that you need to return the data as JSON. For example, my index method in my Users Controller would look like this:

class UsersController < ApplicationController
 def index
        user = User.all
        render json: user
    end
end
Enter fullscreen mode Exit fullscreen mode

I have to render the json, so that when I run my server and visit http://localhost:3000/users in my browser, I will see data similar to this:

{
    "users": [
      {
        "name": 1,
        "name": "Harry Potter",
      },
      {
        "id": 2,
        "name": "Courage The Cowardly Dog"
      }
    ]
  }
Enter fullscreen mode Exit fullscreen mode

The json data is now accessible to me and I can now make a fetch request to my backend.

Making Fetch Requests To My Database

In order to get the information from my backend I had to make a fetch request. First I made sure my server was running by keeping a terminal open in my backend, rails s, so that I could access the json data. Then, I created a file to make the fetch request to my join user_workouts table:

class WorkoutsAdapter {
    getWorkouts(){
        return fetch(`http://localhost:3000/api/v1/user_workouts`).then(res => res.json()
        )
    }
Enter fullscreen mode Exit fullscreen mode

Iterating and Rendering Onto The Page

My biggest challenge

Once the information is returned, I wanted to iterate through the information and sort it by date, I was able to do that with the following code:

class Workouts{
    constructor(){
        this.workouts = {}
        this.adapter = new WorkoutsAdapter()
        this.fetchAndLoadWorkouts()  
    }
   fetchAndLoadWorkouts(){
        this.adapter.getWorkouts().then(workouts =>{
            workouts.forEach(workout => {
                if (this.workouts[workout.date]) {
                  this.workouts[workout.date].push(new Workout(workout))
                } else {
                    this.workouts = {...this.workouts, [workout.date]: [new Workout(workout)]}
                }

            })
        })
        .then(() =>{
            this.render()
        })
    }
Enter fullscreen mode Exit fullscreen mode

The above code makes a request to the WorkoutsAdapter class and fetchAndLoadWorkouts() function, which is essentially the fetch request. The information received is the array of workouts. Then for_each workout it checks to see if the date exist in the this.workouts object. If the date does exist then it will push the workout into that date. If the date does not exist, then it will create a workout with that date as the key. Once it is done iterating through the data, it will go to the render() function that I created.

Below is my render function:

    render(){
   this.workoutContainer = document.getElementById('workout-container')
        const workoutString = Object.keys(this.workouts).map((date) => {
           return `<button type="button" class="collapsible">${date}</button><div id="all-workouts">
            ${this.workouts[date].map((work) => work.renderHTML()).join(' ')}</div>`
        }).join(' ')
        this.workoutContainer.innerHTML = `${workoutString}`
    }
Enter fullscreen mode Exit fullscreen mode

I was able to successfully render onto the page by using the document.getElementById('workout-container') to select the element/div in the HTML. Then, using the Object.keys method on the this.workouts object, I mapped the data by the date and returned the dates in a button. Then I mapped the workouts by the date and placed them in a div and the the workouts successfully rendered onto the page underneath the correct date for each user.

This code was very challenging, but I was so happy when it was accomplished. Feel free to take a look at my project, I welcome all feedback and contributions.

As always, thanks for reading!

Sincerely,
Brittany

Song of the day:

Top comments (0)