Converting Multi-page Layouts To Clean SPAs with JavaScript and JQuery

Written by ethan.jarrell | Published 2018/02/01
Tech Story Tags: javascript | single-page-applications | software-development | clean-spas | jquery

TLDRvia the TL;DR App

Let me start with a simple scenario. In the below example, We have a user, Bill, who wants to access our database of books. He searches for books from 1985. We make an API call, and deliver the results to the page. The main question here is, how should we display the data once it’s been retrieved?

Above is a pretty typical layout for displaying the data. The search bar at the top, and the results below the search bar, once the data is loaded. However, this creates a minor problem, scrolling. Now, we are requiring the user to scroll through so much data to find what they’re looking for. Maybe Bill doesn’t mind. But suppose he’s searching for an obscure book from 1985. Maybe he would recognize the title if he saw it, but he’s got to scroll all the way to the bottom, then go to page 2 and then page 3 and then page 4 before he finds it. What we’re requiring Bill to do might look like this:

Now, in all fairness, this is pretty typical and so, as users, we’ve come to expect this from many sites, and so the average user probably doesn’t mind much anyway. But, just for the sake of fairness, let’s create a more elegant, 1 page design for Bill, using JavaScript and JQuery. I like to draw my ideas out first, so here’s what I had in mind:

In this crude example, we’ve completely cleaned up the site, and leave lots of breathing room and negative space which I’m sure Bill will appreciate. We’ve created a side panel where all the controls will be. The user will search here, and see the results in the same panel. In our simple case, we don’t even need to display all the data for every book. We could simply display the title of each book. Then, when it’s clicked on, the full synopsis, image and information would appear on the main content area.

The data, in this case doesn’t really matter. This could be for toy airplanes, books, video games, or princess dolls. In either case, for me personally, I prefer the simple, clean, Single Page design.

STEP 1 : HTML

The HTML could be pretty basic.

<body><div id="sidepanel" class="sidepanel"><input id="input" placeholder="what do you want?" /><button type="submit" id="submitBtn">Go</button>

<div id="dynamicSideContent">

//--Content goes here--//</div>

</div><div id="content" class="content">

//--Content goes here--//

</div>

</body>

Depending on the scenario, this is really all we need. Yes, eventually we’ll pretty it up with a banner and a logo and all the stuff that real sites usually have. But at the end of the day, since all our data is going to be dynamically generated, this is all we need.

We have:

A) side panel

  1. Search Bar
  1. Content Loading Area

B) Content Div

  1. Content Loading Area

Step 2: Make the API Call

