Data Modeling with Closures

Start a new project for today by right-clicking this link and "Save Link As..." to your Desktop (or somewhere else). Don't forget to rename the folder to something meaningful!

Lesson

Exercises

Building a Todo List

Basic Requirements

  1. In your starter code, write a global variable called todos and set it to an empty array. Decide how to model a single todo item, and then add a few todos into the todos array (you may want to consider writing a factory function).

    Your todo objects should have at least the following properties:

    • id: a unique id for the todo (use the generateID function to create a unique id)
    • completed: indicates whether or not the todo is completed
    • task: a string describing what is to be done
  2. First, find the comment marked EXERCISE 2. Here we'll be completing the implementation of the displayTodo function that accepts a single todo as a parameter, and should output a string that represents the todo. Make sure to include the id of the todo!

  3. The display method of the returned object in makeList should display all of the todos in the list. display should use your displayTodo function to transform all of the todos into strings, join them together with newline characters ('\n') to form a single string, and then output them.

    If you can, use map for this!

    HINT: The output will look nicer if you console.log the resulting string instead of returning it.

  4. We'll need a way to add new todos to the list. Add another method to the object returned from makeList called add that accepts a string as a parameter, creates a new todo object, and adds it to the (internal) todos array.

    Verify that the todo was successfully added using the display method.

  5. Finally, we need a way to mark our todos as completed! In order to reference a specific todo, we'll use its id key. Implement another method called complete that accepts an id as a parameter, finds the todo in the list with that id, and sets it to completed.

More Practice

  1. It's often nice to be able to toggle a todo between complete/incomplete. Change your complete method to toggleComplete that toggles a todo between complete/incomplete.

  2. It would be nice if you could clear all of the completed todos. Add another method that allows you to clear out (remove) all of the todos that are completed.

  3. Instead of just deleting all of the completed todos, it might be nice to have some sort of archival system -- that is, instead of having completed todos be completely removed (like in the previous exercise), they get put into an archive. To properly implement this mechanism, you'll need to add a few more methods:

    • unarchive: Given a todo's id, moves it from the archive back to the list of todos.
    • displayArchived: Displays the archived todos.
    • clearArchived: Actually deletes the archived todos.

Advanced

  1. Implement a way to reorder todos in the list. In order to help make this work, you might first want to ensure that your display method shows the indices at which the todos appear.

    You'll likely want to implement something like a move method, that accepts two parameters, from and to, each of which are indices of todos. When a todo moves to a new position, make sure that the rest of the todos move accordingly.

    HINT: You may want to read about the array splice method on MDN.

  2. Implement a priority system, such that todos can have different priorities that affect the way in which they are presented. Higher priority todos should be shown before those with lower priorities. As a bonus, extend the display method to have the capability to display only todos with a specific priority.

Book Store & Shopping Cart

Basic Requirements: Book Store

  1. This exercise provides a helper function to generate a book store called generateBooks. Experiment with it at the console by trying the following:

    generateBooks(5);
              generateBooks(10);
              generateBooks();
              

    What keys and values do the books have? Note that some of the fields are randomly generated. Which ones?

    Create a function called makeStore that creates an internal variable of generated books (use generateBooks), and returns an empty object. Then, create a book store in the global scope, e.g.

    function makeStore() {
                // YOUR CODE HERE
              }
              
              var bookStore = makeStore();
              
  2. Our first goal will be to create a display method for the bookStore so that we can view some books. Since there can potentially be quite a few of them, it would be best if we could specify how many books we'd like to see, e.g.:

    bookStore.display(3);
              1. Lucky Jim, by Kingsley Amis / CATEGORY: self help / PRICE: 22.77 / ID: 0
              2. Money, by Martin Amis / CATEGORY: classic / PRICE: 32.34 / ID: 1
              3. The Information, by Martin Amis / CATEGORY: classic / PRICE: 23.1 / ID: 2
              

    STEP 1: Inside of makeStore, write a function called displayBook that accepts a book as a parameter and returns a string representing the relevant fields of that book (something like what you see above).

    STEP 2: Also inside of makeStore, write another function called displayBooks that accepts an array of books as a parameter, and outputs a single string consisting of all the books' string representations, separated by the newline character ("\n").

    This can be achieved elegantly using map and join. The version of map that you will be using passes the index of each element as the second parameter to its function argument:

    nums = map(["the", "quick", "brown", "fox"], function(s, i) {
                return i;
              });
              nums; // => [0, 1, 2, 3]
              

    STEP 3: Finally, use displayBooks to implement the display method of the object returned by makeStore so that you can display n books to the console. HINT: You'll want to use console.log (to make it look nice in the console) and slice.

  3. The following function isMatch takes a book and a query as a parameter, and returns true if the book matches the query, and false otherwise:

    function isMatch(book, query) {
                var s = (book.title + book.author + book.category).toLowerCase();
                return s.indexOf(query.toLowerCase()) >= 0;
              }
              

    Using isMatch, implement a search method on the store object that accepts a query as a parameter, and then displays only matching books. HINT: You'll want to use the displayBooks function that you've written to display the matching books in the same way as you did in the display method.

    filter is particularly well-suited to perform this search. If you can, try using filter. To get an idea of how it works, try the following at your console:

    filter([1, 2, 3, 4, 5], function(number) {
                return number % 2 === 0;
              });
              
  4. Implement a method called getBook that accepts a book's id as a parameter, finds that book in the internal books array, and returns the book object.

  5. Implement another method called removeBook that also accepts a book's id as a parameter, but removes the book from the internal books array.

    There are multiple ways to do this -- you can use a normal loop and splice, or there's a more elegant (arguably) approach using filter.

More Practice: Shopping Cart

  1. Implement a function called makeCart that maintains an internal variable cart that will hold books added to it. makeCart should return an object, the methods for which we will implement in subsequent exercises.

    Finally, create a cart object in the global scope using makeCart (similarly to how you did with makeStore).

  2. Add a method to the object returned by makeCart called addBook, that accepts a book as a parameter and adds that book to the internal cart variable.

  3. Add another method called display that displays the contents of the cart. You may want to consider re-using the displayBooks logic from makeStore, or implement new functionality that displays only information relevant in the context of shopping carts -- namely, the book's title, price and id.

  4. Add another method called removeBook that, given a book's id, removes that book from the cart.

  5. Search your bookStore for a book or two, and add them to the cart using the methods you've created. Try removing some books too to verify that it works.

  6. Implement a total method that takes no arguments and returns the total price of all the books in the cart (use reduce for this).

  7. Finally, implement a checkout method that accepts the store on your cart that does the following:

    • Accepts the bookStore as a parameter
    • Removes each book in the cart from the store (since you've now bought it)
    • Clears the cart
    • Returns all the book objects in the cart.

Advanced: Integrate a Bank Account

Using the bank account example from yesterday, modify the checkout method in the shopping cart to also accept a bank account object as a parameter. The checkout method should only succeed if there are sufficient funds in the account, and if it should succeed, the total of the books should be deducted from the bank account.