Vous êtes sur la page 1sur 125

Creating a Web App from Scratch Part 1 of 8: Basic Idea and Design

PUBLISHED NOVEMBER 23, 2009 BY CHRIS COYIER

Today we begin Part 1 of an 8-Part series on building a web application from absolute scratch to a complete product. I am going to kick things off by introducing the idea, and then I will be handling the design, UI, and general front-end stuff. We are going to be going back and forth from here over to my friend Jason Lengstorf's site Copter Labs. Jason will be handling the back-end stuff like application planning and database stuff. At the end of the week, we'll unleash the actual working application for you. Here is the plan:
ALL SERIES NAVIGATION

Part 1 - Planning the App: Basic Idea and Design Part 2 - Planning the App: Database Architecture and Development Approach Part 3 - Designing the App: Workflow Map and Photoshop Design Part 4 - Designing the App: HTML and CSS Part 5 - Developing the App: User Interaction Part 6 - Developing the App: Adding AJAX Interactivity Part 7 - Developing the App: List Interaction Part 8 - Security & The Future

And finally.... the application!

Colored Lists

It's Easy, Right?


What we're going to create is a "list app". The idea being focused on simplicity and usefulness. Sign up for an account, and get started making a list in just a few seconds. Sounds easy right? Even the PHP dabblers out there probably could throw something like this together fairly quickly, right? Well the fact is, no, it's not that easy. First of all, it needs to work and it needs to work well. That means good back end code that does what it's supposed to do and well. That means a good UI that is intuitive, helpful, and pleasurable to use. It means keeping the app secure and users data private. None of these things is trivial. Through this whole 8-part series, we are going to create an app that hopefully does all these things pretty well. We aren't out to tell you this is the greatest app ever made, but rather, we are going to use this app as a walk-through journey of the app creating process and hopefully do as many smart things as we can along the way.

The Big Idea


This "list app" is going to be called Colored Lists. Lists (in real life), can be for anything: a to-do list, a grocery list, things to bring camping list... As you finish things, you cross them off. Things on a list may be of different relative importance as well. This makes paper lists potentially messy and inefficient. With a list on a computer, we can make crossing off items just a click and we can make rearranging them a matter of drag and drop. For dealing with relative importance, we can use colorization, which could also be used for things like grouping. Computers, and the web, are a perfect place for lists.

Sketch It Out
No need to get fancy right away. Here is a very rudimentary sketch of what the app might look like:

Looks like a list to me. Each list item is a long rectangle, because the big idea here is to colorize each list item, so putting them inside a colored box makes sense. There are some interactive elements to the left and right of each list item. Those are going to be for accomplishing the basic things we intent people to be able to do with their colored list. Lets take a closer look.

Early UI Planning

We don't necessarily want to be talking about specific technologies at this point, but we should be thinking about how the UI will operate, so we can make choices about technology that can accommodate our UI desires.

Click-to-edit Drag and drop Two-click delete Automatic saving (after any action)

All this stuff basically adds up to a whole bunch of AJAX. We don't want to load special screens to do relatively trivial tasks like deleting a list item. That stuff should happen seamlessly, smoothly and with proper feedback in response to mouse clicks without page refreshes. In a sense, we are creating a one-page app, where the majority of interaction with this app happens on a single page. This is certainly by design, and not trying to adhere to any particular fad. Lists are easy and quick, that's why are useful. If this app is complicated, it's usefulness is diminished and nobody will use it.

The Screens
Just doing some quick brainstorming of the idea so far, we can come up with quite a number of "screens", or states the application can be in.

Homepage Logged out = Intro/Sales Page Logged in = Your list Log in page

Settings page Lost password page Account activation page Emails Yep, even emails should be considered a part of the "screens", as they are a vital part of the process and interaction with an app.

"Features"
People love "features". Things that your app has that other apps don't have, or that yours does better. This is just as much for marketing as it is for your actual product. All the fancy AJAX this app will have is certainly a feature, but that stuff these days is getting more and more expected rather than a feature. The one feature that we will focus on with this app is "public sharing". Each list will have a unique URL that can be publicly shared. A visitor visiting this URL can see the list in it's exact current state, but not interact with it as far as editing/adding/deleting.

Where Were At

Up to this point, weve planned the way our app is going to look, as well as given ourselves a basic idea of how the app is going to function. The next step is to figure out whats going to happen behind the scenes to allow our app to work the way weve planned.
Okay, So We Know Ho w It Looks, but How Does It Work?

In order to keep a list available after a user logs out of our app, well need to store list information in a database. And, of course, to access that database were going to need some kind of server-side scripting language. For this app, we made the choice to go with a combination of MySQL and PHP to handle all our behind-the-scenes data handling and storage.
Data StorageP lanning and Database Structure

Our first step is to decide how we want to organize list data. Since this app is fairly simple, well only need three tables in our database. The first table will store user information, and the second will store list information. The third table will keep track of list items. Creating the Database Of course, before we can create our tables, well need a database to work with. For anyone working along at home, well be operating under the assumption that youre building and testing locally (we recommend XAMPP). Navigate to http://localhost/phpmyadmin and open the SQL tab. You can use the GUI if you want, but were going to use raw SQL commands for learning purposes. The database will be named cl_db , which is all the information that is required to build the database. However, we want to make sure that our users can use characters from any language

in their lists, so its also a good idea to specify the collation and character set of the database. Well be using the UTF-8 character set with general collation, which supports multilingual characters and is case-insensitive. The command to create this database is:
CREATE DATABASE `cl_db`

DEFAULT CHARACTER SET utf8

COLLATE utf8_general_ci;

Execute this command from the SQL tab in phpMyAdmin and the new database will become available. Now that weve got a database, were ready to build our tables. Table 1: User Information Using our list app doesnt require a high security clearance; all we need to know is that youve got an email address and that its real. To determine that an email address is real, well be sending new users a confirmation link in an email, which they need to follow before using our app. This means we need to have a unique confirmation link and a place to store whether or not an account has been verified. Of course, we also need to store the users email address, and in the interest of keeping redundant data storage to a minimum, well assign each user a unique numeric identifier. The MySQL command to build this table will look like this:
CREATE TABLE cl_db.users ( UserID Username Password ver_code verified ) INT PRIMARY KEY AUTO_INCREMENT, VARCHAR(150) NOT NULL, VARCHAR(150), VARCHAR(150), TINYINT DEFAULT 0

Table 2: List Information List information is fairly straightforward. Each list will have a unique identifier, a unique URL, and the identifier of the user that owns the list. This helps us limit the amount of redundant information that needs to be stored. To build this table, execute the following MySQL command in phpMyAdmins SQL tab:
CREATE TABLE cl_db.lists ( ListID INT PRIMARY KEY AUTO_INCREMENT,

UserID ListURL )

INT NOT NULL, VARCHAR(150)

Table 3: List Items Finally, we need a table that will store our list items. Each list item needs a unique identifier, the ID of the list it belongs to, and the information the user enters as his or her list item. Also, to support features well be adding later on, we also need tokeep a record of the items position and color. Execute this command in the SQL tab of phpMyAdmin:
CREATE TABLE cl_db.list_items

ListItemID

INT PRIMARY KEY AUTO_INCREMENT,

ListID

INT NOT NULL,

ListText

VARCHAR(150),

ListItemDone

INT NOT NULL,

ListItemPosition INT NOT NULL,

ListItemColor

INT NOT NULL

NOTE: The ListItemDone field was omitted in the original post of this article. It was added here after being pointed out in the comments by FuSi0N.

The database with our three tables

Now we have our database and the three tables well need to build our app. Next, well plan how were going to create and access our database information using PHP.
Data HandlingPlanning and Script Organization

Before we start coding, its always a good idea to take a moment and map out everything that needs to be done. That way, we can group tasks into logical arrangements. Because great code starts with great organization, well be using an object-oriented approach. Planning our PHP Classes Object-oriented programming provides an easy way to keep related functions grouped together. After learning object-oriented programming, it becomes an incredibly powerful tool that increases portability, readability, and usability of scripts. Our app is pretty simple, so well only need two classes. The first class is going to handle user interactions, such as registering, updating information, and logging in and out. The second class will handle list interactions, such as adding, deleting, and moving list items. User Actions Class Our first class, which well name ColoredListsUsers , needs to handle all the actions our app will perform that are user account-related. Again, this is a pretty simple application, so when we map out everything that users can do with their account, we end up with pretty short list:

Create an account Verify the account Update the account email address Update the account password Retrieve a forgotten password Delete the account

In addition to those methods, well also need some support methods, such as one that will send a verification email. Well define these methods as we build the app in later installments of this series. List Actions Class The list actions class, which well call ColoredListsItems , also has a pretty short list of methods. This class will handle everything else our app does, which is anything a user can do with his or her list items. The list of available actions ends up looking like this:

Create a list item

Update a list item Delete a list item Change a list items position Change a list items color

Action Handling Scripts Finally, well need a couple action-handling scripts. These will determine what the users desired action is, create an instance of the proper object, and call the correct method. As we build our app, well go into more detail on how these scripts will work.
Moving On

In our next installment of this series, well create the application workflow. Make sure youre subscribed to CSS-Tricks so you dont miss out
Developing a Workflow

We have a great start going on our list application at this point. The big idea is in place, we know how we want the lists to be displayed and interacted with, and we have some back-end structure in place to deal with users and all the data that goes along with these lists. It was a good idea to start with the meat of the app, but there is a little bit more that goes into a full application. Because we have users, that means we need a sign up form and a log in area for returning users. Because users can be forgetful, we need a Lost Password feature. Because users should be just as concerned about security as we are, users need to be able to change their passwords, change their login, and delete their accounts. Our one-page app has just turned into a four or five page app, so were going to need to think about some workflow. There will be two different states for the homepage: logged in and logged out. While logged out, people need a way to sign in and to register, and this will be essentially the sales page too, explaining the app. Logged in, the homepage will be the users list itself. Logged in users will also need to do some ancillary stuff related to their account, like change their email, change their password, and delete their account, as well as a way to log out. These ancillary options are probably best served on an account page. So now we are looking at at least two new pages: Account Settings and Registration. Here is some flow:

Basic app workflow Its not pretty folks, but thats what sketching is. Its fast and its just to help you think and plan for the things you need.
Bringing It to Life Photoshop

Our developer is already ahead of us, thinking about the data they need and how this app is going to actually work. So wed better get started actually designing here. Homepage (Logged In) This is the meat of our application so lets start here. The list is obviously the most important thing, so lets keep the header small and keep the list front and center. List items are big colored blocks with buttons for their associated actions nearby.Below the list a box for entering new list items.

The home page as it appears when logged in Homepage (Logged Out) When logged out, the homepage is going to act more like a sales page. Not that we plan to charge for it, but just to explain and get people interested in using it. There isnt much to say about a list app, so well keep it simple.

When logged out, well encourage the visitor to sign up Small Bits Weve been designing long enough to know we might as well make the little buttons into a separate file and keep them together as a sprite (a sprite is multiple images combined into one to save HTTP requests, in our case, also the rollover states). So well do that and throw together a favicon while were at it.

All the list item tabs

Favicon

Registration Our intention with registration is going to be extremely simple. Were going to ask for a users email and thats it. They will be sent an email with a link in it to complete registration. The link in that email will activate their account and they can choose the password at that time. So, our registration page can be pretty darn simple.

The registration form As small as this is, this registration page sets the stage for other forms. We have a label/input pair here that can be used for any input/pair in any of our sites forms. Account Well use the same form design as the registration page here. Its not cheating or being lazy, its good design through consistency!

Account controls Buttons Notice the change in buttons. They are now big, orange and rounded. Much more button-like dont you think? Again for consistency, lets make this the default for all buttons across the site.

Site buttons, looking button-like

Moving on

The developer now has plenty to go on to start fleshing out the user interactions for the site. And we have plenty to go on to start getting the HTML and CSS for all this ready, and ultimately to AJAX this puppy up and get it working.
It's time to get our hands dirty with some markup!
We know we have a couple different pages to deal with here. The main page of course, which acts as both our list page and sales page depending on login status. But then we have sign in and sign up pages and account pages. So let's be smart and work modularity. That means we'll make files like "header.php" and "close.php" that we can include on multiple pages so we don't have to repeat common code (e.g. the DOCTYPE, analytics code, and ubiquitous things like that.
ALL SERIES NAVIGATION

Part 1 - Planning the App: Basic Idea and Design Part 2 - Planning the App: Database Architecture and Development Approach Part 3 - Designing the App: Workflow Map and Photoshop Design Part 4 - Designing the App: HTML and CSS Part 5 - Developing the App: User Interaction Part 6 - Developing the App: Adding AJAX Interactivity Part 7 - Developing the App: List Interaction Part 8 - Security & The Future

And finally.... the application!

Colored Lists

Web Root Organization


This is what we have for files at the root of our web directory so far. All the major views have their own PHP files. We have subdirectories for images and "common" files. and we have a few loose files like CSS and the favicon.

Our developer will surely be adding more files. He's going to need PHP files for interacting with the database and doing all the list interactions.

Header
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml">

<head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

<title>Colored Lists | <!-- Do Something Smart Here --></title>

<link rel="stylesheet" href="style.css" type="text/css" /> <link rel="shortcut icon" type="image/x-icon" href="http://cdn.csstricks.com/favicon.ico" />

<script type='text/javascript' src='http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js?ver=1.3. 2'></script> </head>

<body>

<div id="page-wrap">

<div id="header">

<h1><a href="/">Colored Lists</a></h1>

<div id="control">

<!-- IF LOGGED IN --> <p><a href="/logout.php" class="button">Log out</a> <a href="/account.php" class="button">Your Account</a></p>

<!-- IF LOGGED OUT --> <p><a class="button" href="/signup.php">Sign up</a> &nbsp; <a class="button" href="/login.php">Log in</a></p> <!-- END OF IF STATEMENT -->

</div>

</div>
Right away in the header we've run across a few things where we need to be smart and leave notes for the developer, but give him the things he needs. In the page title, we've left a note to do something smart there. Different pages need differnet page titles, so clearly something dynamic needs to happen there. Then with our control buttons (e.g. Account / Logout) those buttons will be different depending on the logged in state of the user. So we'll just let the developer jump in there and make those things function correctly. So at this point we have the top of a page. We are leaving the body, html, and #page-wrap elements open, as beyond that is the main content of the page. Before we get into that main content, let's toss in the sidebar and footer areas so we have a complete skin.

Footer
Our design doesn't call for much of a footer, so we'll just close up those open elements and add a note to put analytics here.

</div>

<!-- Analytics here -->

</body>

</html>

Sidebar
Our design calls for a bit of a sidebar. Right now, all we'll use it for is a few notes on using the application. But it's nice to have some open room for content, as it's extremely likely that room will be needed for additional things as the app grows.

<div id="ribbon">

Reminders

<ul> <li>Your list automatically saves</li> <li>Double-click list items to edit them</li> </ul>

