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:
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 aclass
for your data structure, andmain.js
that creates an instance of theBag
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 listget(key)
returns the item with that key. The value is a JS object literal with propertiesval
which is the item andkey
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 tolocalStorage
. 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 tolocalStorage
. If there isn't any, it should set the groceries back to its "new" state. (Counter of 100, empty bag.) It also empties out theli
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
Finally, when you have completed the assignment, make sure you fill out the Time and Work report. That report is required.