A few months ago, I had the idea to build a simple game that functions something like this.
Above, An early, rough concept for the Food or Foe emoji game
The game mechanics changed in the early stages of building it in Ruby on Rails (Rails) and Javascript. One feature, I knew I wanted, was to be able to add a player's name and their score to the leaderboard at the end of a game.
Since a
User
has_many Games
I wanted to be able to create a new Game
instance at the same time as a User
instance. If a User
with the given name already exists, create the Game
and associate it with that User
.Oh and this had to happen without reloading the page.
Yes!
At first, I was overthinking how it might work on the frontend. Then, a classmate pointed out, that the creation of the
User
and Game
could be handled on the backend, since the two were associated.For this to happen, I need two pieces of data
The player’s name from the form input and their score
I could query the Document Object Model (DOM) for the input field with
this.nameInput = document.querySelector("#name")
and access it’s value with .value
. The score was being rendered on the frontend so I could access it in a similar fashion.const body = {
score: this.score.innerText,
name: this.nameInput.value,
}
Formatted as an object, I could pass it into the
createGame()
fetch request in GamesAdapters.js
. createGame(body) {
return fetch(this.baseURL, {
method: 'POST',
headers: {
'content-type': 'application/json'
},
body: JSON.stringify(body)
})
.then(res => res.json())
}
Did I mention the page shouldn't reload either?
Luckily, that could be solved by including
e.preventDefault()
within the createGame()
function that is triggered by the form submission button.createGame(e) {
console.log("Saving the game...")
e.preventDefault()
const body = {
score: this.score.innerText,
name: this.nameInput.value,
}
this.adapter.createGame(body)
}
The POST
fetch()
request created a new record and returns the record object, which looks something like this. #<Game id: 85, user_id: 1, score: 37, created_at: "2020-02-24 00:25:42", updated_at: "2020-02-24 00:25:42">
Oh no.
I see a user_id. Within Rails. It's possible to get a Users name from their ID, but is there a way to access it on the front end without a page reload?
There is, but it would require a page reload, which we want to avoid for this project. But, we can fake it on the frontend UI by pulling the name from the input field and displaying that. Let's add a
.then
statement to the end of createGame(e)
to handle this.createGame(e) {
console.log("Saving the game...")
e.preventDefault()
const body = {
score: this.score.innerText,
name: this.nameInput.value,
}
this.adapter.createGame(body)
.then(game => {
let gameObject = `<li>${game.score} - ${this.nameInput.value}</li>`
this.leaderboard.innerHTML += gameObject
this.nameInput.value = ""
})
}
Our leaderboard, now looks something like this.
A 3rd player and their score has been added to the leaderboard
If we were to refresh/reload the page, visually, we would not see anything different. This is an example of an optimistic user interface. We're anticipating that the data is successfully written to the server and are responding right away by displaying the data. We'll also clear out the input field when we're done, to mimic what would happen if there were a page refresh.
Also published on: https://shannoncrabill.com/blog/ruby-on-rails-api-single-page-javascript-application/