</div>

Main Page
Now that we have our "modules" complete, let's dig into a real page. The template for building any page will be like this:

<?php include_once "common/header.php"; ?>

<div id="main">

<noscript>This site just doesn't work, period, without JavaScript</noscript>

<!-- IF LOGGED IN -->

<!-- Content here -->

<!-- IF LOGGED OUT -->

<!-- Alternate content here -->

</div>

<?php include_once "common/sidebar.php"; ?>

<?php include_once "common/footer.php"; ?>

Logged in (The List)


<ul id="list"> <li class="colorRed"> <span>Walk the dog</span> <div class="draggertab tab"></div> <div class="colortab tab"></div> <div class="deletetab tab"></div>

<div class="donetab tab"></div> </li>

<li class="colorBlue"> <span>Pick up dry cleaning</span> <div class="draggertab tab"></div> <div class="colortab tab"></div> <div class="deletetab tab"></div> <div class="donetab tab"></div> </li>

<li class="colorGreen"> <span>Milk</span> <div class="draggertab tab"></div> <div class="colortab tab"></div> <div class="deletetab tab"></div> <div class="donetab tab"></div> </li> </ul>
The list itself will just be a regular ol' unordered list. We'll use CSS class names for the colors. But then we need a bunch of controls for the list items. That's what all those divs are inside the list items. There are empty divs for dragging, changing color, deleting, and checking off list items. We need these for the CSS so we can target them and style them. We're smart designers though, we know this markup is merely temporary. These lists will be dynamically generated by the application. Just looking at all those empty control divs; we know that those are probably automatically generated by the JavaScript. That's fine, we need the HTML in there now to set the stage and have everyone on the same page. Why the spans inside the list items? Just being smart. Because the list items wrap more than just the text, it's likely we'll need some kind of hook to target just the text later on. Now we need to get an input on this page for adding new list items. Our developer will be all over this, but we'll put the basics in so we can style them.

<form action="" id="add-new">

<div> <input type="text" id="new-list-item-text" name="new-list-item-text" />

<input type="submit" id="add-new-submit" value="Add" class="button" />

</div>

</form>
Then one of our applications features is having sharable public URL's for our lists. Let's put that in here too.

<div id="share-area"> <p>Public list URL: <a href="#">URL GOES HERE</a> <small>(Nobody but YOU will be able to edit this list)</small></p> </div>
Ahhh, more work for the developer! But he's ready for it. This public URL business leads us into another possible scenario. We need this main page to be capable of displaying a list without showing the input form or all the list controls. Basically you can just look at the list but not interact with it. (Like if you wanted to send your mom your Christmas list!)

Logged out (Public list)


<ul id="list"> <li class="colorRed"> <span>Walk the dog</span> </li>

<li class="colorBlue"> <span>Pick up dry cleaning</span> </li>

<li class="colorGreen"> <span>Milk</span> </li> </ul>


This will be exactly the same as the list above, only no control tabs, no form to add new items, and no public URL area (hey, they are already here, what do they need the URL for). We know this this probably will just be a change in how the backend code outputs the list. But whatever, if we create this, everybody is on the same page.

Logged out (Sales)


We might do something fancy someday for this, but for now, our big idea is just a cool graphic showing that this area is potentially where your new list will be and a big ol' arrow showing people where they can sign up.

<img src="http://cdn.css-tricks.com/images/newlist.jpg" alt="Your new list here!" />

Account Page
As a quick reminder, we us this structure for all pages, including this one.

<?php include_once "common/header.php"; ?>

<div id="main">

<!-- IF LOGGED IN -->

<!-- Content here -->

<!-- IF LOGGED OUT -->

<!-- Alternate content here -->

</div>

<?php

include_once "common/sidebar.php";

include_once "common/footer.php";

?>
That's the beauty of working modularly, all common content is included so updates down the line are much easier.

The account page is going to have several forms on it: one for updating email, one for updating password, and a button for users to delete their accounts. Again, our developer will be all over these forms filling them up with hidden inputs that pass along data and adding in action URLs and methods and all that. We'll leave that to him, but this gives us enough to style.

<h2>Your Account</h2>

<form action=""> <div> <input type="text" name="username" id="username" /> <label for="username">Change Email Address</label>

<input type="submit" name="change-email-submit" id="change-emailsubmit" value="Change Email" class="button" /> </div> </form>

<hr />

<h2>Change Password</h2>

<form action="#"> <div> <label for="password">New Password</label> <input type="password" name="r" id="repeat-new-password" />

<label for="password">Repeat New Password</label> <input type="submit" name="change-password-submit" id="change-passwordsubmit" value="Change Password" class="button" /> </div> </form>

<hr />

<form action="" id="delete-account-form"> <div> <input type="submit" name="delete-account-submit" id="delete-accountsubmit" value="Delete Account?" class="button" /> </div> </form>

Other "Form" Pages


Now that we've done the account page, we have pretty much covered all the bases for the other "form" style pages. Sign up, sign in, forgot your password, they are all just simpler versions of the account page. Since we'll have styled the basic label/input format, the header format, and the "button" format, the developer can easily create these pages himself copying the basic format and CSS classes from the account page.

The CSS
Reset
/* RESET */ * { margin: 0; padding: 0; } body { font: 14px/1.1 Helvetica, Sans-Serif; background: url(images/stripe.png) repeat-x; } .clear { clear: both; } img, a img { border: none; } input { outline: none; }
Just getting things cleaned up.

Structure
/* STRUCTURE */ body { font: 14px/1.1 Helvetica, Sans-Serif; background: url(images/stripe.png) repeat-x; } #page-wrap { width: 960px; margin: 6px auto 50px; position: relative; } hr { height: 1px; background: #ccc; clear: both; margin: 20px 0; border: none; display: block; }

Not too much complicated formatting for our little one-page app.

