The Rambling Programmer

Random musings on programming, mostly.

jQuery

Adding A JQuery Frontend To An Existing Rails App

As a part of my journey to become a webdev through the Flatiron School’s program I have been tasked with adding a JQuery front end to an Rails app I built previously. The app in question was my Flavor Flaves recipe managment tool. This task was intimidating. However once you get the basics of JQuery, AJAX, and modifying the DOM on the fly down, it really was not terribly difficult. Let’s look at some of the modifications that needed to be made to make this work.

JS Object Models

One of the first tasks I decided to tackle was to build a JavaScript Object Model to represent the JSON I was going to be getting back from my server. It’s really very simple to do and makes for handling recipes and rendering them to the DOM quick and easy. Below is the code to create the class and the prototype method renderRecipe that takes the recipe object and creates the associated HTML to append to the DOM.

class Recipe {

    //Recipe constructor takes a hash of attributes
    constructor(attributes) {
        this.id = attributes.id;
        this.title = attributes.title;
        this.directions = attributes.directions;
        this.user_id = attributes.user_id;
        this.ingredients = attributes.ingredients;
        this.recipe_ingredients = attributes.recipe_ingredients;
        this.users = attributes.users;
    }

    //this.renderRecipe() renders this recipe in HTML and appends to DOM
    renderRecipe(parent = "#recipes") {
        var htmlString = `<li><h3 id=${this.id} class="recipe"><a href="/recipes/${this.id}">${this.title} 
                                        </a>`;
        htmlString += `<a href="/users/${currentUser()}/recipes/${this.id}/edit">`;

        //Check if current user has saved this recipe to determine star icon to use
        this.users.forEach(function(recipe_user) {
            if (currentUser() == recipe_user.id) {
                htmlString += `<img alt="Saved" src="/assets/saved.png">`;
            }
        });
        if (!htmlString.includes("Saved")) {
            htmlString += `<img alt="Unsaved" src="/assets/unsaved.png">`;
        }
        htmlString += `</a></h3><ul>`

        //Save this to variable for access inside of the forEach loop below
        var recipe = this;
        this.ingredients.forEach(function(ingredient) {
            htmlString += `<li>${ingredient.name}, `;
            recipe.recipe_ingredients.forEach(function(recipe_ingredient) {
                if (recipe_ingredient.ingredient_id == ingredient.id) {
                    htmlString += `${recipe_ingredient.quantity}</li>`;
                }
            });
        });
        htmlString += `</ul><br><p>${recipe.directions}</p>`;
        htmlString += `</ul>`;

        //Check if current user is owner of recipe and if so add edit button
        if(currentUser() == this.user_id) {
            htmlString += `<input type="button" onclick="location.pathname='/recipes/${this.id}/edit'" 
                                            value="Edit This Recipe">`
        }
        $(parent).append(htmlString);
        attachListeners();
    }       
}

Pretty straightforward. Our new recipe object takes a hash of attributes which is will be supplied by the server as JSON. I chose to use ES6 class. It’s really just syntactic sugar for building objects and can still be done the old fashioned way via building prototype methods. The renderRecipe method is admittedly ugly to look at and can probably be refactored. But building HTML strings is cumbersome and ugly regardless of how you do it.

Returning JSON

The next step was to tell the rails server how to respond to JSON requests. This involves a couple of steps. First I had to build a serializer for my recipe model, this is simply a way of telling rails how to build a response based on the recipe object. See below:

class RecipeSerializer < ActiveModel::Serializer
  attributes :id, :title, :directions, :user_id
  has_many :recipe_ingredients
  has_many :ingredients, through: :recipe_ingredients
  has_many :users, through: :user_recipes
end

Next step is to tell the server to actually send that serialized response when the user requests JSON. Simple enough really.

respond_to do |f|
  f.html { render :show }
  f.json { render json: @recipe }
end

This can be used anywhere in your controller that you would normally render HTML. This can also be used to return JSON representing multiple recipes like so f.json { render json: @recipes }

Event Listeners

That’s all grand but it doesn’t do us much good if our app doesn’t request the JSON. Right now it is just requesting whatever link is clicked and the server sends back an HTML response. This is where event listeners come in. We attach them to anything in our DOM and when acted on by the user they perform some action we specify. Below is an example of a click listener attached to the DOM object with an id = “home”.

$("#home").click(function(event) {
        console.log("Home clicked");
        event.preventDefault();
        ajaxGet(event.toElement.pathname);
});

This is a fairly simple listener that intercepts any clicks on the Home link at the top of my app. Notice the event.preventDefault(); This line is critical to preventing the link from doing what it would normally do. So what to do with the event once it’s triggered?

AJAX

AJAX, this is where it gets tricky. I dealt with hijacking the clicking of the Home link. Now I need to request the JSON from the server so it can be rendered to the DOM. I know that my server is going to handle this via the recipes index route so let’s build the AJAX request that will send that request.

var ajaxGet = function(url) {
    $.ajax({url: url,
            method: "GET",
            dataType: "json",
            success: function(data) {
                $("#recipes").empty();
                for(let i=0; i<data.length; i++) {
                  let recipe = new Recipe(data[i]);
                  recipe.renderRecipe();
               }
           }
    });
}

The above function creates a simple get request to the url passed in through the parameters. The tricky thing about AJAX requests are the fact that they are asynchronous. Meaning that once that request is submitted we may not get a response back immediately. However the app doesn’t sit around and wait, we can work on other things while that request is processed by the server and a response is returned. This is where the success block in our AJAX request comes into play. Once it gest a response from the server that response is handled via anonymous function that has the response passed in as data. Then I just used our Recipe JS Model to create the necessary recipes and the renderRecipe method to append to the DOM.

Conclusion

JQuery and AJAX are hard to get a grasp on, but with practice they are fairly easy to work with and very powerful tools. Hopefully this very brief walkthrough has pointed you in the right direction when building your own app powered by JQuery.