Ships

The story so far

This course is about making CRUD apps. You know how to do C(reate), and R(ead), and all the appy goodness behind them. Now let's add D(elete). We'll also firm up the way we connect pages.

This lesson is longer than most. Much of it is review. You've already seen how to work with TaffyDB, list data, and add new records. This lesson explains that again, as one last check. Go through the code. You should be able to follow it without much trouble. It will give you confidence that you know what's going on.

Plan to take a break part-way through the lesson. I'll remind you.

Fleet

Our company owns a fleet of ships. We want an app to manage fleet data.

Here's the initial data.

var startingFleet = [
    {
        id: 11,
        name: "Stan",
        displacement: 26.3
    },
    {
        id: 24,
        name: "Dipper",
        displacement: 29.1
    },
    {
        id: 53,
        name: "Mabel",
        displacement: 28.1
    },
    {
        id: 94,
        name: "Soos",
        displacement: 36.3
    }
];

We have four ship records, each with three fields. The first field is id, the primary key. We'll finally get to use the PK in this app.

You can try the app. First, initialize the database. You can the list the ships. That list page is public. There is also an admin page. Later, you'll learn how to put admin pages behind a login form.

Pages

We're starting to get a bunch of pages now, so let's add a page diagram. It shows the different pages that will make up the app, and how you go from one to another.

Pages and links

There are two parts to the app. First, there's the public part, that anyone can see. It's just one page, index.html in the app's main directory.

Second, there's an admin part. It's used by administrators to update the data.

Some things to notice. First, there are two pages that list ships.

Pages that list ships

One page is for regular users, and another for admin users. Some web apps use one page for both, with if() statements to show extra elements on the page for admins. We won't do that. We'll use separate pages, to make the code easier to write.

Second, all of the admin pages are in their own directory.

Admin directory

Here's how the files will look on the server.

Files on server

The library has the includes, style sheet, and template.

Screens and pseudocode

So, we'll have an app that shows data about a fleet. It will have a public page (just one in this app), and admin pages. The admin pages will be in their own directory.

Let's have a look at the pages, and the pseudocode for each one.

Public fleet list

Let's start with this page:

Public list

Here's the page:

Public fleet page

It just shows the list. No buttons, or links. There is no Add button; it's moved to another page.

We'll only need code for one event:

On page load:
    Make a TaffyDB
    Bind the TaffyDB to localStorage
    For each ship record:
        Show the ship record
Ray
Ray
I've been wondering. Why do you do the pseudocode for every page first? Why not do the HTML and JS for one page, have it finished, and move on to the next page?

Good question. Pseudocode is a plan, or specification, for the code on a page. Planning the code for every page first helps me keep a high-level view of how the different pages fit together, to make the entire app.

You don't have to do things this way. Do whatever makes sense to you.

So we have plan for the public fleet list page.

Admin fleet list

Now the page that shows the fleet to admin users:

Admin fleet list

Here's a screen shot:

Admin fleet list

The screen is almost the same as the public list, except for three things.

  1. Each record has its own delete button.
  2. There's a button to add a new ship.
  3. There's a link to the DB initialization page.

The Add button and the Initialize links are regular <a> tags, with no extra code. We don't need pseudocode for them.

There are only two events that need code:

  • Page load
  • Delete button click

The page load event is going to make the table, with the delete buttons.

On page load:
    Make a TaffyDB
    Bind the TaffyDB to localStorage
    For each ship record:
        Show the record, with a delete button

Now the code for the Delete button.

On delete click:
    If user confirm:
      Delete the record from the database
      Update the fleet list

Adela
Adela
That line, "Delete the record from the database." How does the code know which record to delete?
We've put an id in each record:

{
    id: 11,
    name: "Stan",
    displacement: 26.3
}

We'll use the id to tell the code which record to delete. More in a moment.

So, we've done the list pages.

  • The page for the public list loops over the record set, showing each record.
  • The list page for admins is the same, adding the delete button to each record.

Initialization page

Let's do this one next:

Initialization next

The page looks like:

Page

There's only one event, the button click.

Make a TaffyDB
Bind the TaffyDB to localStorage
Make sure the TaffyDB is empty
Insert the record set into the TaffyDB

Nothing new here.

That the third page out of four. One more.

Add page

The last one:

Add

Here's the page:

Add

We'll need code for three events:

  • Page load
  • Add click
  • Cancel click
On page load:
    Make a TaffyDB
    Bind the TaffyDB to localStorage

Now for the Add button:

When the Add button is pressed:
    Validate the form's data
    Make a new ship record
    Add the ship record to the DB
    Jump to the fleet list page