$(window).load(function() {  
  console.log("talking");

fetch('myApi_URL_thingy').then(function(response) {if (response.status != 200) {window.alert("Oopsie Daisy");return;}

response.json().then(function(data) {

Step3: Parse the Data

We have our data now, in some shape or form. Our end goal here, is to get some book titles, or whatever, do display in our sidepanel. Here are a couple of problems we may face in doing that:

Problem :

Each button will correspond to a book within our data. Each button will have a book title inside it. We don’t want to use a link, since that will navigate us to a new page. Instead, we want to display it in our content area to the right. If we were using links, we could simply store the links in our data file. when the user clicks on a link, we would pull that piece of the data, access the url and redirect the user. But we don’t want to do that. To get around this, we’ll need to dynamically create id’s for each button, and then, store data in the button id about each book so that we can access and display it in the content area. We can do that with a for loop when we parse our data.

In the below example, I’m going to instead use the scenario that our user, Bill is searching by author instead of Date, because that’s just more fun.

let input = document.getElementById('input');let submit = document.getElementById('submitBtn');submit.onclick = searchData;

function searchData () {let currentBook = [];let input = document.getElementById('input');for (var i = 0; i < books.length; i++) {let val3 = input.value;let val2 = val3.toLowerCase();let value = val2.replace(/ /g, '');let text3 = books[i].author;let text2 = text3.toLowerCase();let text = text2.replace(/ /g, '');console.log(text.indexOf(value) >= 0);console.log(text);console.log(value);if (text.indexOf(value) >= 0) {currentBook.push([books[i], i]);}

}

What I did here, is try to think of what a User might do. Bill might want a book by Dr. Laura Ingram. But, poor Bill, his memory is fading…and all he can remember is “Laura”. So he searches for authors by submitting “Laura” into the input. What the above does, is it converts the input to lowercase, then removes any spaces in the input. Then for the current API entry, I do the same thing to the author. This way, if Bill Searches for “Laura” and the actual author is listed as “Dr. Laura Ingram”, JavaScript is actually comparing these two strings:

“laura” and “dr.lauraingram”.

If we hadn’t done that, it would be comparing:

“Laura” to “Dr. Laura Ingram”,

and it might be more difficult to match the results with the uppercase and additional spaces. We want to compare apples to apples instead of apples to oranges. Now that we have two similar strings, we do this:

if (text.indexOf(value) >= 0) {currentBook.push([books[i], i]);}

What we’re doing now that we have these two strings, is seeing if the current iterable book author contains a substring of the user Input. Since “dr.lauraingram” does have the substring “laura”, it would make the match. Now, again, this could create a host of other problems that we would have to tackle, like lots of results that aren’t what Bill intended, but let’s not forget, it’s Bills fault, and his memory problems that got us into this mess in the first place! Moving on…

In any case, at this point, we should have the book that Bill is searching for, and we’re displaying it, and any other matches in the side column. But remember, we want to put the content of the book in the button Id somehow.

We’ve just pushed all the matched books to an array called currentAuthor. Let’s dissect that array with a for loop, and create buttons using backticks. Then we’ll insert the ID into the button using the template expression.

for (var i = 0; i < currentBook.length; i++) {currentBook[i] = `<button id="id_${currentBook[i][1]}" class="btn7">${currentBook[i][0].author}-${currentBook[i][0].title}</button>`;document.getElementById('dynamicSideContent').innerHTML += currentQuote[i];}}

So, what I’ve done here is looped through currentBook. Then, for each entry in the array, I’m assigning it a new value with a template expression. The template expression consists of just a button with an ID, a class and text inside the button:

<button id="id_${currentBook[i][1]}" class="btn7">${currentBook[i][0].author}-${currentBook[i][0].title}</button>

Now, if you noticed, when I looped through my initial book array, I pushed the matched books, “books[i]”, but I also pushed the iteration number “i”, both into an array. Since each iteration number is unique, I can use that as part of my button ID, which I did there. The button, defined in the template expression is :

id_${currentBook[i][1]}

But, which means that each book ID will actually be “id_1”, “id_2”…and so on.

One other thing to point out is that the class is “btn7”. Because we can’t access those IDs within JavaScript, we’ll actually use JQuery to access the class first, and then get the ID after we have accessed the class. We’ll simply do that with each button click.

The pseudo code would be, “If a button with btn7 class is clicked, do something”.

Here’s how we’ll write that with the help of JQuery:

$(document).on('click', '.btn7', function () {let currentBook = [];let id = this.id;let currentId = id.split("_");let idNumber = parseInt(currentId[1]);let disregard = parseInt(currentId[0]);for (var i = 0; i < books.length; i++) {if(i == idNumber ) {currentBook.push(books[i]);}}

What I’ve done here is just that. We listen for any time that a button with the class “btn7” is clicked. We know that button has an ID, so we access it with “this.id” and assign it to a variable called “id”. Then, we split that ID string:

let currentId = id.split("_");

Our Id’s all start with “id” followed by an underscore and then a number. string.split() will turn that into an array, we don’t need the id, or the underscore now…we only want the number. We get that by converting the currentID variable into a number

let idNumber = parseInt(currentId[1]);

Then, in our for loop, we see if the idNumber matches the iteration of the current book. If there’s a match, that’s the one we want.

for (var i = 0; i < books.length; i++) {if(i == idNumber ) {currentBook.push(books[i]);}

When we initially searched our array, we saved that index value, and then we’re using it again to make sure we match the dynamic button that the user clicked.

Now that Bill clicked what he wanted, we need to display that in our content area. We’ll use the same template expression format we used earlier to create that and send it to the HTML.

for (var i = 0; i < currentBook.length; i++) {currentBook[i] = `<span class="container"><h1 class="bookTitle">${currentBook[i].author} - ${currentBook[i].title}</h1><p class="synopsis">${currentBook[i].text}</p></span>`document.getElementById('content').innerHTML += currentBook[i];}

At this point, we’re just parsing the array we just created with a for loop. Unless we also need to do something with this data, we don’t need to create the ID’s again, but we could do that again if we needed to. Here, we’re just taking that button click, and putting the matching book onto the main display.

Again, there are many, many ways to do something like this, and probably a lot that are way better and simpler than mine, but I think it’s at least a step in the right direction. Let me know if you have any feedback. Thanks!


Published by HackerNoon on 2018/02/01