CREATING A WEB APP from SCRATCH

Skullipso
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 blog Ennui Design. 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:

Home [Type the company address] [Type the phone number] [Type the fax number] [Pick the date]

Contents
It s Easy, Right? .................................................................................................................................... 4 The Big Idea ......................................................................................................................................... 4 Sketch It Out ........................................................................................................................................ 4 Early UI Planning .................................................................................................................................. 5 The Screens ......................................................................................................................................... 6 Features ........................................................................................................................................... 7 Moving On ........................................................................................................................................... 7 Part II....................................................................................................................................................... 7 Where We're At ................................................................................................................................... 7 Okay, So We Know How It Looks, but How Does It Work? .................................................................... 7 Data Storage Planning and Database Structure .................................................................................. 7 Creating the Database ......................................................................................................................... 7 Table 1: User Information ............................................................................................................ 8 Table 2: List Information .............................................................................................................. 8 Table 3: List Items ........................................................................................................................ 8 Data Handling Planning and Script Organization ................................................................................ 9 Planning our PHP Classes ................................................................................................................... 10 Moving On ......................................................................................................................................... 10 Part III.................................................................................................................................................... 11 Developing a Workflow...................................................................................................................... 11 Bringing It to Life Photoshop .............................................................................................................. 12 Moving on ......................................................................................................................................... 15 Part IV ................................................................................................................................................... 15 Web Root Organization ...................................................................................................................... 15 Header............................................................................................................................................... 16 Footer ................................................................................................................................................ 17 Sidebar .............................................................................................................................................. 17 Main Page.......................................................................................................................................... 17 Logged in (The List) ............................................................................................................................ 18 Logged out (Public list) ....................................................................................................................... 19 Logged out (Sales).............................................................................................................................. 19

Account Page ..................................................................................................................................... 19 Other Form Pages ........................................................................................................................... 21 The CSS .......................................................................................................................................... 21 Moving Along..................................................................................................................................... 23 Part V .................................................................................................................................................... 23 Where Are We? ................................................................................................................................. 23 Connecting to the Database ............................................................................................................... 24 Building the Class ............................................................................................................................... 26 Connecting the Class to the Database ................................................................................................ 26 Creating an Account........................................................................................................................... 27 Creating the Sign-Up Form ................................................................................................................. 28 Saving the User's Email Address ......................................................................................................... 29 Generating and Sending a Verification Email ...................................................................................... 31 Verifying the User's Account .............................................................................................................. 33 Verifying the User's Email and Verification Code ................................................................................ 34 Updating the User's Password and Verified Status ............................................................................. 35 Logging In .......................................................................................................................................... 37 Building the Login Method ................................................................................................................. 39 Logging Out ....................................................................................................................................... 40 Modifying Account Information ......................................................................................................... 40 Building the Interactions File .............................................................................................................. 44 Updating the Email Address ............................................................................................................... 45 Updating the Password ...................................................................................................................... 46 Deleting the Account ......................................................................................................................... 47 Resetting an Account Password ......................................................................................................... 49 Returning the Account to "Unverified" Status .................................................................................... 50 Building the Reset Pending Page ........................................................................................................ 51 Generating a "Reset Password" Email ................................................................................................ 51 Resetting the Password...................................................................................................................... 52 Moving On... ...................................................................................................................................... 54 Part VI ................................................................................................................................................... 54 The Big Thing: Saving the List ............................................................................................................. 54

............................................................................................................................ 62 Appending new list items ..................................................................................................... 79 Editing Item Text............................................................................... 66 Saving New List Items ........................................... 55 First things first: calling the JavaScript files........................................................................................................................... 73 Reordering List Items ............................ 88 Data Escaping ...................................................................................................................................................................................................................... 88 PDO ................................. 58 Marking items as done ........... 64 Defining the Class ............................ 90 2.............................................. 90 ................................................................ 64 Where We're At .............................................................................................................. 61 Click-to-edit list items .....................................................................................................................0 Features .................................................................................................................................................................................................................................................................................................................... 88 POST vs GET ..................................................... 82 Deleting Items ............ 88 Security in the JavaScript ............................................................ 64 Part VII ..............................................................................................................................................................................................................................................................................................................................................................................................................................................................Interface JavaScript........................................................................................................................................................................................................................ 76 Changing Item Colors ................................................................. 84 Moving On .................. 65 Displaying List Items ................................................................ 87 Object-Oriented Programming .............................................................................................................................................................................................................................................................................................................................................. 87 Security .................. 63 Moving On ............................................................................................ 55 Cleaning up the Markup with JavaScript............................................................................................................................................................................................. 87 Security on the Server Side ................................ 56 Making the list drag / sortable ........................................... 58 Color Cycling ...................................... 81 Marking Items as "Done" ........................................................................................................... 60 Deleting list items ................................................................................................................................................................................................................................................................................................................................................................................................................................................................ 87 Part VIII ...............................................................

I am going to kick things off by introducing the idea. things to bring camping list« As you finish things. Jason will be handling the back-end stuff like application planning and database stuff. The Big Idea This ³list app´ is going to be called Colored Lists. helpful. Computers. That means good back end code that does what it¶s supposed to do and well. Sounds easy right? Even the PHP dabblers out there probably could throw something like this together fairly quickly. At the end of the week. UI. we can make crossing off items just a click and we can make rearranging them a matter of drag and drop. we¶ll unleash the actual working application for you. Sign up for an account. it¶s not that easy. are a perfect place for lists. 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. Sketch It Out No need to get fancy right away. we are going to create an app that hopefully does all these things pretty well. Here is a very rudimentary sketch of what the app might look like: . and general front-end stuff. With a list on a computer. First of all. This makes paper lists potentially messy and inefficient. Right? What we¶re going to create is a ³list app´. and the web. a grocery list. you cross them off. The idea being focused on simplicity and usefulness. can be for anything: a to-do list. we can use colorization. no. That means a good UI that is intuitive. We aren¶t out to tell you this is the greatest app ever made. For dealing with relative importance. and then I will be handling the design. Lists (in real life). which could also be used for things like grouping. and get started making a list in just a few seconds. We are going to be going back and forth from here over to my friend Jason Lengstorf¶s blogEnnui Design. Here is the plan: It s Easy. it needs to work and it needs to work well. None of these things is trivial.Today we begin Part 1 of an 8-Part series on building a web application from absolute scratch to a complete product. but rather. Through this whole 8-part series. right? Well the fact is. and pleasurable to use. Things on a list may be of different relative importance as well. It means keeping the app secure and users data private.

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. y y y y Click-to-edit Drag and drop Two-click delete Automatic saving (after any action) . There are some interactive elements to the left and right of each list item. Lets take a closer look. because the big idea here is to colorize each list item. so putting them inside a colored box makes sense. Those are going to be for accomplishing the basic things we intent people to be able to do with their colored list.Looks like a list to me. Each list item is a long rectangle.

that¶s why are useful. we can come up with quite a number of ³screens´. it¶s usefulness is diminished and nobody will use it.All this stuff basically adds up to a whole bunch of AJAX. Lists are easy and quick. We don¶t want to load special screens to do relatively trivial tasks like deleting a list item. smoothly and with proper feedback in response to mouse clicks without page refreshes. That stuff should happen seamlessly. where the majority of interaction with this app happens on a single page. and not trying to adhere to any particular fad. or states the application can be in. y Homepage y y y y y y Logged out = Intro/Sales Page Logged in = Your list Log in page Settings page Lost password page Account activation page . In a sense. we are creating a one-page app. If this app is complicated. This is certainly by design. The Screens Just doing some quick brainstorming of the idea so far.

or that yours does better. we've planned the way our app is going to look. we made the choice to go with a combination of MySQL and PHP to handle all our behind-the-scenes data handling and stor age. as well as given our selves a basic idea of how the app is going to function. The next step is to figure out what's going to happen behind the scenes to allow our app to work the way we've planned. to access that database we'r e going to need some kind of ser ver -side scripting language. So We Know How It Looks. Each list will have a unique URL that can be publicly shared. However . The thir d table will keep tr ack of list items. Moving On Now that we have the idea in place of what we want to build. For this app. we want to make sur e that our user s can use char acter s fr om any language in their lists. in the next part we¶ll dive into looking at what this is going to take in terms of server-side technology. The database will be named cl_db . Okay.y Emails Yep. but we'r e going to use r aw SQL commands for lear ning pur poses. You can use the GUI if you want. befor e we can cr eate our tables. A visitor visiting this URL can see the list in it¶s exact current state. The fir st table will store user infor mation. All the fancy AJAX this app will have is certainly a feature. of cour se. we'll need to stor e list information in a database. we'll be oper ating under the assumption that you'r e building and testing locally (we r ecommend XAMPP). as they are a vital part of the process and interaction with an app. so it's also a good idea to . Navigate to http://localhost/phpmyadmin and open the SQL tab. even emails should be considered a part of the ³screens´. which is all the information that is r equired to build the database. This is just as much for marketing as it is for your actual product. but How Does It Work? In or der to keep a list available after a user logs out of our app. but not interact with it as far as editing/adding/deleting. Features People love ³features´. Creating the Database Of cour se. The one feature that we will focus on with this app is ³public sharing´. we'll only need thr ee tables in our database. we'll need a database to work with. Part II Where We're At Up to this point. And. Since this app is fairly simple. but that stuff these days is getting more and more expected rather than a feature. Data Storage Planning and Database Structure Our fir st step is to decide how we want to or ganize list data. For anyone working along at home. and the second will stor e list information. Things that your app has that other apps don¶t have.

we also need to keep a r ecor d of the item's position and color. Also.lists( ListID INT PRIMARY KEY AUTO_INCREME NT. UserID INT NOT NULL. we'll assign each user a unique numer ic identifier . verified TINYINT DEFAULT 0) Table 2: List Information List information is fairly str aightforwar d. and the infor mation the user enter s as his or her list item. The MySQL command to build this table will look like this: CREATE TABLE cl_db.users( UserID INT PRIMARY KEY AUTO_INCREME NT. This means we need to have a unique confirmation link and a place to stor e whether or not an account has been verified. Execute this command fr om the SQL tab in phpMyAdmin and the new database will become available. we also need to stor e the user 's email addr ess. The command to cr eate this database is: CREATE DATABASE `cl_db` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci. to suppor t featur es we'll be adding later on.specify the collation and char acter set of the database. To build this table. Now that we've got a database. execute the following MySQL command in phpMyAdmin's SQL tab: CREATE TABLE cl_db. We'll be using the UTF-8 character set with gener al collation. Each list will have a unique identifier. This helps us limit the amount of r edundant information that needs to be stor ed. To determine that an email address is r eal. we need a table that will stor e our list items. which suppor ts multilingual char acter s and is case-insensitive. the ID of the list it belongs to. we'r e ready to build our tables. Username VARCHAR(150) NOT NULL. Password VARCHAR(150) . a unique URL. Each list item needs a unique identifier . and the identifier of the user that owns the list. . we'll be sending new user s a confirmation link in an email. Table 1: User Information Using our list app doesn't r equir e a high security clear ance. and in the inter est of keeping redundant data stor age to a minimum. ListURL VARCHAR(150)) Table 3: List Items Finally. which they need to follow befor e using our app. ver_code VARCHAR(150). all we need to know is that you've got an email addr ess and that it's r eal.Execute this command in the SQL tab of phpMyAdmin: CREATE TABLE cl_db. Of cour se.list_items ( ListItemID INT PRIMARY KEY AUTO_INCREMENT.

it's always a good idea to take a moment and map out ever ything that needs to be done. we'll be using an object-or iented appr oach. Next. INT NOT NULL. INT NOT NULL NOTE: The ListItemDone field was omitted in the original post of this ar ticle. VARCHAR(150). Because gr eat code star ts with gr eat organization.ListID ListText ListItemDone ListItemPosition ListItemColor ) INT NOT NULL. It was added her e after being pointed out in the comments by FuSi0N. . we can gr oup tasks into logical arr angements. we'll plan how we'r e going to cr eate and access our database information using PHP. Data Handling Planning and Script Organization Befor e we star t coding. INT NOT NULL. That way. The database with our thr ee tables Now we have our database and the three tables we'll need to build our app.