Finally, for the cancel button:

When Cancel button pressed:
    If user confirms:
      Jump to the fleet list page

This is a good time to take a break, if you need one. Before we start into the code. Maybe play with your dog.

Where are we?

We're making an app for a fleet of ships. The app will have four pages. We've designed each page, and written the pseudocode.

Now let's code each page.

Public fleet page

We've done this before. We want this:

Public fleet page

Here's the HTML:

<h1>Ships</h1>
<div id="fleet-container" class="hide-on-load"></div>

Our code will work out the HTML needed to show the table, and inject it into fleet-container.

This is the pseudocode again:

On page load:
    Make a TaffyDB
    Bind the TaffyDB to localStorage
    For each ship record:
        Show the ship record

Here's the JS to make a TaffyDb and bind it to localStorage:

//Create an empty Taffy DB.
Fleet.fleetDb = TAFFY();
//Link the DB to localStorage. Existing items will be loaded.
Fleet.fleetDb.store("fleet");

What's left?

On page load:
    Make a TaffyDB  DONE
    Bind the TaffyDB to localStorage  DONE
    For each ship record:
        Show the ship record

Now to loop across the record set, and show each record.

As before, we'll build up the HTML in a variable.

Start by putting the opening tags and table headings into the variable fleetHtml:

var fleetHtml =
    "<table class='table'>" +
    "  <thead>" +
    "      <th>Name</th>" +
    "      <th class='text-center'>Displacement (kT)</th>" +
    "  </thead>" +
    "  <tbody>";

Now loop over the record set, create the HTML for each record, and add it to fleetHtml,

Fleet.fleetDb().order("name").each(function (ship) {
    var shipRow =
        "<tr>" +
        "  <td>" + ship.name + "</td>" +
        "  <td class='text-center'>" + ship.displacement + "</td>" +
        "</tr>";
    fleetHtml += shipRow;
});

Finish off the table HTML in fleetHtml:

//Finish the HTML.
fleetHtml += "</tbody></table>";

Now we just need to inject the HTML into the page, and show it:

//Show it.
$("#fleet-container")
    .html(fleetHtml)
    .show('slow');

Admin fleet page

Now this page:

Fleet admin

The table is the same as the last page, but with Delete buttons.

Here's the page's HTML:

  1. <h1>Ships administration</h1>
  2. <p>Authorized users only.</p>
  3. <div id="fleet-container" class="hide-on-load"></div>
  4. <p>
  5.     <a href="add.html" class="btn btn-primary"
  6.         title="Add a new ship">Add</a>
  7. </p>
  8. <p>
  9.     <a href="initialize.html"
  10.         title="Reset to starting DB">Initialize</a>
  11. </p>
Our code will work out the HTML needed to show the table, and inject it into fleet-container on line 3.

The Add button is next. It's an <a> made to look like a button. It doesn't need any code. Nor does the link to the initialize page.

OK, that's the HTML. Now the JS. We need code for:

  • Page load
  • The Delete buttons.

The page load code has to make the HTML for the table, as before. It's a loop like this:

var fleetHtml = HTML TO START TABLE
Fleet.fleetDb().order("name").each(function (ship) {
    ...
    var shipRow = MAKE HTML TO SHOW A RECORD
    fleetHtml += shipRow;
});
fleetHtml += "</tbody></table>";

Each time through the loop, we make the HTML to show a record, and put it in shipRow. Then we append shipRow to fleetHtml.

Take the line:

var shipRow = MAKE HTML TO SHOW A RECORD

What does the HTML TO SHOW A RECORD look like?

Here's HTML we want our JS to make, for two rows. This is not HTML we type. It's HTML our JS code makes.

  1. <tr>
  2.     <td>Dipper</td>
  3.     <td class="text-center">29.1</td>
  4.     <td>
  5.         <button class="btn btn-danger"
  6.             onclick="Fleet.deleteItem(24)"
  7.             title="Permanently remove this ship"
  8.         >Delete</button>
  9.     </td>
  10. </tr>
  11. <tr>
  12.     <td>Mabel</td>
  13.     <td class="text-center">28.1</td>
  14.     <td>
  15.         <button class="btn btn-danger"
  16.             onclick="Fleet.deleteItem(53)"
  17.             title="Permanently remove this ship"
  18.         >Delete</button>
  19.     </td>
  20. </tr>
The highlighted code shows what will happen when the buttons are clicked. When Dipper's delete button is clicked, this runs:

Fleet.deleteItem(24)

