Creating next/back buttons in HTML

Ian Marshall
5 min readFeb 13, 2021

--

One of the most vexing and difficult parts of creating a Sinatra flashcard application is the core functionality of the flash cards themselves: navigating back and forth within the deck. If I were building this application using Javascript, these navigations could be more streamlined, but since this application relies solely on Ruby, ActiveRecord, and HTML I had to find a RESTful way to execute the three actions of next and back.

My application has a user class, which has many decks and many cards through decks; decks belong to one user and have many cards; finally, cards belong to a deck and belong to a user through the deck. Here’s the same information summarized in the schema:

To understand how we would navigate from one card to the next, we can start by considering the URL that will take us to a specific card in a deck.

The card view URL consists of four components:

‘/decks/:slug/cards/:id’

First, deck tells us that we’re going to be looking at a deck, then the unique slug tells us which deck we’re looking at in particular; similarly, cards tells us we’re going to be looking at a card, then the unique id tells us which card we’re looking at in particular. The most basic way to navigate from one to the next would be by simply adding 1 (+1) to the id so you can go from card number 5 to card number 6.

This relates to how the cards are each made. If I were going to create a new deck from scratch, I would create them all at once — here’s a simple version of the cards controller’s create action:

post '/decks/:slug/cards' do #create action, creates cards   @deck = Deck.find_by_slug(params[:slug])   params[:front].each_with_index do |front, i|      front = params[:front][i]      back = params[:back][i]      card = Card.create(front: front, back: back, deck_id: @deck.id)   end   erb :'/decks/deck_show'end

Let’s break this down: the empty deck is identified by the slug in the URL; next, the front and back of each card, which are accepted as two separate arrays, are matched one by one to create new cards that share the same deck id. When creating a new deck of cards, you would expect each card’s individual ID would be sequential since they are being made at the same time — five cards in a deck would have ids like 1, 2 ,3, 4, and 5 or 43, 44, 45, 46 and 47. This means that creating a very simple next / back navigation in HTML would look like this:

<form action=’/decks/<%=@deck.slug%>/cards/<%=@card.id+1%>’ method=”get”><button type=”submit”>Next</button></form>

By simply adding 1 (or subtracting 1 for ‘back’), a user can navigate through a deck by leveraging the unique, sequential ids that are given to the cards when they’re created. However, this is not a foolproof method if the decks of cards are ever edited. For example, what if we remove one card? The sequence of card ids might change from 4, 5, 6 to 4, then 6, and since there’s no card with a corresponding id of 5, the form action would render a page with no corresponding card object. Another issue would come up when adding cards later on: if the original deck had card ids of 4, 5, 6 before three new cards were added, it’s very likely that those cards wouldn’t be 7, 8, and 9, but rather 33, 34, and 35. Remember, since each card’s id is unique across all decks in the application, there are other users adding cards to their own decks. Taking the card id and adding 1 is not a robust enough method to navigate from card 6 to card 34.

Since cards belong to a deck, ActiveRecord already adds card instances to an array in each deck when they are created, which is a much better place to start when thinking about next and back buttons. Here’s an example of a deck viewed in pry:

You can see how each card has the same deck_id, but the individual card ids in blue go from 7–12 before jumping to 33.

How can we create a meaningful link instead of simply adding or subtracting 1 from the id? First, we first want to create an array of all the card ids in a deck in our cards controller. Here’s a show action route from the cards controller:

get ‘/decks/:slug/cards/:id’ do #show action, displays one card based on id in URL   @deck = Deck.find_by_slug(params[:slug])   @cards = @deck.cards   @ids = []   @cards.each do |card|      @ids << card.id   end   @card = @cards.find_by_id(params[:id])   erb :’cards/cards_show_front’end

The next/back navigation tool begins on the 5th line. First, an empty array named @ids is defined, which is filled with the individual card id of each card in the deck. Then, in the corresponding view, we can render the URL of what the next card will be by finding what the following card id will be after the card we’re looking at:

<form action= ’/decks/<%=@deck.slug%>/cards/<%=@deck.cards[(@deck.cards.find_index(@card)+1)].id%>/ method=”get”><button type=”submit”>Next</button></form>

The difference between this button and the earlier version begins after /cards/. We can take a closer look step by step. Broadly, I’m using the array @deck.cards and asking for the id for a specific card — you can think of it like this:

@deck.cards[“some index”].id #the id of the next card in the deck.

Within the brackets, I first have to identify the index of the card we’re already on by calling the deck’s array of cards, and then finding by the index of the card we’re already looking at:

@deck.cards.find_index(@card) #the index of the card we’re already looking at.

Since the array @ids only holds the unique card ids, we can easily find the next number by adding 1 not to the card’s id, but it’s place in the index. Putting it all together, we get:

@deck.cards[(@deck.cards.find_index(@card)+1)].id

As an added layer of functionality, I can make sure that the button for next only shows up if there is a next card, using the @ids array again. In the card show view, I wrap the the button html in some Ruby logic:

<%if @card.id != @ids.max%>   <form action= ’/decks/<%=@deck.slug%>/cards/<%=@deck.cards[(@deck.cards.find_index(@card)+1)].id%> method=”get”>   <button type=”submit”>Next</button>   </form><%end%>

In the first line, Ruby confirms that the id of the card we’re already looking at is not the largest card id in the deck — if it is, then the statement returns false, and the button does not display. Similarly, for the back button if the id is not the minimum, then the back button displays:

<%if @card.id != @ids.min%>   <form action= ’/decks/<%=@deck.slug%>/cards/<%=@deck.cards[(@deck.cards.find_index(@card)-1)].id%> method=”get”>   <button type=”submit”>Back</button>   </form><%end%>

More complicated methods must be created if you want to sort or reorder your cards beyond their given id numbers, but this method ensures that each card can be viewed one at a time and that the buttons will only show up when they have a function.

--

--