As we build our app. such as one that will send a verification email. it becomes an incr edibly power ful tool that incr eases por tability. Make sur e you'r e subscribed to CSS-Tricks so you don't miss out! . r eadability. Moving On In our next installment of this series. we'll also need some support methods. we'll go into mor e detail on how these scripts will work. The list of available actions ends up looking like this: y y y y y Create a list item Update a list item Delete a list item Change a list item's position Change a list item's color Action Handling Scripts Finally. List Actions Class The list actions class. The fir st class is going to handle user inter actions. Again. and usability of scripts. and logging in and out. which we'll call ColoredListsItems . also has a pr etty shor t list of methods. This class will handle ever ything else our app does. After learning object-or iented pr ogr amming. These will deter mine what the user 's desir ed action is. such as r egister ing. needs to handle all the actions our app will per for m that are user account-r elated. User Actions Class Our fir st class. we'll cr eate the application workflow. Our app is pr etty simple. and moving list items. which is anything a user can do with his or her list items. cr eate an instance of the pr oper object. we'll need a couple action-handling scripts. updating infor mation. which we'll name ColoredListsUsers . we end up with pr etty shor t list: y y y y y y 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. this is a pr etty simple application. so when we map out ever ything that user s can do with their account. We'll define these methods as we build the app in later installments of this series. The second class will handle list inter actions. so we'll only need two classes. and call the corr ect method.Planning our PHP Classes Object-oriented pr ogr amming pr ovides an easy way to keep r elated functions gr ouped together . deleting. such as adding.

but ther e is a little bit mor e that goes into a full application. and this will be essentially the "sales" page too. and delete their account. and we have some back-end str uctur e in place to deal with user s and all the data that goes along with these lists. and delete their accounts. so we'r e going to need to think about some workflow. It's fast and it's just to help you think and plan for the things you need. user s need to be able to change their passwor ds. we need a 'Lost Passwor d' featur e. as well as a way to log out. explaining the app. It was a good idea to star t with the "meat" of the app. While logged out. but that's what sketching is. Because user s should be just as concer ned about secur ity as we ar e. Logged in. like change their email. These ancillar y options ar e pr obably best ser ved on an account page. The "big idea" is in place. Because we have user s. Because user s can be for getful. Her e is some flow: Basic app workflow It's not pr etty folks. Ther e will be two differ ent states for the homepage: logged in and logged out. that means we need a sign up for m and a log in ar ea for r eturning user s. change their password. the homepage will be the user 's list itself. So now we ar e looking at at least two new pages: Account Settings and Registr ation. people need a way to sign in and to r egister . Logged in user s will also need to do some ancillar y stuff r elated to their account. we know how we want the lists to be displayed and inter acted with. Our one-page app has just tur ned into a four or five page app. change their login.Part III Developing a Workflow We have a gr eat star t going on our list application at this point. .

thinking about the data they need and how this app is going to actually work. So we'd better get star ted actually designing her e. . Not that we plan to char ge for it. the homepage is going to act mor e like a "sales" page. Below the list a box for entering new list items. List items ar e big color ed blocks with buttons for their associated actions near by. The home page as it appears when logged in Homepage (Logged Out) When logged out. Ther e isn't much to say about a list app. but just to explain and get people inter ested in using it. The list is obviously the most impor tant thing. so let's keep the header small and keep the list fr ont and center . so we'll keep it simple. Homepage (Logged In) This is the meat of our application so let's star t her e.Bringing It to Life Photoshop Our developer is alr eady ahead of us.

When logged out. So we'll do that and throw together a favicon while we'r e at it. All the list item tabs Favicon Registration . also the r ollover states). in our case. we'll encourage the visitor to sign up Small Bits We've been designing long enough to know we might as well make the little buttons into a separ ate file and keep them together as a sprite (a sprite is multiple images combined into one to save HTTP r equests.

The link in that email will "activate" their account and they can choose the password at that time.Our intention with r egistr ation is going to be extr emely simple. It's not cheating or being lazy. Account We'll use the same form design as the r egistr ation page her e. The registration form As small as this is. our r egistr ation page can be pr etty dar n simple. So. this r egistr ation page sets the stage for other forms. it's good design thr ough consistency! Account controls Buttons . We have a label/input pair her e that can be used for any input/pair in any of our site's forms. They will be sent an email with a link in it to complete r egistr ation. We'r e going to ask for a user 's email and that's it.

Part IV It¶s time to get our hands dirty with some markup! We know we have a couple different pages to deal with here. and we have a few loose files like CSS and the favicon. analytics code. Much mor e button-like don't you think? Again for consistency.Notice the change in buttons.g. . or ange and r ounded. Site buttons. The main page of course. All the major views have their own PHP files. and ubiquitous things like that. and ultimately to AJAX this puppy up and get it working. looking button-like Moving on The developer now has plenty to go on to star t fleshing out the user inter actions for the site. So let¶s be smart and work modularity. And we have plenty to go on to star t getting the HTML and CSS for all this r eady. let's make this the default for all buttons acr oss the site. which acts as both our list page and sales page depending on login status. Web Root Organization This is what we have for files at the root of our web directory so far. But then we have sign in and sign up pages and account pages. the DOCTYPE. We have subdirectories for images and ³common´ files.php´ that we can include on multiple pages so we don¶t have to repeat common code (e.php´ and ³close. They ar e now big. That means we¶ll make files like ³header.

3.org/TR/xhtml1/DTD/xhtml1strict.2/jquery.googleapis. charset=utf-8" /> <title>Colored Lists | <!-.php" class="button">Your Account</a></p> .org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html.0 Strict//EN" "http://www. Header <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1. He¶s going to need PHP files for interacting with the database and doing all the list interactions.Do Something Smart Here --></title> <link rel="stylesheet" href="style.css" type="text/css" /> <link rel="shortcut icon" type="image/x-icon" href="/favicon.min.com/ajax/libs/jquery/1.2'></script> </head> <body> <div id="page-wrap"> <div id="header"> <h1><a href="/">Colored Lists</a></h1> <div id="control"> <!-.php" class="button">Log out</a><a href="/account.3.w3.IF LOGGED IN --> <p><a href="/logout.ico" /> <script type='text/javascript' src='http://ajax.dtd"> <html xmlns="http://www.w3.Our developer will surely be adding more files.js?ver=1.

<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. but give him the things he needs. Account / Logout) those buttons will be different depending on the logged in state of the user. We are leaving the body.g.php">Sign up</a>&nbsp. all we¶ll use it for is a few notes on using the application. Then with our control buttons (e.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.Analytics here --> </body> </html> Sidebar Our design calls for a bit of a sidebar. In the page title.php">Log in</a></p> <!-. so we¶ll just close up those open elements and add a note to put analytics here.php". </div> <!-.<!-. and #page-wrap elements open. Right now. Before we get into that main content. ?> <div id="main"> . as beyond that is the main content of the page. let¶s dig into a real page. Different pages need differnet page titles. Footer Our design doesn¶t call for much of a footer. so clearly something dynamic needs to happen there. So at this point we have the top of a page. let¶s toss in the sidebar and footer areas so we have a complete skin. The template for building any page will be like this: <?php include_once "common/header. html. as it¶s extremely likely that room will be needed for additional things as the app grows.IF LOGGED OUT --> <p><a class="button" href="/signup. So we¶ll just let the developer jump in there and make those things function correctly. we¶ve left a note to do something smart there. <a class="button" href="/login. But it¶s nice to have some open room for content.

We¶re smart designers though.IF LOGGED IN --> <!-. it¶s likely we¶ll need some kind of hook to target just the text later on. Just looking at all those empty control divs. we know that those are probably automatically generated by the JavaScript.IF LOGGED OUT --> <!-. We¶ll use CSS class names for the colors.Content here --> <!-. ?> <?php include_once "common/footer. we know this markup is merely temporary. and checking off list items. . period. But then we need a bunch of controls for the list items. ?> 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. There are empty divs for dragging.Alternate content here --> </div> <?php include_once "common/sidebar. Now we need to get an input on this page for adding new list items. Because the list items wrap more than just the text. That¶s fine. Our developer will be all over this. We need these for the CSS so we can target them and style them. changing color. we need the HTML in there now to set the stage and have everyone on the same page. That¶s what all those divs are inside the list items. Why the spans inside the list items? Just being smart. deleting. without JavaScript</noscript> <!-.<noscript>This site just doesn't work.php".php". but we¶ll put the basics in so we can style them. These lists will be dynamically generated by the application.

we us this structure for all pages. 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.IF LOGGED IN --> . But whatever. 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. what do they need the URL for). they are already here. We need this main page to be capable of displaying a list without showing the input form or all the list controls. only no control tabs.jpg" alt="Your new list here!" /> Account Page As a quick reminder. We know this this probably will just be a change in how the backend code outputs the list. and no public URL area (hey. more work for the developer! But he¶s ready for it. <img src="/images/newlist. Let¶s put that in here too.php". <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. ?> <div id="main"> <!-. everybody is on the same page. Logged out (Sales) We might do something fancy someday for this.<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. including this one. if we create this. but for now. no form to add new items. This public URL business leads us into another possible scenario. <?php include_once "common/header.

Content here --> <!-. The account page is going to have several forms on it: one for updating email.IF LOGGED OUT --> <!-. We¶ll leave that to him. all common content is included so updates down the line are much easier. ?> That¶s the beauty of working modularly. 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.php". include_once "common/footer.Alternate content here --> </div> <?php include_once "common/sidebar.php".<!-. <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-email-submit" 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-password-submit" value="Change Password" class="button" /> </div> </form> <hr /> <form action="" id="delete-account-form"> <div> <input type="submit" name="delete-account-submit" id="delete-account-submit" value="Delete Account?" class="button" /> </div> . but this gives us enough to style. one for updating password.