24 is Dipper's id. That is how the code will know which ship to remove. Here's Dipper's record:

  1. {
  2.     id: 24,
  3.     name: "Dipper",
  4.     displacement: 29.1
  5. }
When Mabel's delete button is clicked:

Fleet.deleteItem(53)

53 is Mabel's id. Here's Mabel's record:

  1. {
  2.     id: 53,
  3.     name: "Mabel",
  4.     displacement: 28.1
  5. }
So, we want our JS code to create a table row for each record. The table row includes a call to a delete function, that has the id of the record.

Here's how we do it.

  1. //Start building the output table.
  2. var fleetHtml =
  3.     "<table class='table'>" +
  4.     "  <thead>" +
  5.     "      <th>Name</th>" +
  6.     "      <th class='text-center'>Displacement (kT)</th>" +
  7.     "      <th>Operations</th>" +
  8.     "  </thead>" +
  9.     "  <tbody>";
  10. //Sort by name, then generate HTML for each record.
  11. Fleet.fleetDb().order("name").each(function (ship) {
  12.     var deleteButton = "<button class='btn btn-danger' "
  13.         +    "onclick='Fleet.deleteItem(" + ship.id + ") '"
  14.         +    "title='Permanently remove this ship' "
  15.         + ">Delete</button>";
  16.     var shipRow =
  17.         "<tr>" +
  18.         "  <td>" + ship.name + "</td>" +
  19.         "  <td class='text-center'>" + ship.displacement + "</td>" +
  20.         "  <td>" + deleteButton + "</td>" +
  21.         "</tr>";
  22.     fleetHtml += shipRow;
  23. });
  24. //Finish the pack HTML.
  25. fleetHtml += "</tbody></table>";
The new stuff is on lines 12 to 15:

var deleteButton = "<button class='btn btn-danger' "
    +    "onclick='Fleet.deleteItem(" + ship.id + ") '"
    +    "title='Permanently remove this ship' "
    + ">Delete</button>";

These lines create the HTML that shows a Delete button. The code puts the ship's id in the middle of the HTML. So, if ship.id is 24, we get:

Fleet.deleteItem(24)

if ship.id is 53, we get:

Fleet.deleteItem(53)

The code for the Delete button is added to the code for a record:

Remember, you can try the app, and use the debugger to see how the code works.

There's a piece missing. The HTML has code like this in it:

Fleet.deleteItem(24)

We need to write Fleet.deleteItem(). Here's the pseudocode we decided on earlier.

On delete click:
    If user confirm:
      Delete the record from the database
      Update the fleet list

Here's the JS that will do it:

Fleet.deleteItem = function(itemId) {
    //Is the user sure they want to delete?
    var confirmation = confirm('Are you sure?');
    if ( confirmation ) {
        //Delete the item.
        Fleet.fleetDb({id:itemId}).remove();
        //Show the revised admin page.
        window.location.href = 'index.html';
    }
};

The line that removes the data is:

Fleet.fleetDb({id:itemId}).remove();

The first part, fleetDb({id:itemId}), is a TaffyDb query. It says, "Hey, Taffy! Look in fleetDb, and give me all the records that have an id of itemId." The second part says, "Whatever you get back from the query, remove it."

itemId is passed into the function:

