Fake news (multipage, library)

The story so far

You have a BS template with some JS to load a navbar, and footer. You know how to make input fields, and buttons. You know how to tie JS code to events. You know how to validate. You know how to use variables to coordinate what happens across events. You know how to persist data, and erase persisted data when needed.

This lesson adds a few new things:

  • An app with multiple pages.
  • Generating HTML with JavaScript.
  • Adding a library of utility functions copied from the web

Requirements

Goals

Our app will make a fake news feed. The user can add news items. They'll be listed from newest to oldest.

You can try it.

The news will be persistent. It will hang around until the user clears it.

Screens

There'll be two screens. Here's a mock up of the first, that lists news items:

Mockup

The second screen will add new items:

Mockup

Events

News list screen

  • Page load: show the existing news items.
  • Add: jump to the add page.
  • Reset: after confirming, erase all the news.

Add screen:

  • Page load: nothing special to do.
  • Add: add the new news item to the list of current news, then go back to the news list page.
  • Cancel: forghedaboudit. Go back to the news list page.

Pseudocode

The news list page is simpler than most we've seen.

News list screen event: page load

If there is fake news in localStorage:
    Show it
Else:
    Show "There is no news," or something like that

News list screen event: add button

Jump to add page

News list screen event: reset button

If user confirms:
    Erase the news from localStorage
    Update the display

Now for the add page. Here's the mockup again.

Mockup

Add page: add button

If there is no username:
    Set the username to SmallFingers13
If the news field is empty:
    Show an error message
Else:
    Add the new item to localStorage
    Jump to the news list page

Add page: cancel button

If the user confirms:
    Jump to the news list page

OK, let's implement.

HTML for the news list

A reminder about what we want:

Mockup

Let's call the file index.html. Remember, that's the default file name that gets served, if the URL doesn't have a file name in it. We need to know the file name for later.

Here's the HTML:

<h1>Fake News</h1>
<p>
    <a class="btn btn-primary" href="add.html"
        role="button" title="Add new news">Add</a>
    &nbsp;&nbsp;
    <button onclick="FakeNews.reset()"
        type="button" class="btn btn-danger"
        title="Erase all the news"
    >Reset</button>
</p>
<div id="news"></div>

That's all there is. Let's break it down.

First, we want Add and Reset buttons. Here's Add:

<a class="btn btn-primary" href="add.html"
    role="button" title="Add new news">Add</a>

This is a link, styled to look like a button. Why? Remember the pseudocode for the Add button:

Jump to add page

An <a> tag jumps to page. It already does what we need. So, no JS code to write. Woohoo!

Here's the Reset button:

<button onclick="FakeNews.reset()"
    type="button" class="btn btn-danger"
    title="Erase all the news"
>Reset</button>

It's a normal button, like ones we've seen before. It calls FakeNews.reset().

Marcus
Marcus
Wait, what? Where's the HTML for all those news items?
All of the HTML for all of the news items will be in one localStorage item. You'll see that in a minute.

JS for the news list

Here it is:

var FakeNews = FakeNews || {};
(function($) {
    /**
    * Erase the fake news.
    */
    FakeNews.reset = function() {
        if ( confirm("Erase the fake news?") ) {
            localStorage.removeItem("fakeNews");
            //Refresh the page.
            window.location.href = "index.html";
        }
    };
    $(document).ready(function () {
        //Template stuff.
        ...
        var news;
        if ( localStorage.getItem("fakeNews") ) {
            news = localStorage.getItem("fakeNews");
        }
        else {
            news = "There is no news.";
        }
        $("#news").html(news);
    });
}(jQuery));

I called the app FakeNews, as you can see in the first line.

Here's the code that runs when the page loads:

var news;
if ( localStorage.getItem("fakeNews") ) {
    news = localStorage.getItem("fakeNews");
}
else {
    news = "There is no news.";
}
$("#news").html(news);

The code declares a variable news. Then the code tests if localStorage has any fake news:

