Packing List Web App
Filed under: Projects — posted by Rob on July 26, 2012
When I go to the beach or Atlantic City or Florida or basically any trip, I noticed that I almost always need to pack the same things. I have been perfecting a checklist of things to pack over the past several years, and now I use it whenever I am packing to make sure I don’t forget anything useful. Right now it exists as a Google Tasks checklist. Before I start packing for a trip, I go through the checklist and make sure that everything I want to bring is unchecked, while everything I don’t need is checked (so I effectively ignore it). See the below screenshot for an example. Then, during the course of packing, I check the boxes next to each item I pack. When there are no unchecked boxes remaining, I know I’m good to go for the trip.
The problem with this method is that on the way back from the trip I don’t know which items I packed and which ones I decided to not pack (because everything will be checked at the end, but some of those were never unchecked to begin with). Therefore, I am never sure if I remembered to re-pack everything on the way home.
Designing a Solution
I decided I wanted this to be more robust than just a copy of Google Tasks with the ability to mark things are ‘packed’ or ‘left at home’. The key feature I wanted is the ability to create a “List” for each category of items — e.g. Beach Stuff, Electronics, Toiletries, etc. — and then the ability to pull those lists’ contents into a “Trip” (I am quoting and capitalizing List and Trip because they will later be entities in my data model, so I want the meaning behind them to be clear). The idea is that if I am going on a Beach Vacation “Trip”, I’ll certainly want to include the contents of the Beach Stuff “List”, but wouldn’t want to include the contents of my Winter Clothing “List”. Similarly, for an Overnight “Trip”, I will want to include items from my Bedding “List”. This concept of breaking items into category-specific Lists will allow me to break my master packing list into several re-usable chunks that I can include or exclude at will for each type of Trip I go on.
As an aside, I original thought of making both Trips and Lists the same entity. This same entity could contain either items or links to another entity (which in turn would have items and links of its own). The problem with this approach is that you can introduce cycles by linking in a circle; a more important issue is that querying all the items in a list would require a recursive SQL query — something not even possible in MySQL. I decided that allow a single level of linking worked for my use case, and this allowed me to keep Trips and Lists separate — Lists only contain items (like “toothbrush”, or “Kindle”) and Trips only contain links to Lists.
There was an extra level that I wanted. The whole purpose of this solution is to allow me to mark items as “Should Pack”, “Packed”, and “Repacked” (in contrast to my Google Tasks system which only allowed “Packed/Left at Home” with no distinction between them). This would be done each time I go on a trip. Did I want to have to reset the data for a given Trip each time I go on a new trip, or would I prefer to save a history of all my past trips? I decided that since I was building a whole app for this, I might as well add the feature of historical packing lists. This introduced an important requirement for a new entity type. The program would need to allow creating “Instances” of “Trips”. Let’s say today I go on a Beach Vacation “Trip” to Ocean City, NJ — this “Trip” may reference the “Beach Stuff” list and “Toiletries” list. I would want to create a copy of the Beach Vacation Trip and then within that copy is where I would work with the items and mark things as packed/repacked/etc. I could make an Instance work by referencing all the list items (referencing used here in a database foreign-key sense), but this would defeat the purpose of maintaining a history. As soon as someone modifies the item, deletes the item, or deletes the list containing it (deleting it through cascading), it would break the Instance. Therefore, I decided that an Instance should contain a copy of every List item referenced by the Trip that is being instanced. Think of it as a snapshot of a Trip, resolved to its underlying List items, at a given point in time.
This design led to the following DB schema (diagram made using MySQL Workbench):
This design led to the following implementation requirements:
- Mobile accessibility (e.g. iPhone)
- CRUD for packing “Lists” and “List Items”
- CRUD for packing “Trips” and “Trip Links”
- CRUD for packing “Instances” — application-level logic will be needed to copy underlying List items into Instance
- Interface for working with Instances to mark items as Should Pack, Packed, and Repacked
I’ve noticed lately that I’ve fallen into what Maslow calls the “law of the instrument“, or more famously the “golden hammer” concept. In my case, the golden hammer has been WordPress. I’ve been using it as if it’s a PHP framework, using it whenever I need to do database work or user authentication in a web app. While I certainly could have developed this Packing List program using WP as the backend for DB work, it just isn’t a good fit. The strong relational nature of this program pointed to the need for a dedicated database, not piggy-backing on WP’s user metadata model (as I did in my Flashcard JS app). I decided it was about time I learned a real PHP framework. I really wanted to like Symfony2 and went through a tutorial on it, but it just didn’t click fast enough for me. I then found CodeIgniter, which felt perfect to me given my Rails experience. Although the video tutorials on its main page are well-done, they’re out-dated for the 2.0 version which led to some early confusion. However, I soon got the hang of everything — again, it was so similar to Rails that my mind was already wired to do this kind of development. I learned later that there were some nice CRUD generation utilities for CodeIgniter, but I am glad that I did everything manually — it helped me learn CodeIgniter better.
There wasn’t much to the backend coding. It’s your usual CRUD coding work, most of it pretty tedious creation of forms and DB inserts. CodeIgniter made form validation ridiculously easy, though — probably better than any other framework I’ve ever used. The ActiveRecord database class also made insertions dead easy; you can just write something like
$this->db->insert('list', $_POST) and it “just works”, assuming the form that was POSTed contained all the non-nullable fields for the database table, with the “name” attribute of the
<input> elements equal to the database columns — which it already is in almost any application.
The authentication work was done very simply. This is a single-user app, so a password was all that was needed. It’s just a password field which, if valid, stores a flag into the User Session. The main controller of the program redirects to the Login screen if that flag is not present in the session. Couldn’t be easier.
Most of the work was done on the frontend work for the Instance View page. This is where the user marks items as Should Pack, Packed, or Repacked. Since conceptually something cannot be packed unless it Should be Packed (i.e. “Should Pack”=true), and an item cannot be Repacked without having already been Packed (i.e. “Packed”=true), I represented this with a 3-column view. You have a column for Should Pack, containing all items in the instance. Then you have a column for Packed, containing all items marked under “Should Pack”. Finally, you have a column for Repacked that contains all items marked under “Packed”. Some jQuery fires AJAX calls to the backend controller to save the information in the DB, while also updating the interface with a nice effect. Something I found remarkable is that the only backend code needed to power the entire Instance View page’s AJAX stuff is this one line:
$this->db->update('instance_item', $_POST, array('item_id'=>$_POST['item_id']));
This was my first CodeIgniter project, and I think it went quite well. The one glaring problem with my implementation is that I did not use Models at all; I put all the DB code directly into the Controller, which is obviously not the best practice, but I was in a rush. I will likely fix that, as well as turn this into a multi-user application (and potentially make it publicly available) in the near future, especially if I find it useful.
You can check out a video of the final product being used below (video captured with CamStudio):
You can download the source code by clicking here.