display: block. } hr { height: 1px. } Not too much complicated formatting for our little one-page app. Sans-Serif. a img { border: none. padding: 0. they are all just simpler versions of the account page. background: url(images/stripe. the developer can easily create these pages himself copying the basic format and CSS classes from the account page.1 Helvetica. border: none. } . } . font-size: 12px.clear { clear: both. } Just getting things cleaned up.button { background: url(/images/button-bg. Sign up. text-shadow: 0 1px 1px #666. border-bottom: 1px dotted #900. color: white. margin: 0 0 8px 0. } .1 Helvetica.png) repeat-x. background: url(images/stripe. cursor: pointer. } img. and the ³button´ format. } body { font: 14px/1. -moz-box-shadow: 0 1px 3px #999. Structure /* STRUCTURE */ body { font: 14px/1. } input { outline: none. background: #ccc. -webkit-box-shadow: 0 1px 3px #999. } h1 { font: bold 36px Helvetica. clear: both.</form> Other Form Pages Now that we¶ve done the account page. Sans-Serif. -webkit-border-radius: 5px.red { background: red. font: bold 16px Helvetica. margin: 20px 0. } #page-wrap { width: 960px. color: white. } p { margin: 0 0 6px 0. -moz-border-radius: 5px. Sans-Serif. padding: 3px. } h2 { margin: 0 0 10px 0. sign in. Since we¶ll have styled the basic label/input format. } . margin: 6px auto 50px. the header format. we have pretty much covered all the bases for the other ³form´ style pages. padding: 6px 12px. Typography /* TYPOGRAPHY */ a { text-decoration: none. forgot your password. position: relative.png) repeat-x. } . color: #900.button:hover { background-position: bottom left. The CSS Reset /* RESET */ * { margin: 0. outline: none. border: none.png) repeat-x.

} #list li { position: relative. float: right.tab { background: url(images/minibuttons. } . height: 38px. height: 1px. Notice only a single image is used. 184. background: url(/images/logo. width: 607px. color: white. text-indent: -9999px. right: -17px. -moz-border-radius: 5px. background-position: -31px 0.colorRed span { background: rgb(187. position: relative. left: 34px. } . border: none. A single CSS Sprite for mad efficiency! Forms /* FORM STUFF */ label { background: #999. z-index: 2. } #header h1 a { display: block. 191. } . text-align: right. padding: 10px 237px 0 0. } . cursor: pointer. position: relative.png.deletetab { position: absolute. 255). } Our little stripe header doesn¶t take much. cursor: pointer. top: 4px.draggertab { position: absolute. and buttons. minibuttons. } . left: 0.colorGreen span { background: rgb(145. background-position: -82px 0. 255. right: -35px. } . 49. so we don¶t have a whole heck of a lot of text formatting. width: 589px. } . 47). } . However we do have page headers. 75). margin: 0 0 8px 0. padding: 3px.colortab { position: absolute. etc. -webkit-border-radius: 5px. width: 200px. width: 34px. display: block. } #list li span { padding: 8px.crossout { position: absolute. } . padding: 0 0 0 70px. the spacing.colorYellow span { background: rgb(255. width: 16px. left: 0. Just a little CSS image replacement for the logo and placement of our control buttons. } . } A lot more stuff needed here. top: 50%. width: 600px. text-indent: -9999px. Lists /* LISTS */ #list { list-style: none. } . links. color: white.donetab { position: absolute.png) no-repeat. left: 0px. overflow: hidden. } #header h1 { position: absolute.colortab:hover { background-position: -31px -21px.This isn¶t really a content-based app. width: 31px. top: 0. } . background-position: -65px 0. } #control { width: 500px. We¶ll also position all the little helper controls and give them appropriate backgrounds. } . cursor: move. } .donetab:hover { background-position: -65px -21px. height: 21px. so we¶ll set those up here.deletetab:hover { background-position: -82px -21px. } .draggertab:hover { background-position: 0 -21px. 191). the rounded corners. Here we¶ll set up how the lists look: the colors. cursor: pointer.colorBlue span { background: rgb(115. } #share-area { margin: 20px 0 0 69px.png) no-repeat. Header /* HEADER */ #header { height: 68px. width: 15px.

right: 0.good { background: #9ff5b6. Once he¶s gotten a good start on that. input[type="password"] { width: 324px. We¶ll set up one class for messages in general and then classes for good and bad versions. you have successfully done something. } #add-new input[type="text"] { width: 532px.0. } #ribbon ul { list-style: none. margin: 0 0 10px 0. Since we plan to use click-to-edit. border: 3px solid #999. we¶ll tackle AJAX and making all those list interactions happen almost like a desktop app. } Forms across our whole site will be the same. height: 756px. margin: 0 10px 0 69px.0.bad { color: #ef0040. } #ribbon ul li { background: rgba(0. } ul#list li span input[style] { width: 90% !important. Messaging /* MESSAGING */ . there will be some of it (for example. width: 607px. which is basically the same as any other input except bigger and is floated to the left to accommodate the ³Add´ button. width: 125px. } . we can actually star t building the classes that will r un this puppy. Sidebar /* SIDEBAR */ #ribbon { position: absolute. } . color: white. background: url(/images/ribbon-bg. display: block. } Just some simple stuff for our list of reminders.png) no-repeat. } We haven¶t talked too much about error messaging. float: left. top: -6px. . so we set that up here. etc). padding: 5px.message { padding: 10px.8). padding: 60px 30px 0 47px.0. margin: 0 0 5px 0. padding: 7px. } #add-new input[type="text"]:focus { border-color: #73B8BF. font-size: 18px.input[type="text"]. you enter in a wrong password. so we¶ll plan for that by shortening the length of them to accommodate for a ³Save´ button. the list items temporarily turn into text inputs when doing that. Moving Along Our developer now has plenty to work with to make this app functional. your passwords don¶t match. Part V Where Are We? Now that we have a workflow put together and the HTML and CSS to make it look good. The one exception is the ³Add New´ area on our lists. } #add-new input[type="submit"] { padding: 10px 12px. font-size: 12px. but we can assume that because this is a web app.

define('DB_USER'. we'll add mor e to this file. 'localhost'). but in the inter est of keeping them easy to maintain. you'r e able to change ever y database connection simply by swapping out the information in one file. footer . . we'llcreate a separ ate file to contain any infor mation that is site-wide. as well as setting up a couple other site-wide featur es: err or r epor ting and opening a session.inc. Inside constants. ''). Cr eating a constants file is a good idea for pieces of information that will be used often and in differ ent scopes thr oughout a site. To do this.inc. define('DB_PASS'. and sidebar files. 'root').We'll focus this installment of the series on the user 's account inter actions. 'cl_db'). That way. we want to define our database cr edentials. This will be called constants. The file will look like this when all's said and done: <?php // Set the error reporting level error_reporting(E_ALL). Creating a PDO Object Next. Since we'r e star ting out by developing locally. we need to connect to our database. we'll need to cr eate a couple of small files.inc.php . Defining Site-Wide Constants Our site won't r equir e many constants. and it will r eside in a new folder called inc ³ this folder will contain our PHP classes as well. ini_set("display_errors".This file will r eside in the common folder along with the header. constants. ?> As we develop. we want to cr eate a connection so that our application can communicate with our database. These include: y y y y Creating an Account Modifying Account Information Resetting a Lost Password Deleting an Account Connecting to the Database Befor e our class will be able to do much of anything. 1). define('DB_NAME'. if your database changes.php .php will look like this: <?php // Database credentials define('DB_HOST'. This file will cr eate a database connection using PDO (PHP Data Objects).

php". we'r e simply going to output the err or message. Also. we'll be taking the object-or iented appr oach with this app. which we'll cover as we get to them.dbname=". // Include site constants include_once "inc/constants. . and as such will pr obably r equir e some r eworking to accommodate your changes.inc. Ther e ar e other options that allow pr epar ed statements. This will allow our user s to stay logged in when we build that functionality later. we want to see any and ever y err or that occur s on the site.DB_NAME.php and cr eate a PDO object using the constants defined within it.DB_HOST. it's just my per sonal pr efer ence to use PDO.which vir tually eliminates the risk of SQL injection. Feel fr ee to use whatever method of connecting to the database you pr efer . PDO is not database-specific. exit. $e->getMessage(). However . we ensur e that even notices will be displayed. if the database connection fails.// Start a PHP session session_start(). } catch (PDOException $e) { echo 'Connection failed: ' . we use session_start() to star t a PHP session. Why PDO? The r eason we'r e using PDO for this pr oject is because of its suppor t for pr epar ed statements. having used both MySQLi and PDO in pr ojects. By setting error_reporting() to E_ALL and changing the display_errors dir ective to 1 using ini_set() . such as the MySQLi extension. } ?> Because we'r e in the development stage. we include config. DB_PASS). but keep in mind that all database inter actions in this exer cise ar e assuming the use of PDO. so migr ating the app to Or acle or Postgr eSQL wouldn't r equir e a full r ewrite of our code. Note the use of the try-catch statement³this gives us the ability to use Exceptions. which will keep our code cleaner and mor e secur e. We'll also need to cr eate sever al files that will display information to the user and inter act with the class. $db = new PDO($dsn. In this case. which help impr ove err or handling.". Next. Finally. DB_USER.All of these actions will be contained within our ColoredListsUsers class.inc. Framing Out a User Interactions Class As we discussed in Par t 2 of this ser ies. // Create a database object try { $dsn = "mysql:host=".

one will be created by the constr uctor.opensource.org/licenses/mitlicense.html MIT License * */ class ColoredListsUsers { } ?> Connecting the Class to the Database Befor e our class can do much of anything. .Building the Class To get star ted. With the file cr eated.inc. which will accept the instance of PDO cr eated in base. Our database connection within the object will be stor ed in a pr ivate pr oper ty called $_db . which we'll place in the inc folder .php . let's 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.php as an ar gument.users.php to contain the PHP class. we need to cr eate the file class. and this pr oper ty will be set by the class constr uctor. it needs to have access to the database object we cr eated in base. This ends up looking like this: class ColoredListsUsers { /** * The database object * * @var object */ private $_db. If no instance of PDO is passed.

let's star t building user inter actions! Creating an Account Fir st and for emost. This will give them access to the r est of the site's functionality. they'r e gr eeted with our "sales" page./** * 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. } } } Now we ar e able to cr eate an instance of our ColoredListsUsers object and use it to communicate with our database. a user needs to be able to cr eate an account. DB_USER. } else { $dsn = "mysql:host=". when a user visits our app. DB_PASS).". As it stands. which encour ages them to click the "Sign Up" button in the top r ight of their scr een: .dbname=". $this->_db = new PDO($dsn. Next.DB_HOST.DB_NAME.

$users = new ColoredListsUsers($db).users.inc. cr eate a file called signup. if(!empty($_POST['username'])): include_once "inc/class.php ³ our fir st or der of business should pr obably be to build that page.php".php and place the following code inside: <?php include_once "common/base. else: ?> <h2>Sign up</h2> <form method="post" action="signup. Creating the Sign-Up Form In our app's r oot dir ector y. echo $users->createAccount().php". $pageTitle = "Register".php".php" id="registerform"> <div> <label for="username">Email:</label> <input type="text" name="username" id="username" /><b r /> <input type="submit" name="register" id="register" va lue="Sign up" /> .The home screen of our app Clicking that "Sign Up" button dir ects the user to /signup. include_once "common/header.

Notice the use of alter native syntax for the if-else statement. If so. which helps with r eadability in the scr ipt.users. Let's go back to inc/class. include_once 'common/close. Our sign-up page should look like this: The sign-up page. I pr efer the way it ends with endif. ?> To star t.php'. we close the if-else statement and include the footer . Remember in Par t 4 when we built the header file and left that comment in the title tag? <title>Colored Lists | <!-. we need to write the createAccount() method that will be called when a user submits the form. Saving the User's Email Address With our sign-up form r eady. we cr eate a new ColoredListsUsers object and call the createAccount() method (which we'll write in the next section).php and common/header. we can then cr eate our sign-up for m.inc. Normally.php files. With the pr oper files included. notice that we'r e declaring a var iable called $pageTitle just befor e we include the header. we include our common/base. Also. The form will submit to signup. instead of a closing curly br ace ( } ). This method will be public.</div> </form> <?php endif.Do Something Smart Here --></title> We'r e going to r eplace that with a snippet of PHP that r eads: <title>Colored Lists | <?php echo $pageTitle ?></title> That gives us the oppor tunity to post a differ ent title for each page of our app.php ³itself³so we need to place an if-else check to see if the form has been submitted. but in the case of outputting HTML. Finally. I don't like to use this for mat.php and declar e this method: class ColoredListsUsers { // Class properties and other methods omitted to save space .

} $sql = "INSERT INTO users(Username. if($stmt = $this->_db->prepare($sql)) { $stmt->bindParam(":email". $stmt->closeCursor().com###BOT_TEXT###quot;>conta ct " . $v. "inconvenience. /* . Please " . } if(!$this->sendVerificationEmail($u. if($stmt = $this->_db->prepare($sql)) { $stmt->bindParam(":email". $u. if($row['theCount']!=0) { return "<h2> Error </h2>" . "Please try again. " verification email. "<a href=###BOT_TEXT###quot;mailto:help@coloredlists. ver_code) VALUES(:email. "<p> There was an error sending your" ./** * 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']). $url = dechex($userID). $v = sha1(time()). $stmt->bindParam(":ver". PDO::PARAM_STR). $v)) { return "<h2> Error </h2>" . $u. $row = $stmt->fetch(). $sql = "SELECT COUNT(Username) AS theCount FROM users WHERE Username=:email". We apologize for the " . </p>". $stmt->execute(). } $stmt->closeCursor(). PDO::PARAM_STR). that email is already in use. :ver)". PDO::PARAM_STR). "us</a> for support. $userID = $this->_db->lastInsertId(). "<p> Sorry. </p>". " . $stmt->execute().

"creating your first list failed. thir d. it gener ates and sends a verification email to the user with instructions on how to verify their account (we'll define the method that does this in the next section). "<p> Your account was successfully " . and finally. */ $sql = "INSERT INTO lists (UserID.users. if(!$this->_db->query($sql)) { return "<h2> Error </h2>" . $url)". "created with the username <strong>$u</strong>. "<p> Your account was created. it cr eates a list for the user . it stor es the email addr ess and verification code in the database. </p>". In inc/class.inc. it makes sur e the supplied email addr ess isn't alr eady in use. } else { return "<h2> Success! </h2>" . Upon success. " Check your email!".* If the UserID was successfully * retrieved.php . Generating and Sending a Verification Email When the user cr eates an account. </p>". a specific err or message is gener ated. we'll be using the built-in mail() function. To send the email. second. a message is gener ated to let the user know they should expect an email. and if any of them should fail. " . cr eate the private sendVerificationEmail() method by inser ting 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 accou . "user information into the database. Each of these steps is monitor ed. } } else { return "<h2> Error </h2><p> Couldn't insert the " . } } } This method follows sever al steps to cr eate an account: fir st. but " . This is a pr ecautionar y measur e that pr oves the user pr ovided a r eal email addr ess that they have access to and pr events a ton of spam accounts fr om being cr eated easily. ListURL) VALUES ($userID. create a default list. we need to send them an email with a link that will confirm their account. four th. it r etrieves the posted email addr ess fr om the form (stor ed in the $_POST super global) and generates a hard-to-guess ver ification code (the SHA1 hash of the curr ent timestamp).

// For verification purposes $to = trim($email). return mail($to. -Thanks! Chris and Jason www.com> Content-Type: text/plain. $headers). $msg = <<<EMAIL You have a new account at Colored Lists! To get started. Your Username: $email Activate your account: http://coloredlists. } } The most impor tant par t of this method is the activation link. $msg. http://coloredlists. $headers = <<<MESSAGE From: Colored Lists <donotreply@coloredlists. $subject = "[Colored Lists] Please Verify Your Account".com EMAIL. $ver) { $e = sha1($email). $subject.com/accountverify. please activate your account and choose a password by following the link below. MESSAGE. This link sends the user to our app's account verification file (which we'll write in the next step) and sends the The user's email address The random verification code for the use TRUE on successful send and FALSE on fa .ColoredLists. please contact help@coloredlists.nt * * @param string $email * @param string $ver r * @return boolean ilure */ private function sendVerificationEmail($email.com.php?v=$v er&e=$e If you have any questions.php?v=$ver&e=$e .com/accountverify.

php in the r oot level of our app. $users = new ColoredListsUsers($db). $pageTitle = "Verify Your Account". include_once "common/header.php"> <div> <label for="p">Choose a Password:</label> <input type="password" name="p" id="p" /><br /> . Fir st.users. exit. } elseif(isset($_POST['v'])) { include_once "inc/class. $users = new ColoredListsUsers($db).php". let's cr eate a new file called accountverify. $ret = $users->updatePassword().inc. This will allow us to identify and verify the user when they follow the link.inc.php"). we need to update the database to r eflect the user 's new passwor d. place the following code: <?php include_once "common/base.users. Inside. as well as setting the account's status to verified.php". if(isset($_GET['v']) && isset($_GET['e'])) { include_once "inc/class. Verifying the User's Account After our user follows the verification link in the email. we need to check that their email and ver ification code ar e valid. $ret = $users->verifyAccount(). if($ret[0]<3): ?> <h2>Choose a Password</h2> <form method="post" action="accountverify. } else { header("Location: /signup. After they choose a passwor d.php". } if(isset($ret[0])): echo isset($ret[1]) ? $ret[1] : NULL.php".user 's hashed email addr ess along with their verification code in the URI. and then allow them to choose a passwor d.

./">'. include_once("common/ads. To do that. $stmt->bindParam(':user'.php"). include_once 'common/close. ?> Verifying the User's Email and Verification Code Befor e we can allow our user to select a password. that their email matches their verification code.inc.users. $_GET['e']. we need to make sure that their account exists. PDO::PARAM_STR).<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. if($stmt = $this->_db->prepare($sql)) { $stmt->bindParam(':ver'.php'. PDO::PARAM_STR).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". endif. and that their account is unverified. $_GET['v']. else: echo '<meta http-equiv="refresh" content="0. we need a new method in inc/class.

Let's 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 . } else { return array(4. The err or code 0 means nothing went wr ong. "<h2>Error</h2>\n<p>Database error. if(isset($row['Username'])) { // Logs the user in if verification is successful $_SESSION['Username'] = $row['Username']. NULL).$stmt->execute(). Updating the User's Password and Verified Status Once the user has selected a passwor d and submitted the for m. $_SESSION['LoggedIn'] = 1. } else { return array(2. hashed user name. } $stmt->closeCursor().php###BOT_TEXT###quot;>forget " . and a verified status of 0 . and a message in the second. "<h2>Verification Error</h2>\n" . This method r eturns an arr ay with an err or code in the fir st index. If a user name is r etur ned. } } } This method executes a quer y that loads the user name stor ed in the database with the verification code. "<p>This account has already been verified. $row = $stmt->fetch(). // No error message is required if verification is succes sful return array(0.</p>") . the if-else statement will catch the verification code sent using the POST method and execute the updatePassword() method. " . "your password?</a>"). login cr edentials ar e stor ed. "Did you <a href=###BOT_TEXT###quot;/password. This method needs to set the account status to verified and save the user 's hashed passwor d in the database.

php">Log in</a></p> <!-. } } else { return FALSE. we need to modify this snippet with an if-else block: . } catch(PDOException $e) { return FALSE. In Par t 4.IF LOGGED OUT --> <p><a class="button" href="/signup. common/header.php tor ecognize that a user is logged in and display differ ent options.END OF IF STATEMENT --> To make those comments into functional code. PDO::PARAM_STR). <a class="button" href="/login.php" class="button">Your Account</a></p> <!-. $stmt>bindParam(":pass".*/ public function updatePassword() { if(isset($_POST['p']) && isset($_POST['r']) && $_POST['p']==$_POST['r']) { $sql = "UPDATE users SET Password=MD5(:pass).php" class="button">Log out</a> < a href="/account. since ver ifying an account logs a user in. we need to update common/header. $stmt>bindParam(":ver".php featur ed a code snippet that looked like this: <!-. $stmt->execute(). PDO::PARAM_STR). try { $stmt = $this->_db->prepare($sql). } } } Finally. verified=1 WHERE ver_code=:ver LIMIT 1". $_POST['v']. $stmt->closeCursor().php">Sign up</a> & nbsp. $_POST['p']. return TRUE.IF LOGGED IN --> <p><a href="/logout.

the user is notified of this fact and asked if he or she wishes to log out. If logged in. let's create a new file named login. <a class="button" href="/login.php">Log in</a></p> <?php endif. it should look like this: <?php include_once "common/base. or if the user needs to log in. the user is dir ected to the home page. To star t. Finally.php'.php".</strong></p> <p><a href="/logout.<?php if(isset($_SESSION['LoggedIn']) && isset($_SESSION['Username']) && $_SESSION['LoggedIn']==1): ?> <p><a href="/logout. $users = new ColoredListsUsers($db). the login for m is displayed. If the login succeeds. If the form has been submitted. if the login form was submitted.inc. $pageTitle = "Home".php" class="button">Your Account</a></p> <?php else: ?> <p><a class="button" href="/signup. the sidebar ads and footer ar e included to r ound out the file. other wise. Then it checks if a user is alr eady logged in. When the file is all put together .users. a new ColoredListsUsers object is cr eated and the accountLogin() method is called. If neither of the pr evious conditions exists.php" class="button">Log out</a> < a href="/account. ?> Notice that we stor e in the session both the user name ( $_SESSION['Username'] ) and a flag that tells us if the user is logged in ( $_SESSION['LoggedIn'] ). Like our other publicly displayed files. Logging In Next.php at the r oot level of our app.php". if(!empty($_SESSION['LoggedIn']) && !empty($_SESSION['Username']) ): ?> <p>You are currently <strong>logged in. this will include the base and header files. wher e his or her list will appear . include_once "common/header. let's build the login form and allow our user to log in.php">Log out</a></p> <?php elseif(!empty($_POST['username']) && !empty($_POST['password'])): include_once 'inc/class.php">Sign up</a> & nbsp. if($users->accountLogin()===TRUE): . the login form is displayed again with an err or .

php" name="loginform" id="l oginform"> <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="L ogin" class="button" /> </div> </form> <p><a href="/password. else: ?> <h2>Login Failed&mdash./'>".. ?> .</h2> <form method="post" action="login. else: ?> <h2>Your list awaits.php">Did you forget your password?</a>< /p> <?php endif.echo "<meta http-equiv='refresh' content='0.php" name="loginform" id="l oginform"> <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="L ogin" class="button" /> </div> </form><br /><br /> <p><a href="/password.Try Again?</h2> <form method="post" action="login. exit..php">Did you forget your password?</a>< /p> <?php endif.

include_once "common/close. PDO::PARAM_STR). the method retur ns FALSE . Building the Login Method Now we need to build the accountLogin() method .php". If a match is found. If no match is found.php". Build this method in ColoredListsUsers by inser ting the following code: class ColoredListsUsers { // 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". if($stmt->rowCount()==1) { $_SESSION['Username'] = htmlentities($_POST['username ']. $stmt>bindParam(':pass'. try { $stmt = $this->_db->prepare($sql). ENT_QUOTES). PDO::PARAM_STR). .<div style="clear: both. $stmt>bindParam(':user'. $_POST['username']. ?> Notice the "Did you for get your passwor d?" links ³ we'll be building this functionality a little later on in the ar ticle. the user 's name and a login flag ar e stored in the session and the method r eturns TRUE . $_POST['password']. $stmt->execute()."></div> <?php include_once "common/ads. $_SESSION['LoggedIn'] = 1. This method will compar e the supplied user name and the MD5 hash of the supplied password to ver ify that ther e is a matching pair in the database.

our user needs to be able to log out. Cr eate a file named account. one to change the account passwor d. If not.php"> Modifying Account Information Next. unset($_SESSION['Username']). } else { return FALSE. he or she gets sent out to the main page. If the user is logged in. This is as easy as destr oying the login data stor ed in the session and sending the user back to the login page. ?> <meta http-equiv="refresh" content="0.php at the r oot level of the app and place the following code inside: <?php session_start(). and one to delete the account.return TRUE. Cr eate a new file named logout. r emember ). we need to allow our user s to modify their account infor mation. There's a lot going on her e because we'r e essentially combining thr ee app functions within one file. } } } Logging Out Next. we need to pr ovide an "Account" page that will give them options to change their user name or passwor d. unset($_SESSION['LoggedIn']). } } catch(PDOException $e) { return FALSE. In or der to do that. as well as the option to delete their account.php at the r oot level of the app. . we check if any actions have alr eady been attempted and assemble the corr esponding success or failur e messages if any ar e found.login. Fir st. Then we load the user 's ID and ver ification code using the method retrieveAccountInfo() and build thr ee for ms: one to update the user name (which is an email addr ess. we include the base file and check that the user is logged in.

php"> <div> <input type="hidden" name="userid" value="<?php echo $userID ?>" /> <input type="hidden" name="action" . ?> <h2>Your Account</h2> <form method="post" action="db-interaction/users.</div>". include_once 'inc/class. Altogether. "did not match.users.inc. include_once "common/header.php". Try again!</div>". $v) = $users->retrieveAccountInfo().php". if(isset($_GET['email']) && $_GET['email']=="changed") { echo "<div class='message good'>Your email address " . } if(isset($_GET['delete']) && $_GET['delete']=="failed") { echo "<div class='message bad'>There was an error " .</div>". } list($userID. } elseif(isset($_GET['password']) && $_GET['password']=="nomatc h") { echo "<div class='message bad'>The two passwords " . "has been changed. $users = new ColoredListsUsers($db). "changing your email address. "has been changed.php'. the file should look like this: <?php include_once "common/base. we include the sidebar ads and the footer .Finally. } else if(isset($_GET['email']) && $_GET['email']=="failed") { echo "<div class='message bad'>There was an error " . if(isset($_SESSION['LoggedIn']) && $_SESSION['LoggedIn']==1): $pageTitle = "Your Account".</div>".</div>". "deleting your account. } if(isset($_GET['password']) && $_GET['password']=="changed") { echo "<div class='message good'>Your password " .

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-passwordsubmit" 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-accountsubmit" value="Delete Account?" class="button" /> </div> </form> .

?> Creating the Method to Retrieve Account Info In or der to have the user 's login name and ver ification code available to our account option forms.users. $stmt->closeCursor(). $stmt>bindParam(':user'. return array($row['UserID']. $row['ver_code']).php". try { $stmt = $this->_db->prepare($sql). ?> <div class="clear"></div> <?php include_once "common/ads.inc. $_SESSION['Username']. endif. $stmt->execute(). PDO::PARAM_STR). cr eate 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. include_once "common/close. In inc/class.<?php else: header("Location: /"). ver_code FROM users WHERE Username=:user". we need to build a new method that will load this information fr om the database.php". } catch(PDOException $e) . $row = $stmt->fetch().php . exit.

cr eating a ColoredListsUsers object./inc/constants.php when submitted.inc. $userObj = new ColoredListsUsers()..php by determining form actions. default: header("Location: /"). break.users. if(!empty($_POST['action']) && isset($_SESSION['LoggedIn']) && $_SESSION['LoggedIn']==1) { switch($_POST['action']) { case 'changeemail': $status = $userObj>updateEmail() ? "changed" : "failed".php?email=$status"). Place the following code in the new file: <?php session_start()./inc/class..php". include_once ". and it will be named users. case 'changepassword': $status = $userObj>updatePassword() ? "changed" : "nomatch". all thr ee for ms dir ect to a file called db-interaction/users.inc. break.php .{ return FALSE. case 'deleteaccount': $userObj->deleteAccount(). break. This file will be placed in a new folder called db-interaction . header("Location: /account.php .php?password=$status"). } } . } } } Building the Interactions File In account.php". and calling the appr opr iate methods to handle the action. header("Location: /account. break. This file helps r elieve some of the clutter in account. include_once ".

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 quer y that changes the email addr ess associated with an account. It r eturns TRUE if the email is successfully changed, and FALSE other wise.

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 similar ly to updateEmail() , updatePassword() is called if the user submits a r equest to change their password. The only differ ence in the methods is that this one will compar e the password and the passwor d confirmation to make sur e they match befor e 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 thr ough sever al steps. Fir st, we need todoublecheck that the user is logged in, because we cer tainly don't want any accidental account deletions. If the user is logged in, we then delete their list items. If the list items ar e successfully deleted, we move on to delete the user 's lists. Finally, if the lists ar e successfully deleted, we delete the user fr om the database, destr oy their session infor mation, and send them to a page called gone.php , which we'll build in a minute. The method, when it's 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

} catch(PDOException $e) { die($e->getMessage()). } // Delete the user's list(s) $sql = "DELETE FROM lists WHERE UserID=:user". $_POST['userid']. PDO::PARAM_STR). $stmt->closeCursor(). $stmt->execute(). $stmt->bindParam(":user". $_POST['userid']. } catch(PDOException $e) . try { $stmt = $this->_db->prepare($sql). PDO::PARAM_INT). $_SESSION['Username']. $stmt->bindParam(":user". PDO::PARAM_INT). $stmt->closeCursor(). try { $stmt = $this->_db->prepare($sql). $stmt->execute(). } catch(PDOException $e) { die($e->getMessage()). PDO::PARAM_INT). $stmt->execute(). $_POST['userid']. $stmt->bindParam(":user". $stmt>bindParam(":email". try { $stmt = $this->_db->prepare($sql).WHERE UserID=:user LIMIT 1 )". } // Delete the user $sql = "DELETE FROM users WHERE UserID=:user AND Username=:email". $stmt->closeCursor().

The last thing we need to do is allow a user to r eset a for gotten passwor d. To do this. } } } Resetting an Account Password At this point.php". header("Location: /gone. ?> .php?delete=failed").php". include_once "common/close. $_SESSION['Username']). ?> <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.php at the r oot level of our app and place the following code inside: <?php include_once "common/base. include_once "common/header. } else { header("Location: /account.</p> <form action="db-interaction/users. } // Destroy the user's session and send to a confirmation page unset($_SESSION['LoggedIn'].php").php". we'r e almost done.php".{ die($e->getMessage()). $pageTitle = "Reset Your Password". we need to cr eate the file password.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. exit. exit.

} catch(PDOException $e) { return $e->getMessage().php is submitted.When a user visits this page. The resetPassword() method sets the verified field of our user 's database entr y to 0 . $stmt>bindParam(":user". } // Send the reset email if(!$this->sendResetEmail($_POST['username']. try { $stmt = $this->_db->prepare($sql). . Returning the Account to "Unverified" Status When the form in password. PDO::PARAM_STR). $stmt->execute().php . the infor mation is sent to dbinteraction/users. $stmt->closeCursor(). Submitting the form willr eturn the account to unver ified and send the user an email with a link to r eset their passwor d. they'll be able to enter their email addr ess. $v)) { return "Sending the email failed!". 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". } return TRUE. $_POST['username']. then calls the sendResetEmail() method.php and the resetPassword() method is called befor e sending the user to resetpending.

?> Generating a "Reset Password" Email The sendResetEmail() method is ver y similar to the sendVerificationEmail() method. $subject = "[Colored Lists] Request to Reset Your Password".} } Building the Reset Pending Page After the user 's account is back in an unverified state and the email has been sent with their passwor d r eset link. ?> <h2>Password Reset Requested</h2> <p>Check your email to finish the reset process. $headers = <<<MESSAGE . include_once "common/close. // For verification purposes $to = trim($email).php to let them know what their next steps ar e. $pageTitle = "Reset Pending".php".php".php".</p> <?php include_once "common/ads.php". The main differ ence her e is that the link sent to the user dir ects them to a page called resetpassword. we send them to resetpending. Cr eate this file at the r oot level of the app and inser t the following: <?php include_once "common/base. 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. include_once "common/header. $ver) { $e = sha1($email).php wher e they'r e able to choose a new passwor d.

com> Content-Type: text/plain.users.com EMAIL. MESSAGE. we ar e able to use the verifyAccount() method we wr ote earlier to ensur e that their cr edentials ar e corr ect. return mail($to. After verifying their cr edentials. -Thanks! Chris and Jason www. Follow this link to reset your password: http://coloredlists.com/resetpassword.com.php .inc. After submitting the form. it checks if the user is just arr iving fr om their r eset email. our scr ipt will fir e the updatePassword() method we cr eated ear lier tosave the new passwor d. After including the base and header files. Inside resetpassword. } } Resetting the Password Our ver y last step in this par t of the app is to cr eate the file resetpassword. $ret = $users->verifyAccount(). add the following code: <?php include_once "common/base. wher e they'r e shown a confirmation message letting them know that their passwor d was changed. head over to the link below and choose a new password. If so.php?v=$ver&e=$e If you have any questions. } .ColoredLists.php .php". $users = new ColoredListsUsers($db).php". $msg. $headers).From: Colored Lists <donotreply@coloredlists. if(isset($_GET['v']) && isset($_GET['e'])) { include_once "inc/class. we display a form that allows them to choose a passwor d and confirm it. $subject. $msg = <<<EMAIL We just heard you forgot your password! Bummer! To get going again. This file is ver y similar to the accountverify.php in the r oot level of the site. Then we r edir ect the user to account.php file we cr eated ear lier . please contact help@coloredlists.

$status = $users->updatePassword() ? "changed" : "failed".elseif(isset($_POST['v'])) { include_once "inc/class. } $pageTitle = "Reset Your Password".inc. $users = new ColoredListsUsers($db).php". header("Location: /account. ./">'. } else { header("Location: /login.php").users. else: echo '<meta http-equiv="refresh" content="0.php?password=$status").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.php"). exit. include_once("common/ads. if(isset($ret[0])): echo isset($ret[1]) ? $ret[1] : NULL. include_once "common/header. exit.php". if($ret[0]<3): ?> <h2>Reset Your Password</h2> <form method="post" action="accountverify. endif.

php'. the new text should be saved. We decided from the get-go that this was going to be an AJAX-y app. When a list item is marked as done. When an items color is changed. that status should be saved. the new color should be saved. easy to use. the new order should be saved. We didn¶t chose AJAX because it¶s a popular buzzword. Part VI Our developer has already done a massive amount of work turning this idea into a real application. When the text of a list item is altered.include_once 'common/close. our fr ont-end designer will use some dummy lists to cr eate AJAXeffects. it should be saved to the list. we chose it because we know it¶s the best path toward making a responsive. When a list item is deleted. After he's finished with the dummy lists. When the list is reordered. natural feeling application on the web. that functionality is going to be used primarily for saving. 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.. Let¶s think through each of those times something needs to be saved: y y y y y y When a new list items is added.. Now let¶s make some more for him! The most important part of this app is creating and managing your list. . Please don't hesitate to ask for clar ification in the comments! In the next par t of this series. and I went over some of it pr etty quickly. we'll explor e how to combine those AJAX effects with our backend and build the list inter actions class in par t 7. This ar ticle cover ed a whole lot of gr ound. ?> Moving On. it should be deleted from the list.

This isn¶t the time for a lengthy discussion about that. the list item crosses out and and fades down. 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. a confirmation slides out. text turns into a text-input for editing. Interface JavaScript Alongside all that AJAX saving is all the stuff that makes the interface do what visually it¶s saying it will do. Click again. But how does that actually work? Don¶t worry. Double-click the list item. the main list page. that needs JavaScript at all. because each of those little events needs to have a PHP file that is ready to receive that request and deal with it. And again. list items can be dragged around and reordered. Click the checkmark. Our developer has his work cut out for him.That¶s a lot of saving going on. So we¶ll be dropping the script files right into the index. list item is whisked away. the list items color is toggled between some predefined choices. Click the X.php file. Fortunately he has some cool object oriented stuff gong on already and can certainly extend that to deal with this. new list item is appended to bottom of the list. We are saying after that happens we are going to save the list. but suffice it to say that that isn¶t required as it¶s generally considered a performance . That little drag tab is saying it can drag list items up and down. we¶ll get to it. You¶ll often see JavaScript file linked in the header of sites. Click the color tab. Type in the large box below and click add. For now let¶s think through all the interface JavaScript things that we need: y y y y y y Click and drag the drag tab.

com/ajax/libs/jquery/1.js"></script> <script type="text/javascript"> initialize(). we¶ll be creating a number of different functions.min. The very first one is the one that gets called directly in the index.more list items --> . after we¶ve output the list.7.2'></script> <script type="text/javascript" src="js/jquery-ui-1.3.3.googleapis.mini. 4.js"></script> <script type="text/javascript" src="js/jquery. </script> 1.js?ver=1.more list items --> </ul> Much of that is redundant across all the list items.js file. Call the initialization function (our own kickstarter. kind of like a DOM ready statement) Cleaning up the Markup with JavaScript In our custom lists.2. In our index.min. That¶s just what we¶ll do here. 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.enhancement to list them at the end of pages instead. 3.2/jquery. What we want is more like this: <ul id="list"> <li class="colorRed"> Walk the dog </li> <!-.js"></script> <script type="text/javascript" src="js/lists.custom. 2. we¶ll call the JavaScript we need. <script type='text/javascript' src='http://ajax. Load Load Load Load jQuery (from Google for better speed) a customized jQuery UI library (for the draggable stuff) the jEditable plugin (for click-to-edit) our custom script 5.jeditable.php file after the lists have been output: function initialize() { }. 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> <!-.php file.

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.donetab"). meaning all those fancy little tabs won¶t work right. Killer.</ul> All the divs and the span has been removed. 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. but« jQuery is smarter than that. Target the list items and wrap each of the the innards using the wrapInner() function and append the extra divs with the append() function. That will definitely do the trick.append("<div class='draggertab tab'></div><div class='colortab tab'></div></div><div class='deletetab tab'></div><div class='donetab tab'></div>"). }. So for our little functionality tabs.live("click". $("li"). How do we append all that extra HTML? Easy with jQuery. we¶ll be using them like this: $(". AND APPLY FUNCTIONALITY TABS $("#list li") .live("click". that gets plugged into the DOM right then and there. There is nothing wrong with that. The class name on the list item is fine. jQuery provides a function called live() that eliminates this problem entirely. the smart way Binding an event to an HTML element is pretty easy in JavaScript. but we are in a bit of a unique situation with our list app here. Don¶t worry about all that too much. 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. 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. function initialize() { // WRAP LIST TEXT IN A SPAN. Bind events to the new functionality tabs. function() { // do stuff }). function() { // do something }). That new list item will not be bound as the others are. . Boo hoo.click(function() { // do something }).wrapInner("<span>") . because PHP will be spitting that out for us when it reads the database and outputs the list. It¶s like this: $("li"). for the current state of the DOM.

live("click".live("click".$(". forcePlaceholderSize: true }).deletetab"). we have already decided to do two things.colortab"). commence list saving! }. function(){ // do stuff }). function(){ // do stuff }). Draw a line through the list item and then fade that whole list item out. The draggable module is exactly perfect for making a list like our sortable.draggertab". 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"). Marking items as done When the user clicks the little checkmark tab. . But then there is the consideration of what to do if the item is already marked as done and that tab is clicked. ui){ // Developer.sortable({ handle : ". update : function(event. tell it the ³handle´ we wish to use (which part of the list item you can click and drag to move them). Making the list drag / sortable Mega thanks to jQuery UI for making such a useful set of functions. $(". it¶s actually going to use jQuery UI¶s draggable functionality to do it¶s thing. this function fires after a list sort. The drag tab doesn¶t have click event. We target the parent <ul>. Let¶s check that out.

.parent() . function() { if(!$(this).append("<img src='/images/crossout. "slow".length) { $(this) .png' class='crossout' />") . we¶ll make sure to check which state we are in first.crossout") . So when the click happens. function() { // DEVELOPER.crossout').crossout') .find("span") .animate({ opacity: "0. commence saving! }) } else { $(this) .remove() .live("click". the user has UNmarked this item as done. we¶ll uncross it out and fade it back up.find('img.end() .animate({ opacity : 1 }.donetab").siblings('span').end() . "swing".siblings('span') . $(". the user has marked this item as done. "swing". "slow".animate({ width: "100%" }) . commence saving! }) } }).Well. function() { // DEVELOPER.find(".5" }.children('img.

"3"). function(){ $(this).live("click". Color isn¶t a valid attribute in XHMTL (it¶s fine in HTML5).nextColor(). $(".attr("color".nextColor = function() { var curColor = $(this). } else { $(this). } else if (curColor == "colorRed") { $(this).removeClass("colorYellow"). }.removeClass("colorRed").addClass("colorBlue"). That nextColor() function isn¶t a built-in function. so what we¶ll be doing with JavaScript is just cycling the class names applied to those list items on clicks.parent(). it¶s because we are about 50% of they way in doing this really . })."2"). Basically this check what color the list item already is and moves it to the next color.attr("color". Why are we using this? We¶ll.removeClass("colorGreen").addClass("colorYellow").addClass("colorRed"). }. but oh well.addClass("colorGreen").attr("color". 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. It¶s not in the markup so it doesn¶t really matter. $.colortab"). jQuery.fn."1").removeClass("colorBlue").attr("color"."4"). it will be custom written by us. commence saving! }).Color Cycling We¶d better get on this whole ³colored´ part of Colored Lists eh? CSS will be applying the actual color. It¶s abstracted away here for code clarity. } else if (curColor == "colorYellow") { $(this). if (curColor == "colorBlue") { $(this).ajax({ // DEVELOPER. the user has toggled the color on this list item. No problem. Notice how we are altering an attribute on the list items too.attr("class").

If they click again then deleting may commence. 1 = Blue. he needs something to save.deletetab"). Some applications resort to a nasty ³ARE YOU SURE´ modal popup dialog box. So we are going to require two clicks to actually delete something. He figured that would be the smartest way to do it as it¶s lightweight.parent() . $(". We want to have a little extra insurance against accidentally fat-fingerings though. Deleting list items Our little ³X´ tab is in charge of allowing users to delete list items. which he made an INT (integer). Back in Part 2 of this series. We can decide later what they key is.g. because we should probably extend that smartness to the class names themselves. and abstract.live("click". if we have an integer representing the color right in the DOM for him. a little notice will pop out to the right asking about sureness. that makes it really easy to snag out and pass along to save to the database. // Make sure to reorder list items after a delete! } }).ajax({ // DEVELOPER. e.animate({ width: "44px". if down the line we decide to drop yellow from the lineup and replace it. The data itself doesn¶t need to know it¶s red. we¶ll be a little more sly than that. We are using colorYellow for example. we can see that our developer already anticipated this and created a field for color called listItemColor. So. easy. function(){ var thiscache = $(this). the user wants to delete this list item. right: "-64px" }. As you click the X. Why is this only 50% smart? Well. 2 = Red. commence deleting! success: function(r){ thiscache .. function(){$(this).remove()}). when color-1 might make more sense. 400. etc. if (thiscache.intelligently. 200) . } else { thiscache. When our developer goes to save the color information about this list item to the database.hide("explode".data("readyToDelete") == "go for it") { $. Or even perhaps let users declare their own colors.

. After the first click. All we need to do with this plugin is target an element and use the editable() function on it with some parameters. event : 'dblclick'. function bindAllTabs(editableTarget) { $(editableTarget). jEditable.'.'. "go for it"). { id : 'listItemID'.data("readyToDelete".php". we did what we talked briefly earlier. Upon a second click. Back before we had live. Since we are using jQuery UI. we tossed in a little extra fun flair with the ³explode´ option for hiding elements. we¶ll stand on the shoulders of others and use a jQuery plugin. We¶ll lean on that technique now. submitdata: {action : "update"} }). all we need to do is expand the width of that tab to display the message. indicator : 'Saving... Click-to-edit list items In order to make our list items click-to-edit. we can¶t use the live() function with this plugin because it¶s not a standard jQuery event.. submit : 'Save'. } . } }). 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 append a little bit of data (jQuery¶s data() function) to that list item saying to ³go for it´. tooltip : 'Double-click to edit. that test will be TRUE and we know we can commence the deletion of that list item.editable("/path/for/DEVELOPER/to/save.. On big caveat though. Because we were smart earlier and our little tab graphic is all a part of one sprite graphic.

Error message? } .val(). what the tooltip will be.With those parameters. URLtext = escape(newListItemText). Very nicely customizeable! Remember those spans we inserted around the text of the list items earlier. error: function(){ // uh oh. We¶re also declaring that we want list items to require a double-click to edit.val(""). $("#new-list-item-text"). save new list item! success: function(theResponse){ $("#list"). $('#add-new'). we¶ll bind it to those spans. didn't work. }.append("<li color='1' class='colorBlue' rel='"+newListItemRel+"' id='" + theResponse + "'><span id=""+theResponse+"listitem" title='Click to edit. 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.ajax({ // DEVELOPER. We already have the markup in place for that. $whitelist).submit(function(){ var $whitelist = '<b><i><strong><em><a>'.length > 0) { $.size()+1. So right after DOM ready we¶ll call: bindAllTabs("#list li span").'>" + newListItemText + "</span><div class='draggertab tab'></div><div class='colortab tab'></div><div class='deletetab tab'></div><div class='donetab tab'></div></li>"). so let¶s look at the JavaScript that powers it.. newListItemRel = $('#list li'). newListItemText = strip_tags(cleanHREF($("#new-list-item-text").. if(newListItemText.val()). we¶re giving the developer what he needs for a callback file path. and what the text in the saving button will say. forList = $("#current-list"). Now we are cashing in on that. bindAllTabs("#list li[rel='"+newListItemRel+"'] span"). When we bind this editable function.

} else { $("#new-list-item-text"). This is no exception. Part VII Where We're At Now that we've got a set of AJAX contr ols mostly assembled.inc. due to the live() binding. This class will contain methods to handle all of the actions per formed by our app or our user s r egar ding list items. } return false. 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. Also.}). The rest of the tabs are automatically cool. it needs to be scrubbed. One last thing we did there was to clear the input field after submission.val(""). Anytime we accept input from the user. these actions ar e: y y y Displaying list items Saving new list items Reordering list items . we can star t building our PHP class tohandle list inter actions. // 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. That ensures the click-to-edit functionality is working on newly appended list items even before a page refresh. Namely. so we could call it when new list items were appended AJAX style. Then we¶ll finish up talking a little security and wrapping up any loose ends. That makes adding a bunch of list items in sequence very easy and natural.php in the inc folder . This class will be called ColoredListsItems and it will r eside in the file class.lists. 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.

Inside inc/class. we need to have our ColoredListsItems class defined.y y y y Changing item colors Editing item text Marking an item as "done" Deleting items Also.php .lists. we'll need to add that functionality as well. /** * 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. Defining the Class Befor e we can do much of anything.html MIT License * */ class ColoredListsItems { /** * The database object * * @var object */ private $_db.opensource.org/licenses/mitlicense. add the following class declar ation 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. because we need to be able to load a non-editable ver sion of a list when viewed fr om the public URL.inc. } .

list URL. we can write our output functions to display our list items to user s. * * @return array an array containing list ID. define the method loadListItemsByUser() and inser t 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 retur ns an * array with the list ID. and ne xt order */ public function loadListItemsByUser() { $sql = "SELECT list_items. ListURL . we'll be loading their list by their user name. we know what they'r e going to look like. If the User Is Logged In When our user is logged in. ListItem Color. } } } ?> Notice that the constr uctor is identical to the one we used in ColoredListsUsers (see Par t 5³ Developing the App: User Inter action). it checks for a database object and cr eates one if none ar e available.ListID. both in a logged in and logged out state. This user name is stor ed in the $_SESSION super global.lists.DB_HOST.else { $dsn = "mysql:host=". and the order number for a n ew item.". ListText. In inc/class. Displaying List Items Even though we don't currently have any items saved in our database. ListItemID. DB_USER.dbname=". $this->_db = new PDO($dsn.php .DB_NAME. With that in mind. list URL. DB_PASS).inc. ListItemDone.

$URL = $row['ListURL']. PDO::PARAM_STR).FROM list_items LEFT JOIN lists USING (ListID) WHERE list_items. // If there aren't any list items saved. $_SESSION['Username'].Username=:user ) ) ORDER BY ListItemPosition".UserID FROM users WHERE users.ListID=( SELECT lists. echo $this->formatListItems($row.UserID=( SELECT users. $stmt->execute(). $URL = $row['ListURL']. $stmt->closeCursor(). $LID = $row['ListID'].ListID FROM lists WHERE lists. PDO::PARAM_STR). ++$order). no list ID is re turned if(!isset($LID)) { $sql = "SELECT ListID. $order = 0. $stmt->execute(). if($stmt = $this->_db->prepare($sql)) { $stmt>bindParam(':user'. } $stmt->closeCursor(). while($row = $stmt->fetch()) { $LID = $row['ListID']. ListURL FROM lists WHERE UserID = ( SELECT UserID FROM users WHERE Username=:user )". if($stmt = $this->_db->prepare($sql)) { $stmt>bindParam(':user'. . $row = $stmt->fetch(). $_SESSION['Username'].

} } This method star ts with a somewhat complex quer y that star ts by joining the list_items and lists tables.we need to use the list's ID to load items.} } } else { echo "\t\t\t\t<li> Something went wrong. ". we don't have access to a user name with which to load the items. $URL. However. If the User Is Not Logged In If no user is logged in. $order). then uses a sub-quer y to filter the list items by user ID. and the list ID and URL ar e saved. Each formatted item is output immediately using echo() . } return array($LID. $db>errorInfo. The method looks like this in inc/class. If no r esults ar e r eturned (meaning the user doesn't have any items on their list). A check is in place to see if the list ID variable ( $LID ) is set. ListItemColor. If not.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. We'r e able to determine the list's ID using the list's URL. we need this information in or der to allow user s to submit new items and share their lists. If the quer y r eturns r esults. ListItemD one .inc. which is the only way a user who isn't logged in will be able to view a list in the fir st place. an additional query is executed to r etr ieve the user 's list ID and URL. ListItemID. "</li>\n". the list ID and URL won't be r eturned. Ther efor e. we loop thr ough them and call the yet-to-bedefined formatListItems() method. The list ID and URL ar e then r eturned as an arr ay.lists.

Inser t both methods into inc/class. $order = 1. but it also needs a helper method to determine the CSS class for each item. PDO::PARAM_STR). $order).FROM list_items WHERE ListID=( SELECT ListID FROM lists WHERE ListURL=:list ) ORDER BY ListItemPosition".lists. "</li>". ++$order. } else { echo "<li> Something went wrong. } } } Formatting List Items To make our pr evious two methods work pr oper ly. } $stmt->closeCursor(). $_GET['list']. if($stmt = $this->_db->prepare($sql)) { $stmt>bindParam(':list'. This helper method only exists to simplify our code. This method is fair ly str aightfor war d. we also need to define our formatListItems() method. which we'll call getColorClass() . $db->error. $stmt->execute().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 * @return string */ the position of the current list item the formatted HTML string .inc. ". while($row = $stmt->fetch()) { echo $this->formatListItems($row.

private function formatListItems($row, $order) { $c = $this->getColorClass($row['ListItemColor']); if($row['ListItemDone']==1) { $d = '<img class="crossout" src="/images/crossout.png" ' . 'style="width: 100%; display: block;"/>'; } else { $d = NULL; } // If not logged in, manually append the <span> tag to each i tem if(!isset($_SESSION['LoggedIn'])||$_SESSION['LoggedIn']!=1) { $ss = "<span>"; $se = "</span>"; } else { $ss = NULL; $se = NULL; } return "\t\t\t\t<li id=###BOT_TEXT###quot;$row[ListItemID]###BOT_TEXT###quot; rel=###BOT_TEXT###quot;$order###BOT_TEXT###quot; " . "class=###BOT_TEXT###quot;$c###BOT_TEXT###quot; color=###BOT_TEXT###quot;$row[ListItemColor]###BOT_TEXT###quot;>$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 arr ay containing each list item's attr ibutes is passed to formatListItems() , and differ ent attr ibutes ar e cr eated depending on the values that ar e passed. If a user isn't logged in, we manually append a <span> to the markup to keep our CSS fr om br eaking, and then we wr ap the whole thing in a <li> and r etur n it.

Calling Our New Methods in the Main View
Our main page ( index.php ) currently has notes fr om 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 or der to make these notes into a functional scr ipt, we need to add the following logic to index.php to call the pr oper methods:

<div id="main"> <noscript>This site just doesn't work, period, without Ja vaScript</noscript> <?php if(isset($_SESSION['LoggedIn']) && isset($_SESSION['Username'])): echo "\t\t\t<ul id=###BOT_TEXT###quot;list###BOT_TEXT###quot;>\n"; include_once 'inc/class.lists.inc.php'; $lists = new ColoredListsItems($db); list($LID, $URL, $order) = $lists->loadListItemsByUser();

echo "\t\t\t</ul>"; ?> <br /> <form action="db-interaction/lists.php" id="addnew" method="post"> <input type="text" id="new-list-item-text" name="newlist-item-text" /> <input type="hidden" id="current-list" name="currentlist" value="<?php echo $LID; ?>" /> <input type="hidden" id="new-list-itemposition" name="new-list-itemposition" value="<?php echo ++$order; ?>" /> <input type="submit" id="add-newsubmit" value="Add" class="button" /> </form> <div class="clear"></div> <div id="share-area"> <p>Public list URL: <a target="_blank" href="http://c oloredlists.com/<?php echo $URL ?>.html">http://coloredlists.com/<?ph p echo $URL ?>.html</a> &nbsp; <small>(Nobody but YOU will be able to edit th is list)</small></p> </div> <script type="text/javascript" src="js/jquery-ui1.7.2.custom.min.js"></script> <script type="text/javascript" src="js/jquery.jeditable.m ini.js"></script> <script type="text/javascript" src="js/lists.js"></script > <script type="text/javascript"> initialize(); </script> <?php elseif(isset($_GET['list'])): echo "\t\t\t<ul id='list'>\n"; include_once 'inc/class.lists.inc.php'; $lists = new ColoredListsItems($db);

We simply grab all of the new item's information out of the $_POST super global. If not. $text = strip_tags(urldecode(trim($_POST['text'])). $pos = $_POST['pos']. our app will function pr oper ly for a user that is logged in. echo "\t\t\t</ul>". . pr epar e a statement. encour aging the viewer to sign up.list($LID. error message on failure */ public function addListItem() { $list = $_POST['list']. Fir st. ?> </div> This scr ipt checks if a user is logged in. The PHP Saving an item is simple enough on the ser ver side. else: ?> <img src="/images/newlist. This is a r edundant check since we'r e using JavaScr ipt to r emove any unwanted tags. we need to write a PHP method that will add list items to our database. we check if ther e was a list URL supplied and outputs a non-editable list if one is found. $URL) = $lists->loadListItemsByListId(). Saving New List Items At this point. but we shouldn't r ely on data that was sanitized client-side. Now we just need to plug in the contr ols that will allow him or her to inter act with the list. WHITELIST ). and then we need to complete the jQuer y star ted by our designer in Par t 6. Other wise. then outputs their list and the pr oper contr ols if so.jpg" alt="Your new list here!" /> <?php endif. the "sales" page is displayed. To do this. 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. we need to allow for new items to be cr eated. and save the info in the database. Note that we'r e r unning strip_tags() on the list item's text.

$stmt->bindParam(':text'. data: "action=add&list=" + forList + "&text=" + URLte xt + "&pos=" + newListItemRel. :pos.php : // HTML Whitelist define('WHITELIST'.js . . $list. we should assume that we'll want to change this list at some point in the futur e. forList = $("#current-list"). newListItemText = strip_tags(cleanHREF($("#new-list-itemtext").submit(function(){ // HTML tag whitelist. newListItemRel = $('#list li'). $whitelist). PDO::PARAM_INT). PDO::PARAM_INT). ListItemPosition. var $whitelist = '<b><i><strong><em><a>'. $stmt->bindParam(':pos'.$sql = "INSERT INTO list_items (ListID. } } } Notice that we used a constant called WHITELIST in the strip_tags() function. $stmt->closeCursor(). 1)". we need to modify the script with the code below: // AJAX style adding of list items $('#add-new').php". which is why we'r e saving the list as a constant. All other tags are stripped.size()+1.val(). $stmt->bindParam(':list'. However. '<b><i><strong><em><a>'). which we'll define in inc/constants. Finishing the JavaScript To complete the jQuer y in js/lists. This is a list of allowed tags that our user s have access to. $pos. url: "db-interaction/lists.inc. try { $stmt = $this->_db->prepare($sql). URLtext = escape(newListItemText).val()). :text.ajax({ type: "POST". ListItemColo r) VALUES (:list. PDO::PARAM_STR). $stmt->execute(). } catch(PDOException $e) { return $e->getMessage(). $text. if(newListItemText.length > 0) { $. return $this->_db->lastInsertId(). ListText.

} return false.php .inc./inc/class.append("<li color='1' class='colorBlue' rel='"+newListItemRel+"' id='" + theResponse + "'><span id=###BOT_TEXT###quot;"+theRes ponse+"listitem###BOT_TEXT###quot; title='Click to edit. so let's just define the whole file her e. Error message? } }).'>" + newListItemText + "</s pan><div class='draggertab tab'></div><div class='colortab tab'></div ><div class='deletetab tab'></div><div class='donetab tab'></div></li >").success: function(theResponse){ $("#list"). include_once ".ajax() call sends to db-interaction/lists.. The successfully added item is then appended to our list. all without a page r efr esh.lists... }.php in the db-interaction folder and inser t the following code into it: <?php session_start(). error: function(){ // uh oh. Cr eate new file called lists. didn't work.php". include_once ".. bindAllTabs("#list li[rel='"+newListItemRel+"'] spa n"). All r equests ar e handled the same way.php .php"./inc/constants.ajax() call by submitting the new item via the POST method to dbinteration/lists. // prevent default form submission }). switch($_POST['action']) { .inc. $("#new-list-item-text"). } else { $("#new-list-item-text").val(""). which doesn't exist yet. if(!empty($_POST['action']) && isset($_SESSION['LoggedIn']) && $_SESSION['LoggedIn']==1) { $listObj = new ColoredListsItems(). Handling List Interactions Our $. We'r e completing the $.val(""). This script acts as a switch that will determine what action is needed and execute the pr oper method.

This is definitely the most complex par t of our whole app. default: header("Location: /"). case 'done': echo $listObj->toggleListItemDone(). break. it ends up in a new place in the list. exit. case 'update': $listObj->updateListItem(). break. case 'delete': echo $listObj->deleteListItem(). } } else { header("Location: /"). using . both the item's star ting position and curr ent position ar e passed. break. break. we need to allow user s to save the or der of their items after they've dr agged and dr opped them. We select all the items in the curr ent list with a position falling between the star ting and current positions. we'r e going to call this new position it's curr ent position. then. break. as well as the dir ection it moved. } ?> Reordering List Items Next. This is wher e it gets tricky. The PHP Each item is assigned a position when it's r ead out of the database. When it is dragged. case 'color': echo $listObj->changeListItemColor(). case 'sort': $listObj->changeListItemPosition(). break. break. This is the item's star ting position. we set up one of two conditional queries.case 'add': echo $listObj->addListItem(). Depending on the dir ection the item was moved. When changeListItemPosition() is called.

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']. the position is simply incremente d. then the query sets its position to the new * position. at which point we set the item's position to the current position.the CASE clause. */ $sql = "UPDATE list_items SET ListItemPosition=( CASE WHEN ListItemPosition+1>$startPos THEN $c urrentPos ELSE ListItemPosition+1 END) WHERE ListID=$listid AND ListItemPosition BETWEEN $currentPos AND $sta rtPos". $currentPos = (int) $_POST['currentPos']. incr ement or decr ement their positions by 1 unless the item's position plus or minus one falls outside the r ange we've selected. In this way. if($direction == 'up') { /* * This query modifies all items with a position between the item's * original position and the position it was moved to. } . which could potentially cause a per formance bottleneck. If the * change makes the item's position greater than the item 's * starting position. we'r e able to avoid firing an individual quer y for each item. $startPos = (int) $_POST['startPos']. Otherwise. $direction = $_POST['direction'].

attr('id') == itemID) { var startPos = itemREL. . echo "Query executed successfully. $('#list li'). ". except item positions are decremented. } else { var direction = 'up'. } } Finishing the JavaScript To call our method. and if the * item's changed position is less than the starting posi tion. we need to modify js/lists. its * position is set to the new position. if(startPos < currentPos) { var direction = 'down'.val().each(function() { if($(this). "Affected rows: $rows". } $rows = $this->_db->exec($sql). } var postURL = "action=sort&currentListID="+currentListID +"&startPos="+startPos +"&currentPos="+currentPos +"&direction="+direction. itemREL){ var i = 1. */ $sql = "UPDATE list_items SET ListItemPosition=( CASE WHEN ListItemPosition1<$startPos THEN $currentPos ELSE ListItemPosition-1 END) WHERE ListID=$listid AND ListItemPosition BETWEEN $startPos AND $curre ntPos".else { /* * Same as above. currentPos = i. currentListID = $('#current-list').js by adding a new function called saveListOrder() : function saveListOrder(itemID.

This function needs to be called when a sor table item is updated. }). var t = setTimeout("saveListOrder('"+id+"'. When we find the list item that matches the moved item's ID. otherwise it's too fast.draggertab".php for pr ocessing.sortable({ handle : ". '"+rel+"')". our counter now r eflects the item's current position. Then we loop thr ough each list item while incr ementing a counter ( i ).php". The r el attr ibute contains the or iginal position of the item.ajax({ type: "POST". count). which we accomplish by modifying the following in js/lists. forcePlaceholderSize: true }). var rel = ui.item.item. success: function(msg) { // Resets the rel attribute to reflect current po sitions var count=1. $('#list li').and client-side.attr('rel'). url: "db-interaction/lists. } This function accepts the ID and r el attr ibute of the item that was moved. Changing Item Colors Changing an item's color is fairly simple on both the ser ver .5 00).attr('rel'. for the DOM to set. We can then determine which dir ection the item was moved and send the info to db-interaction/lists.attr('id'). error: function(msg) { // error handling here } }). } i++. }. count++. }). update : function(event. . }. which we need as its star ting position. $("#list").$. data: postURL.js : // MAKE THE LIST SORTABLE VIA JQUERY UI // calls the SaveListOrder function after a change // waits for one second first.each(function() { $(this). ui){ var id = ui.

parent(). return TRUE. $stmt->bindParam(':item'. 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. PDO::PARAM_INT). } } } Finishing the JavaScript The function that saves new color s is called by submitting the item ID and new color via POST in the $.js : // COLOR CYCLING // Does AJAX save. color = $(this). $_POST['color']. . error message on fai lure */ public function changeListItemColor() { $sql = "UPDATE list_items SET ListItemColor=:color WHERE ListItemID=:item LIMIT 1". $stmt>bindParam(':color'.attr("color"). $stmt->closeCursor(). function(){ $(this). we simply pass it's ID and the new color code to the method changeListItemColor() and cr eate and execute a quer y.The PHP To update an item's color . but no visual feedback $(". try { $stmt = $this->_db->prepare($sql). $stmt->execute().attr("id").live("click". $_POST['id'].parent().ajax() call below in js/lists.nextColor(). var id = $(this).colortab").parent(). } catch(PDOException $e) { return $e->getMessage(). PDO::PARAM_INT).

error messa ge on fail */ public function updateListItem() { $listItemID = $_POST["listItemID"]. $stmt->bindParam(':id'. $newValue.$. success: function(msg) { // error message } }). $stmt->closeCursor(). WHIT ELIST).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. Add the following method in inc/class. $sql = "UPDATE list_items SET ListText=:text WHERE ListItemID=:id LIMIT 1". and pr epar e and execute a quer y to update the item in the database. }).inc. we need to cr eate a method called updateListItem() . url: "db-interaction/lists. let's make sur e edited items ar e updated in the database. if($stmt = $this->_db->prepare($sql)) { $stmt->bindParam(':text'. Editing Item Text Next.php". $listItemID. . data: "action=color&id=" + id + "&color=" + color. PDO::PARAM_STR). $stmt->execute(). $newValue = strip_tags(urldecode(trim($_POST["value"])).lists. This method will extr act the ID of the modified item and the new text fr om the $_POST superglobal. doublecheck the item text for disallowed tags. The PHP To save updated items in the database.ajax({ type: "POST". PDO::PARAM_INT).

echo $newValue. sorry about that!". tooltip : 'Double-click to edit..editable("db-interaction/lists. { id : 'listItemID'...'. } } } Finishing the JavaScript Activate this method by modifying the path in bindAllTabs() in js/lists. submit : 'Save'. } else { echo "Error saving.php". The PHP The toggleListItemDone() method r etr ieves the item's ID and "done" status fr om the $_POST super global 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.. submitdata: {action : "update"} }). error message on fai lure . event : 'dblclick'.js : // This is seperated to a function so that it can be called at page l oad // as well as when new list items are appended via AJAX function bindAllTabs(editableTarget) { // CLICK-TO-EDIT on list items $(editableTarget).'. the user needs to be able to save a flag in the database that will indicate the item's "done" status. indicator : 'Saving. } Marking Items as "Done" To mark an item as done.

we write a function called toggleDone() in js/lists.parent() .attr('id'). data: "action=done&id="+id+"&done="+isDone }) } Next. function() { var id = $(this). isDone) { $. } catch(PDOException $e) { return $e->getMessage().parent().siblings('span'). $stmt->closeCursor(). return TRUE. $_POST['done'].find("span") . PDO::PARAM_INT). $_POST['id']. if(!$(this).js .append("<img src='/images/crossout. $stmt->bindParam(':item'.length) { $(this) .children('img. function toggleDone(id. $stmt->execute().ajax() function and sends the item ID and "done" status to our list handler.php". This function simply executes a call to the $. PDO::PARAM_INT). url: "db-interaction/lists. try { $stmt = $this->_db->prepare($sql). } } } Finishing the JavaScript To call our method.crossout').*/ public function toggleListItemDone() { $sql = "UPDATE list_items SET ListItemDone=:done WHERE ListItemID=:item LIMIT 1". we assign toggleDone() as the callback function for the animate() even that happens when our user clicks the done tab: $(".donetab").ajax({ type: "POST".png' class='c rossout' />") .live("click". $stmt>bindParam(':done'.

. This method will r etr ieve the item and list IDs fr om the $_POST super global.end() . 0)). class ColoredListsItems { // Class properties and other methods omitted to save space /** * Removes a list item from the database * . } else { $(this) .5" }. "slow".php . } }). we need to allow our user s to delete items that they no longer want on their list.find('img.siblings('span') .crossout") . all items in the list with a position higher than that of the item that was deleted need to be decr emented by 1.end() . "swing". to pr eser ve pr oper or der in the list.find(". "slow". Then. 1)).animate({ opacity: "0.inc.animate({ width: "100%" }) . "swing". we need to cr eate a method called deleteListItem() in inc/class. toggleDone(id.crossout') .lists. toggleDone(id. The PHP To delete an item.remove() . then r emove the item fr om the list. Deleting Items Finally.animate({ opacity : 1 }.

} } } Finishing the JavaScript To activate this method. $stmt->closeCursor(). $stmt->execute(). we need to modify our jQuer y by updating the section in js/lists.* @return string message indicating success or failure */ public function deleteListItem() { $list = $_POST['list']. try { $stmt = $this->_db->prepare($sql). $item. } } catch(Exception $e) { return $e->getMessage(). $stmt>bindParam(':pos'. $stmt->closeCursor(). $list. PDO::PARAM_INT). PDO::PARAM_INT). PDO::PARAM_INT).js that deals with item deletion: . return "Success!". $_POST['pos']. $stmt->bindParam(':list'. try { $stmt = $this->_db->prepare($sql). $item = $_POST['id']. $sql = "DELETE FROM list_items WHERE ListItemID=:item AND ListID=:list LIMIT 1". $stmt->execute(). $list. PDO::PARAM_INT). } catch(PDOException $e) { return $e->getMessage(). $stmt->bindParam(':list'. $stmt->bindParam(':item'. $sql = "UPDATE list_items SET ListItemPosition=ListItemPosition-1 WHERE ListID=:list AND ListItemPosition>:pos".

"pos":pos }. thiscache .ajax({ type: "POST". success: function(r){ var $li = $('#list'). }.") .deletetab").parent()) . list = $('#current-list'). $('#list') . "go for it").animate({ width: "44px". 200) .attr('rel'.parent() . "action":"delete".data("readyToDelete") == "go for it") { $. id = thiscache.each(function(){ $(this). if (thiscache.val().// AJAX style deletion of list items $(". error: function() { $("#main"). data: { "list":list. url: "db-interaction/lists.attr('rel'). ++positio n)..parent(). 400. . }).children('li').parents('li'). } }). "id":id. } else { thiscache.hide("explode".. right: "-64px" }.live("click".children('li') . function(){$(th is).data("readyToDelete".php".attr("id"). function(){ var thiscache = $(this).remove()}). pos = thiscache.not(thiscache.prepend("Deleting the item failed. position = 0. } }).

In our opinion. Object-Oriented Programming Because we should always aim to be efficient when programming. Passwords are stored in encrypted formats and never sent in the clear via Email. All the interaction that happens with the database is secure. For a little more information on OOP and why it¶s beneficial. Only users who are logged in can issue commands which result in database changes.com Below we¶re going to wrap up a few things by talking about some of the choices we made. we built this app with the concept of DRY programming in mind. OOP allows us to group common methods together and separate tasks out without needing to pass parameters from function to function. security precautions. and those users are only able to issue commands that affect their own data. First. taking the object-oriented programming (OOP) approach was the best way to keep this app DRY. and we went thr ough it r ather quickly. Those users are putting their trust in us to make sure their data is safe. we'll go over the security measur es and other finishing touches this app needs to be r eady for public use. our JavaScript (like all JavaScript) is publically . But because there is a variety of AJAX stuff going on in this app.0 of this app. You can go check out the real live app for yourselves: http://coloredlists. We'll also go over some of the featur es we hope to add in the futur e. thanks for following along this whole journey. This app is already pretty darn secure. which includes their password and all the information they¶ve entered into the lists. so please post any questions you have in the comments! In the final installment of this ser ies. We have users with accounts who are storing data with us. read Jason¶s introduction to OOP. Part VIII Hooray we made it! First of all. DRY stands for ³Don¶t Repeat Yourself´ and should lie somewhere near the core of our programming philosophy. and ideas we (and you) have for a version 2.Moving On We have now succeeded in building all of the AJAX functionality for our app! Ther e was a ton of information in this ar ticle. our security needs to take into account a few more scenarios. Security Security is incredibly important in any application.

Because of this. Fortunately. and second.) Data Escaping While PDO is powerful against SQL injection. Also. or deleted entirely by a malicious user. Lucky for us. (Keep in mind that prepared statements are not exclusive to PDO. Fortunately. When a user . so it¶s virtually impossible for SQL injection to occur while using prepared statements. other database extensions. All the escaping is done for us when the parameters are inserted into the query. also support them. which are like query templates that we can customize with parameters. Client Side Sanitization Secondly. 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. This means that it is really important that we keep any kind of SQL injection from happening. the potential for database attacks. If a malicious user injects dangerous tags into our database. it¶s best to do some of that input scrubbing directly in the JavaScript. PDO Database attacks. including this oneby Dean Edwards. It was because of this powerful security advantage that we chose PDO for this app. This JavaScript contains the code for making AJAX calls. There are a lot of tools available to do this. PHP has built-in functions that will allow us to perform basic sanitization of user input. meaning the URL we are sending to and what data that URL is expecting. we¶re performing this escaping before the data is inserted into the database.viewable. as well as downloads faster. because we never want that sort of thing to be allowed. This tells potential attackers a good bit of information regarding how they might send malicious requests. manipulated. because we are inputting data and turn it around to display immediately on the screen. are a particularly nasty form of attack. 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. Security in the JavaScript First. PHP provides us with several methods to combat these risks. such as MySQLi. called SQL injection. we need to be very careful and ensure that all incoming data is escaped properly. it doesn¶t help us when we¶ve read the information out of the database. a good measure is to ³Pack´ the javascript so it isn¶t so easily readable. Security on the Server Side Avoiding attacks on the server side involves two major risk factors: first. A vulnerable database can be read. PHP Data Objects (PDO) virtually eliminates the risk for SQL injection through the use of prepared statements. they¶ll still be dangerous when they¶re retrieved unless we take further measures to sanitize user data.

var i = 0. // Match tags matches = str. var allowed_array = [].*?)href=['"](javascript:)(. } // Save HTML tag html = matches[key].indexOf('<'+allowed_tag+'>').join(replace). 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. // Go through all allowed tags for (k in allowed_array) { // Init allowed_tag = allowed_array[k]. "Naughty!"). } Then we¶ll also scrub that input text for any other HTML. }. // Build allowes tags associative array if (allowed_tags) { allowed_array = allowed_tags.enters a new list item. allowed_tags) { var key = ''.split(search).toString(). var $whitelist = '<b><i><strong><em><a>'. var replacer = function(search. if (i != 0) { i = html. Some HTML we will allow. i = -1. var matches = []. replace. we¶ll strip away all tags except those set up in a whitelist. str) { return str.match(/([a-zA-Z]+)/gi). // Is tag not in allowed list? Remove from str! allowed = false. in case users want to format their lists a bit like with <strong> tags and the like. which has ported a number of useful PHP functions to JavaScript. NOTE: The strip_tags() function used below is part of the php. we¶ll take two steps to scrub it. var html = ''. var allowed_tag = ''. // Go through all HTML tags for (key in matches) { if (isNaN(key)) { // IE7 Hack continue.} .js project.toLowerCase(). With the function below.replace(/###BOT_TEXT###lt;a(. } str += ''. var k = ''. allowed = false.match(/(<\/?[\S][^>]*>)/gi).+?)<\/a>/gi. // Strip HTML tags with a whitelist function strip_tags(str.

.val(). // Custom replace. which provides a (minor) deterrent to malicious users. var $whitelist = '<b><i><strong><em><a>'.js before sending off the AJAX request that adds a new list item« .} if (i != 0) { i = html. A secondary. so if they already have one they would just be emailed and asked to join the list (they can accept or not accept). newListItemText = strip_tags(cleanHREF($("#new-list-item-text"). they would be promoted to join first.indexOf('<'+allowed_tag+' '). but are all probably great ideas assuming they are implemented well..} // Determine if (i == 0) { allowed = true. . } } if (!allowed) { str = replacer(html. } These functions are implemented in js/lists. Sharing meaning literally collaborative editing. This is a great start on a simple and usable list application. The user would need an account. less important reason to use POST is that it¶s a little harder to send a bogus request using POST.indexOf('</'+allowed_tag) . The primary reason not to use GET for modifying data is that a request made using GET is sent in the URL (i. This is done because the GET method should only be used for retrieval. Here are some ideas of ways to expand functionality.val()).e. and not for any action that will modify data in any way. Perhaps they slightly complicate things.toLowerCase(). // AJAX style adding of list items $('#add-new').0 Features Of course our work as designers and developers is never done. 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. 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. 2. $whitelist). "".toLowerCase(). forList = $("#current-list").com?get=request&is=this&part=here). All other tags are stripped. http://example.submit(function(){ // HTML tag whitelist. break.. but right away new features jump to mind. str).. No regexing } } return str. y List sharing Enter an email address for someone to share the list with. If that email address did not have an account.if (i != 0) { i = html..

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