Shopping Lists

New solo assignment for Fall 2021. Now ready for you.

This assignment is intended to prepare you for the final project. It has several parts:

  • some important data-structure work in it
  • forms and related event handling
  • delegated event handlers, and
  • and working with localStorage and cloud storage using Ajax

Idea

The basic idea is that you will be able to create one or more "shopping lists". We will only create one shopping list (groceries), but you'll use classes for the underlying data structure so that, in principle, we could have multiple shopping lists (groceries, clothing, gifts, office supplies, etc.).

You can play with my obfuscated solution

There are several sections to that page:

  • The top section is the grocery list. The UI:
    • enter the name of an item, (such as "milk") into the form and hit return/enter or click the "add item" button. That adds the item to the OL list, which is initially empty.
    • click on an item to delete it from the list
  • The second section is for local storage: saving to localStorage, loading previously saved values, and clearing it. My solution saves to the key "scott_groceries", but that's based on a global variable, so you can change it. See below.
  • The third section is for cloud storage: logging in, saving, loading previously saved data and clearing. You'll need to create an account.

The data is saved to the LOCAL_STORAGE_KEY, which is set like this:

var LOCAL_STORAGE_KEY = 'scott_groceries';

Feel free to change that variable to a different value when saving/loading.

Using the App

You can run the app through its paces with the following:

  • add a bunch of items using the form
  • delete a few by clicking on them

Next,

  • save it to local storage
  • check in the Dev Tools to see that the value has been saved
  • shift-reload the page, to see that the page is cleared
  • load from local storage; should look like before

Lastly,

  • login to the cloud (after you make an account)
  • save to the cloud
  • shift-reload the page
  • login again
  • load from the cloud

You can also try the last steps from another browser or even a different device.

Cloud Account

Both for playing with my solution and for creating your own, you'll need an account on the cloud storage server. You can create one here:

cloud storage server

Fill out the "join" form in the usual way and submit it. Once you've done that, you can also try the "login" form.

Once you have an account, you can use it to login to my solution and, of course, in creating your own solution.

Getting Started

You're welcome to use some of my files, just not the obfuscated .js files. The following will create an assigment folder and copy my HTML file, which loads the two supplied files, supplied.js and test-Bag.js.

cd ~/public_html/cs204-assignments
mkdir shopping
cp ~cs204/pub/assignments/shopping/solution/shopping.html shopping/

That file loads several .js files (look at the bottom). You'll implement two:

  • Bag.js that defines a class for your data structure, and
  • main.js that creates an instance of the Bag class for holding groceries (stored in a global variable) and sets up the UI.

What You Must Do

As mentioned in the introduction, the work breaks down into the following pieces:

  • some important data-structure work in it
  • forms and related event handling
  • delegated event handlers, and
  • and working with localStorage and cloud storage using Ajax

Let's describe these in turn.

Bags

The items in a shopping list are stored in a "bag", each of which is an instance of a Bag class that you will define. It has the following constructor and methods:

  • the constructor takes no arguments and initializes an empty shopping bag. There are two instance variables:
    • a counter, used to create item ids (see next section) that is initialized to 100.
    • a list of items, initialized to an empty array
  • getAll() returns the whole list
  • get(key) returns the item with that key. The value is a JS object literal with properties val which is the item and key which is its key.
  • add(value) adds an item to the bag. The item will be a string, like "milk". The method generates an id for the item, like "key101", and stores the key/value pair into the list. Returns the key. Note that if "milk" is added twice, each will have its own key.
  • remove(key) removes the item with the given key.
  • size() returns the number of items currently in the bag.
  • load(dict) given a JS object literal containing values for the counter and the bag contents, replaces the instance variables in this object with those. This method allows you to load the bag from some previously saved value (either localstorage or cloud).
  • print() nicely prints the bag, saying the number of items and then the index, key and value of each item. Like this:
>> groceries.print();
bag contains 4 items
0 :  key101 :  apples
1 :  key103 :  chocolate
2 :  key104 :  dates
3 :  key105 :  eggs

You can create multiple instances when testing and debugging, but your main.js file will create one for storing grocery items.

Testing Bag Objects

I've supplied some code to help you test your Bag objects. See the two functions in test-Bag.js.

Item IDs

Note that an item's key is not the same as its location in the array. In the final project, we'll use sorting, so items will move around in the array, but their key (ID) will never change. The counter will help you generate keys.

