Survey App for Flatiron Project
Introduction
Requirements
This was an exciting project as it pulled together all that I have been learning at Flatiron School over the last four months. I have come to appreciate the simplicity of setting up an API with Rails. The project requirements were as follows:
- Build a Single Page Application (SPA), with the frontend built with HTML, CSS, and JavaScript and a backend API with Ruby and Rails.
- Communication between the front and backend using asynchronously (AJAX) communication and JSON as the communication format.
- The JavaScript application must use Object Oriented JavaScript (classes) to encapsulate related data and behavior.
- The domain model served by the Rails backend must include a resource with at least one has-many relationship.
- The backend and frontend must collaborate to demonstrate Client-Server Communication. Your application should have at least 3 AJAX calls, covering at least 2 of Create, Read, Update, and Delete (CRUD). Your client-side JavaScript code must use fetch with the appropriate HTTP verb, and your Rails API should use RESTful conventions.
App Design
HTML - the footprint starts with a HTML file loosely based on HTML5 Boilerplate project, with a few of my own modification. I prefer to group the folder structure to separate concerns so, the source files are grouped into a src
which includes separate folders for js
, styling
, and images
. The compiled and minified files for production, are grouped into a 'dist' folder structure, again separated by js
, styling
, and images
.
Styling - Most projects I have spun put pretty quickly and have relied on Component UI's to decrease the development time. I have used Bootstrap, and TailwindCSS in the past. This site is built with Bulma, which I love.
- SCSS Boilerplate I customized based on original work by Hugo Giraudel and his SASS_Boilerplate
- Styling is formatted, compiled, and minified using Gulp, and Browersync. The Gulp file is my tweaks to a Gulp-Boilerplate originally designed by Chris Ferdinandi
- The App allows users to create, delete, and complete (update) surveys, which then will display a running result (this is not the best design, as a admin account needs to added to handle delete, but this meets the requirements of the project). It has been very satisfy to code the styling for this project.
API - the API is changed with Ruby on Rails in API Mode utilizing a Postgres database. There are two database tables: 1) Surveys to save each survey list and three questions, and 2) an Answers table which saves the survey responses and the corresponding survey_id
.
Fetch API
To set up the index page when I user visits the site, I used a simple GET
request using the Fetch API. It is with this design I encountered a bug and an opportunity for learning. The following fetch call was at the head of the index.js
file.
fetch('http://localhost:3000/surveys')
.then(res => res.json())
.then(surveys => {
surveys.forEach(survey => {
const { id, title, question1, question2, question3 } = survey
new Survey(id, title, question1, question2, question3)
})
})
When the user visited a single Survey page and clicked delete, the survey was in fact deleted, but it required a manual refresh to bring back the index display. I refactored the root fetch call:
function fetchSurveys() {
fetch('http://localhost:3000/surveys')
.then(res => res.json())
.then(surveys => {
surveys.forEach(survey => {
const { id, title, question1, question2, question3 } = survey
new Survey(id, title, question1, question2, question3)
})
})
}
fetchSurveys()
This refactor meant in the deleteSurvey
method in the Survey
class I could call this function to display the Surveys again:
async deleteSurvey() {
await fetch(`http://localhost:3000/surveys/${ this.id }`, {
method: 'DELETE'
})
.then(() => {
document.getElementById('survey-container')
.removeChild(document.getElementById(this.id))
})
fetchSurveys()
}
Promise Pretty Please?
The next lesson I learned in this project was that a Promise is NOT the same as DATA. I struggled when I realized I could not really create a "global variable" to use throughout the project. I ended up utilizing JavaScript to Manipulate the Document Object Model to interject the results of the survey. I would love to abstract this code but it works:
getResults() {
const fetchPromise = fetch('http://localhost:3000/answers')
const resultsReport1 = document.getElementById('q1')
const resultsReport2 = document.getElementById('q2')
const resultsReport3 = document.getElementById('q3')
fetchPromise.then(resp => {
return resp.json()
}).then(questionResults => {
const myResults1 = questionResults.filter(a => a.surveys_id && a.responded === 'question1').length
resultsReport1.innerHTML += myResults1
const myResults2 = questionResults.filter(a => a.surveys_id && a.responded === 'question2').length
resultsReport2.innerHTML += myResults2
const myResults3 = questionResults.filter(a => a.surveys_id && a.responded === 'question3').length
resultsReport3.innerHTML += myResults3
})
}
Which manipulates the DOM based on this template:
resultsHTML() {
return `
<div id="results-card">
<h3>Results:</h3>
<ul class="report-list">
<li>${ this.question1 }: <span id="q1"></span></li>
<li>${ this.question2 }: <span id="q2"></span></li>
<li>${ this.question3 }: <span id="q3"></span></li>
</ul>
</div>
<button class="card__btn done">Done</button>
`
}
Overall, this has been a great learning experience of a Single Page Application and there is plenty of room for future upgrades. Are you interested? Go look at the repo for future features.