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:
The second screen will add new items:
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.
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:
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>
<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()
.
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);
news
variable is declared in the document ready function, right?
Yes, that's right.
news
is a local variable, right? Not available outside document ready? 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:
Here's what I ended up making:
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>
<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:
Bootstrap gives you HTML you can copy-and-paste to make a card. Here's the HTML for this card:
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:
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:
(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
.
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:
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:
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?
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:
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()
:
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.