Furthermore, the counter will increase when you add an item, but it will never decrease. So if you add "milk" 6 times, delete all 6, and add another one (we go through a lot of milk in my house), the 7th one will have a different key from all the preceding ones. Keys are never reused.

This will prepare us for the sorting in the final project.

UI

Once you have some of the methods for bags implemented, you can start setting up your UI. There are two aspects:

Adding an Item Set up an event handler for the form that adds an item to the groceries bag and adds an LI to the UL for the existing ul#groceries DOM element, which is initially empty.

Deleting an Item Set up a delegated event handler so that clicking on a dynamic LI descendant of UL#groceries deletes that item from the bag and from the page.

Note that deleting an item requires its key, so I suggest to store the id in the DOM, possibly as a data- attribute. That means adding an item will need both the value and the key, and the event handler for deleting an item will need to extract its key from the DOM element in order to delete the item from the bag.

Adding an item can be modularized with helper functions, though the implementation is up to you. In my implementation, I had a core addItem helper function that took the necessary information (the item text and its key) and created the li element and added it to the page. I also had another helper function, addItemFromForm that extracted the item text from the form, added it to the groceries bag (getting the key as a return value), and used the addItem function to add the item to the page. With those in place, the event handler was straightforward. I was also able to use addItem when loading data from either localStorage or the cloud.

Local Storage

Once your groceries list and UI is working, you can start saving and loading.

Set up event handlers for the three buttons:

  • save saves the contents of the grocery bag to localStorage. Use your own name for the key, as I did with mine. There's a trick that makes this pretty easy, described just below.
  • load loads the grocery bag from a value previously saved to localStorage. If there isn't any, it should set the groceries back to its "new" state. (Counter of 100, empty bag.) It also empties out the li elements from the page and replaces them with the ones from the saved data.
  • clear deletes any previously saved value. That's useful if you mess up with saving. You might implement this first.

For saving the value, it's helpful to remember that JSON.stringify will turn a JS data structure into a string, including a JS object (in the sense of a dictionary). So your bag object can be turned into a JS object:

var bag1 = new Bag();
var str1 = JSON.stringify(bag1);

It will lose all of its methods and such, but the instance variables will be stringified.

When you load saved values, you'll need to set the state of the global variable that holds our bag of groceries, as well as resetting the UI. To reset the UI, empty out any existing list items, if any, and re-create items for all the ones in the grocery bag. The jQuery empty method can be helpful here.

Ajax Storage

Once you've done that, you're ready for saving and loading from the cloud. These are button event handlers, very similar to the ones you implemented for saving and loading from local storage, except that you first have to login.

In the supplied.js file, you'll find some code I've given you to help with login. You are welcome to use it.

Note that once you have logged in, the global variable USER_URL is modified to the base URL that you'll need for your cloud storage Ajax interactions. For your Ajax interactions, you'll need that, plus the key that you are storing under (analogous to the key you used in localStorage).

Load: To load from the cloud, you'll use an Ajax GET request. The URL will be the USER_URL above, plus a slash, plus the key under which you are storing. You'll have to pick a key that you'll store your grocery list under. So, the code will be something like:

$.get(USER_URL + '/' + KEY, function (resp) { ... });

The response will be null if there's nothing previously stored, or a JSON object/dictionary where the .value is the stored value. Your code will:

  • initialize the contents of the bag, and
  • restore the UL, removing any existing stuff and putting all the new ones on there.

save: To save to the cloud, you'll use an Ajax PUT request. The URL will be the same as for GET. The data will be similar to GET: a JS object literal with the value in it:

 $.ajax(USER_URL + '/' + KEY,
        {method: 'PUT',
         data: {'value': some_value},
         success: function (resp) { console.log(resp); other_code }});

clear: to clear the cloud data, you'll use an Ajax DELETE request. The key is the same as for GET and PUT, and there is no data.

 $.ajax(USER_URL + '/' + KEY,
        {method: 'DELETE',
         success: function () { some_code }});

Most of your work will go into the callback for the load.

Hints and FAQ

  • To find the location (index) of something in an array, you can use findIndex
  • To remove something from an array given its index, you can use splice
  • To remove all descendants of an existing DOM element, the jQuery empty method is helpful.

How to turn this in

Do all your work in a new folder called shopping; turn in a Gradescope item.

Tutors/Graders

You can use the password to view the solution

Time and Work

The following link has been updated for Fall 2023.

Finally, when you have completed the assignment, make sure you fill out the Time and Work Fall 2023 That report is required.