Fleet.deleteItem = function(itemId) {

Check it out. Our page load code created this HTML for Dipper:

onclick="Fleet.deleteItem(24)"

So this code run by a Delete button…

Fleet.deleteItem(24)

… gives us…

Fleet.deleteItem = function(24) {
...
Fleet.fleetDb({id:24}).remove()

That means, "Find the record with an id of 24, and delete it."

For Mabel, our page load code created this HTML:

onclick="Fleet.deleteItem(53)"

That runs:

Fleet.deleteItem = function(53) {

Fleet.deleteItem(53) does this:

Fleet.fleetDb({id:53}).remove()

Bye bye, Mabel.

We're using the primary key, to tell us which record to delete. W00t!

Git 'er done

Where are we? We're making this app:

Pages and links

We've done the index pages. They're the most complex. Let's do the others.

Initialize

This is the same as before. Here's the page:

Initialize page

Here's the HTML:

<h1>Initialize</h1>
<p>
    Click the Initialize button to
    initialize the ship database.
</p>
<p><em>Warning</em>: database will be reset.</p>
<p>
    <button id="initialize" class="btn btn-primary"
        onclick="Fleet.initDb()"
    >Initialize</button></p>
<p>
    <a href="../index.html" title="Show the fleet">List</a>
</p>

The button runs Fleet.initDb().

Here's the pseudocode from before:

Make a TaffyDB
Bind the TaffyDB to localStorage
Make sure the TaffyDB is empty
Insert the record set into the TaffyDB

The JS:

  1. Fleet.initDb = function() {
  2.     //Set up initial data set.
  3.     var startingFleet = [
  4.         {
  5.             id: 11,
  6.             name: "Stan",
  7.             displacement: 26.3
  8.         },
  9.         ...
  10.     ];
  11.     //Create an empty Taffy DB.
  12.     Fleet.fleetDb = TAFFY();
  13.     //Link the DB to localStorage. Existing items will be loaded.
  14.     Fleet.fleetDb.store("fleet");
  15.     //Remove all items.
  16.     Fleet.fleetDb().remove();
  17.     //Insert new records into the DB.
  18.     Fleet.fleetDb.insert(startingFleet);
  19.     alert("Done");
  20. };
Nothing new here. You should be able to match the pseudocode to the JS code.

The Add page

It looks like:

Add

The HTML is as before:

<h1>Ships</h1>
<p>
    Add a ship to the fleet, or click Cancel
    to forghedaboudit.
</p>
<div class="form-group">
    <label for="ship-name">Name</label>
    <input type="text" class="form-control"
        id="ship-name"
        aria-describedby="ship-name-help"
        placeholder="Enter a name">
    <small id="ship-name-help"
        class="form-text text-muted"
        >E.g., Fred.</small>
</div>
<div class="form-group">
    <label for="ship-displacement"
        >Displacement (kT)</label>
    <input type="number" min="1" class="form-control"
        id="ship-displacement"
        aria-describedby="ship-displacement-help"
        placeholder="Enter a displacement">
    <small id="ship-displacement-help"
        class="form-text text-muted"
        >E.g., 28.1.</small>
</div>
<p>
    <button onclick="Fleet.add()"
      class="btn btn-primary" title="Add new ship"
      >Save</button>
    &nbsp;&nbsp;
    <button onclick="Fleet.forghedaboudit()"
        class="btn btn-danger"
        title="Forghedaboudit"
        >Cancel</button>
</p>

It has the same structure as the dog pack add form.

We'll need code for three events:

  • Page load
  • Add click
  • Cancel click

Pseudocode:

On page load:
    Make a TaffyDB
    Bind the TaffyDB to localStorage

JS:

//Create an empty Taffy DB.
Fleet.fleetDb = TAFFY();
//Link the DB to localStorage. Existing items will be loaded.
Fleet.fleetDb.store("fleet");

Now for the Add button. Pseudocode:

When the Add button is pressed:
    Validate the form's data
    Make a new ship record
    Add the ship record to the DB
    Jump to the fleet list page

The validation is as we've seen before:

var newShipName = $("#ship-name").val().trim();
if ( newShipName == "" ) {
    alert("Sorry, you didn't give a ship name.");
    return;
}
var newShipDisplacement = $("#ship-displacement").val();
if ( newShipDisplacement == "" ) {
    alert("Sorry, you didn't give a displacement.");
    return;
}

Now to make a new ship record. We'll need to have an id for the ship. We'll copy what we did for the dog pack.

//Add a new record.
//Find the highest id so far.
var highestId = Fleet.fleetDb().max("id");
//New ship's id is one more.
var newShipId = highestId + 1;
//Create a new object.
var newShip = {
    id: newShipId,
    name: newShipName,
    displacement: newShipDisplacement
};

The pseudocode again:

When the Add button is pressed:
    Validate the form's data  DONE
    Make a new ship record  DONE
    Add the ship record to the DB  UP TO HERE
    Jump to the fleet list page

Just one line to add the ship record:

Fleet.fleetDb.insert(newShip);

Now to jump to the fleet list:

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

Finally, for the cancel button. Pseudocode:

When Cancel button pressed:
    If user confirms:
      Jump to the fleet list page

We've done this before:

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

All done.

Ray
Ray
There's a lot to this one.

Yes, there is. However, most of it was copied from the dog pack app. There were only two new things:

  • Creating an admin section, with its own fleet page.
  • Adding Delete buttons.

When you can, reuse what you've done before.

Exercise

Exercise: Bands
Make a web app to track your favorite bands. Give it the same structure as the ships app. Use your own data.

Add a public index page:

Index

Bands are sorted by name.

Add an admin index page:

Admin index page

The Delete buttons should work. Confirm the action with the user.

Include an add form:

Add form

All fields are required. Make sure that ratings are from 1 to 10:

Rating error

Include an initialization page, linked from the admin index page:

Initialization page

Submit the URLs of both your index pages.

(If you were logged in as a student, you could submit an exercise solution, and get some feedback.)

Summary