Phase 4 project - Favorite Places (Vikram Chatterjee, March 14th 2021, Flatiron - Software Engineering)
Published: 2021-03-14 21:47:42

For my Phase 4 project, I wrote a simple SPA (single page application) that allows users to document their favorite places. Users are able to click on the "Add a Place" and enter the name of the place, a description, and an image URL which will cause the image to display on the list of places below the description. Users are able to add comments to places, which will be displayed in the 'div' (divider) of a place below its description or below the image if one was created. 

The Github repository is divided into two major components, the frontend and the backend. The backend is a Rails API, which I generated using the rails command 'rails new phase_four_project --api'. The models in the backend represent Places and Comments, where a Place has many Comments and each comment belongs to a place. The models for Places and Comments were generated using the command 'rails g scaffold' which creates ActiveRecord models, controllers, migrations and routes in the backend. When using 'rails g scaffold' in API mode, the controllers are automatically populated with CRUD actions that render json objects that represent the instance or instances of the objects that get accessed by the frontend or the Rails route of the backend (for example, http://localhost:3000/places). In order to run the application, the user must 'cd' into the backend and run 'rails s' so that the fetch requests in the frontend have an active server to communicate with. 

The frontend of the application is rendered using the 'index.html' file, which employs several javascript files that provide access to the models of Place and Comment (via comment.js and place.js), global variables that use Javascript to access HTML nodes (via globals.js), API fetch requests (via api.js), and miscellaneous functions (via index.js). The functionality of the javascript files is divided according to separation of concerns, separating models from services and other functions, which makes it easier for developers to find what they're looking for. 

Globals.js
'globals.js' is a javascript file in the root directory of the Javascripts folder which contains arrow functions that get elements in the DOM by their ids. These functions can be chained onto in order to add an event listener or some other functionality to elements in the DOM. For example, the function formLink() is used by the formLinkEvent() function in index.js to add an event listener to the "Add a Place" link so that when the link is clicked, the DOM will render the form to instantiate a new place. 

Place.js
The functions for the model that represents 'Places', the primary model of the application, are stored in place.js. This javascript file contains a constructor for instances of Places, which sets the attributes of id, name, description, and imageUrl in order to create a javascript object that corresponds with the ActiveRecord object in the backend. A static function called 'all' contains an empty array into which these javascript objects are added. The static function 'createFromCollection' takes a list of places formatted as JSON data and creates (instantiates and saves) javascript objects with the given attributes, saving them into the 'all' array. The 'place.js' file includes two templates, placesTemplate and formTemplate, which add HTML to the DOM. placesTemplate creates an h3 containing the string "My Favorite Places" and a div with an id of places in which each div listing a place will be stored. The formTemplate adds an HTML form to the DOM which contains fields for the name of the place, a description, and an imageUrl, as well as a submit button. The renderForm function clears out the DOM  and adds the placesTemplate and the formTemplate to the DOM, and adds an event listener to the submit button which runs the submitForm function when clicked. The submitForm function, like many functions in this app, runs a function called 'e.preventDefault' which prevents the page from redirecting. The submit form then creates a variable, strongParams, which contains the values of the name, description, and imageUrl of the instance of a place which is being created, and then uses the Api.post method to create a Place, assigning the attributes in the strongParams variable to attributes of a new Javascript 'place' object, and saves it into the 'all' array, and then calls the renderPlaces function to iterate over each place and render it using renderPlace, and then get the comments for each place. The renderPlace function creates a div to contain the attributes of the place, and creates an h4 for the title, a p for the description, and an img to render whatever image was submitted in the imageUrl section of the form. It also creates a delete link and a comment button, and adds event listeners that run functions that create the comment or delete the place. At the end of the renderPlace function, the elements containing the place name, place description, and image, as well as the delete link and the comment button, are appended to the div (an image is only appended if the imageUrl is populated with a string). Then, the div containing the attributes of the place is appended to the div containing the list of places. The static function getPlaces gets the JSON data from the '/places' route and creates all of the places using createFromCollection, and then renders the list of places. The deletePlace function is attached to the event listener for the deleteLink in renderPlace. A fetch request to delete the place is called using Api.delete where '/places/' + id is filled into the arguments, which deletes the place JSON object from the Api. The function then iterates over each place until a place with a matching name is found, and then mutates the Place.all array using '.splice' to remove one item at the index where the place with the matching name was found. 

Comment.js
The comment.js file contains all of the function pertaining to comments, and starts with a constructor that creates a javascript object, taking in the attributes of id, text, and place_id. The static function all contains an empty array into which these comments are populated. The static asynchronous function getComments clears all of the comments from the all array and iterates over each JSON object in the comments index route, and instantiates a new comment using the constructor to assign attributes, and then renders each comment using the function renderComment. The renderComment function gets the div of the place that the comment belongs to and adds a p tag to the div, appending it to the div and setting the text of the p tag to the text of the comment. The getComments function is used to render all of the comments for the places when the 'listofplaces' is requested, and the addComment function is used when a comment is being added to a place. The addComment function is tied to an event listener on the commentButton which was created in the renderPlace function. In the addComment function, an input is assigned to the variable commentInput, and it is assigned a type of Text and a name, id, and placeholder. This is where the comment text can be typed in. A variable commentSubmit is assigned to a button, whose id is assigned as commentSubmit and whose innerHTML is assigned as "Add Comment". If the commentInput has not already been added to the DOM, it will be appended to the div of the place that the comment is being added to. An event listener is added to the commentSubmit button, which prevents the default redirect and assigns a variable of text to the value typed into commentInput, and a variable called 'comment' is assigned the value of an object that contains the text of the comment and the id of the place that the comment is being added to. Then a post request is made using the Api.post function, with '/comments' and the 'comment' variable being passed into its arguments. Then an arrow function taking in the variable comment is called, which creates a variable called 'c' and assigns it to a new Comment which is instantiated using the constructor. Then the text stored in the javascript object 'c' is assigned to a new p tag which is appended to the div of the place that the comment is being added to. Finally, c.save() is called which adds the comment to the total array of comments.  

Api.js
The api.js file is a javascript class that contains the baseUrl of the rails Api where all of the fetch requests are coming from, and handles all of the fetch requests. The static baseUrl is set to 'localhost:3000', where the rails server is running. The static headers contains an object that has two parameters, "Accept" and "Content-Type", which are set to "application/json" so that the fetch requests know that they are taking in JSON data. The static async function 'get' takes in a path that is appended to the baseUrl (either '/places' or '/comments') in the fetch request, which includes an object containing with the method parameter 'GET' and a parameter set to the 'Api.headers' static function. The variable data is assigned to the response of the fetch request, which is parsed into JSON, and then the value of that 'data' variable is returned to whatever function made the GET request. A similar technique is used in the static async function "post". The only difference between the get and post functions is that the post function takes in params (for example, the strongParams variable in the submitFunction form) and includes a 'body' attribute which is set to JSON.stringify(params) so that the data being submitted to the server is parsed into JSON. A variable 'data' is again assigned to the response from the JSON server, which is then returned to the function making the POST request. The static async function 'delete' works just like the 'get' function, except that the 'method' assigned to the parameters within the fetch request is a 'DELETE' method instead of a 'GET' method. The object that is being deleted is assigned to the 'data' variable and returned to the function that made the delete request.

Index.js
Whatever functions did not neatly fit into any of the concerns of the models, services, or global element variables are put into the index.js javascript file, which is located in the root directory of the javascripts folder. The index.js file contains four functions, resetMain(), formLinkEvent, placesLinkEvent, and an event listener which is appended to the document. The resetMain function is called when a form or the list of places is being rendered, and resets the innerHTML of the DOM so that new data can be appended. The formLinkEvent function listens for when the formLink (assigned to the "Add a Place" link in index.html) is clicked, and prevents a redirect and calls Place.renderForm, which renders a form for the user to enter a new place into the list. the placesLinkEvent() function adds an event listener to the placesLink() ("List of Places" link in index.html) and prevents a redirect, and then calls Place.renderPlaces to render the list of places that are stored in the API. The document.addEventListener function activates when the content in the DOM is loaded, and adds the placesTemplate (containing the div that contains each place's div), calls getPlaces(which in turn, calls renderPlaces which calls getComments) so that all of the places and comments are listed in the DOM when the page is refreshed. Then, formLinkEvent() and placesLinkEvent are called on, which causes the links to add a place and to view the list of places to work. 

Conclusion:
The 'Favorite Places' app uses a Ruby on Rails backend to render JSON-formatted objects to the Javascript frontend, which contains several classes that are separated into concerns and use that JSON data to allow the user to fill in forms that add places to a list of places, delete places from the list, and add comments to each respective place. It is my hope that users have fun exploring the world and documenting their favorite places using my app, creating comments to share their memories or observations relating to each of their Favorite Places.