Typography
/* TYPOGRAPHY */ a { text-decoration: none; color: #900; border-bottom: 1px dotted #900; outline: none; } h1 { font: bold 36px Helvetica, Sans-Serif; margin: 0 0 8px 0; } h2 { margin: 0 0 10px 0; } p { margin: 0 0 6px 0; } .button { background: url(http://cdn.css-tricks.com/images/button-bg.png) repeat-x; -moz-border-radius: 5px; padding: 6px 12px; border: none; color: white; cursor: pointer; text-shadow: 0 1px 1px #666; -webkit-border-radius: 5px; -webkit-box-shadow: 0 1px 3px #999; -moz-box-shadow: 0 1px 3px #999; font: bold 16px Helvetica; } .button:hover { background-position: bottom left; } .red { background: red; color: white; font-size: 12px; padding: 3px; }
This isn't really a content-based app, so we don't have a whole heck of a lot of text formatting. However we do have page headers, links, and buttons, so we'll set those up here.

Header
/* HEADER */ #header { height: 68px; position: relative; } #header h1 { position: absolute; top: 0; left: 0; z-index: 2; text-indent: 9999px; overflow: hidden; } #header h1 a { display: block; text-indent: -9999px; width: 200px; height: 38px; border: none; background: url(http://cdn.csstricks.com/images/logo.png) no-repeat; } #control { width: 500px; float: right; padding: 10px 237px 0 0; text-align: right; }
Our little stripe header doesn't take much. Just a little CSS image replacement for the logo and placement of our control buttons.

Lists
/* LISTS */

#list { list-style: none; } #list li { position: relative; margin: 0 0 8px 0; padding: 0 0 0 70px; width: 607px; } #list li span { padding: 8px; -moz-border-radius: 5px; -webkit-border-radius: 5px; width: 589px; display: block; position: relative; } .colorBlue span { background: rgb(115, 184, 191); } .colorYellow span { background: rgb(255, 255, 255); } .colorRed span { background: rgb(187, 49, 47); color: white; } .colorGreen span { background: rgb(145, 191, 75); } .tab { background: url(images/minibuttons.png) no-repeat; height: 21px; top: 4px; } .draggertab { position: absolute; left: 0px; width: 31px; cursor: move; } .draggertab:hover { background-position: 0 -21px; } .colortab { position: absolute; left: 34px; width: 34px; background-position: -31px 0; cursor: pointer; } .colortab:hover { background-position: -31px -21px; } .deletetab { position: absolute; right: -35px; width: 15px; backgroundposition: -82px 0; cursor: pointer; } .deletetab:hover { background-position: -82px -21px; } .donetab { position: absolute; right: -17px; width: 16px; backgroundposition: -65px 0; cursor: pointer; } .donetab:hover { background-position: -65px -21px; } .crossout { position: absolute; top: 50%; left: 0; height: 1px; }

#share-area { margin: 20px 0 0 69px; width: 600px; }


A lot more stuff needed here. Here we'll set up how the lists look: the colors, the spacing, the rounded corners, etc. We'll also position all the little helper controls and give them appropriate backgrounds. Notice only a single image is used, minibuttons.png. A single CSS Sprite for mad efficiency!

Forms
/* FORM STUFF */ label { background: #999; color: white; padding: 3px; } input[type="text"], input[type="password"] { width: 324px; border: 3px solid #999; font-size: 18px; padding: 7px; display: block; }

#add-new input[type="text"] { width: 532px; float: left; margin: 0 10px 0 69px; } #add-new input[type="text"]:focus { border-color: #73B8BF; } #add-new input[type="submit"] { padding: 10px 12px; }

ul#list li span input[style] { width: 90% !important; }


Forms across our whole site will be the same, so we set that up here. The one exception is the "Add New" area on our lists, which is basically the same as any other input except bigger and is floated to the left to accommodate the "Add" button. Since we plan to use click-to-edit, the list items temporarily turn into text inputs when doing that, so we'll plan for that by shortening the length of them to accommodate for a "Save" button.

Messaging
/* MESSAGING */ .message { padding: 10px; margin: 0 0 10px 0; width: 607px; } .good { background: #9ff5b6; } .bad { color: #ef0040; }
We haven't talked too much about error messaging, but we can assume that because this is a web app, there will be some of it (for example, you enter in a wrong password, your passwords don't match, you have successfully done something, etc). We'll set up one class for messages in general and then classes for good and bad versions.

Sidebar
/* SIDEBAR */ #ribbon { position: absolute; right: 0; width: 125px; padding: 60px 30px 0 47px; height: 756px; top: -6px; background: url(http://cdn.csstricks.com/images/ribbon-bg.png) no-repeat; }

#ribbon ul { list-style: none; } #ribbon ul li { background: rgba(0,0,0,0.8); color: white; padding: 5px; margin: 0 0 5px 0; font-size: 12px; }
Just some simple stuff for our list of reminders.

Moving Along

Our developer now has plenty to work with to make this app functional. Once he's gotten a good start on that, we'll tackle AJAX and making all those list interactions happen almost like a desktop app.

Where Are We?

Now that we have a workflow put together and the HTML and CSS to make it look good, we can actually start building the classes that will run this puppy. Well focus this installment of the series on the users account interactions. These include:

Creating an Account Modifying Account Information Resetting a Lost Password Deleting an Account

Connecting to the Database

Before our class will be able to do much of anything, we need to connect to our database. To do this, well need to create a couple of small files. Defining Site-Wide Constants Our site wont require many constants, but in the interest of keeping them easy to maintain, well create a separate file to contain any information that is site-wide. This will be called constants.inc.php , and it will reside in a new folder called inc this folder will contain our PHP classes as well. Creating a constants file is a good idea for pieces of information that will be used often and in different scopes throughout a site. That way, if your database changes, youre able to change every database connection simply by swapping out the information in one file. Inside constants.inc.php , we want to define our database credentials. Since were starting out by developing locally, constants.inc.php will look like this:
<?php

// Database credentials

define('DB_HOST', 'localhost');

define('DB_USER', 'root');

define('DB_PASS', '');

define('DB_NAME', 'cl_db');

?>

As we develop, well add more to this file. Creating a PDO Object Next, we want to create a connection so that our application can communicate with our database. This file will reside in the common folder along with the header, footer, and sidebar files. This file will create a database connection using PDO (PHP Data Objects), as well as setting up a couple other site-wide features: error reporting and opening a session. The file will look like this when alls said and done:
<?php

// Set the error reporting level

error_reporting(E_ALL);

ini_set("display_errors", 1);

// Start a PHP session

session_start();

// Include site constants

include_once "inc/constants.inc.php";

// Create a database object

try {

$dsn = "mysql:host=".DB_HOST.";dbname=".DB_NAME;

$db = new PDO($dsn, DB_USER, DB_PASS);

} catch (PDOException $e) {

echo 'Connection failed: ' . $e->getMessage();

exit;

?>

Because were in the development stage, we want to see any and every error that occurs on the site. By setting error_reporting() to E_ALL and changing the display_errors directive to 1 using ini_set() , we ensure that even notices will be displayed, which will keep our code cleaner and more secure. Next, we use session_start() to start a PHP session. This will allow our users to stay logged in when we build that functionality later. Finally, we include config.inc.php and create a PDO object using the constants defined within it. Note the use of the try-catch statementthis gives us the ability to use Exceptions, which help improve error handling. In this case, if the database connection fails, were simply going to output the error message. Why PDO? The reason were using PDO for this project is because of its support for prepared statements, which virtually eliminates the risk of SQL injection. There are other options that allow prepared statements, such as the MySQLi extension. However, PDO is not database-specific, so migrating the app to Oracle or PostgreSQL wouldnt require a full rewrite of our code. Also, having used both MySQLi and PDO in projects, its just my personal preference to use PDO. Feel free to use whatever method of connecting to the database you prefer, but keep in mind that all database interactions in this exercise are assuming the use of PDO, and as such will probably require some reworking to accommodate your changes. Framing Out a User Interactions Class As we discussed in Part 2 of this series, well be taking the object-oriented approachwith this app. All of these actions will be contained within our ColoredListsUsers class. Well also need to create several files that will display information to the user and interact with the class, which well cover as we get to them.
Building the Class

To get started, we need to create the file well place in the inc folder.

class.users.inc.php

to contain the PHP class, which

With the file created, lets build the skeleton of the class:
<?php

/**

* Handles user interactions within the app

* PHP version 5

* @author Jason Lengstorf

* @author Chris Coyier

* @copyright 2009 Chris Coyier and Jason Lengstorf

* @license

http://www.opensource.org/licenses/mit-license.html

MIT License

*/

class ColoredListsUsers

?>

Connecting the Class to the Database Before our class can do much of anything, it needs to have access to the database object we created in base.php . Our database connection within the object will be stored in a private property called $_db , and this property will be set by the classconstructor, which will accept the instance of PDO created in base.php as an argument. If no instance of PDO is passed, one will be created by the constructor. This ends up looking like this:
class ColoredListsUsers

/**

* The database object

* @var object

*/

private $_db;

/**

* Checks for a database object and creates one if none is found

* @param object $db

* @return void

*/

public function __construct($db=NULL)

if(is_object($db))

$this->_db = $db;

else

$dsn = "mysql:host=".DB_HOST.";dbname=".DB_NAME;

$this->_db = new PDO($dsn, DB_USER, DB_PASS);

Now we are able to create an instance of our ColoredListsUsers object and use it to communicate with our database. Next, lets start building user interactions!
Creating an Account

First and foremost, a user needs to be able to create an account. This will give them access to the rest of the sites functionality. As it stands, when a user visits our app, theyre greeted with our sales page, which encourages them to click the Sign Up button in the top right of their screen:

The home screen of our app Clicking that Sign Up button directs the user to should probably be to build that page. Creating the Sign-Up Form In our apps root directory, create a file called inside:
<?php /signup.php

our first order of business

signup.php

and place the following code

include_once "common/base.php";

$pageTitle = "Register";

include_once "common/header.php";

if(!empty($_POST['username'])):

include_once "inc/class.users.inc.php";

$users = new ColoredListsUsers($db);

echo $users->createAccount();

else:

?>

<h2>Sign up</h2>

<form method="post" action="signup.php" id="registerform">

<div>

<label for="username">Email:</label>

<input type="text" name="username" id="username" /><br />

<input type="submit" name="register" id="register" value="Sign up" />

</div>

</form>

<?php

endif;

include_once 'common/close.php';

?>

To start, we include our common/base.php and common/header.php files. Also, notice that were declaring a variable called $pageTitle just before we include the header. Remember in Part 4 when we built the header file and left that comment in the title tag?
<title>Colored Lists | <!-- Do Something Smart Here --></title>

Were going to replace that with a snippet of PHP that reads:


<title>Colored Lists | <?php echo $pageTitle ?></title>

That gives us the opportunity to post a different title for each page of our app. With the proper files included, we can then create our sign-up form. The form will submit to signup.php itselfso we need to place an if-else check to see if the form has been submitted. If so, we create a new ColoredListsUsers object and call the createAccount() method (which well write in the next section). Finally, we close the look like this:
if-else

statement and include the footer. Our sign-up page should

The signup page. Notice the use of alternative syntax for the if-else statement. Normally, I dont like to use this format, but in the case of outputting HTML, I prefer the way it ends with endif; instead of a closing curly brace ( } ), which helps with readability in the script. Saving the Users Email Address With our sign-up form ready, we need to write the createAccount() method that will be called when a user submits the form. This method will be public. Lets go back to inc/class.users.inc.php and declare this method:
class ColoredListsUsers

// Class properties and other methods omitted to save space

/**

* Checks and inserts a new account email into the database

* @return string

a message indicating the action status

*/

public function createAccount()

$u = trim($_POST['username']);

$v = sha1(time());

$sql = "SELECT COUNT(Username) AS theCount

FROM users

WHERE Username=:email";

if($stmt = $this->_db->prepare($sql)) {

$stmt->bindParam(":email", $u, PDO::PARAM_STR);

$stmt->execute();

$row = $stmt->fetch();

if($row['theCount']!=0) {

return "<h2> Error </h2>"

. "<p> Sorry, that email is already in use. "

. "Please try again. </p>";

if(!$this->sendVerificationEmail($u, $v)) {

return "<h2> Error </h2>"

. "<p> There was an error sending your"

. " verification email. Please "

. "<a href="mailto:help@coloredlists.com">contact "

. "us</a> for support. We apologize for the "

. "inconvenience. </p>";

$stmt->closeCursor();

$sql = "INSERT INTO users(Username, ver_code)

VALUES(:email, :ver)";

if($stmt = $this->_db->prepare($sql)) {

$stmt->bindParam(":email", $u, PDO::PARAM_STR);

$stmt->bindParam(":ver", $v, PDO::PARAM_STR);

$stmt->execute();

$stmt->closeCursor();

$userID = $this->_db->lastInsertId();

$url = dechex($userID);

/*

* If the UserID was successfully

* retrieved, create a default list.

*/

$sql = "INSERT INTO lists (UserID, ListURL)

VALUES ($userID, $url)";

if(!$this->_db->query($sql)) {

return "<h2> Error </h2>"

. "<p> Your account was created, but "

. "creating your first list failed. </p>";

} else {

return "<h2> Success! </h2>"

. "<p> Your account was successfully "

. "created with the username <strong>$u</strong>."

. " Check your email!";

} else {

return "<h2> Error </h2><p> Couldn't insert the "

. "user information into the database. </p>";

This method follows several steps to create an account: first, it retrieves the posted email address from the form (stored in the $_POST superglobal) and generates a hard-toguess verification code (the SHA1 hash of the current timestamp); second, it makes sure the supplied email address isnt already in use; third, it generates and sends a verification email to the user with instructions on how to verify their account (well define the method that does this in the next section); fourth, it stores the email address and verification code in the database; and finally, it creates a list for the user.

Each of these steps is monitored, and if any of them should fail, a specific error message is generated. Upon success, a message is generated to let the user know they should expect an email. Generating and Sending a Verification Email When the user creates an account, we need to send them an email with a link that will confirm their account. This is a precautionary measure that proves the user provided a real email address that they have access to and prevents a ton of spam accounts from being created easily. To send the email, well be using the built-in mail() function. In inc/class.users.inc.php , create the private sendVerificationEmail() method by inserting the following code:
class ColoredListsUsers

// Class properties and other methods omitted to save space

/**

* Sends an email to a user with a link to verify their new account

* @param string $email

The user's email address

* @param string $ver

The random verification code for the user

* @return boolean

TRUE on successful send and FALSE on failure

*/

private function sendVerificationEmail($email, $ver)

$e = sha1($email); // For verification purposes

$to = trim($email);

$subject = "[Colored Lists] Please Verify Your Account";

$headers = <<<MESSAGE

From: Colored Lists <donotreply@coloredlists.com>

Content-Type: text/plain;

MESSAGE;

$msg = <<<EMAIL

You have a new account at Colored Lists!

To get started, please activate your account and choose a

password by following the link below.

Your Username: $email

Activate your account: http://coloredlists.com/accountverify.php?v=$ver&e=$e

If you have any questions, please contact help@coloredlists.com.

--

Thanks!

Chris and Jason

www.ColoredLists.com

EMAIL;

return mail($to, $subject, $msg, $headers);

The most important part of this method is the activation link, http://coloredlists.com/accountverify.php?v=$ver&e=$e . This link sends the user to our apps account verification file (which well write in the next step) and sends the users hashed email address along with their verification code in the URI. This will allow us to identify and verify the user when they follow the link.

Verifying the Users Account After our user follows the verification link in the email, we need to check that their email and verification code are valid, and then allow them to choose a password.After they choose a password, we need to update the database to reflect the users new password, as well as setting the accounts status to verified. First, lets create a new file called place the following code:
<?php accountverify.php

in the root level of our app. Inside,

include_once "common/base.php";

$pageTitle = "Verify Your Account";

include_once "common/header.php";

if(isset($_GET['v']) && isset($_GET['e']))

include_once "inc/class.users.inc.php";

$users = new ColoredListsUsers($db);

$ret = $users->verifyAccount();

elseif(isset($_POST['v']))

include_once "inc/class.users.inc.php";

$users = new ColoredListsUsers($db);

$ret = $users->updatePassword();

else

header("Location: /signup.php");

exit;

if(isset($ret[0])):

echo isset($ret[1]) ? $ret[1] : NULL;

if($ret[0]<3):

?>

<h2>Choose a Password</h2>

<form method="post" action="accountverify.php">

<div>

<label for="p">Choose a Password:</label>

<input type="password" name="p" id="p" /><br />

<label for="r">Re-Type Password:</label>

<input type="password" name="r" id="r" /><br />

<input type="hidden" name="v" value="<?php echo $_GET['v'] ?>" />

<input type="submit" name="verify" id="verify" value="Verify Your Account" />

</div>

</form>

<?php

endif;

else:

echo '<meta http-equiv="refresh" content="0;/">';

endif;

include_once("common/ads.php");

include_once 'common/close.php';

?>

Verifying the Users Email and Verification Code Before we can allow our user to select a password, we need to make sure that their account exists, that their email matches their verification code, and that their account is unverified. To do that, we need a new method in inc/class.users.inc.php called verifyAccount() :

class ColoredListsUsers

// Class properties and other methods omitted to save space

/**

* Checks credentials and verifies a user account

* @return array

an array containing a status code and status message

*/

public function verifyAccount()

$sql = "SELECT Username

FROM users

WHERE ver_code=:ver

AND SHA1(Username)=:user

AND verified=0";

if($stmt = $this->_db->prepare($sql))

$stmt->bindParam(':ver', $_GET['v'], PDO::PARAM_STR);

$stmt->bindParam(':user', $_GET['e'], PDO::PARAM_STR);

$stmt->execute();

$row = $stmt->fetch();

if(isset($row['Username']))

// Logs the user in if verification is successful

$_SESSION['Username'] = $row['Username'];

$_SESSION['LoggedIn'] = 1;

else

return array(4, "<h2>Verification Error</h2>n"

. "<p>This account has already been verified. "

. "Did you <a href="/password.php">forget "

. "your password?</a>");

$stmt->closeCursor();

// No error message is required if verification is successful

return array(0, NULL);

else

return array(2, "<h2>Error</h2>n<p>Database error.</p>");

This method executes a query that loads the user name stored in the database with the verification code, hashed user name, and a verified status of 0 . If a user name is returned, login credentials are stored. This method returns an array with an error code in the first index, and a message in the second. The error code 0 means nothing went wrong. Updating the Users Password and Verified Status Once the user has selected a password and submitted the form, the if-else statement will catch the verification code sent using the POST method and execute the updatePassword() method. This method needs to set the account status to verified and save the users hashed password in the database. Lets build this method in ColoredListsUsers :
class ColoredListsUsers

// Class properties and other methods omitted to save space

/**

* Changes the user's password

* @return boolean

TRUE on success and FALSE on failure

*/

public function updatePassword()

if(isset($_POST['p'])

&& isset($_POST['r'])

&& $_POST['p']==$_POST['r'])

$sql = "UPDATE users

SET Password=MD5(:pass), verified=1

WHERE ver_code=:ver

LIMIT 1";

try

$stmt = $this->_db->prepare($sql);

$stmt->bindParam(":pass", $_POST['p'], PDO::PARAM_STR);

$stmt->bindParam(":ver", $_POST['v'], PDO::PARAM_STR);

$stmt->execute();

$stmt->closeCursor();

return TRUE;

catch(PDOException $e)

return FALSE;

else

return FALSE;

Finally, since verifying an account logs a user in, we need to update common/header.php to recognize that a user is logged in and display different options. In Part 4, common/header.php featured a code snippet that looked like this:
<!-- IF LOGGED IN -->

<p><a href="/logout.php" class="button">Log out</a> <a href="/account.php" class="button">Your Account</a></p>

<!-- IF LOGGED OUT -->

<p><a class="button" href="/signup.php">Sign up</a> &nbsp; <a class="button" href="/login.php">Log in</a></p>

<!-- END OF IF STATEMENT -->

To make those comments into functional code, we need to modify this snippet with an if-else block:
<?php

if(isset($_SESSION['LoggedIn']) && isset($_SESSION['Username'])

&& $_SESSION['LoggedIn']==1):

?>

<p><a href="/logout.php" class="button">Log out</a> <a href="/account.php" class="button">Your Account</a></p>

<?php else: ?>

<p><a class="button" href="/signup.php">Sign up</a> &nbsp; <a class="button" href="/login.php">Log in</a></p>

<?php endif; ?>

Notice that we store in the session both the user name ( $_SESSION['Username'] ) and a flag that tells us if the user is logged in ( $_SESSION['LoggedIn'] ). Logging In Next, lets build the login form and allow our user to log in. To start, lets create a new file named login.php at the root level of our app. Like our other publicly displayed

files, this will include the base and header files. Then it checks if a user is already logged in, if the login form was submitted, or if the user needs to log in. If logged in, the user is notified of this fact and asked if he or she wishes to log out. If the form has been submitted, a new ColoredListsUsers object is created and the accountLogin() method is called. If the login succeeds, the user is directed to the home page, where his or her list will appear; otherwise, the login form is displayed again with an error. If neither of the previous conditions exists, the login form is displayed. Finally, the sidebar ads and footer are included to round out the file. When the file is all put together, it should look like this:
<?php

include_once "common/base.php";

$pageTitle = "Home";

include_once "common/header.php";

if(!empty($_SESSION['LoggedIn']) && !empty($_SESSION['Username'])):

?>

<p>You are currently <strong>logged in.</strong></p>

<p><a href="/logout.php">Log out</a></p>

<?php

elseif(!empty($_POST['username']) && !empty($_POST['password'])):

include_once 'inc/class.users.inc.php';

$users = new ColoredListsUsers($db);

if($users->accountLogin()===TRUE):

echo "<meta http-equiv='refresh' content='0;/'>";

exit;

else:

?>

<h2>Login Failed&mdash;Try Again?</h2>

<form method="post" action="login.php" name="loginform" id="loginform">

<div>

<input type="text" name="username" id="username" />

<label for="username">Email</label>

<br /><br />

<input type="password" name="password" id="password" />

<label for="password">Password</label>

<br /><br />

<input type="submit" name="login" id="login" value="Login" class="button" />

</div>

</form>

<p><a href="/password.php">Did you forget your password?</a></p>

<?php

endif;

else:

?>

<h2>Your list awaits...</h2>

<form method="post" action="login.php" name="loginform" id="loginform">

<div>

<input type="text" name="username" id="username" />

<label for="username">Email</label>

<br /><br />

<input type="password" name="password" id="password" />

<label for="password">Password</label>

<br /><br />

<input type="submit" name="login" id="login" value="Login" class="button" />

</div>

</form><br /><br />

<p><a href="/password.php">Did you forget your password?</a></p>

<?php

endif;

?>

<div style="clear: both;"></div>

<?php

include_once "common/ads.php";

include_once "common/close.php";

?>

Notice the Did you forget your password? links well be building this functionality a little later on in the article. Building the Login Method Now we need to build the accountLogin() method. This method will compare the supplied user name and the MD5 hash of the supplied password to verify that there is a matching pair in the database. If a match is found, the users name and a login flag are stored in the session and the method returns TRUE . If no match is found, the method returns FALSE . Build this method in
class ColoredListsUsers ColoredListsUsers

by inserting the following code:

// Class properties and other methods omitted to save space

/**

* Checks credentials and logs in the user

* @return boolean

TRUE on success and FALSE on failure

*/

public function accountLogin()

$sql = "SELECT Username

FROM users

WHERE Username=:user

AND Password=MD5(:pass)

LIMIT 1";

try

$stmt = $this->_db->prepare($sql);

$stmt->bindParam(':user', $_POST['username'], PDO::PARAM_STR);

$stmt->bindParam(':pass', $_POST['password'], PDO::PARAM_STR);

$stmt->execute();

if($stmt->rowCount()==1)

$_SESSION['Username'] = htmlentities($_POST['username'], ENT_QUOTES);

$_SESSION['LoggedIn'] = 1;

return TRUE;

else

return FALSE;

catch(PDOException $e)

return FALSE;

Logging Out Next, our user needs to be able to log out. This is as easy as destroying the login data stored in the session and sending the user back to the login page.

Create a new file named code inside:


<?php

logout.php

at the root level of the app and place the following

session_start();

unset($_SESSION['LoggedIn']);

unset($_SESSION['Username']);

?>

<meta http-equiv="refresh" content="0;login.php">

Modifying Account Information Next, we need to allow our users to modify their account information. In order to do that, we need to provide an Account page that will give them options to change their user name or password, as well as the option to delete their account. Create a file named account.php at the root level of the app. Theres a lot going on here because were essentially combining three app functions within one file. First, we include the base file and check that the user is logged in. If not, he or she gets sent out to the main page. If the user is logged in, we check if any actions have already been attempted and assemble the corresponding success or failure messages if any are found. Then we load the users ID and verification code using the method retrieveAccountInfo() and build three forms: one to update the user name (which is an email address, remember), one to change the account password, and one to delete the account. Finally, we include the sidebar ads and the footer. Altogether, the file should look like this:
<?php

include_once "common/base.php";

if(isset($_SESSION['LoggedIn']) && $_SESSION['LoggedIn']==1):

$pageTitle = "Your Account";

include_once "common/header.php";

include_once 'inc/class.users.inc.php';

$users = new ColoredListsUsers($db);

if(isset($_GET['email']) && $_GET['email']=="changed")

echo "<div class='message good'>Your email address "

. "has been changed.</div>";

else if(isset($_GET['email']) && $_GET['email']=="failed")

echo "<div class='message bad'>There was an error "

. "changing your email address.</div>";

if(isset($_GET['password']) && $_GET['password']=="changed")

echo "<div class='message good'>Your password "

. "has been changed.</div>";

elseif(isset($_GET['password']) && $_GET['password']=="nomatch")

echo "<div class='message bad'>The two passwords "

. "did not match. Try again!</div>";

if(isset($_GET['delete']) && $_GET['delete']=="failed")

echo "<div class='message bad'>There was an error "

. "deleting your account.</div>";

list($userID, $v) = $users->retrieveAccountInfo();

?>

<h2>Your Account</h2>

<form method="post" action="db-interaction/users.php">

<div>

<input type="hidden" name="userid"

value="<?php echo $userID ?>" />

<input type="hidden" name="action"

value="changeemail" />

<input type="text" name="username" id="username" />

<label for="username">Change Email Address</label>

<br /><br />

<input type="submit" name="change-email-submit"

id="change-email-submit" value="Change Email"

class="button" />

</div>

</form><br /><br />

<form method="post" action="db-interaction/users.php"

id="change-password-form">

<div>

<input type="hidden" name="user-id"

value="<?php echo $userID ?>" />

<input type="hidden" name="v"

value="<?php echo $v ?>" />

<input type="hidden" name="action"

value="changepassword" />

<input type="password"

name="p" id="new-password" />

<label for="password">New Password</label>

<br /><br />

<input type="password" name="r"

id="repeat-new-password" />

<label for="password">Repeat New Password</label>

<br /><br />

<input type="submit" name="change-password-submit"

id="change-password-submit" value="Change Password"

class="button" />

</div>

</form>

<hr />

<form method="post" action="deleteaccount.php"

id="delete-account-form">

<div>

<input type="hidden" name="user-id"

value="<?php echo $userID ?>" />

<input type="submit"

name="delete-account-submit" id="delete-account-submit"

value="Delete Account?" class="button" />

</div>

</form>

<?php

else:

header("Location: /");

exit;

endif;

?>

<div class="clear"></div>

<?php

include_once "common/ads.php";

include_once "common/close.php";

?>

Creating the Method to Retrieve Account Info In order to have the users login name and verification code available to our account option forms, we need to build a new method that will load this information from the database. In inc/class.users.inc.php , create a new method in ColoredListsUsers called retrieveAccountInfo() and add the following code:
class ColoredListsUsers

// Class properties and other methods omitted to save space

/**

* Retrieves the ID and verification code for a user

* @return mixed

an array of info or FALSE on failure

*/

public function retrieveAccountInfo()

$sql = "SELECT UserID, ver_code

FROM users

WHERE Username=:user";

try

$stmt = $this->_db->prepare($sql);

$stmt->bindParam(':user', $_SESSION['Username'], PDO::PARAM_STR);

$stmt->execute();

$row = $stmt->fetch();

$stmt->closeCursor();

return array($row['UserID'], $row['ver_code']);

catch(PDOException $e)

return FALSE;

Building the Interactions File

In account.php , all three forms direct to a file called db-interaction/users.php when submitted. This file helps relieve some of the clutter in account.php by determining form actions, creating a ColoredListsUsers object, and calling the appropriate methods to handle the action. This file will be placed in a new folder called Place the following code in the new file:
<?php db-interaction

, and it will be named

users.php

session_start();

include_once "../inc/constants.inc.php";

include_once "../inc/class.users.inc.php";

$userObj = new ColoredListsUsers();

if(!empty($_POST['action'])

&& isset($_SESSION['LoggedIn'])

&& $_SESSION['LoggedIn']==1)

switch($_POST['action'])

case 'changeemail':

$status = $userObj->updateEmail() ? "changed" : "failed";

header("Location: /account.php?email=$status");

break;

case 'changepassword':

$status = $userObj->updatePassword() ? "changed" : "nomatch";

header("Location: /account.php?password=$status");

break;

case 'deleteaccount':

$userObj->deleteAccount();

break;

default:

header("Location: /");

break;

elseif($_POST['action']=="resetpassword")

if($resp=$userObj->resetPassword()===TRUE)

header("Location: /resetpending.php");

else

echo $resp;

exit;

else

header("Location: /");

exit;

?>

Updating the Email Address

When a user submits a request to change their email address, the method updateEmail() is called. This function simply executes a query that changes the email address associated with an account. It returns TRUE if the email is successfully changed, and FALSE otherwise.
class ColoredListsUsers

// Class properties and other methods omitted to save space

/**

* Changes a user's email address

* @return boolean

TRUE on success and FALSE on failure

*/

public function updateEmail()

$sql = "UPDATE users

SET Username=:email

WHERE UserID=:user

LIMIT 1";

try

$stmt = $this->_db->prepare($sql);

$stmt->bindParam(':email', $_POST['username'], PDO::PARAM_STR);

$stmt->bindParam(':user', $_POST['userid'], PDO::PARAM_INT);

$stmt->execute();

$stmt->closeCursor();

// Updates the session variable

$_SESSION['Username'] = htmlentities($_POST['username'], ENT_QUOTES);

return TRUE;

catch(PDOException $e)

return FALSE;

Updating the Password

Quite similarly to updateEmail() , updatePassword() is called if the user submits a request to change their password. The only difference in the methods is that this one willcompare the password and the password confirmation to make sure they match before saving.
class ColoredListsUsers

// Class properties and other methods omitted to save space

/**

* Changes the user's password

* @return boolean

TRUE on success and FALSE on failure

*/

public function updatePassword()

if(isset($_POST['p'])

&& isset($_POST['r'])

&& $_POST['p']==$_POST['r'])

$sql = "UPDATE users

SET Password=MD5(:pass), verified=1

WHERE ver_code=:ver

LIMIT 1";

try

$stmt = $this->_db->prepare($sql);

$stmt->bindParam(":pass", $_POST['p'], PDO::PARAM_STR);

$stmt->bindParam(":ver", $_POST['v'], PDO::PARAM_STR);

$stmt->execute();

$stmt->closeCursor();

return TRUE;

catch(PDOException $e)

return FALSE;

else

return FALSE;

Deleting the Account

If the user wants to delete their account, we need to go through several steps. First, we need to double-check that the user is logged in, because we certainly dont want any accidental account deletions. If the user is logged in, we then delete their list items. If the list items are successfully deleted, we move on to delete the users lists.Finally, if the lists are successfully deleted, we delete the user from the database, destroy their session information, and send them to a page called gone.php , which well build in a minute. The method, when its all written, will look like this:
class ColoredListsUsers

// Class properties and other methods omitted to save space

/**

* Deletes an account and all associated lists and items

* @return void

*/

public function deleteAccount()

if(isset($_SESSION['LoggedIn']) && $_SESSION['LoggedIn']==1)

// Delete list items

$sql = "DELETE FROM list_items

WHERE ListID=(

SELECT ListID

FROM lists

WHERE UserID=:user

LIMIT 1

)";

try

$stmt = $this->_db->prepare($sql);

$stmt->bindParam(":user", $_POST['user-id'], PDO::PARAM_INT);

$stmt->execute();

$stmt->closeCursor();

catch(PDOException $e)

die($e->getMessage());

// Delete the user's list(s)

$sql = "DELETE FROM lists

WHERE UserID=:user";

try

$stmt = $this->_db->prepare($sql);

$stmt->bindParam(":user", $_POST['user-id'], PDO::PARAM_INT);

$stmt->execute();

$stmt->closeCursor();

catch(PDOException $e)

die($e->getMessage());

// Delete the user

$sql = "DELETE FROM users

WHERE UserID=:user

AND Username=:email";

try

$stmt = $this->_db->prepare($sql);

$stmt->bindParam(":user", $_POST['user-id'], PDO::PARAM_INT);

$stmt->bindParam(":email", $_SESSION['Username'], PDO::PARAM_STR);

$stmt->execute();

$stmt->closeCursor();

catch(PDOException $e)

die($e->getMessage());

// Destroy the user's session and send to a confirmation page

unset($_SESSION['LoggedIn'], $_SESSION['Username']);

header("Location: /gone.php");

exit;

else

header("Location: /account.php?delete=failed");

exit;

Resetting an Account Password

At this point, were almost done. The last thing we need to do is allow a user to reset a forgotten password. To do this, we need to create the file password.php at the root level of our app and place the following code inside:
<?php

include_once "common/base.php";

$pageTitle = "Reset Your Password";

include_once "common/header.php";

?>

<h2>Reset Your Password</h2>

<p>Enter the email address you signed up with and we'll send

you a link to reset your password.</p>

<form action="db-interaction/users.php" method="post">

<div>

<input type="hidden" name="action"

value="resetpassword" />

<input type="text" name="username" id="username" />

<label for="username">Email</label><br /><br />

<input type="submit" name="reset" id="reset"

value="Reset Password" class="button" />

</div>

</form>

<?php

include_once "common/ads.php";

include_once "common/close.php";

?>

When a user visits this page, theyll be able to enter their email address. Submitting the form will return the account to unverified and send the user an email with a link to reset their password. Returning the Account to Unverified Status When the form in password.php is submitted, the information is sent to dbinteraction/users.php and the resetPassword() method is called before sending the user to resetpending.php . The resetPassword() method sets the verified field of our users database entry to 0 , then calls the sendResetEmail() method.
class ColoredListsUsers

// Class properties and other methods omitted to save space

/**

* Resets a user's status to unverified and sends them an email

* @return mixed

TRUE on success and a message on failure

*/

public function resetPassword()

$sql = "UPDATE users

SET verified=0

WHERE Username=:user

LIMIT 1";

try

$stmt = $this->_db->prepare($sql);

$stmt->bindParam(":user", $_POST['username'], PDO::PARAM_STR);

$stmt->execute();

$stmt->closeCursor();

catch(PDOException $e)

return $e->getMessage();

// Send the reset email

if(!$this->sendResetEmail($_POST['username'], $v))

return "Sending the email failed!";

return TRUE;

Building the Reset Pending Page After the users account is back in an unverified state and the email has been sent with their password reset link, we send them to resetpending.php to let them know what their next steps are. Create this file at the root level of the app and insert the following:
<?php

include_once "common/base.php";

$pageTitle = "Reset Pending";

include_once "common/header.php";

?>

<h2>Password Reset Requested</h2>

<p>Check your email to finish the reset process.</p>

<?php

include_once "common/ads.php";

include_once "common/close.php";

?>

Generating a Reset Password Email The sendResetEmail() method is very similar to the sendVerificationEmail() method. The main difference here is that the link sent to the user directs them to a page called resetpassword.php where theyre able to choose a new password.
class ColoredListsUsers

// Class properties and other methods omitted to save space

/**

* Sends a link to a user that lets them reset their password

* @param string $email

the user's email address

* @param string $ver

the user's verification code

* @return boolean

TRUE on success and FALSE on failure

*/

private function sendResetEmail($email, $ver)

$e = sha1($email); // For verification purposes

$to = trim($email);

$subject = "[Colored Lists] Request to Reset Your Password";

$headers = <<<MESSAGE

From: Colored Lists <donotreply@coloredlists.com>

Content-Type: text/plain;

MESSAGE;

$msg = <<<EMAIL

We just heard you forgot your password! Bummer! To get going again,

head over to the link below and choose a new password.

Follow this link to reset your password:

http://coloredlists.com/resetpassword.php?v=$ver&e=$e

If you have any questions, please contact help@coloredlists.com.

--

Thanks!

Chris and Jason

www.ColoredLists.com

EMAIL;

return mail($to, $subject, $msg, $headers);

Resetting the Password Our very last step in this part of the app is to create the file resetpassword.php in the root level of the site. This file is very similar to the accountverify.php file we created earlier. After including the base and header files, it checks if the user is just arriving from their reset email. If so, we are able to use the verifyAccount() method we wrote earlier to ensure that their credentials are correct. After verifying their credentials, we display a form that allows them to choose a password and confirm it. After submitting the form, our script will fire the updatePassword() method we created earlier to save the new password. Then we redirect the user to account.php , where theyre shown a confirmation message letting them know that their password was changed. Inside
<?php resetpassword.php

, add the following code:

include_once "common/base.php";

if(isset($_GET['v']) && isset($_GET['e']))

include_once "inc/class.users.inc.php";

$users = new ColoredListsUsers($db);

$ret = $users->verifyAccount();

elseif(isset($_POST['v']))

include_once "inc/class.users.inc.php";

$users = new ColoredListsUsers($db);

$status = $users->updatePassword() ? "changed" : "failed";

header("Location: /account.php?password=$status");

exit;

else

header("Location: /login.php");

exit;

$pageTitle = "Reset Your Password";

include_once "common/header.php";

if(isset($ret[0])):

echo isset($ret[1]) ? $ret[1] : NULL;

if($ret[0]<3):

?>

<h2>Reset Your Password</h2>

<form method="post" action="accountverify.php">

<div>

<label for="p">Choose a New Password:</label>

<input type="password" name="p" id="p" /><br />

<label for="r">Re-Type Password:</label>

<input type="password" name="r" id="r" /><br />

<input type="hidden" name="v" value="<?php echo $_GET['v'] ?>" />

<input type="submit" name="verify" id="verify" value="Reset Your Password" />

</div>

</form>

<?php

endif;

else:

echo '<meta http-equiv="refresh" content="0;/">';

endif;

include_once("common/ads.php");

include_once 'common/close.php';

?>

Moving On

This article covered a whole lot of ground, and I went over some of it pretty quickly.Please dont hesitate to ask for clarification in the comments! In the next part of this series, our front-end designer will use some dummy lists to create AJAX effects. After hes finished with the dummy lists, well explore how to combine those AJAX effects with our back-end and build the list interactions class in part 7.
Our developer has already done a massive amount of work turning this idea into a real application. Now let's make some more for him! The most important part of this app is creating and managing your list. We

decided from the get-go that this was going to be an AJAX-y app. We didn't chose AJAX because it's a popular buzzword, we chose it because we know it's the best path toward making a responsive, easy to use, natural feeling application on the web.
ALL SERIES NAVIGATION

Part 1 - Planning the App: Basic Idea and Design Part 2 - Planning the App: Database Architecture and Development Approach Part 3 - Designing the App: Workflow Map and Photoshop Design Part 4 - Designing the App: HTML and CSS Part 5 - Developing the App: User Interaction Part 6 - Developing the App: Adding AJAX Interactivity Part 7 - Developing the App: List Interaction Part 8 - Security & The Future

And finally.... the application!

Colored Lists

The Big Thing: Saving the List


AJAX allows for us to send requests and get responses from the server without a page refresh. In our app, that functionality is going to be used primarily for saving. Let's think through each of those times something needs to be saved:

When a new list items is added, it should be saved to the list. When a list item is deleted, it should be deleted from the list. When an items color is changed, the new color should be saved. When a list item is marked as done, that status should be saved. When the list is reordered, the new order should be saved. When the text of a list item is altered, the new text should be saved.

That's a lot of saving going on. Our developer has his work cut out for him, because each of those little events needs to have a PHP file that is ready to receive that request and deal with it. Fortunately he has some cool object oriented stuff gong on already and can certainly extend that to deal with this.

Interface JavaScript
Alongside all that AJAX saving is all the stuff that makes the interface do what visually it's saying it will do. That little drag tab is saying it can drag list items up and down. We are saying after that happens we are going to save the list. But how does that actually work? Don't worry, we'll get to it. For now let's think through all the interface JavaScript things that we need:

Click and drag the drag tab, list items can be dragged around and reordered. Click the color tab, the list items color is toggled between some predefined choices. Click the checkmark, the list item crosses out and and fades down. Click the X, a confirmation slides out. Click again, list item is whisked away. Double-click the list item, text turns into a text-input for editing. Type in the large box below and click add, new list item is appended to bottom of the list. And again, we're going to get to all that. Just a little more setup to do!

First things first: calling the JavaScript files


There is really only one page on our site, the main list page, that needs JavaScript at all. So we'll be dropping the script files right into the index.php file. You'll often see JavaScript file linked in the header of sites. This isn't the time for a lengthy discussion about that, but suffice it to say that that isn't required as it's generally considered a performance enhancement to list them at the end of pages instead. That's just what we'll do here. In our index.php file, after we've output the list, we'll call the JavaScript we need.

<script type='text/javascript' src='http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js?ver=1.3. 2'></script> <script type="text/javascript" src="js/jquery-ui1.7.2.custom.min.js"></script> <script type="text/javascript" src="js/jquery.jeditable.mini.js"></script> <script type="text/javascript" src="js/lists.js"></script> <script type="text/javascript"> initialize(); </script>
1. 2. 3. 4. 5. Load jQuery (from Google for better speed) Load a customized jQuery UI library (for the draggable stuff) Load the jEditable plugin (for click-to-edit) Load our custom script Call the initialization function (our own kickstarter, kind of like a DOM ready statement)

Cleaning up the Markup with JavaScript


In our custom lists.js file, we'll be creating a number of different functions. The very first one is the one that gets called directly in the index.php file after the lists have been output:

function initialize() {

};
The first thing we are going to do there is clean up the markup a bit by inserting common elements with JavaScript rather than have them directly in the markup. Remember what the markup for the list looked like when we mocked it up?

<ul id="list"> <li class="colorRed"> <span>Walk the dog</span> <div class="draggertab tab"></div> <div class="colortab tab"></div> <div class="deletetab tab"></div> <div class="donetab tab"></div> </li>

<!-- more list items -->

</ul>
Much of that is redundant across all the list items. What we want is more like this:

<ul id="list"> <li class="colorRed"> Walk the dog </li>

<!-- more list items -->

</ul>
All the divs and the span has been removed. The class name on the list item is fine, because PHP will be spitting that out for us when it reads the database and outputs the list. How do we append all that extra HTML? Easy with jQuery. Target the list items and wrap each of the the innards using the wrapInner() function and append the extra divs with the append() function.

function initialize() {

// WRAP LIST TEXT IN A SPAN, AND APPLY FUNCTIONALITY TABS $("#list li")

.wrapInner("<span>") .append("<div class='draggertab tab'></div><div class='colortab tab'></div></div><div class='deletetab tab'></div><div class='donetab tab'></div>");

};
Bind events to the new functionality tabs, the smart way Binding an event to an HTML element is pretty easy in JavaScript. It's like this:

$("li").click(function() { // do something });


There is nothing wrong with that, but we are in a bit of a unique situation with our list app here. When you bind events like that 1) it creates a unique event handler for every single list item on the page, each one taking up browser memory and 2) it only does it once, for the current state of the DOM. Don't worry about all that too much, the point is binding events this way isn't ideal for us because we will be inserting new list items dynamically. When a user inserts a new list item, that gets plugged into the DOM right then and there. That new list item will not be bound as the others are, meaning all those fancy little tabs won't work right. Boo hoo. What can we do to solve that? Well we can create a new function that will be called when the page loads and when new list items are appended that does all that event binding work. That will definitely do the trick, but... jQuery is smarter than that. jQuery provides a function called live() that eliminates this problem entirely.

$("li").live("click", function() { // do something });


Binding events with the live() function is fantastic for us because 1) it only creates one event handler which is far more efficient and 2) new items appended to the page are automatically bound by the same handler. Killer. So for our little functionality tabs, we'll be using them like this:

$(".donetab").live("click", function() { // do stuff });

$(".colortab").live("click", function(){ // do stuff });

$(".deletetab").live("click", function(){

// do stuff });
The drag tab doesn't have click event, it's actually going to use jQuery UI's draggable functionality to do it's thing. Let's check that out.

Making the list drag / sortable


Mega thanks to jQuery UI for making such a useful set of functions. The draggable module is exactly perfect for making a list like our sortable. We target the parent <ul>, tell it the "handle" we wish to use (which part of the list item you can click and drag to move them). We also use a parameter forcePlaceholderSize for some visual feedback when the list items are dragged around (a white block space pops in to indicate where the list item would "land" if released)

$("#list").sortable({ handle update : ".draggertab", : function(event, ui){

// Developer, this function fires after a list sort, commence list saving!

}, forcePlaceholderSize: true });

Marking items as "done"


When the user clicks the little checkmark tab, we have already decided to do two things. Draw a line through the list item and then fade that whole list item out. But then there is the consideration of what to do if the item is already marked as done and that tab is

clicked. Well, we'll uncross it out and fade it back up. So when the click happens, we'll make sure to check which state we are in first.

$(".donetab").live("click", function() {

if(!$(this).siblings('span').children('img.crossout').length) { $(this) .parent() .find("span") .append("<img src='http://cdn.csstricks.com/images/crossout.png' class='crossout' />") .find(".crossout") .animate({ width: "100%" }) .end() .animate({ opacity: "0.5" }, "slow", "swing", function() {

// DEVELOPER, the user has marked this item as done, commence saving!

}) } else { $(this) .siblings('span') .find('img.crossout')

.remove() .end() .animate({ opacity : 1 }, "slow", "swing", function() {

// DEVELOPER, the user has UNmarked this item as done, commence saving!

})

} });

Color Cycling
We'd better get on this whole "colored" part of Colored Lists eh? CSS will be applying the actual color, so what we'll be doing with JavaScript is just cycling the class names applied to those list items on clicks.

$(".colortab").live("click", function(){

$(this).parent().nextColor();

$.ajax({

// DEVELOPER, the user has toggled the color on this list item, commence saving!

}); });
That nextColor() function isn't a built-in function, it will be custom written by us. It's abstracted away here for code clarity. The way that we've used it here (as a part of the "chain") is such that we'll need to make a little jQuery plugin out of it. No problem.

jQuery.fn.nextColor = function() {

var curColor = $(this).attr("class");

if (curColor == "colorBlue") { $(this).removeClass("colorBlue").addClass("colorYellow").attr("color","2"); } else if (curColor == "colorYellow") { $(this).removeClass("colorYellow").addClass("colorRed").attr("color","3"); } else if (curColor == "colorRed") { $(this).removeClass("colorRed").addClass("colorGreen").attr("color","4"); } else { $(this).removeClass("colorGreen").addClass("colorBlue").attr("color","1"); };

};
Basically this check what color the list item already is and moves it to the next color. Notice how we are altering an attribute on the list items too. Color isn't a valid attribute in XHMTL (it's fine in HTML5), but oh well. It's not in the markup so it doesn't really matter. Why are we using this? We'll, it's because we are about 50% of they way in doing this really intelligently. When our developer goes to save the color information about this list item to the database, he needs something to save. Back in Part 2 of this series, we can see that our developer already anticipated this and created a field for color called listItemColor, which he made an INT (integer). He figured that would be the smartest way to do it as it's lightweight, easy, and abstract. We can decide later what they key is, e.g., 1 = Blue, 2 = Red, etc. The data itself doesn't need to know it's red. So, if we have an integer representing the color right in the DOM for him, that makes it really easy to snag out and pass along to save to the database.

Why is this only 50% smart? Well, because we should probably extend that smartness to the class names themselves. We are using colorYellow for example, when color-1 might make more sense, if down the line we decide to drop yellow from the lineup and replace it. Or even perhaps let users declare their own colors.

Deleting list items


Our little "X" tab is in charge of allowing users to delete list items. We want to have a little extra insurance against accidentally fatfingerings though. So we are going to require two clicks to actually delete something. Some applications resort to a nasty "ARE YOU SURE" modal popup dialog box, we'll be a little more sly than that. As you click the X, a little notice will pop out to the right asking about sureness. If they click again then deleting may commence.

$(".deletetab").live("click", function(){

var thiscache = $(this);

if (thiscache.data("readyToDelete") == "go for it") { $.ajax({

// DEVELOPER, the user wants to delete this list item, commence deleting!

success: function(r){ thiscache .parent() .hide("explode", 400, function(){$(this).remove()});

// Make sure to reorder list items after a delete!

}); } else { thiscache.animate({

width: "44px", right: "-64px" }, 200) .data("readyToDelete", "go for it"); } });
Because we were smart earlier and our little tab graphic is all a part of one sprite graphic, all we need to do is expand the width of that tab to display the message. After the first click, we append a little bit of data (jQuery's data() function) to that list item saying to "go for it". Upon a second click, that test will be TRUE and we know we can commence the deletion of that list item.

Since we are using jQuery UI, we tossed in a little extra fun flair with the "explode" option for hiding elements.

Click-to-edit list items


In order to make our list items click-to-edit, we'll stand on the shoulders of others and use a jQuery plugin, jEditable. All we need to do with this plugin is target an element and use the editable() function on it with some parameters. On big caveat though, we can't use the live() function with this plugin because it's not a standard jQuery event. Back before we had live, we did what we talked briefly earlier. We called a function that did all our binding. That way we could call it on DOM ready as well as after any AJAX insertions. We'll lean on that technique now.

function bindAllTabs(editableTarget) {

$(editableTarget).editable("/path/for/DEVELOPER/to/save.php", { id : 'listItemID',

indicator : 'Saving...', tooltip event submit : 'Double-click to edit...', : 'dblclick', : 'Save',

submitdata: {action : "update"} });

}
With those parameters, we're giving the developer what he needs for a callback file path. We're also declaring that we want list items to require a double-click to edit, what the tooltip will be, and what the text in the saving button will say. Very nicely customizeable!

Remember those spans we inserted around the text of the list items earlier. Now we are cashing in on that. When we bind this editable function, we'll bind it to those spans. So right after DOM ready we'll call:

bindAllTabs("#list li span");
We're going to need this function again when appending new list items...

Appending new list items


Core to the functionality of our list app is allowing people to add new list items. We already have the markup in place for that, so let's look at the JavaScript that powers it.

$('#add-new').submit(function(){

var $whitelist = '<b><i><strong><em><a>', forList = $("#current-list").val(), newListItemText = strip_tags(cleanHREF($("#new-list-itemtext").val()), $whitelist), URLtext = escape(newListItemText), newListItemRel = $('#list li').size()+1;

if(newListItemText.length > 0) { $.ajax({

// DEVELOPER, save new list item!

success: function(theResponse){ $("#list").append("<li color='1' class='colorBlue' rel='"+newListItemRel+"' id='" + theResponse + "'><span id=""+theResponse+"listitem" title='Click to edit...'>" + newListItemText + "</span><div class='draggertab tab'></div><div class='colortab tab'></div><div class='deletetab tab'></div><div class='donetab tab'></div></li>"); bindAllTabs("#list li[rel='"+newListItemRel+"'] span"); $("#new-list-item-text").val(""); }, error: function(){ // uh oh, didn't work. Error message? } }); } else { $("#new-list-item-text").val(""); } return false; // prevent default form submission });
NOTE: Stay tuned for the final part in the series where will go over some extra security that needs to happen here. Anytime we accept input from the user, it needs to be scrubbed. This is no exception. One last thing we did there was to clear the input field after submission. That makes adding a bunch of list items in sequence very easy and natural. Also, note that we called that bindAllTabs function again after the new list item was appended. That's why we abstracted that function away to begin with, so we could call it when new list items were appended AJAX style. That ensures the click-to-edit functionality is working on newly appended list items even before a page refresh. The rest of the tabs are automatically cool, due to the live() binding.

Moving On
Up next we'll pass it back to the developer for filling in those gaps on how those list interactions work from the PHP / Database side. Then we'll finish up talking a little security and wrapping up any loose ends.

Where Were At

Now that weve got a set of AJAX controls mostly assembled, we can start building our PHP class to handle list interactions. This class will be called ColoredListsItems and it will reside in the file class.lists.inc.php in the inc folder.

This class will contain methods to handle all of the actions performed by our app or our users regarding list items. Namely, these actions are:

Displaying list items Saving new list items Reordering list items Changing item colors Editing item text Marking an item as done Deleting items

Also, because we need to be able to load a non-editable version of a list when viewed from the public URL, well need to add that functionality as well.
Defining the Class

Before we can do much of anything, we need to have our ColoredListsItems class defined. Inside inc/class.lists.inc.php , add the following class declaration and constructor:
<?php

/**

* Handles list interactions within the app

* PHP version 5

* @author Jason Lengstorf

* @author Chris Coyier

* @copyright 2009 Chris Coyier and Jason Lengstorf

* @license

http://www.opensource.org/licenses/mit-license.html

MIT License

*/

class ColoredListsItems

/**

* The database object

* @var object

*/

private $_db;

/**

* Checks for a database object and creates one if none is found

* @param object $db

* @return void

*/

public function __construct($db=NULL)

if(is_object($db))

$this->_db = $db;

else

$dsn = "mysql:host=".DB_HOST.";dbname=".DB_NAME;

$this->_db = new PDO($dsn, DB_USER, DB_PASS);

?>

Notice that the constructor is identical to the one we used in ColoredListsUsers (seePart 5 Developing the App: User Interaction); it checks for a database object and creates one if none are available.
Displaying List Items

Even though we dont currently have any items saved in our database, we know what theyre going to look like. With that in mind, we can write our output functions to display our list items to users, both in a logged in and logged out state.

If the User Is Logged In When our user is logged in, well be loading their list by their user name. This user name is stored in the $_SESSION superglobal. In
inc/class.lists.inc.php

, define the method

loadListItemsByUser()

and insert the following code:

class ColoredListsItems

// Class properties and other methods omitted to save space

/**

* Loads all list items associated with a user ID

* This function both outputs <li> tags with list items and returns an

* array with the list ID, list URL, and the order number for a new item.

* @return array

an array containing list ID, list URL, and next order

*/

public function loadListItemsByUser()

$sql = "SELECT

list_items.ListID, ListText, ListItemID, ListItemColor,

ListItemDone, ListURL

FROM list_items

LEFT JOIN lists

USING (ListID)

WHERE list_items.ListID=(

SELECT lists.ListID

FROM lists

WHERE lists.UserID=(

SELECT users.UserID

FROM users

WHERE users.Username=:user

ORDER BY ListItemPosition";

if($stmt = $this->_db->prepare($sql))

$stmt->bindParam(':user', $_SESSION['Username'], PDO::PARAM_STR);

$stmt->execute();

$order = 0;

while($row = $stmt->fetch())

$LID = $row['ListID'];

$URL = $row['ListURL'];

echo $this->formatListItems($row,

$order);

$stmt->closeCursor();

// If there aren't any list items saved, no list ID is returned

if(!isset($LID))

$sql = "SELECT ListID, ListURL

FROM lists

WHERE UserID = (

SELECT UserID

FROM users

WHERE Username=:user

)";

if($stmt = $this->_db->prepare($sql))

$stmt->bindParam(':user', $_SESSION['Username'], PDO::PARAM_STR);

$stmt->execute();

$row = $stmt->fetch();

$LID = $row['ListID'];

$URL = $row['ListURL'];

$stmt->closeCursor();

else

echo "tttt<li> Something went wrong. ", $db->errorInfo, "</li>n";

return array($LID, $URL, $order);

This method starts with a somewhat complex query that starts by joining the list_items and lists tables, then uses a sub-query to filter the list items by user ID. If the query returns results, we loop through them and call the yet-to-bedefined formatListItems() method. Each formatted item is output immediately using and the list ID and URL are saved. ,

echo()

If no results are returned (meaning the user doesnt have any items on their list), the list ID and URL wont be returned. However, we need this information in order to allow users to submit new items and share their lists. A check is in place to see if the list ID variable ( $LID ) is set. If not, an additional query is executed to retrieve the users list ID and URL. The list ID and URL are then returned as an array. If the User Is Not Logged In If no user is logged in, we dont have access to a user name with which to load the items. Therefore, we need to use the lists ID to load items. Were able to determine

the lists ID using the lists URL, which is the only way a user who isnt logged in will be able to view a list in the first place. The method looks like this in inc/class.lists.inc.php :
class ColoredListsItems

// Class properties and other methods omitted to save space

/**

* Outputs all list items corresponding to a particular list ID

* @return void

*/

public function loadListItemsByListId()

$sql = "SELECT ListText, ListItemID, ListItemColor, ListItemDone

FROM list_items

WHERE ListID=(

SELECT ListID

FROM lists

WHERE ListURL=:list

ORDER BY ListItemPosition";

if($stmt = $this->_db->prepare($sql)) {

$stmt->bindParam(':list', $_GET['list'], PDO::PARAM_STR);

$stmt->execute();

$order = 1;

while($row = $stmt->fetch())

echo $this->formatListItems($row, $order);

$order;

$stmt->closeCursor();

} else {

echo "<li> Something went wrong. ", $db->error, "</li>";

Formatting List Items To make our previous two methods work properly, we also need to define our formatListItems() method. This method is fairly straightforward, but it also needs a helper method to determine the CSS class for each item, which well call getColorClass() . This helper method only exists to simplify our code. Insert both methods into inc/class.lists.inc.php as follows:
class ColoredListsItems

// Class properties and other methods omitted to save space

/**

* Generates HTML markup for each list item

* @param array $row

an array of the current item's attributes

* @param int $order

the position of the current list item

* @return string

the formatted HTML string

*/

private function formatListItems($row, $order)

$c = $this->getColorClass($row['ListItemColor']);

if($row['ListItemDone']==1)

$d = '<img class="crossout" src="/assets/images/crossout.png" '

. 'style="width: 100%; display: block;"/>';

else

$d = NULL;

// If not logged in, manually append the <span> tag to each item

if(!isset($_SESSION['LoggedIn'])||$_SESSION['LoggedIn']!=1)

$ss = "<span>";

$se = "</span>";

else

$ss = NULL;

$se = NULL;

return "tttt<li id="$row[ListItemID]" rel="$order" "

. "class="$c" color="$row[ListItemColor]">$ss"

. htmlentities(strip_tags($row['ListText'])).$d

. "$se</li>n";

/**

* Returns the CSS class that determines color for the list item

* @param int $color

the color code of an item

* @return string

the corresponding CSS class for the color code

*/

private function getColorClass($color)

switch($color)

case 1:

return 'colorBlue';

case 2:

return 'colorYellow';

case 3:

return 'colorRed';

default:

return 'colorGreen';

An array containing each list items attributes is passed to formatListItems() , anddifferent attributes are created depending on the values that are passed. If a user isnt logged in, we manually append a <span> to the markup to keep our CSS from breaking, and then we wrap the whole thing in a <li> and return it. Calling Our New Methods in the Main View Our main page ( index.php ) currently has notes from the designer that look like this:
<div id="main">

<noscript>This site just doesn't work, period, without JavaScript</noscript>

<!-- IF LOGGED IN -->

<!-- Content here -->

<!-- IF LOGGED OUT -->

<!-- Alternate content here -->

</div>

In order to make these notes into a functional script, we need to add the following logic to index.php to call the proper methods:
<div id="main">

<noscript>This site just doesn't work, period, without JavaScript</noscript>

<?php

if(isset($_SESSION['LoggedIn']) && isset($_SESSION['Username'])):

echo "ttt<ul id="list">n";

include_once 'inc/class.lists.inc.php';

$lists = new ColoredListsItems($db);

list($LID, $URL, $order) = $lists->loadListItemsByUser();

echo "ttt</ul>";

?>

<br />

<form action="db-interaction/lists.php" id="add-new" method="post">

<input type="text" id="new-list-item-text" name="new-list-item-text" />

<input type="hidden" id="current-list" name="current-list" value="<?php echo $LID; ?>" />

<input type="hidden" id="new-list-item-position" name="new-list-item-position" value="<?php echo

$order; ?>" />

<input type="submit" id="add-new-submit" value="Add" class="button" />

</form>

<div class="clear"></div>

<div id="share-area">

<p>Public list URL: <a target="_blank" href="http://coloredlists.com/<?php echo $URL ?>.html">http://coloredlists.com/<?php echo $URL ?>.html</a>

&nbsp; <small>(Nobody but YOU will be able to edit this list)</small></p>

</div>

<script type="text/javascript" src="js/jquery-ui-1.7.2.custom.min.js"></script>

<script type="text/javascript" src="js/jquery.jeditable.mini.js"></script>

<script type="text/javascript" src="js/lists.js"></script>

<script type="text/javascript">

initialize();

</script>

<?php

elseif(isset($_GET['list'])):

echo "ttt<ul id='list'>n";

include_once 'inc/class.lists.inc.php';

$lists = new ColoredListsItems($db);

list($LID, $URL) = $lists->loadListItemsByListId();

echo "ttt</ul>";

else:

?>

<img src="/assets/images/newlist.jpg" alt="Your new list here!" />

<?php endif; ?>

</div>

This script checks if a user is logged in, then outputs their list and the proper controls if so. If not, we check if there was a list URL supplied and outputs a non-editable list if one is found. Otherwise, the sales page is displayed, encouraging the viewer to sign up.
Saving New List Items

At this point, our app will function properly for a user that is logged in. Now we just need to plug in the controls that will allow him or her to interact with the list. First, we need to allow for new items to be created. To do this, we need to write a PHP method that will add list items to our database, and then we need to complete the jQuery started by our designer in Part 6. The PHP Saving an item is simple enough on the server side. We simply grab all of the new items information out of the $_POST superglobal, prepare a statement, and save the info in the database. Note that were running strip_tags() on the list items text. This is a redundant check since were using JavaScript to remove any unwanted tags, but we shouldnt rely on data that was sanitized client-side.
class ColoredListsItems

// Class properties and other methods omitted to save space

/**

* Adds a list item to the database

* @return mixed

ID of the new item on success, error message on failure

*/

public function addListItem()

$list = $_POST['list'];

$text = strip_tags(urldecode(trim($_POST['text'])), WHITELIST);

$pos = $_POST['pos'];

$sql = "INSERT INTO list_items

(ListID, ListText, ListItemPosition, ListItemColor)

VALUES (:list, :text, :pos, 1)";

try

$stmt = $this->_db->prepare($sql);

$stmt->bindParam(':list', $list, PDO::PARAM_INT);

$stmt->bindParam(':text', $text, PDO::PARAM_STR);

$stmt->bindParam(':pos', $pos, PDO::PARAM_INT);

$stmt->execute();

$stmt->closeCursor();

return $this->_db->lastInsertId();

catch(PDOException $e)

return $e->getMessage();

Notice that we used a constant called WHITELIST in the strip_tags() function. This is a list of allowed tags that our users have access to. However, we should assume that well want to change this list at some point in the future, which is why wer e saving the list as a constant, which well define in inc/constants.inc.php :
// HTML Whitelist

define('WHITELIST', '<b><i><strong><em><a>');

Finishing the JavaScript To complete the jQuery in js/lists.js , we need to modify the script with the code below:
// AJAX style adding of list items

$('#add-new').submit(function(){

// HTML tag whitelist. All other tags are stripped.

var $whitelist = '<b><i><strong><em><a>',

forList = $("#current-list").val(),

newListItemText = strip_tags(cleanHREF($("#new-list-item-text").val()), $whitelist),

URLtext = escape(newListItemText),

newListItemRel = $('#list li').size() 1;

if(newListItemText.length > 0) {

$.ajax({

type: "POST",

url: "db-interaction/lists.php",

data: "action=add&list="

forList

"&text="

URLtext

"&pos="

newListItemRel,

success: function(theResponse){

$("#list").append("<li color='1' class='colorBlue' rel='" newListItemRel "' id='" id="" theResponse "listitem" title='Click to edit...'>" newListItemText class='colortab tab'></div><div class='deletetab tab'></div><div class='donetab tab'></div></li>");

theResponse

"'><span

"</span><div class='draggertab tab'></div><div

bindAllTabs("#list li[rel='" newListItemRel "'] span");

$("#new-list-item-text").val("");

},

error: function(){

// uh oh, didn't work. Error message?

});

} else {

$("#new-list-item-text").val("");

return false; // prevent default form submission

});

Were completing the $.ajax() call by submitting the new item via the POST method to dbinteration/lists.php . The successfully added item is then appended to our list, all without a page refresh. Handling List Interactions Our $.ajax() call sends to db-interaction/lists.php , which doesnt exist yet. This script acts as a switch that will determine what action is needed and execute the proper method. All requests are handled the same way, so lets just define the whole file here. Create new file called lists.php in the db-interaction folder and insert the following code into it:
<?php

session_start();

include_once "../inc/constants.inc.php";

include_once "../inc/class.lists.inc.php";

if(!empty($_POST['action'])

&& isset($_SESSION['LoggedIn'])

&& $_SESSION['LoggedIn']==1)

$listObj = new ColoredListsItems();

switch($_POST['action'])

case 'add':

echo $listObj->addListItem();

break;

case 'update':

$listObj->updateListItem();

break;

case 'sort':

$listObj->changeListItemPosition();

break;

case 'color':

echo $listObj->changeListItemColor();

break;

case 'done':

echo $listObj->toggleListItemDone();

break;

case 'delete':

echo $listObj->deleteListItem();

break;

default:

header("Location: /");

break;

else

header("Location: /");

exit;

?>

Reordering List Items

Next, we need to allow users to save the order of their items after theyve dragged and dropped them. This is definitely the most complex part of our whole app. The PHP Each item is assigned a position when its read out of the database. This is the items starting position. When it is dragged, it ends up in a new place in the list; were going to call this new position its current position.

When changeListItemPosition() is called, both the items starting position and current position are passed, as well as the direction it moved. This is where it gets tricky. Depending on the direction the item was moved, we set up one of two conditional queries. We select all the items in the current list with a position falling between the starting and current positions, then, using the CASE clause, increment or decrement their positions by 1 unless the items position plus or minus one falls outside the range weve selected, at which point we set the items position to the current position. In this way, were able to avoid firing an individual query for each item, which could potentially cause a performance bottleneck.
class ColoredListsItems

// Class properties and other methods omitted to save space

/**

* Changes the order of a list's items

* @return string

a message indicating the number of affected items

*/

public function changeListItemPosition()

$listid = (int) $_POST['currentListID'];

$startPos = (int) $_POST['startPos'];

$currentPos = (int) $_POST['currentPos'];

$direction = $_POST['direction'];

if($direction == 'up')

/*

* This query modifies all items with a position between the item's

* original position and the position it was moved to. If the

* change makes the item's position greater than the item's

* starting position, then the query sets its position to the new

* position. Otherwise, the position is simply incremented.

*/

$sql = "UPDATE list_items

SET ListItemPosition=(

CASE

WHEN ListItemPosition 1>$startPos THEN $currentPos

ELSE ListItemPosition 1

END)

WHERE ListID=$listid

AND ListItemPosition BETWEEN $currentPos AND $startPos";

else

/*

* Same as above, except item positions are decremented, and if the

* item's changed position is less than the starting position, its

* position is set to the new position.

*/

$sql = "UPDATE list_items

SET ListItemPosition=(

CASE

WHEN ListItemPosition-1<$startPos THEN $currentPos

ELSE ListItemPosition-1

END)

WHERE ListID=$listid

AND ListItemPosition BETWEEN $startPos AND $currentPos";

$rows = $this->_db->exec($sql);

echo "Query executed successfully. ",

"Affected rows: $rows";

Finishing th e JavaScript To call our method, we need to modify called saveListOrder() :


function saveListOrder(itemID, itemREL){

js/lists.js

by adding a new function

var i = 1,

currentListID = $('#current-list').val();

$('#list li').each(function() {

if($(this).attr('id') == itemID) {

var startPos = itemREL,

currentPos = i;

if(startPos < currentPos) {

var direction = 'down';

} else {

var direction = 'up';

var postURL = "action=sort&currentListID=" currentListID

"&startPos=" startPos

"&currentPos=" currentPos

"&direction=" direction;

$.ajax({

type: "POST",

url: "db-interaction/lists.php",

data: postURL,

success: function(msg) {

// Resets the rel attribute to reflect current positions

var count=1;

$('#list li').each(function() {

$(this).attr('rel', count);

count

});

},

error: function(msg) {

// error handling here

});

});

This function accepts the ID and rel attribute of the item that was moved. The rel attribute contains the original position of the item, which we need as its starting position. Then we loop through each list item while incrementing a counter ( i ). When we find the list item that matches the moved items ID, our counter now reflects the items current position. We can then determine which direction the item was moved and send the info to db-interaction/lists.php for processing. This function needs to be called when a sortable item is updated, which we accomplish by modifying the following in js/lists.js :
// MAKE THE LIST SORTABLE VIA JQUERY UI

// calls the SaveListOrder function after a change

// waits for one second first, for the DOM to set, otherwise it's too fast.

$("#list").sortable({

handle

: ".draggertab",

update

: function(event, ui){

var id = ui.item.attr('id');

var rel = ui.item.attr('rel');

var t = setTimeout("saveListOrder('" id "', '" rel "')",500);

},

forcePlaceholderSize: true

});

Changing Item Colors

Changing an items color is fairly simple on both the server- and client-side. The PHP To update an items color, we simply pass its ID and the new color code to the method changeListItemColor() and create and execute a query.
class ColoredListsItems

// Class properties and other methods omitted to save space

/**

* Changes the color code of a list item

* @return mixed

returns TRUE on success, error message on failure

*/

public function changeListItemColor()

$sql = "UPDATE list_items

SET ListItemColor=:color

WHERE ListItemID=:item

LIMIT 1";

try

$stmt = $this->_db->prepare($sql);

$stmt->bindParam(':color', $_POST['color'], PDO::PARAM_INT);

$stmt->bindParam(':item', $_POST['id'], PDO::PARAM_INT);

$stmt->execute();

$stmt->closeCursor();

return TRUE;

} catch(PDOException $e) {

return $e->getMessage();

Finishing the JavaScript The function that saves new colors is called by submitting the item ID and new color via POST in the $.ajax() call below in js/lists.js :
// COLOR CYCLING

// Does AJAX save, but no visual feedback

$(".colortab").live("click", function(){

$(this).parent().nextColor();

var id = $(this).parent().attr("id"),

color = $(this).parent().attr("color");

$.ajax({

type: "POST",

url: "db-interaction/lists.php",

data: "action=color&id="

id

"&color="

color,

success: function(msg) {

// error message

});

});

Editing Item Text

Next, lets make sure edited items are updated in the database.

The PHP To save updated items in the database, we need to create a method called updateListItem() . This method will extract the ID of the modified item and the new text from the $_POST superglobal, double-check the item text for disallowed tags, and prepare and execute a query to update the item in the database. Add the following method in inc/class.lists.inc.php :
class ColoredListsItems

// Class properties and other methods omitted to save space

/**

* Updates the text for a list item

* @return string

Sanitized saved text on success, error message on fail

*/

public function updateListItem()

$listItemID = $_POST["listItemID"];

$newValue = strip_tags(urldecode(trim($_POST["value"])), WHITELIST);

$sql = "UPDATE list_items

SET ListText=:text

WHERE ListItemID=:id

LIMIT 1";

if($stmt = $this->_db->prepare($sql)) {

$stmt->bindParam(':text', $newValue, PDO::PARAM_STR);

$stmt->bindParam(':id', $listItemID, PDO::PARAM_INT);

$stmt->execute();

$stmt->closeCursor();

echo $newValue;

} else {

echo "Error saving, sorry about that!";

Finishing the JavaScript Activate this method by modifying the path in

bindAllTabs()

in

js/lists.js

// This is seperated to a function so that it can be called at page load

// as well as when new list items are appended via AJAX

function bindAllTabs(editableTarget) {

// CLICK-TO-EDIT on list items

$(editableTarget).editable("db-interaction/lists.php", {

id

: 'listItemID',

indicator : 'Saving...',

tooltip

: 'Double-click to edit...',

event

: 'dblclick',

submit

: 'Save',

submitdata: {action : "update"}

});

Marking Items as Done

To mark an item as done, the user needs to be able to save a flag in the database that will indicate the items done status. The PHP The toggleListItemDone() method retrieves the items ID and done status from the $_POST superglobal and uses them to update the item in the database:
class ColoredListsItems

// Class properties and other methods omitted to save space

/**

* Changes the ListItemDone state of an item

* @return mixed

returns TRUE on success, error message on failure

*/

public function toggleListItemDone()

$sql = "UPDATE list_items

SET ListItemDone=:done

WHERE ListItemID=:item

LIMIT 1";

try

$stmt = $this->_db->prepare($sql);

$stmt->bindParam(':done', $_POST['done'], PDO::PARAM_INT);

$stmt->bindParam(':item', $_POST['id'], PDO::PARAM_INT);

$stmt->execute();

$stmt->closeCursor();

return TRUE;

} catch(PDOException $e) {

return $e->getMessage();

Finishing the JavaScript To call our method, we write a function called toggleDone() in js/lists.js . This function simply executes a call to the $.ajax() function and sends the item ID and done status to our list handler.
function toggleDone(id, isDone)

$.ajax({

type: "POST",

url: "db-interaction/lists.php",

data: "action=done&id=" id "&done=" isDone

})

Next, we assign toggleDone() as the callback function for the when our user clicks the done tab:
$(".donetab").live("click", function() {

animate()

even that happens

var id = $(this).parent().attr('id');

if(!$(this).siblings('span').children('img.crossout').length)

$(this)

.parent()

.find("span")

.append("<img src='/images/crossout.png' class='crossout' />")

.find(".crossout")

.animate({

width: "100%"

})

.end()

.animate({

opacity: "0.5"

},

"slow",

"swing",

toggleDone(id, 1));

else

$(this)

.siblings('span')

.find('img.crossout')

.remove()

.end()

.animate({

opacity : 1

},

"slow",

"swing",

toggleDone(id, 0));

});

Deleting Items

Finally, we need to allow our users to delete items that they no longer want on their list. The PHP To delete an item, we need to create a method called deleteListItem() in inc/class.lists.inc.php . This method will retrieve the item and list IDs from the $_POST superglobal, then remove the item from the list. Then, to preserve proper order in the list, all items in the list with a position higher than that of the item that was deleted need to be decremented by 1.
class ColoredListsItems

// Class properties and other methods omitted to save space

/**

* Removes a list item from the database

* @return string

message indicating success or failure

*/

public function deleteListItem()

$list = $_POST['list'];

$item = $_POST['id'];

$sql = "DELETE FROM list_items

WHERE ListItemID=:item

AND ListID=:list

LIMIT 1";

try

$stmt = $this->_db->prepare($sql);

$stmt->bindParam(':item', $item, PDO::PARAM_INT);

$stmt->bindParam(':list', $list, PDO::PARAM_INT);

$stmt->execute();

$stmt->closeCursor();

$sql = "UPDATE list_items

SET ListItemPosition=ListItemPosition-1

WHERE ListID=:list

AND ListItemPosition>:pos";

try

$stmt = $this->_db->prepare($sql);

$stmt->bindParam(':list', $list, PDO::PARAM_INT);

$stmt->bindParam(':pos', $_POST['pos'], PDO::PARAM_INT);

$stmt->execute();

$stmt->closeCursor();

return "Success!";

catch(PDOException $e)

return $e->getMessage();

catch(Exception $e)

return $e->getMessage();

Finishing the JavaScript To activate this method, we need to modify our jQuery by updating the section in js/lists.js that deals with item deletion:
// AJAX style deletion of list items

$(".deletetab").live("click", function(){

var thiscache = $(this),

list = $('#current-list').val(),

id = thiscache.parent().attr("id"),

pos = thiscache.parents('li').attr('rel');

if (thiscache.data("readyToDelete") == "go for it") {

$.ajax({

type: "POST",

url: "db-interaction/lists.php",

data: {

"list":list,

"id":id,

"action":"delete",

"pos":pos

},

success: function(r){

var $li = $('#list').children('li'),

position = 0;

thiscache

.parent()

.hide("explode", 400, function(){$(this).remove()});

$('#list')

.children('li')

.not(thiscache.parent())

.each(function(){

$(this).attr('rel',

position);

});

},

error: function() {

$("#main").prepend("Deleting the item failed...");

});

else

thiscache.animate({

width: "44px",

right: "-64px"

}, 200)

.data("readyToDelete", "go for it");

});

Moving On

We have now succeeded in building all of the AJAX functionality for our app! There was a ton of information in this article, and we went through it rather quickly, so please post any questions you have in the comments! In the final installment of this series, well go over the security measures and other finishing touches this app needs to be ready for public use. Well also go over some of the features we hope to add in the future.
Hooray we made it! First of all, thanks for following along this whole journey. You can go check out the real live app for yourselves:
http://coloredlists.com Below we're going to wrap up a few things by talking about some of the choices we made, security precautions, and ideas we (and you) have for a version 2.0 of this app.
ALL SERIES NAVIGATION

Part 1 - Planning the App: Basic Idea and Design Part 2 - Planning the App: Database Architecture and Development Approach Part 3 - Designing the App: Workflow Map and Photoshop Design Part 4 - Designing the App: HTML and CSS Part 5 - Developing the App: User Interaction Part 6 - Developing the App: Adding AJAX Interactivity Part 7 - Developing the App: List Interaction Part 8 - Security & The Future

And finally.... the application!

Colored Lists

Object-Oriented Programming
Because we should always aim to be efficient when programming, we built this app with the concept of DRY programming in mind. DRY stands for "Don't Repeat Yourself" and should lie somewhere near the core of our programming philosophy. In our opinion, taking the object-oriented programming (OOP) approach was the best way to keep this app DRY. OOP allows us to group common methods together and separate tasks out without needing to pass parameters from function to function. For a little more information on OOP and why it's beneficial, read Jason's introduction to OOP.

Security
Security is incredibly important in any application. We have users with accounts who are storing data with us. Those users are putting their trust in us to make sure their data is safe, which includes their password and all the information they've entered into the lists. This app is already pretty darn secure. Passwords are stored in encrypted formats and never sent in the clear via Email. All the

interaction that happens with the database is secure. Only users who are logged in can issue commands which result in database changes, and those users are only able to issue commands that affect their own data. But because there is a variety of AJAX stuff going on in this app, our security needs to take into account a few more scenarios. First, our JavaScript (like all JavaScript) is publically viewable. This JavaScript contains the code for making AJAX calls, meaning the URL we are sending to and what data that URL is expecting. This tells potential attackers a good bit of information regarding how they might send malicious requests. Because of this, we need to be very careful and ensure that all incoming data is escaped properly.

Security on the Server Side


Avoiding attacks on the server side involves two major risk factors: first, the potential for database attacks; and second, the potential that a malicious user could submit dangerous data that hurts our app or users in some way when read out of the database and displayed. Fortunately, PHP provides us with several methods to combat these risks.

PDO
Database attacks, called SQL injection, are a particularly nasty form of attack. A vulnerable database can be read, manipulated, or deleted entirely by a malicious user. This means that it is really important that we keep any kind of SQL injection from happening. Lucky for us, PHP Data Objects (PDO) virtually eliminates the risk for SQL injection through the use of prepared statements, which are like query templates that we can customize with parameters. All the escaping is done for us when the parameters are inserted into the query, so it's virtually impossible for SQL injection to occur while using prepared statements. It was because of this powerful security advantage that we chose PDO for this app. (Keep in mind that prepared statements are not exclusive to PDO; other database extensions, such as MySQLi, also support them.)

Data Escaping
While PDO is powerful against SQL injection, it doesn't help us when we've read the information out of the database. If a malicious user injects dangerous tags into our database, they'll still be dangerous when they're retrieved unless we take further measures to sanitize user data. Fortunately, PHP has built-in functions that will allow us to perform basic sanitization of user input. We're namely using strip_tags() with a whitelist to make sure no <script> tags or other potentially dangerous tags make it into the database. Also, because we never want that sort of thing to be allowed, we're performing this escaping before the data is inserted into the database.

Security in the JavaScript


First, a good measure is to "Pack" the javascript so it isn't so easily readable, as well as downloads faster. There are a lot of tools available to do this, including this one by Dean Edwards.

Client Side Sanitization


Secondly, because we are inputting data and turn it around to display immediately on the screen, it's best to do some of that input scrubbing directly in the JavaScript. When a user enters a new list item, we'll take two steps to scrub it. First we'll ensure they aren't naughtily trying to insert immediately executable JavaScript into links:

// Check for JS in the href attribute function cleanHREF(str) { return str.replace(/\<a(.*?)href=['"](javascript:)(.+?)<\/a>/gi, "Naughty!"); }

Then we'll also scrub that input text for any other HTML. Some HTML we will allow, in case users want to format their lists a bit like with <strong> tags and the like. With the function below, we'll strip away all tags except those set up in a whitelist. NOTE: The strip_tags() function used below is part of the php.js project, which has ported a number of useful PHP functions to JavaScript.

var $whitelist = '<b><i><strong><em><a>',

// Strip HTML tags with a whitelist function strip_tags(str, allowed_tags) {

var key = '', allowed = false; var matches = []; var allowed_array = []; var allowed_tag = ''; var i = 0; var k = ''; var html = '';

var replacer = function(search, replace, str) { return str.split(search).join(replace); };

// Build allowes tags associative array if (allowed_tags) { allowed_array = allowed_tags.match(/([a-zA-Z]+)/gi); }

str += '';

// Match tags matches = str.match(/(<\/?[\S][^>]*>)/gi);

// Go through all HTML tags for (key in matches) { if (isNaN(key)) { // IE7 Hack continue; }

// Save HTML tag html = matches[key].toString();

// Is tag not in allowed list? Remove from str! allowed = false;

// Go through all allowed tags for (k in allowed_array) { // Init allowed_tag = allowed_array[k]; i = -1;

if (i != 0) { i = html.toLowerCase().indexOf('<'+allowed_tag+'>');} if (i != 0) { i = html.toLowerCase().indexOf('<'+allowed_tag+' ');} if (i != 0) { i = html.toLowerCase().indexOf('</'+allowed_tag) ;}

// Determine if (i == 0) { allowed = true; break; } }

if (!allowed) { str = replacer(html, "", str); // Custom replace. No regexing } }

return str; }
These functions are implemented in js/lists.js before sending off the AJAX request that adds a new list item...

. . . // AJAX style adding of list items $('#add-new').submit(function(){ // HTML tag whitelist. All other tags are stripped. var $whitelist = '<b><i><strong><em><a>', forList = $("#current-list").val(); newListItemText = strip_tags(cleanHREF($("#new-list-itemtext").val()), $whitelist), . . .

POST vs GET
One last small measure we've taken to secure our app is to use POST over GET for all of our AJAX calls. This is done because the GET method should only be used for retrieval, and not for any action that will modify data in any way. The primary reason not to use GET for modifying data is that a request made using GET is sent in the URL (i.e. http://example.com?get=request&is=this&part=here). There's an inherent danger in modifying data based on the information passed in the URL in that a user can cause duplicate processing by accidentally refreshing his or her browser. A secondary, less important reason to use POST is that it's a little harder to send a bogus request using POST, which provides a (minor) deterrent to malicious users.

2.0 Features
Of course our work as designers and developers is never done. This is a great start on a simple and usable list application, but right away new features jump to mind. Here are some ideas of ways to expand functionality. Perhaps they slightly complicate things, but are all probably great ideas assuming they are implemented well.

List sharing Enter an email address for someone to share the list with. Sharing meaning literally collaborative editing. The user would need

an account, so if they already have one they would just be emailed and asked to join the list (they can accept or not accept). If that email address did not have an account, they would be promoted to join first.

Multiple lists Right now a user can have only one list. It would probably be useful for users to keep multiple lists. Perhaps a dropdown menu for toggling between lists and a simple button for adding new ones. Plenty of interface to think about here, including figuring out how to delete lists. RSS Each list could have it's own RSS feed. Options would probably be necessary, like what the RSS feed would contain (e.g. Do you wish to see entries for when list items are completed or not?). Feed URLs could be long gibberish URL's, so they are essentially completely private unless specifically shared. iPhone interface Logging in via iPhone or other mobile device would have a better more optimized experience.

End It with a Giveaway!


In an effort to promote his new book, PHP for Absolute Beginners, Jason is giving away five copies of it at random. To enter the contest, leave a comment on this article and use the text "PHP for Absolute Beginners" in the comment. Make sure you use your real email address so we can get in touch with you. We'll pick the random winners next Friday.

Even if you don't win a free copy (or don't want to wait) we want to give you a little somethin' somethin' for sticking through this series: you can get 10% off the eBook version of PHP for Absolute Beginners using this discount code: PHPXBRZQXSIKG (good through 12/31/2009).

What Do You Think?


Give it to us straight: what do you think? What features would you like to see included in the 2.0 version of Colored Lists? Did we miss anything? Are there holes in our code? We'd love to see your optimizations, ideas, and other constructive criticisms; let's hear it in the comments!

Bugs, Security, and Other Tweaks

There were supposed to be only eight parts to this series, but as we started releasing them, Chris and I realized that there was going to need to be a follow-up post to address some of the bug fixes, security patches, and a few other minor changes. NOTE: All the changes were going to cover in this article are already included in the source code.
Bug Fixes

After releasing the live app, a handful of bugs showed up in the comments. We tried to address these as quickly as possible to keep the app from causing unnecessary grief for our users. Well go over the major bugs here. Account Created, List Failed Error The first thing we saw was that when there were more than just one or two people trying to create accounts, the app started failing to create user lists after an account was created. Upon reviewing the code, I found that the error seemed to be coming from the following line:
$userID = $this->_db->lastInsertId();

seemed to be unreliable, and therefore the query to insert a new list into the database was failing regularly. To fix this, we implemented a complex query that worked around the use of lastInsertId() :
$userID /*

* If the UserID was successfully

* retrieved, create a default list.

*/

$sql = "INSERT INTO lists (UserID, ListURL) VALUES

SELECT UserID

FROM users

WHERE Username=:email

),

SELECT MD5(UserID)

FROM users

WHERE Username=:email

)";

Performance-wise, this is going to be slower than the original post, but its incredibly more reliable (since implementing this fix, weve had no reports of this error). Any MySQL supergeeks who may have a better solution, please post it in the comments! Double- Clicking Add Sometimes Added Duplicate Entries One little user interface quirk that was discovered was that you could click multiple times in succession on the Add button. In our original JavaScript, we only cleared the value of the input field upon a successful AJAX result. That is ideal, since when that text disappears that is your visual queue that its been added to your list successfully. Plus, generally that happens quickly enough it feels pretty instant. However, if you straight up double-click on that add button (which people absolutely still do that),you might get two or more requests off before the success comes back and clears the fields (when the field is clear, the submit button will do nothing). One method to fix this could have been to clear the field as soon as a click happens, but the problem there is that if the save is unsuccessful youll lose your text. Instead, we just add a little more smarts. When the submit button is clicked and there is text ready to add, the AJAX request is made and the button is disabled. Upon success, the field is cleared and the button is re-enabled. This ensures only one submission is possible. In
/js/lists.js

, we added the following at line 114:

$('#add-new').submit(function(){

//

... variables and whitelist stuff ...

if (newListItemText.length > 0) {

// Button is DISABLED

$("#add-new-submit").attr("disabled", true);

$.ajax({

// Ajax params stuff

success: function(theResponse){

// list adding stuff

// field is cleared and button is RE-ENABLED

$("#new-list-item-text").val("");

$("#add-new-submit").removeAttr("disabled");

NOTE: As you can see, we remove the disabled attribute entirely upon a successful response from our query. That is the only way to re-able a submit button. Setting disabled to false has no effect. Changing Email Address with a Blank Email Crippled Account It was also brought to our attention that clicking the Change Email button with a blank field would not only succeed, but would cripple the account and make it unusable. Fixing this was as simple as making sure the email address submitted was valid by inserting the following in the updateEmail() method in /inc/class.users.inc.php :
if( FALSE === $username = $this->validateUsername($_POST['username']) )

return FALSE;

Then, instead of binding the $_POST value to the query, we bind the new variable $username , which contains the valid email address if the check didnt fail. Note the use o f the new function validateUserEmail() well go over that in the next section on security.
Security Issues

After we worked the bugs out of our app, we turned to the security holes that were pointed out by commenters. Some of these were simple oversights on our part, and some of the problems were news to us. With the help of our readers, though, we tried to patch everything up.

JavaScript Could Be Inserted Into Edited Items When creating new items, we checked for any JavaScript in the input using the cleanHREF() function, then stripped out unwanted tags on the server side using strip_tags() and a whitelist of acceptable tags. However, we had missed that JavaScript could be inserted into existing items when they were edited. To correct this issue, we turned to a preexisting input sanitizing function (lines 00326-00384) posted by Zoran in the comments of Part 8. We wrapped the code in a method called
/** cleanInput()

and placed it in

/inc/class.users.inc.php

* Removes dangerous code from the href attribute of a submitted link

* @param string $input

The string to be cleansed

* @return string

The clean string

*/

private function cleanInput($data)

// http://svn.bitflux.ch/repos/public/popoon/trunk/classes/externalinput.php

//

----------------------------------------------------------------------

// | Copyright (c) 2001-2006 Bitflux GmbH

//

----------------------------------------------------------------------

// | Licensed under the Apache License, Version 2.0 (the "License");

// | you may not use this file except in compliance with the License.

// | You may obtain a copy of the License at

// | http://www.apache.org/licenses/LICENSE-2.0

// | Unless required by applicable law or agreed to in writing, software

// | distributed under the License is distributed on an "AS IS" BASIS,

// | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or

// | implied. See the License for the specific language governing

// | permissions and limitations under the License.

//

----------------------------------------------------------------------

// | Author: Christian Stocker <[email protected]>

//

----------------------------------------------------------------------

//

// Kohana Modifications:

// * Changed double quotes to single quotes, changed indenting and spacing

// * Removed magic_quotes stuff

// * Increased regex readability:

//

* Used delimeters that aren't found in the pattern

//

* Removed all unneeded escapes

//

* Deleted U modifiers and swapped greediness where needed

// * Increased regex speed:

//

* Made capturing parentheses non-capturing where possible

//

* Removed parentheses where possible

//

* Split up alternation alternatives

//

* Made some quantifiers possessive

// Fix &entityn;

$data = str_replace(array('&amp;','&lt;','&gt;'), array('&amp;amp;','&amp;lt;','&amp;gt;'), $data);

$data = preg_replace('/(&#*w )[x00-x20] ;/u', '$1;', $data);

$data = preg_replace('/(&#x*[0-9A-F] );*/iu', '$1;', $data);

$data = html_entity_decode($data, ENT_COMPAT, 'UTF-8');

// Remove any attribute starting with "on" or xmlns

$data = preg_replace('#(<[^>] ?[x00-x20"'])(?:on|xmlns)[^>]* >#iu', '$1>', $data);

// Remove javascript: and vbscript: protocols

$data = preg_replace('#([a-z]*)[x00-x20]*=[x00-x20]*([`'"]*)[x00-x20]*j[x00-x20]*a[x00-x20]*v[x00-x20]*a[x00-x20]*s[x00x20]*c[x00-x20]*r[x00-x20]*i[x00-x20]*p[x00-x20]*t[x00-x20]*:#iu', '$1=$2nojavascript...', $data);

$data = preg_replace('#([a-z]*)[x00-x20]*=(['"]*)[x00-x20]*v[x00-x20]*b[x00-x20]*s[x00-x20]*c[x00-x20]*r[x00-x20]*i[x00x20]*p[x00-x20]*t[x00-x20]*:#iu', '$1=$2novbscript...', $data);

$data = preg_replace('#([a-z]*)[x00-x20]*=(['"]*)[x00-x20]*-moz-binding[x00-x20]*:#u', '$1=$2nomozbinding...', $data);

// Only works in IE: <span style="width: expression(alert('Ping!'));"></span>

$data = preg_replace('#(<[^>] ?)style[x00-x20]*=[x00-x20]*[`'"]*.*?expression[x00-x20]*([^>]* >#i', '$1>', $data);

$data = preg_replace('#(<[^>] ?)style[x00-x20]*=[x00-x20]*[`'"]*.*?behaviour[x00-x20]*([^>]* >#i', '$1>', $data);

$data = preg_replace('#(<[^>] ?)style[x00-x20]*=[x00-x20]*[`'"]*.*?s[x00-x20]*c[x00-x20]*r[x00-x20]*i[x00-x20]*p[x00x20]*t[x00-x20]*:*[^>]* >#iu', '$1>', $data);

// Remove namespaced elements (we do not need them)

$data = preg_replace('#</*w :w[^>]* >#i', '', $data);

do

// Remove really unwanted tags

$old_data = $data;

$data = preg_replace('#</*(?:applet|b(?:ase|gsound|link)|embed|frame(?:set)?|i(?:frame|layer)|l(?:ayer|ink)|meta|object|s(?:cript|tyle)|t itle|xml)[^>]* >#i', '', $data);

while ($old_data !== $data);

return $data;

Then, we modified

updateListItem()

on line 239 to call the new method:

$newValue = $this->cleanInput(strip_tags(urldecode(trim($_POST["value"])), WHITELIST));

CATCH: This function appears to encode any non-English characters. Foreign language users may see some unexpected behavior. Cross-Site Request Forgeries Another risk we hadnt considered when building this app was the possibility that a malicious user could send bogus requests to our app by piggybacking on a Colored Lists users session in a form of attack called Cross-Site Request Forgeries (CSRF). The snippet of JavaScript below, placed on any site, would be executed if a user that was logged in to our app were to visit the page. (Huge thanks to Dan at Sketchpad for pointing this out and providing the above sample attack.)
<script type="text/javascript">

var form = document.createElement("form");

form.setAttribute("method", "post");

form.setAttribute("action", "http://coloredlists.com/db-interaction/users.php");

var fields = new Array();

fields["user-id"] = "158";

fields["action"] = "deleteaccount";

for(var key in fields)

var hiddenField = document.createElement("input");

hiddenField.setAttribute("type", "hidden");

hiddenField.setAttribute("name", key);

hiddenField.setAttribute("value", fields[key]);

form.appendChild(hiddenField);

document.body.appendChild(form);

form.submit();

</script>

To remedy this, we need to generate a token to include with each form submission that is also stored in the session. That way we can make sure the two match before executing any requests. In doing this, CSRF is virtually eliminated. In
/common/base.php

, we added following at line 19:

if ( !isset($_SESSION['token']) )

$_SESSION['token'] = md5(uniqid(rand(), TRUE));

This creates a unique token for the users session. Then, on every form on our site, we added the following hidden input:
<input type="hidden" name="token"

value="<?php echo $_SESSION['token']; ?>" />

And updated both /db-interaction/lists.inc.php and starting at line 15:


if ( $_POST['token'] == $_SESSION['token']

/db-interactions/users.inc.php

with the following

&& !empty($_POST['action'])

&& isset($_SESSION['LoggedIn'])

&& $_SESSION['LoggedIn']==1 )

Now any request made without a valid token will fail. For more on CSRF, visit Chris Shifletts blog. Some Input Was Improperly Sanitized Above, we talked about the problem with blank email change requests breaking accounts, and we created a method called validateUsername() that made sure only valid email addresses were allowed to change an existing user email. That method looks like this:
/**

* Verifies that a valid email address was passed

* @param string $email

The email address to check

* @return mixed

The email address on success, FALSE on failure

*/

private function validateUsername($email)

$pattern = "/^([ a-zA-Z0-9]) ([ a-zA-Z0-9._-])*@([a-zA-Z0-9_-]) ([a-zA-Z0-9._-] ) $/";

$username = htmlentities(trim($email), ENT_QUOTES);

return preg_match($pattern, $username) ? $username : FALSE;

Essentially, it uses a regular expression to match the pattern of a valid email address, and either returns the validated email address of boolean FALSE .

Other Changes

Aside from bugs and security patches, there were a couple parts of the site that we just felt should have been better. Made Public URLs Tougher to Guess First, the original public list URLs were determined using dechex() , and they were short and easy to guess. We modified them to use MD5 instead to create longer, much more difficult to guess public URLs. This happens right at the lists creation when the query calls SELECT MD5(UserID) in createAccount() on line 100. Allowed Safe Links in List Items Some links are acceptable, and we felt that our app would be much more useful if safe links were allowed in list items. To allow this, we simply removed the call to strip_tags() in formatListItems() (found in /inc/lists.inc.php on line 173):
return "tttt<li id="$row[ListItemID]" rel="$order" "

. "class="$c" color="$row[ListItemColor]">$ss"

. $row['ListText'].$d

. "$se</li>n";

The items are now sanitized on the way in, so we dont need to worry about them on the way out.
Summary

The steps we took above helped make our app more secure and dependable. However, we know that nothing is ever perfect, so if youve got other bugs, security holes, or suggestions, let us know in the comments!

Vous aimerez peut-être aussi