if ( localStorage.getItem("fakeNews") ) {

If so, the code puts it into the news variable.

news = localStorage.getItem("fakeNews");

If there is no localStorage item with a key of fakeNews, then:

news = "There is no news.";

Finally, the news is shown on the page:

$("#news").html(news);

Adela
Adela
The news variable is declared in the document ready function, right?
Let's check:

  1. $(document).ready(function () {
  2.     //Template stuff.
  3.     ...
  4.     var news;
  5.     if ( localStorage.getItem("fakeNews") ) {
  6.         news = localStorage.getItem("fakeNews");
  7.     }
  8.     else {
  9.         news = "There is no news.";
  10.     }
  11.     $("#news").html(news);
  12. });
Yes, that's right.

Adela
Adela
So news is a local variable, right? Not available outside document ready?
Right! The news variable is created when document ready runs, and destroyed when it is done.

There are no shared variables coordinating this app.

The last thing on the news list page is the Reset button. Here's the pseudocode:

If user confirms:
    Erase the news from localStorage
    Update the display

Here's the JS:

/**
* Erase the fake news.
*/
FakeNews.reset = function() {
    if ( confirm("Erase the fake news?") ) {
        localStorage.removeItem("fakeNews");
        //Refresh the page.
        window.location.href = "index.html";
    }
};

Nothing new here except for the last statement:

window.location.href = "index.html";

This tells the browser to jump to the page index.html, as if the user had clicked on a link with that on it. But wait, the news list page is index.html. The page is jumping to itself?

Yes, that's what's happening. The page will reload. Reloading will run the document ready code again, which shows the current news items.

So, to implement this line of pseudocode…

Update the display

… we used the JS…

window.location.href = "index.html";

That is, we told the page to reload itself.

Seems a bit odd, but it saves us some work. Not much in this app, but later, this approach will save us a lot of effort.

HTML for the add page

The add page is at add.html. Here's the mock up:

Mock up

Here's what I ended up making:

Display

The News field is a little different. Instead of one line of text, it can have multiple lines.

Here's the HTML:

<h1>Fake News</h1>
<p>Add your fake news, or click Cancel to forghedaboudit.</p>
<div class="form-group">
    <label for="userName">Username</label>
    <input type="text" class="form-control" id="userName"
        aria-describedby="userNameHelp" placeholder="Enter a username">
    <small id="userNameHelp" class="form-text text-muted"
        >E.g., BigTerry603. Defaults to SmallHands13.</small>
</div>
<div class="form-group">
    <label for="news">News</label>
    <textarea class="form-control" id="news" rows="3"></textarea>
</div>
<p>
    <button onclick="FakeNews.add()"
            type="button" class="btn btn-primary" title="Add new news">Add</button>
    &nbsp;&nbsp;
    <button onclick="FakeNews.forghedaboudit()"
            type="button" class="btn btn-danger" title="Forghedaboudit">Cancel</button>
</p>

The only new thing is:

<textarea class="form-control" id="news" rows="3"></textarea>

<textarea> is an HTML tag that makes a multirow input. You give the number of rows with the rows attribute.

Add page: JS for the Add button

Here's the pseudocode for the Add button, as a reminder:

If there is no username:
    Set the username to SmallFingers13
If the news field is empty:
    Show an error message
Else:
    Add the new item to localStorage
    Jump to the news list page

Here's the JS:

FakeNews.add = function() {
    var userName = $("#userName").val().trim();
    if ( userName == "" ) {
        userName = "SmallHands13";
    }
    var news = $("#news").val().trim();
    if ( news == "" ) {
        alert("Sorry, you didn't give any fake news.");
        return;
    }
    //Make HTML for a new card.
    var newHtml =
        "<div class='card'>" +
        "  <div class='card-body'>" +
        "    <h5 class='card-title'>" +
        "      From " + userName + " at " + KrmUtilities.getNow() +
        "    </h5>" +
        "    <p class='card-text'>" + news + "</p>" +
        "  </div>" +
        "</div>";
    //Prepend to existing news.
    var existingNews = "";
    if ( localStorage.getItem("fakeNews") ) {
        existingNews = localStorage.getItem("fakeNews");
    }
    var newNews = newHtml + existingNews;
    localStorage.setItem("fakeNews", newNews);
    //Back the news list.
    window.location.href = "index.html";
};

The first bit is:

var userName = $("#userName").val().trim();

This gets the value of the userName field, trims it, and puts it in the local variable userName. trim() removes extra white space (like blanks) at the beginning and end of a string. Sometimes extra spaces get in fields when users copy-and-paste.

Then, if the userName is empty, we give it a default value:

if ( userName == "" ) {
    userName = "SmallHands13";
}

Next, we grab the news the user typed, and make sure that there is some:

var news = $("#news").val().trim();
if ( news == "" ) {
    alert("Sorry, you didn't give any fake news.");
    return;
}

OK, to understand the next bit, you need to know some more Bootstrap. Remember that there are a bunch of Bootstrap components. One of them is the card. It's a rectangular area with a border, and maybe a title, like this:

Card

Bootstrap gives you HTML you can copy-and-paste to make a card. Here's the HTML for this card:

  1. <div class="card">
  2.   <h5 class="card-title">
  3.     From SmallHands13 at 2017-12-30 16:51:5
  4.   </h5>
  5.   <div class="card-body">
  6.     <p class="card-text">
  7.       Programmers have to deal with reality. SAD!
  8.     </p>
  9.   </div>
  10. </div>
It's easy to follow. There's a card <div>. Inside that there's a card-title and a card-body, with some card-text.

To get the spacing right, I added this to project-styles.css:

.card {
    margin-bottom: 1rem;
}
.card-title {
    margin: 0.5rem;
}

Just a detail.

Here's the HTML again:

  1. <div class="card">
  2.   <h5 class="card-title">
  3.     From SmallHands13 at 2017-12-30 16:51:5
  4.   </h5>
  5.   <div class="card-body">
  6.     <p class="card-text">
  7.       Programmers have to deal with reality. SAD!
  8.     </p>
  9.   </div>
  10. </div>
The user name and news are the values from the input fields:

var userName = $("#userName").val().trim();
...
var news = $("#news").val().trim();

So, if we could insert those values into the HTML for the card, we'd have what we want.

Here's the code:

  1. //Make HTML for a new card.
  2. var newHtml =
  3.     "<div class='card'>" +
  4.     "  <div class='card-body'>" +
  5.     "    <h5 class='card-title'>" +
  6.     "      From " + userName + " at " + KrmUtilities.getNow() +
  7.     "    </h5>" +
  8.     "    <p class='card-text'>" + news + "</p>" +
  9.     "  </div>" +
  10.     "</div>";
(Ignore KrmUtilities.getNow() for a second.)

This code will create the HTML for a new card, in the variable newHtml. Cool!

Now we need to get that into localStorage.

  1. //Prepend to existing news.
  2. var existingNews = "";
  3. if ( localStorage.getItem("fakeNews") ) {
  4.     existingNews = localStorage.getItem("fakeNews");
  5. }
  6. var newNews = newHtml + existingNews;
  7. localStorage.setItem("fakeNews", newNews);
Lines 2 to 5 get the existing fake news from localStorage, if there is any, into the local variable existingNews.

Line 6 creates a new variable, with the new news item before the existing ones. That's what "prepend" means: add before.

Line 7 saves the news into localStorage.

Just one thing left for the Add button. Here's the pseudocode:

  1. bc. If there is no username:
  2.     Set the username to SmallFingers13
  3. If the news field is empty:
  4.     Show an error message
  5. Else:
  6.     Add the new item to localStorage
  7.     Jump to the news list page
The code:

//Back the news list.
window.location.href = "index.html";

The cancel button

Here's the pseudocode for the cancel button

If the user confirms:
    Jump to the news list page

The JS:

/**
* Forghedaboudit.
*/
FakeNews.forghedaboudit = function(){
    if ( confirm("Are you sure?") ) {
        window.location.href = "index.html";
    }
};

If confirm("Are you sure?") is false, the function just ends. There is an implied return at the end of every function.

Adding the news item date and time

Here's a sample news item again:

Card

The user types in the user name, and the news, but not the time when the news item was posted. There is no point. The computer knows what time it is, so we can generate that in code.

There are three things to do:

  • Write or find JS code that shows the current date and time.
  • Add that code to our app.
  • Call the code when needed.

Rather than write the code, I googled it. We did this earlier with some rounding code for the piggy bank. You can find code for just about anything.

The page https://www.codexworld.com/how-to/get-current-date-time-using-javascript/ gives some code. Here it is:

var today = new Date();
var date = today.getFullYear()+'-'+(today.getMonth()+1)+'-'+today.getDate();
var time = today.getHours() + ":" + today.getMinutes() + ":" + today.getSeconds();
var dateTime = date+' '+time;

So, run this, and the variable dateTime should contain the current date and time. It's in international format, but that's OK.

The code should work, but does it? Best to check, before you add it to your program.

Here's a sandbox.

Hey, it worked!

Is it legal to copy?

Adela
Adela
Is it legal to copy code like that?
Each source is different. Go to https://www.codexworld.com/how-to/get-current-date-time-using-javascript/, where the code came from. Scroll to the bottom of the page, and you'll see this:

License

CC means that the page has a Creative Commons license. That means it can be used. The text after the CC gives some conditions. For example, BY means that the author must be given credit. That has been done here by giving a link to the original page. We'll also put a link in our JS code, as you'll see. The full license is available on the Creative Commons site.

CC licenses let people reuse content, in one way or another. The web site you're looking at now uses a liberal CC license, as you can see at the bottom of every page.

Adding the code

OK, we found some code that works. Now what?

Remember the three steps:

  • Write or find JS code that shows the current date and time.
  • Add that code to our app.
  • Call the code when needed.

We've done the first one. The second step is to add the code to our app. The quickest way is to simply paste it into FakeNews.add(). We did that with the piggy bank's rounding code.

That would be just fine. However, let's see another way that's better in the long run.

There are many other useful code snippets out there, thousands of them. As we make more apps, we'll find more and more snippets that we want to use.

Maybe we could collect the snippets into a library. When we add a new snippet, it will be available to all of the apps we write in the future. FTW!

Let's make a file called utilities.js, and put it in the library directory:

New file

That will be a collection of code snippets.

We need to include the file where it is needed. So, this line gets added to the HTML, either to the template, or as needed in each HTML file:

<script src="/library/utilities.js"></script>

Use the right path in src. For example, it might not have the first /.

<script src="library/utilities.js"></script>

Whatever works in your code.

Let's look inside the file. First, we don't need a <script> tag. That's an HTML tag for adding JS inside an HTML file. utilities.js is not an HTML file. It's a pure JS file.

I want to put the utility code into a namespace. But which one? I could put it in FakeNews, but then I'd have to edit utilities.js for each app.

Let's create a new namespace, that we can use in every app. It should be something that no other library will use. Utilities isn't a good choice, since other libraries might use that.

Hmm… how about KrmUtilities? That's my initials. It's unlikely that anyone else will use the namespace KrmUtilities.

You can call namespace anything you want, as long as it's unlikely to be used by anyone but you.

Here's the file utilities.js:

"use strict";
var KrmUtilities = KrmUtilities || {};
(function($) {
    /**
    * Return the current date and time.
    * Adapted from https://www.codexworld.com/how-to/get-current-date-time-using-javascript/
    */
    KrmUtilities.getNow = function() {
        var today = new Date();
        var date = today.getFullYear()+'-'+(today.getMonth()+1)+'-'+today.getDate();
        var time = today.getHours() + ":" + today.getMinutes() + ":" + today.getSeconds();
        var dateTime = date+' '+time;
        return dateTime;
    }
}(jQuery));

The first few lines are much the same as the template.

Note the comment. It explains what the function does, and also gives the original author credit.

The line…

KrmUtilities.getNow = function() {

… adds a new function to the KrmUtilities namespace.

Here's the function itself:

KrmUtilities.getNow = function() {
    var today = new Date();
    var date = today.getFullYear()+'-'+(today.getMonth()+1)+'-'+today.getDate();
    var time = today.getHours() + ":" + today.getMinutes() + ":" + today.getSeconds();
    var dateTime = date+' '+time;
    return dateTime;
}

OK. We're adding a way to get the current date and time to our app. Here are the three steps again:

  • Write or find JS code that shows the current date and time. DONE
  • Add that code to our app. DONE
  • Call the code when needed.

We found the code at https://www.codexworld.com/how-to/get-current-date-time-using-javascript/. First step done.

We packaged the code into a library file that we can reuse in other apps. Second step done.

The last step is to call the code. This is from FakeNews.add():

  1. //Make HTML for a new card.
  2. var newHtml =
  3.     "<div class='card'>" +
  4.     "  <div class='card-body'>" +
  5.     "    <h5 class='card-title'>" +
  6.     "      From " + userName + " at " + KrmUtilities.getNow() +
  7.     "    </h5>" +
  8.     "    <p class='card-text'>" + news + "</p>" +
  9.     "  </div>" +
  10.     "</div>";
The call to KrmUtilities.getNow() returns what we want.

More testing

We should test things as we go, to make sure that we catch errors as soon as possible.

Try this sandbox. Some of the outer code has been changed because of the way the sandbox works.

Hey, it worked!

All done

That's it. Remember, you can try the app yourself, and copy the code for your own use.

Summary

  • This app has two pages, using window.location.href to jump between them.
  • The app creates HTML for each news item, following copy-and-pasted code from the BS docs.
  • We started building a library of utility functions copied from the web.
  • We used to console to check that things are working.