How-to's & Tutorials

jQuery Animations: A 7-Step Program

NETTUTS - Wed, 2010-01-06 09:23

A dash of animation can spruce up a dull interface. In this tutorial, you’ll learn how to do it the right way with jQuery.


A Word From the Author

Animation: a concept which often evokes divisive reactions from people. Some swear by its usefulness, whilst others rage at its overuse. Nevertheless, animations, when used right, often spruce up a user interface, and make it feel much more snappy and clean. In this tutorial, we are going to start with a little jQuery basics, then learn how to create our first animation, and then on to building the effect and finally crafting something that will be of actual real world use.

Interested? Let’s start right away! Please do note that as this is geared towards the beginner, I might harp a little too much on some parts. Bear with me. In case you are curious, check out the first part of the demo for the simple effect we’ll be building today.

Step 1. jQuery Basics

jQuery is a JavaScript library which intends to make it easier for you, a developer, to construct better, feature rich, interactive web sites and user interfaces with as few lines of code as possible.

A typical line of code looks like so:

$(DOM Element).something();

Let’s take a look at each part:

  • $ – Shorthand for the jquery object. Use jquery in case you are using another framework in the same page, as in: jquery(DOM Element).something();
  • (DOM Element) – The element you want to do something with. This is one of jQuery’s ace in the hole. You can use CSS selectors to obtain an element. Any declaration which works in a CSS document can be utilized here. IDs, classes, pseudo classes, anything.
  • something() – The thing you want to do to the obtained element. This can be anything from hiding an element to making an AJAX call to an event handler.

Here, we are going to just stick with animation and effects related functionality.

Step 2. Using the Baked-in Effects

jQuery provides a ton of built in methods you can use right out of the box. These include methods for showing/hiding elements with a number of variations including sliding the element and fading the element in and out. You also get to use a number of toggle methods which toggle the visibility of the element in question.

Before we take a look at each of these methods, here is the general format for calling each method:

$("#element").effect([speed, callback]);

Following the general jQuery paradigm, we initially target the required element using CSS selectors. Next, we just call any of the built in methods.

While, most of the methods can be called passing no parameters, often you’d want to customize it’s functionality. Each methods takes at least the speed and callback parameters.

speed denotes the duration, in seconds, of the animation. You can pass in strings including slow, normal or fast or you can be more precise and set the time in milliseconds.

callback is a function which gets executed once the animations completes. You can use it to do anything, make an AJAX call silently in the background, update another part of the user interface and so on. Your imagination is the limit.

Here is a list of the functions bundled with jQuery:

  • show/hide – Methods to show or hide an element. Takes speed and a callback as parameters.
  • toggle – Method which manipulates the display of the element depending on the current state of the element i.e. if it is hidden, it is displayed and vice versa. Uses the show or hide methods.
  • slideDown/slideUp – Self explanatory. Varies the height of the element to create a sliding animation to reveal or hide an element.
  • slideToggle – Same as the toggle method except that it use the slideDown/slideUp methods to reveal or hide elements.
  • fadeIn/fadeOut – Varies the opacity of the element in question to create a fade effect.
  • fadeTo – Alters the opacity of the element to match the passed in value. Obviously, it takes an additional opacity parameter where 0 is completely transparent and 1 is completely opaque.

As an additional feature, the toggle method mentioned above has an alternative implementation where it takes an expression as a parameter to decide whether to display or hide an element.

For example, if you want to toggle only list elements which have a effect class, your code would look like:

$("li").toggle( $(this).hasClass("effect") );

To put it simply, the toggle functions checks the expression passed to it and if it is true, it is toggled. Else, it is left alone. The expression we’ve passed here checks whether the element has a specific class.

Step 3. Building a Custom Animation

Often, the built in methods don’t quite fit your needs in which case you’d definitely want to build your own custom effects. jQuery lets you do that too. Quite easily, actually.

To make a custom animation effect, you make use of the animate method. Let me show you.

$("#somelement").animate({property: value}, [speed, callback] );

The animate method is just like any other method in that it is invoked the same way. We acquire an element and then pass some parameters to it. The parameters is where this method differs from the pre built effects.

The speed and callback parameters serve the same function as in the previous methods. The object property which holds a number of key/value pairs is what it makes this method unique. You pass in each property you want animated along with the final value. For example, suppose you want to animate an element to 90% of its current width, you’d have to do something like:

$("#somelement").animate({width: "90%"}, 350, function(){alert ("The animation has finished running.");});

The above snippet will animate the element to 90% of its width and then alert the user denoting that it has finished.

Note that you aren’t limited to dimensions. You can animate a wide array of properties including opacity, margins, padding’s, borders, font sizes. The method is also pretty flexible when it comes to units. Pixels, ems, percentages all work. So something as convoluted as the below will work. It just won’t look cohesive.

$("#somelement").animate({ width: "90%" fontSize: "14em", height: "183px", opacity: 0.8, marginTop: "2cm", marginLeft: "0.3in", borderBottom: "30mm", }, 350, function(){alert ("The animation has finished running.");});

When defining a property which consists of more than one word, make a note to define it in camel case. This is in sharp contrast to the usual CSS syntax so make a special note here. It’s borderTop, not border-top.

Note: jQuery allows only properties taking numerical values to be animated. This means utilizing only the core, all color related properties are out. Fret not though. With some help from jQuery UI, we’ll be animating colors in no time.

Step 4. Tweaking the Effect

If you take a look at the simple effect in the demo, you might have noticed it is a little broken. Hovering on and off the element repeatedly leads to a long queue which inturn leads to the animation repeating itself.

The simplest way to avoid this issue is by using the stop method immediately before beginning the animation. This effectively clears the queue and the animation can proceed as usual. For example, this would be your normal code.

$("#someelement") .hover(function() { $(this).animate({ top: 20 }, 'fast'); }, function() { $(this).animate({ top: 0 }, 'fast'); });

Using stop to avoid animation buildups, your new code would look like:

$("#someelement") .hover(function() { $(this).stop().animate({ top: 20 }, 'fast'); }, function() { $(this).stop().animate({ top: 0 }, 'fast'); });

Pretty easy, no? But this method does have a small issue. Fast interactions won’t lead to buildups but instead to incomplete animations. If you want to completely get rid of this issue, you’ll have to turn to a plugin like hoverFlow.

Step 5. Adding a Bit More Realism – Easing

If you’d like to add a little more realism, you’d need more control over the rate at which the animation progresses. This is where easing comes in. Easing essentially controls the acceleration and deceleration of the animation over time.

The default easing method is swing which is built into jQuery. Robert Penner’s easing plugin lets you use a number of easing effects. Check out the easing section in the demo to take a look at each easing effect.

There is only caveat when it comes to using a custom easing effect: you can only use it with custom animation effects i.e. with the animate method. Once you have the easing plugin included, making use of a custom easing method is as simple as passing it along as a parameter like so:

$("#somelement").animate({ width: "90%" height: "183px", }, 550, "easeInElastic");

Do check out the easing section of the demo to see each method in action. While some of the easing effects may not be suitable for all situations, your animations will certainly look a lot more appealing with the appropriate use of certain easing methods.

Step 6. Kicking it up a Notch – jQuery UI

Upgrading to jQuery UI nets us a number of much needed features. You, in fact, don’t need the entire library to make use of the additional features. You just need the core effects file to obtain the functionality. Not the UI core, just the effects core file which weighs in at a relatively miniscule 10 kilobytes.

The important features which the jQuery UI effects library brings to the table are support for animating colors, easing and class transitions.

If you remember, I mentioned previously that with the core jQuery library, you are limited to only animating properties which take numbers. You are rid of this limitation with jQ UI. You can now easily animate an element’s background color, its border color and so on. Also, instead of creating a separate function for these extended features, this merely extends the base animate function. This means that if you have the core library included in your file, you can use the ordinary animate method to do all the dirty work for you.

For example, if you’d want to animate an element’s border color on hover, your code would look like so:

$(".block").hover(function() { $(this).animate({ borderColor: "black" }, 1000); },function() { $(this).animate({ borderColor: "red" }, 500); });

Class transitions are responsible for animating between classes. With the base library, if you removed and then added a class which changed the appearance of an element, it’d happen instantenously. When you have the UI library on board, you get the option of passing in additional parameters to take care of the speed of the animation, the easing method and a callback. As with the previous feature, this functionality is built on top of the existing jQuery API which eases the transition process.

The final feature which jQuery brings to the table is integrated easing equations. Previously you had to make use of an additional plugin to take care of this but now it comes bundled so you need not worry about it anymore.

Step 7. Building our First Real World Effect

All the above examples were meant to be just demos of the functionality in question. Wouldn’t it be nice to actually build something of real world use? That is exactly what we are going to do today. It’s not really something radically new or groundbreaking but this demo will let you put what we’ve learnt today to use in a real world scenario.

Imagine this: You want to display an image and when a user mouse overs it, you display 2 sections inside the image. One shows a heading and the other shows a tiny description of the image. I’m sure you’ll find literally a gazillion plugins which do the same but today we are going to build it from scratch. I promise you it’s not as hard as it sounds. In fact, it’s actually pretty easy and can be very quickly built. Let’s get started.

The HTML

First, we need a solid base of markup to build on.

<!DOCTYPE html> <html lang="en-GB"> <head> <title>Beginning Animations with jQuery - by Siddharth for NetTuts</title> <link type="text/css" href="css/style.css" rel="stylesheet" /> <script type="text/javascript" src="js/jquery.js"></script> <script type="text/javascript" src="js/jqueryui.js"></script> <script type="text/javascript" src="js/mojo.js"></script> </head> <body> <div id="container"> <h1>Beginning Animations with jQuery</h1> <div>by Siddharth for the lovely folks at Net Tuts</div> <p>A simple real world usage of the animation effects followed by demos for the article's text demonstrating a fix for animation build up and the different easing methods available.</p> <div class="bblock"> <h2>A Simple Real World Effect</h2> <p>When the method is changed, it goes to zero height using the standard jQuery easing and then expands using the specified easing method.</p> <div class="item"> <div class="title">ThemeForest</div> <img src="images/tf.jpg" alt="Image" /> <div class="desc">The only place you need for site templates and themes </div> </div> <div class="item last"> <div class="title">CodeCanyon</div> <img src="images/cc.jpg" alt="Image" /> <div class="desc">The premier destination for scripts and code snippets</div> </div> </div> <!-- Rest of the code for the demo --> </div> </body> </html>

For this effect, we’ll first need to decide on a structure for each item. Each item is wrapped by a div with a class of item. Inside the div there are 3 elements: the image itself and 2 div elements holding the title and description.

The other parts are rather mundane and self explanatory. We begin by including the jQuery library, the jQuery UI library and our file which holds our custom JS code. Do note that I only needed the effects core part of jQuery UI so I downloaded only that. If you’d like more effects built in, you’ll need a custom build. You can download a customized build here.

Here is how our page looks with this phase complete.

Tutorial ImageThe CSS .item { position: relative; margin: 20px 60px 40px 0; overflow: hidden; } .item .title, .item .desc { background: #000; color: #fff; position: absolute; display: block; width: 638px; opacity: 0.4; } .item .title { top: 0; font-size: 16px; padding: 12px 10px 25px 0; text-align: right; } .item .desc { bottom: 0; font-size: 12px; padding: 5px 0 15px 10px; }

A few things you need to make a note of here. Each item element has its position property set to relative so that elements inside it can be positioned easily. Also, its overflow property is set to hidden so we can hide the title and description outside when they aren’t needed.

The title and description have their position property set to absolute so that they can be positioned precisely within the item element. The title has a top value of 0 so its at the top and the description has its bottom value set to 0 so its right at the bottom.

Other than that, the CSS is pretty boilerplate and concerns itself mainly with typography, a little positioning and styling. Nothing too radical.

Here is how our page looks with this phase complete.

Tutorial ImageThe jQuery-Enabled JavaScript Magic $(document).ready(function() { // Code for other parts of the demo $(".item").children("div.title").animate({top: -60}, 300); $(".item").children("div.desc").animate({bottom: -40}, 300); $(".item").hover( function() { $(this).children("div.title").stop().animate({top: 0}, 700, "easeOutBounce"); $(this).children("div.desc").stop().animate({bottom: 0}, 700, "easeOutBounce"); }, function(){ $(this).children("div.title").stop().animate({top: -60}, 500); $(this).children("div.desc").stop().animate({bottom: -40}, 400); } ); $(".title, .desc").hover( function() { $(this).stop().animate({backgroundColor: "#444"}, 700, "easeOutSine"); }, function(){ $(this).stop().animate({backgroundColor: "#000"}, 700); } ); });

It may look a little imposing but it’s not. Let me explain each part.

The logic of this effect is pretty simple. Since the elements are placed absolutely, we initially place the elements out of the viewport. When the image is hovered over, we just need to move it back to its original position i.e. at the top and bottom of the image.

First, we move the title and description out of view. We do this with JavaScript instead of with CSS for a very specific reason. Even if JS is disabled, it degrades pretty gracefully. The title and description are still overlaid over the image and it looks just like the hover state. If we were to instead use CSS for hiding the title and description and if JS is disabled, they’d be completely hidden and thus useless. I chose to use JS for the dirty work in the interest of progressive enhancement.

We initially bind the our custom code to the hover function of each item element. This lets use make this handler pretty generic and reusable. Adding this functionality to others is as simple as giving it a item class. The first function is for the hover event and the second is for the unhover event.

Within the scope of the function, this refers to the element which triggered the event. We use the animate method to modify the appropriate values. We also use a bit of easing to make it a little more catchy. On mouseout or unhover, we just change the values back to their defaults.

As a little extra, when the containers for the title and description are hovered over, they slowly morph colors, thanks to jQuery UI.

And we are actually done. Didn’t take that much time, did it? Not bad for a tiny script which is even more tiny considering how many lines were allocated solely for the curly braces.

Conclusion

And there you have it. How to create animations with jQuery along with a real world example of putting what you’ve learned to good use. Hopefully, you’ve found this tutorial interesting, and useful. Feel free to reuse this code elsewhere in your projects and chime in here if you are running into difficulties.

Questions? Nice things to say? Criticisms? Hit the comments section and leave me a comment. Happy coding!

Write a Plus Tutorial

Did you know that you can earn up to $600 for writing a PLUS tutorial and/or screencast for us? We’re looking for in depth and well-written tutorials on HTML, CSS, PHP, and JavaScript. If you’re of the ability, please contact Jeffrey at nettuts@tutsplus.com.

Please note that actual compensation will be dependent upon the quality of the final tutorial and screencast.

Write a PLUS tutorial


Categories: How-to's & Tutorials

Filtering Blocks v2

CSS-Tricks - Wed, 2010-01-06 05:35

This is an update to the first version of filtering blocks I did a while back. The idea is that you have a long list or large set of “blocks” on the page. Each block belongs to a certain group. There is navigation on the page for viewing all of them at once, or selecting which group you would like to see. Selecting a particular group hides the blocks from any other group, hence “filtering”.

 

View Demo   Download Files

 

HTML

Each block has class names applied to it for the groups it belongs to. Here are two examples. The both belong to the group “all”, the top one “adv” and the bottom one “cla”.

<div class="all adv"> <img src="images/flavor-curry.jpg" alt="" /> <h4>Sweet Curry With Saffron</h4> <p>Lusciously mellow with notes of overripe berries, 55% Hawaiian dark chocolate meets its soulmate in sweet curry - awash in spices including coriander, tumeric, cumin and cardamom and sprinkled with rare saffron. This spicy melange is slowly steeped in fresh coconut puree and gently blended with the chocolate. The taste rushes over you in waves - fragrant curry, chased by coconut, then the lingering, raisiny sweetness of chocolate. Available in <a href="/store/products/Chocolatier_s_Choice-5-2.html">Chocolatier's Choice</a> and <a href="/store/products/Adventurous_Collection-3-2.html">Adventurous Collection</a>.</p> </div> <div class="all cla"> <img src="images/flavor-vanilla.jpg" alt="" /> <h4>Lucille's Vanilla </h4> <p>For those who prefer milder chocolate, this winsome truffle will ignite a lifelong love affair. Gail uses a dark blend in this recipe handed down through four generations of the Guittard family of chocolate makers. It tastes like a rich spoonful of homemade chocolate pudding - just like Gail's mom, Lucille, made on the stovetop in their family farm's kitchen. Available in <a href="/store/products/Chocolatier_s_Choice-5-2.html">Chocolatier's Choice</a> and <a href="http://gailambrosius.com/store/products/Classic_Collection-2-2.html">Classic Collection</a>.</p> </div>

Then the navigation includes REL attributes that reference those classes:

<div id="flavor-nav"> <a rel="all" class="current">All</a> <a rel="cla">Classic</a> <a rel="adv">Adventurous</a> <a rel="tea">Tea-Inspired</a> </div> jQuery JavaScript

The plan in plain English:

  1. When a link in the filtering navigation is clicked…
  2. Fade down all blocks (visual indication something is changing)
  3. Remove “current” class from all navigation and apply it to newly clicked navigation
  4. Figure out which group should be showing from the REL attribute
  5. Any flavor NOT a part of the group, slide up
  6. Any flavor that IS a part of the group, slide down
  7. Fade back up all blocks
$(function() { var newSelection = ""; $("#flavor-nav a").click(function(){ $("#all-flavors").fadeTo(200, 0.10); $("#flavor-nav a").removeClass("current"); $(this).addClass("current"); newSelection = $(this).attr("rel"); $(".flavor").not("."+newSelection).slideUp(); $("."+newSelection).slideDown(); $("#all-flavors").fadeTo(600, 1); }); });

This leads to a smoother experience than we had in version 1. Primarily because of the use of the .not() native jQuery filter. Before, we just “slid up” all the flavors and then “slid down” the correct ones. That means that every single flavor went through animation on every single navigation change. But that isn’t always necessary. For example if you are viewing one sub-set, then click back to “all”, those currently showing blocks don’t need to be animated, just all the other ones need to grow out. This solves that.

Categories: How-to's & Tutorials

A Bullet-Proof Content Viewer

NETTUTS - Tue, 2010-01-05 08:33

In this tutorial, we’re going to look at how we can easily create an attractive and space-saving content viewer which even works with JavaScript disabled. We’ll build a solid core of semantic HTML, styled with some basic CSS and we’ll then use jQuery to add further enhancements in the form of transition animations.

The following screenshot shows what we’ll end up with by the end of the tutorial:


Getting Started

First, let’s create the underlying HTML page for our content viewer; in a new file in your text editor create the following page:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>Bullet-proof Content Viewer</title> <link rel="stylesheet" type="text/css" href="contentviewer.css"> </head> <body> <div id="viewer"> <ul id="nav"> <li class="thumb1"><a href="#panel1" title="Panel 1">Panel 1</a></li> <li class="thumb2"><a href="#panel2" title="Panel 2">Panel 2</a></li> <li class="thumb3"><a href="#panel3" title="Panel 3">Panel 3</a></li> <li class="thumb4"><a href="#panel4" title="Panel 4">Panel 4</a></li> <li class="thumb5"><a href="#panel5" title="Panel 5">Panel 5</a></li> </ul> <div id="panels"> <div id="slider"> <div id="panel1"> <img src="img/image1.jpg" alt="Image 1"> <p>Supernova 1994D, visible as the bright spot at the lower left, occurred in the outskirts of the disk galaxy NGC 4526.</p> </div> <div id="panel2"> <img src="img/image2.jpg" alt="Image 2"> <p>Radiation from hot stars off the top of the picture illuminates and erodes this giant, gaseous pillar.</p> </div> <div id="panel3"> <img src="img/image3.jpg" alt="Image 3"> <p>V838 Mon is located about 20,000 light-years away from Earth at the outer edge of the Milky Way.</p> </div> <div id="panel4"> <img src="img/image4.jpg" alt="Image 4"> <p>The Sombrero Galaxy is an unbarred spiral galaxy in the constellation Virgo approximately 30 million lights years away.</p> </div> <div id="panel5"> <img src="img/image5.jpg" alt="Image 5"> <p>This region of active current star formation is part of a diffuse emission nebula about 6,500 light-years away.</p> </div> </div> </div> </div> <script type="text/javascript" src="jquery-1.3.2.min.js"></script> <script type="text/javascript"> </script> </body> </html>

Save this as contentviewer.html. We’ll start off with some clean and semantic HTML, using no more elements than is strictly necessary. We have an outer container for the viewer in its entirety, which contains a navigation structure for selecting which content panel to view and a second container for the content panels themselves.

Within the content panel container, we have another container used to enclose all of the content panels (this is needed to display the content panels correctly) and the content panels themselves. Each panel contains an image and a span describing the image.

The main images are added to the document as proper HTML images using the <img> element – this is because they are content and should be visible to assistive technologies or users with both scripting and CSS disabled or otherwise not available. The navigation structure will also contain images, but as these are not classed as content they do not need to be visible in all situations and can therefore be added using CSS, hence the additional class names on the <li> elements.

Right now the page should appear like this:

It doesn’t look great, but the document flows correctly and the elements are all clearly visible and usable.

Making it work with CSS

We can now use CSS to transform the content viewer into a functioning interface that doesn’t look terrible. W already linked to the style sheet in the head of our page so let’s create it now; in a new file in your text editor add the following selectors and rules:

#viewer { width:700px; margin:auto; } #nav { width:200px; float:left; margin:0; padding:0; list-style-type:none; } #nav li { width:200px; height:100px; padding:0; } #nav li a { display:block; width:100%; height:100%; text-indent:-9999px; overflow:hidden; background:url(img/thumbs.png) no-repeat 0 0; } #nav li a:hover, #nav li a.on { background-position:-200px 0; } #nav li.thumb2 a { background-position:0 -100px; } #nav li.thumb2 a:hover, #nav li.thumb2 a.on { background-position:-200px -100px; } #nav li.thumb3 a { background-position:0 -200px; } #nav li.thumb3 a:hover, #nav li.thumb3 a.on { background-position:-200px -200px; } #nav li.thumb4 a { background-position:0 -300px; } #nav li.thumb4 a:hover, #nav li.thumb4 a.on { background-position:-200px -300px; } #nav li.thumb5 a { background-position:0 -400px; } #nav li.thumb5 a:hover, #nav li.thumb5 a.on { background-position:-200px -400px; } #panels { width:500px; height:500px; overflow:hidden; position:relative; float:left; }

Save this as contentviewer.css in the same directory as the HTML page. I’ve kept the design minimal so that we can focus on what makes it work; the navigation and viewing panel are floated next to each other and the individual list items are given their background images and hover states. We’ve also added on states as well. This part of the CSS is purely for layout/presentation and does not affect functionality.

What’s important is how the containers and content images are arranged. The outer container (#panels) is given a fixed size that matches the height and width of a single content image and has its overflow property set to hidden to ensure that only a single image is displayed at any one time. This is the only really required CSS for the content viewer to work to a basic degree. If you look at the page now, you’ll see that you can click any of the thumbnails and the corresponding full-sized image will be displayed in the viewing panel:

This is great because it remains functional and accessible without relying on JavaScript. We’ll move on to use jQuery to add some smooth transitional effects in just a moment, but first we should add a few more styles that are required for the animations, and to display the paragraphs correctly. Add the following code to the bottom of contentviewer.css:

#slider { width:2500px; height:500px; } #slider div { float:left; position:relative; } #slider p { position:absolute; bottom:0; left:0; color:#fff; font:16px "Trebuchet MS"; margin:0; width:90%; height:45px; padding:5px 5% 10px; background-color:#000; }

The inner container (#slider) is given a fixed height equal to a single content image, but a width equal to all of the images. Then the individual containers holding the images and paragraphs are floated to the left to make them stack up horizontally. Finally, the paragraphs are styled and positioned as well so that they overlay each image:

Floating the individual content panels to the left and setting the size of the slider is not strictly necessary, without these the images will just stack up vertically. This would mean that any animations we added would have to move the content panels vertically as well, but we’ll be animating them horizontally.

One point I should make here is that the code so far does not work in Opera; for some reason, Opera cannot use the anchors on the page to show the different content panels when one of the navigation items is clicked. This is a big fail and seems to be a problem in more than one version of Opera. There is a fix apparently and anyone that uses Opera as their main browser will have hopefully implemented this fix already. It isn’t a problem when the JavaScript has been added though.

Adding the jQuery Effects

As the page now works on its own, we can add the JavaScript that will turn this from a functional page into an attractive interface. We left an empty <script> element at the bottom of our page, let’s fill it up now; start with the following code:

(function($){ //code here... })(jQuery);

What we’re doing here with this first bit of code is ensuring that the $ character will always refer to the jQuery object; if this code is to be used on a page with another JS library which also uses the $ character we can be sure that within our code $ will always refer to jQuery. We’re creating an alias for jQuery that matches the $ character by passing the jQuery object into a self-executing anonymous function. This is a tip I picked up from Cody Lindley’s excellent jQuery Enlightenment book.

Within our self-executing anonymous function add the rest of the code:

//object containing margin settings var margins = { panel1: 0, panel2: -500, panel3: -1000, panel4: -1500, panel5: -2000 } //handle nav click $("#nav a").click(function(e){ //stop browser default e.preventDefault(); //remove on states for all nav links $("#nav a").removeClass("on"); //add on state to selected nav link $(this).addClass("on"); //set margin of slider to move $("#slider").animate({ marginLeft: margins[$(this).attr("href").split("#")[1]] }); });

First of all we define a simple object that is used to hold the margin positions of each of the different panels; to show the first panel, the left margin of the #slider container should be at 0, to show the second panel it should be at -500px, and so on and so forth. Using an object like this is an easy way to store the values for each panel.

Next we add a click-handler for the navigation items; we want to show the panels using a nice animation so we have to prevent the browser from following the anchor. We do this using the event object (e) which we pass into our click-handler. The event object has the preventDefault() method built into it, so we call this on the event object and it stops the browser from executing its default action.

We use this part of the script to set the on class for the currently selected navigation item; this is the only part of the script that relies purely on JavaScript to function and simply removes the on class from any of the items on which it already exists, then adds back to the item that was clicked. There may be a way of doing this purely with CSS using the :active pseudo class, but as it is purely for a visual aid I don’t think it matters as much if this aspect doesn’t work with scripting disabled.

We now need to show the correct panel by animating the #slider container. Remember, we do this by setting the margin-left style property of the slider. In order to get the correct value for the marginLeft depending on whichever navigation link is clicked we just get the href attribute using the $(this) reference (which will point to whichever link was clicked) and perform a standard JavaScript split based on the # symbol.

We’re doing this the most semantic way – by using information we already have available, in this case the href attribute. We could add id attributes to each of the navigation links, but then we’re adding unnecessary extra information purely for our script which really we should avoid doing. Using the href makes sense but we can’t use # at the start of our object keys (panel1, panel2, etc) so we need to get rid of this character from the value that is returned.

Save the page and view it in your browser; you should find now that when one of the navigation items is clicked, the corresponding panel will slide smoothly into view. There’s just one more thing we need to do – the descriptive paragraphs don’t need to be visible all of the time (as long as JavaScript is enabled) so we can set these so that they are initially hidden from view and are shown when the content image is moused-over. We can add this behaviour easily using just a little bit more script; directly after the click-handler add the following code:

//hide descriptive text $("#slider p").hide();

By hiding the paragraphs with JavaScript we ensure that the content is only hidden if scripting is enabled. This means that the content remains accessible even if JavaScript is disabled on the client. Next we just need to show the text when the main image is hovered over, to do that we can use jQuery’s hover() method; after the code we just added continue with the following:

//show descriptive text on mouseover (hide on mouseout) $("#slider").hover( function() { $(this).find("p").slideDown(); }, function() { $(this).find("p").slideUp(); });

All we do is use the slideDown() and slideUp() methods within the hover() method to show and hide the paragraphs when appropriate.

Summary

This now brings us to the end of this tutorial; we’ve seen how we should build our interfaces so that they work even when JavaScript is disabled, and that, instead of relying on JavaScript for functionality, we’re just relying on it to add attractive effects. We also saw that when we want to show content using JavaScript, such as the descriptive paragraphs, we first hide it using JavaScript so that it is only hidden when scripting is enabled which ensures the content is accessible even without scripting.

Your thoughts?

Write a Plus Tutorial

Did you know that you can earn up to $600 for writing a PLUS tutorial and/or screencast for us? We’re looking for in depth and well-written tutorials on HTML, CSS, PHP, and JavaScript. If you’re of the ability, please contact Jeffrey at nettuts@tutsplus.com.

Please note that actual compensation will be dependent upon the quality of the final tutorial and screencast.

Write a PLUS tutorial


Categories: How-to's & Tutorials

Cross Domain iframe Resizing

CSS-Tricks - Tue, 2010-01-05 06:08

<iframe>’s which display content from different domains have security measures in place to prevent all sorts of stuff. For example, you can’t have JavaScript access anything inside it. It can be very frustrating, for example, if you just want to do something normal and white-hat like adjust the height of the iframe to fit the content inside it. These security measures are in place to prevent all the black-hat kind of things you could do if you did have JavaScript access to the innards of an iframe.

I’ve literally tried to work on different solutions for this for years and always came up short. I recently came across a solution from Kazi Manzur Rashid (about two years old now) that looks pretty solid so I thought I’d try it out. The results are the closest I’ve been able to come yet:

 

View Demo
Warning: the demo kinda freaks out WebKit browsers like Safari and Chrome, see issues below.

To those who have come before…

To do this with an iframe with source content on the same domain, you can do this. Same-domain iframes aren’t subject to the same restrictions so it’s far easier.

Adam Fortuna explored some options using kind of a man-in-the-middle idea. This may have been inspired by a technique by John McKerrell.

The following technique doesn’t require the middle man thing though, which is why it’s closer to ideal.

Prereqs

This solution presupposes that you have control over both the hosting site and the source site. You’ll need to run JavaScript on both ends. So this isn’t going to work for an iframe of google.com.

The Big Idea

The work-around is using hash tags in the URL to relay information back and forth. This circumvents the security restrictions. It is unlikely that this will ever break, so it’s not really a “hack”. You can’t really do anything malicious with just a hash tag. In our case we’re just reading in that information and using it to do the resizing.

The HOST Domain

Actually has the iframe on it:

<iframe id="frame-one" scrolling="no" frameborder="0" src="http://digwp.com/examples/iFrameSource/source.html" onload="FrameManager.registerFrame(this)"></iframe>

The iframe has an onload event on it, which calls a function from the FrameManager class, which we’ll need to call in the <head>:

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

And here is the magical FrameManager class:

var FrameManager = { currentFrameId : '', currentFrameHeight : 0, lastFrameId : '', lastFrameHeight : 0, resizeTimerId : null, init: function() { if (FrameManager.resizeTimerId == null) { FrameManager.resizeTimerId = window.setInterval(FrameManager.resizeFrames, 500); } }, resizeFrames: function() { FrameManager.retrieveFrameIdAndHeight(); if ((FrameManager.currentFrameId != FrameManager.lastFrameId) || (FrameManager.currentFrameHeight != FrameManager.lastFrameHeight)) { var iframe = document.getElementById(FrameManager.currentFrameId.toString()); if (iframe == null) return; iframe.style.height = FrameManager.currentFrameHeight.toString() + "px"; FrameManager.lastFrameId = FrameManager.currentFrameId; FrameManager.lastFrameHeight = FrameManager.currentFrameHeight; window.location.hash = ''; } }, retrieveFrameIdAndHeight: function() { if (window.location.hash.length == 0) return; var hashValue = window.location.hash.substring(1); if ((hashValue == null) || (hashValue.length == 0)) return; var pairs = hashValue.split('&'); if ((pairs != null) && (pairs.length > 0)) { for(var i = 0; i < pairs.length; i++) { var pair = pairs[i].split('='); if ((pair != null) && (pair.length > 0)) { if (pair[0] == 'frameId') { if ((pair[1] != null) && (pair[1].length > 0)) { FrameManager.currentFrameId = pair[1]; } } else if (pair[0] == 'height') { var height = parseInt(pair[1]); if (!isNaN(height)) { FrameManager.currentFrameHeight = height; FrameManager.currentFrameHeight += 15; } } } } } }, registerFrame: function(frame) { var currentLocation = location.href; var hashIndex = currentLocation.indexOf('#'); if (hashIndex > -1) { currentLocation = currentLocation.substring(0, hashIndex); } frame.contentWindow.location = frame.src + '?frameId=' + frame.id + '#' + currentLocation; } }; window.setTimeout(FrameManager.init, 300); The SOURCE site

The source content could pretty much be anything, located on a different server. Perhaps:

<body> <div id="content"> Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Fusce in tortor sit amet sem luctus ornare. Nam sed augue id erat commodo gravida. Nulla in pede. Nunc sed elit non pede aliquam eleifend. Cras varius. Sed non lorem eget ipsum accumsan suscipit. Donec bibendum enim. Phasellus a ligula. Fusce turpis diam, ultricies at, ullamcorper a, consectetuer et, mauris. Pellentesque neque felis, scelerisque non, vestibulum at, luctus quis, velit. Quisque sit amet mi sed sem facilisis ornare. In leo ante, hendrerit nec, lobortis eget, feugiat ac, orci. </div> </body>

The most important thing we do on the source site is run some JavaScript to “publish” the height of itself. In my demo, I’m also throwing some jQuery in there to do some font-size animation so that the source content grows taller and shorter.

<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js?ver=1.3.2"></script> <script type="text/javascript" src="frame.js"></script> <script type="text/javascript"> window.onload = function(event) { window.setInterval(publishHeight, 300); } $(function() { var $content = $("#content"); function toggleFontSize() { if ($content.css("font-size") == "22px") { $("#content").animate({ fontSize: "15px" }); } else { $("#content").animate({ fontSize: "22px" }); } } var int = setInterval(toggleFontSize, 5000); }); </script>

So we are calling publishHeight() every 300 milliseconds. Here is that function, and it’s rag-tag gang of fellow supporting functions from the frame.js file.

function publishHeight() { if (window.location.hash.length == 0) return; var frameId = getFrameId(); if (frameId == '') return; var actualHeight = getBodyHeight(); var currentHeight = getViewPortHeight(); if (Math.abs(actualHeight - currentHeight) > 15) { var hostUrl = window.location.hash.substring(1); hostUrl += "#"; hostUrl += 'frameId=' + frameId; hostUrl += '&'; hostUrl += 'height=' + actualHeight.toString(); window.top.location = hostUrl; } } function getFrameId() { var qs = parseQueryString(window.location.href); var frameId = qs["frameId"]; var hashIndex = frameId.indexOf('#'); if (hashIndex > -1) { frameId = frameId.substring(0, hashIndex); } return frameId; } function getBodyHeight() { var height, scrollHeight, offsetHeight; if (document.height) { height = document.height; } else if (document.body) { if (document.body.scrollHeight) { height = scrollHeight = document.body.scrollHeight; } if (document.body.offsetHeight) { height = offsetHeight = document.body.offsetHeight; } if (scrollHeight && offsetHeight) { height = Math.max(scrollHeight, offsetHeight); } } return height; } function getViewPortHeight() { var height = 0; if (window.innerHeight) { height = window.innerHeight - 18; } else if ((document.documentElement) && (document.documentElement.clientHeight)) { height = document.documentElement.clientHeight; } else if ((document.body) && (document.body.clientHeight)) { height = document.body.clientHeight; } return height; } function parseQueryString(url) { url = new String(url); var queryStringValues = new Object(), querystring = url.substring((url.indexOf('?') + 1), url.length), querystringSplit = querystring.split('&'); for (i = 0; i < querystringSplit.length; i++) { var pair = querystringSplit[i].split('='), name = pair[0], value = pair[1]; queryStringValues[name] = value; } return queryStringValues; } Issues
  • Refresh happy in WebKit. Apparently it used to be Firefox that got all refresh happy, but apparently with the latest version it is flip flopped. Watch out of visiting the demo in Safari or Chrome, it’s a little choppy. If anyone has any ideas here, this is probably the biggest problem.
  • Messes up back button. Because of all the hash tags flying around on the host page, it may screw up the back button functionality on that page.
  • Intervals, intervals, intervals. There are a lot of intervals flying around here which are nearly always hacky-red-flags. The quicker the intervals, the smoother but more resource intensive. The slower, the more choppy but easier. Either way, sucky.
  • Limit of information sent via hash. If you were thinking about using this technique to send other information, because it happens via URL, you are limited by the amount of information that can pass. Presumably the same as a GET request… around 1k.
The holy grail

I think the reason I’m so obsessed with this because Wufoo forms seem to handle this so perfectly. Wufoo forms used to only be embeddable with iframes. I always had to set the height of them literally almost 50% taller than the content itself to accommodate for the innards growing when the form was submitted with errors (the error messaging expanded the height). If I didn’t, the submit button would get cut off making the form un-submittable.

Wufoo now has a JavaScript embed option, but ultimately the form still comes in via iframe. However they do it, the iframe can magically resize itself as needed. I have no idea how it’s done, but I imagine it something somewhat similar to what we are doing here. Because Wufoo has access to both the host page and the source page. My best guess is that the JavaScript on the host page can send requests back to the source page which can somehow accurately tell the host page what height it should be.

Got better?

It’s a lot of code, but hey, it works (again, thanks for Kazi for the smarts). Got better? Please, share.

Categories: How-to's & Tutorials

On Web Advertising

CSS-Tricks - Mon, 2010-01-04 05:10

On this week’s screencast I talk about online advertising. What it is, how it works, and mostly, my opinions about it. I thought I’d recap here and touch on some things I forgot about.

Content Websites vs. Product/Service Websites

There are some websites that spend their time building content. They build it to keep visitors coming back because it’s funny or interesting or important or otherwise compelling. Content websites, by and large, are free to visit and consume. Creators of content website spend considerable time and effort creating and maintaining the site and it’s content.

Some websites exist to support a product or service that is for sale. The creators of these sites spend the majority of their time and effort working on that product or service. Making it better, improving features, etc. They aren’t working on building content, that’s not their forte.

Content websites get the traffic (but need money). Product/service websites make the money (but need traffic). Advertising is a way to trade and share some love back and forth.

Not Evil

There is nothing “evil” about advertising. Creating content takes blood, sweat and tears. Creating products takes blood, sweat, and tears. Both deserve to be paid for that. Advertising makes sure they do.

Advertising can certainly be overdone though. Having to watch a 30 second video before reading an article… I feel that’s too much. A page-peel that you come within 100 pixel of and it comes down and covers half the page? Too much.

Except when it is

The “evil” stuff only starts entering the picture when websites start do things under the table. Selling emails/spam, that’s clearly evil. Writing an article about how great a book is, without ever reading it, so you can link to it with an Amazon affiliate code, is evil. Filling a page with bullshit keyword-strewn content in hopes to get some search traffic and covering the page in ads, is evil.

Honest websites creating honest content displaying ads for honest companies creating honest products, that’s always OK. When any one of those things loses the “honest” part, that’s problematic.

Services

On this site, I use BuySellAds. They specialize in the design website community, which means that they have a base of advertisers with products and services that cater to this community. That’s awesome, as it means that ads on this site will be filled with things that web designers might be interested in. They take 25% of sales, a hefty fee at a glance, but completely worth it when I consider that I spend almost no time at all thinking about advertising. I can use that time building content instead. Otherwise I’d be spending my time finding/communicating with advertisers, invoicing them, keeping track of live dates, flipping in and out graphics, etc. And because BSA comes in over JavaScript, it keeps paid links away from Google bots which frown upon that. Also great about the BSA model, advertisers pay for what they get. They look at ad spots, they see what kind of impressions the site has, they see a fixed monthly price, and they make the call. It’s as straight-forward as it could be.

A much more popular advertising service on a global scale is Google AdSense. AdSense reads the content on the page it resides upon and serves up advertising relevant to that content. In general, I think that’s great. Relevant ads benefit publishers (more people click them), advertisers (the people clicking are a targeted audience), and the users themselves (they find something they are actually looking for). On my site, I don’t need it, since I already have a service that is targetted. But it’s great for something like Ning.com, where people can build their own social network for free. Someone starts a knitting site, content on the site is about knitting, there are Google ads on the site about knitting, everybody wins. Forums are also good targets for AdSense, since topics are set by your visitors themselves, and the ads can mimic those topics. Personally, I don’t like Google ads. I think they are ugly. I think people know exactly what they look like and know how to avoid them. I think it attracts lots of crappy advertisers. Most of all, I don’t like how it pays via clicks.

There is one place on CSS-Tricks that uses them, and that is next to the search results. I love Google Custom Search Engines. It does a great job in searching this site. I tied my AdSense account to the CSE so that I earn from the clicks on the ads in search. It amounts to a few dollars a month. The reason I do it is because it costs $100 to remove them and I don’t really think it’s worth it. They sit harmlessly over to the right which would be blank white space anyway.

The cool kid club

There are a couple of ad serving services in the web design niche that are exclusively for the cool kids: The Deck and Fusion Ads. They are pretty great. Publishers using these services display only one ad per page. The ads are typically very well designed and classy. I like this idea very much. Unfortunately you have to be a lot cooler than you or me to get in on it. I also tend to think that sites serving these ads aren’t primarily concerned with advertising income. While I’m sure they pay fairly well, I’m betting one high paying ad can’t beat 9 medium paying ads.

Affiliates

Affiliate programs are another form of online advertising. Product/service sites tend to love them, as they only have to pay out when successful sales are made. In traditional advertising, they have to pay no matter what and if the ad does poorly, it’s a loss for them. If an affiliate ad does poorly, it’s a loss for the publisher.

I like the idea of affiliate programs. If someone goes through the trouble of spreading the word and sending in sales, they deserve to be paid for that work.

I’m also wary of them. I think the advertiser gets a pretty significant advantage. Advertising isn’t just direct sales, it’s brand exposure and mind share as well. The advertiser gets this part for free, as well as only paying for direct sales.

I think if you become an affiliate for anything, you should do it because you like what you are selling and you think you’ll sell enough of it to turn the balance in your favor.

I have affiliate programs both for Are My Sites Up and Digging Into WordPress. I like how they have worked out.

Common sizes

I have no idea what these sizes are based on or what the history is, but the Interactive Advertising Bureau offers up this list (based on what is “commonly sold” in the marketplace). They say that the goal is to provide some standardization to reduce stuff like having different publishers using sizes only trivially different like 300×95, 300×100, 300×105 etc., which is a damn fine goal.

Recommend Max File Size Recommended Animation Length 300 x 250 Medium Rectangle 40k :15 250 x 250 Square Pop-Up 40k :15 240 x 400 Vertical Rectangle 40k :15 336 x 280 Large Rectangle 40k :15 180 x 150 Rectangle 40k :15 300×100 3:1 Rectangle 40k :15 720×300 Pop-Under 40k :15 468 x 60 Full Banner 40k :15 234 x 60 Half Banner 30k :15 88 x 31 Micro Bar 10k :15 120 x 90 Button 1 20k :15 120 x 60 Button 2 20k :15 120 x 240 Vertical Banner 30k :15 125 x 125 Square Button 30k :15 728 x 90 Leaderboard 40k :15 160 x 600 Wide Skyscraper 40k :15 120 x 600 Skyscraper 40k :15 300 x 600 Half Page Ad 40k :15

Personally I think the sizes are rather asinine – with one ad size having nearly no relationship to the next. Standards are great, and of course it will be hard to fight against the stream now, but I think this whole “common sizes” business needs a re-think.

Randomization

If you offer a number of similar sized ad blocks in one area, the fair thing to do for the advertisers is randomize their position within the block. I’ll post a snippet of how to do this soon.

A/B Testing

You may not have the ability to always do this if you are using an ad service, but a good idea in web advertising is to use A/B testing. That is, have two versions of the same ad and measure which one does better, everything else being equal. Randomly display each one, and add different tracking information to the end of the URL that each links to. If you are using Google Analytics, you could use their link builder.

When to start

A classic problem of online advertising is deciding how to approach it on a brand new site. There are some pretty different schools of thought.

Some people say you should wait to put ads on a site until it has grown up enough that it’s “worth it”. I can buy into that. You are going to make pennies when launching a brand new site with no audience. Your ads aren’t worth anything, so why put them there? It also preoccupies you with thinking about advertising when clearly the most important thing for a fledgling site is growing that audience and traffic.

The flip side is putting ads on a site from day one. Even though they aren’t worth anything, at least you are ready, in the design/layout of the site for when they are worth something. It also sets up user expectations on the site. If you build an audience on a site with zero ads, then one day fill the site with ads, that’s rocking the boat quite a bit. Users might not take kindly to that. Having ads from the beginning sets expectations straight from the beginning.

For the sake of offering advice, I’d say if you think your site will have advertising on it eventually, plan for it design-wise and throw some placeholder ads up for now (perhaps display ads for your friends sites?). Then once you’ve grown enough (perhaps 50k pageviews a month) then start working on paid ads.

 

Feel free to chime in with your own thoughts about online advertising!

Categories: How-to's & Tutorials

Zero-to-Sixty: Creating and Deploying a Rails App in Under an Hour

NETTUTS - Mon, 2010-01-04 01:30

Give me an hour of your time, and I’ll take you on a fly by of the Ruby on Rails framework. We’ll create controllers, models, views, add admin logins, and deploy using Heroku’s service in under an hour! In this article we’ll create a simple bookshelf application where you can add books and write thoughts about them. Then we’ll deploy the application in just a few minutes. So buckle up because this article moves fast!

This article assumes that you may know what Ruby on Rails is, but not exactly how it works. This article doesn’t describe in-depth how each step works, but it does describe what we need to do, then the code to do that.

Zero

Ruby on Rails is a full stack MVC web application framework. Full stack means you get everything: a simple web server you can use to test your apps, a database layer, testing framework, and an MVC based design. MVC stands for Model-View-Controller.

Model

A model stores information. Models are stored in the database. Rails supports MySQL, PostgreSQL, or SQLite. Each model has its own class and table. Say we want to model a “game.” A game has things like number of players, a start time, end time, teams playing, and a winner. These attributes become columns in the “games” table. Table names are lowercase, underscored, and pluralized. The model’s class name is Game. In Rails you create models through migrations and generators. A migration describes how to add/remove columns and tables from the database.

Controller

A controller is the manager. It takes information and does some logic like CRUD, or maybe import some stuff from a file, add/remove permissions–you name it a controller can do it. Controllers are the part of your app that does. How do we call controllers? Rails uses routes. A route is a formatted url that is tied to an action with a set of parameters. Going back to the Game model, we need a controller for functionality. At some point we’ll to need to list all the games in the system. A basic REST url for this route looks like “/games” How does Rails know what controller to look for and what action to call? It looks at your routes.rb file. You may have a route that looks like this “GET /makes {:name => ‘games’, :action => ‘index’”}. This translates to GamesController and it’s index method. Just like models, class names are CamelCase and file names are underscored. So our GamesController would be stored in /app/controllers/games_controller.rb. After the logic, the controller renders a view.

View

A view is the easiest part to understand. It’s what you see. It’s the HTML you generate to show the user what they need. Views are ERB templates. ERB stands for Embedded Ruby. You use ERB similar to how you embed php into a document. If you want to insert an instance variable @game.time into some html write <%= @game.time %>

Ten

First install Rails. Installing Rails is very easy depending on your platform. If you are on a Linux/OSX, it’s no problem. Windows is more complicated and I have no experience with it. This section will give you a brief overview of installing Rails through RubyGems, the Ruby package manager. A gem is a bundle of ruby code in a package that can be used in your programs. For UNIX based system, install RubyGems, then install the Rails gem. This process will go something like this:

# ubuntu sudo apt-get install rubygems # fedora sudo yum install rubygems # gentoo sudo emerge rubygems # OSX sudo port install rubygems # after you have rubygems installed sudo gem install gemcutter # ruby gem hosting service sudo gem tumble sudo gem install Rails

Here are some links to help you through the setup process

Once you can run the “rails” command you’re ready for the next step.

Fifteen

Now it’s time to install database support before we get started. Rails has support for all the popular DB’s, but for this example we’ll use SQLite because it’s lightweight.. Depending on your platform (again) installing sqlite support can be easy or painful. It can be a pain since the gem has to be built against C extensions, which means the sqlite3 package has to be installed on your system. Again the process will go something like this:

# ubuntu sudo apt-get install sqlite3 sqlite3-devel # fedora sudo yum install sqlite3 sqlite3-devel # OSX sudo port install sqlite3 # then once you have the sqlite3 package installed sudo gem install sqlite3-ruby

Read the previous links if you’re having problems with these steps. They describe installing sqlite as well.

Twenty

Time to generate our app. The rails command creates a base application structure. All we need to do is be in a directory and run it like so:

$ cd ~/projects $ Rails bookshelf #this will create a new directory named bookshelf that holds our app $ cd bookshelf

It’s important to note that the Rails default is an SQLite based app. You may be thinking, what if I don’t want that? The rails command is a generator. All it does is copy stored files into a new directory. By default it will create sqlite3 databases in /bookshelf/db/development.sqlite3, /bookshelf/db/production.sqlite3, and /bookshelf/db/testing.sqlite3. Database connection information is stored in /bookshelf/config/database.yml. You don’t need to edit this file since it contains default information for an sqlite setup. It should look like this:

# SQLite version 3.x # gem install sqlite3-ruby (not necessary on OS X Leopard) development: adapter: sqlite3 database: db/development.sqlite3 pool: 5 timeout: 5000 # Warning: The database defined as "test" will be erased and # re-generated from your development database when you run "rake". # Do not set this db to the same as development or production. test: adapter: sqlite3 database: db/test.sqlite3 pool: 5 timeout: 5000 production: adapter: sqlite3 database: db/production.sqlite3 pool: 5 timeout: 5000

Notice there are different environments assigned. Rails has three modes: Development, Testing, and Production. Each has different settings and databases. Development is the default environment. At this point we can start our app to make sure it’s working. You can see there’s a directory called /script. This directory contains ruby scripts for interacting with our application. Some important ones are /script/console, and /script/server. We will use the /script/server command to start a simple server for our application.

bookshelf $ ./script/server # then you should see something like this. Rails will start a different server depending on your platform, but it should look something like this: => Booting Mongrel => Rails 2.3.5 application starting on http://0.0.0.0:3000 => Call with -d to detach => Ctrl-C to shutdown server

Time to visit the application. Point your browser to “http://localhost:3000″ and you should see this splash page:

Splash Screen

You’re riding on Rails. Now that the code working on a basic level, it’s time to delete the splash page and get started with some code.

bookshelf $ rm /public/index.html Twenty Five

Our application needs data. Remember what this means? It means models. Great, but how we generate a model? Rails comes with some generators to common tasks. The generator is the file /script/generate. The generator will create our model.rb file along with a migration to add the table to the database. A migration file contains code to add/drop tables, or alter/add/remove columns from tables. Migrations are executed in sequence to create the tables. Run migrations (and various other commands) with “rake”. Rake is a ruby code runner. Before we get any further, let’s start by defining some basic information for the books. A book has these attributes:

  • Title : String
  • Thoughts : Text

That’s enough to start the application. Start by generating a model with these fields using the model generator:

bookshelf $ ./script/generate model Book title:string thoughts:text # notice how the attributes/types are passed to the generator. This will automatically create a migration for these attributes # These are optional. If you leave them out, the generator will create an empty migration. exists app/models/ exists test/unit/ exists test/fixtures/ create app/models/book.rb create test/unit/book_test.rb create test/fixtures/books.yml create db/migrate create db/migrate/20091202052507_create_books.rb # The generator created all the files we need to get our model up and running. We need to pay the most attention to these files: # app/models/book.rb # where our code resides # db/migrate/20091202052507_create_books.rb # code to create our books table.

Open up the migration file:

class CreateBooks < ActiveRecord::Migration def self.up create_table :books do |t| t.string :title t.text :thoughts t.timestamps end end def self.down drop_table :books end end

Notice the create_table :books block. This is where columns are created. An id primary key is created automatically. t.timestamps adds columns for created_at and updated_at. Now, run the migration using the rake task db:migrate. db:migrate applies pending migrations:

bookshelf $ rake db:migrate == CreateBooks: migrating ==================================================== -- create_table(:books) -> 0.0037s == CreateBooks: migrated (0.0038s) ===========================================

Cool, now we have a table, let’s create a dummy book just for kicks in the console. The Rails console uses IRB (interactive ruby) and loads all classes for your project. IE you can access to all your models. Open the console like this:

bookshelf $ ./script/console >> # let's create a new model. You can specify a hash of assignments in the constructor to assign values like this: >> book = Book.new :title => 'Rails is awesome!' , :thoughts => 'Some sentence from a super long paragraph' => #<Book id: nil, title: "Rails is awesome!", thoughts: "Some sentence from a super long paragraph", created_at: nil, updated_at: nil> # and ruby will display it back >> book.save => true # now are book is saved in the database. We can query it like this: >> Book.all # find all books and return them in an array => [#<Book id: 1, title: "Rails is awesome!", thoughts: "Some sentence from a super long paragraph", created_at: "2009-12-02 06:05:38", updated_at: "2009-12-02 06:05:38">] >> exit # now that our model is saved, let's exit the console.

Now that we can create books, we need some way to show them to the user

Thirty

Remember controllers? We need a controller to display all the books in the system. This scenario corresponds to the index action in our BooksController (books_controller.rb) which we don’t have yet. Just like generating models, use a generator to create the controller:

bookshelf $ ./script/generate controller Books exists app/controllers/ exists app/helpers/ create app/views/books exists test/functional/ create test/unit/helpers/ create app/controllers/books_controller.rb create test/functional/books_controller_test.rb create app/helpers/books_helper.rb create test/unit/helpers/books_helper_test.rb # notice Rails created the file app/controllers/books_controller.rb? This is where we are going to define our actions or methods for the BooksController class

We need to define an action that finds and displays all books. How did we find all the books? Earlier we used Book.all. Our strategy is use Book.all and assign it to an instance variable. Why an instance variable? We assign instance variables because views are rendered with the controllers binding. You’re probably thinking bindings and instance variables…what’s going on? Views have access to variables defined in actions but only instance variables. Why, because instance variables are scoped to the object and not the action. Let’s see some code:

class BooksController < ApplicationController # notice we've defined a method called index for a BooksController instance. We tie this together with routes def index @books = Book.all # instance variables are prefixed with an @. If we said books = Book.all, we wouldn't be able to access books in the template end end

Now the controller can find all the books. But how do we tie this to a url? We have to create some routes. Rails comes with some handy functions for generating RESTful routes (another Rails design principle). This will generate urls like /makes and /makes/1 combined with HTTP verbs to determine what method to call in our controller. Use map.resources to create RESTful routes. Open up /config/routes.rb and change it to this:

ActionController::Routing::Routes.draw do |map| map.resources :books end

Routes.rb can look arcane to new users. Luckily there is a way to decipher this mess. There is routes rake task to display all your routing information. Run that now and take a peek inside:

bookshelf $ rake routes books GET /books(.:format) {:controller=>"books", :action=>"index"} POST /books(.:format) {:controller=>"books", :action=>"create"} new_book GET /books/new(.:format) {:controller=>"books", :action=>"new"} edit_book GET /books/:id/edit(.:format) {:controller=>"books", :action=>"edit"} book GET /books/:id(.:format) {:controller=>"books", :action=>"show"} PUT /books/:id(.:format) {:controller=>"books", :action=>"update"} DELETE /books/:id(.:format) {:controller=>"books", :action=>"destroy"} # as you can see this command can display a lot of information. On the left column we have a helper to generate a url, then the HTTP verb associated with the url, then the url, and finally the controller and action to call. # for example GET /books will call BooksController#index or # GET /books/1 will call BooksController#show # the url helpers are very important but we'll get to them later. For now know that we are going to create a /books page to list all books

Now we need to create a template to display all our books. Create a new file called /app/views/books/index.html.erb and paste this:

<% for book in @books do %> <h2><%=h book.title %></h2> <p><%= book.thoughts %></p> <% end %>

This simple view loops over all @books and displays some HTML for each book. Notice a subtle difference. <%= is used when we need to output some text. <% is used when we aren’t. If you don’t follow this rule, you’ll get an exception. Also notice the h before book.title. h is a method that escapes HTML entities. If you’re not familiar with ruby, you can leave off ()’s on method calls if they’re not needed. h text translates to: h(text).

Time to run the server and see what we’ve got. Start the server, then go to http://localhost/books.

bookshelf $ ./script/server

If all goes according to plan you should see some basic HTML.

Books Thirty Five

We have one book in our system, but we need some more books to play with. There are a few ways to go about doing this. I like the forgery gem. Forgery can create random strings like names, or lorem ipsum stuff. We are going to set a gem dependency in our app, install the gem, then use it to create a rake task to populate our data. Step 1: open up /config/environment.rb and add this line:

config.gem 'forgery' # now let's tell Rails to install all gems dependencies for this project # gem install gemcutter # if you haven't already # gem tumble # again, if you haven't already bookshelf $ sudo rake gems:install

Now we’re going to use the Forgery classes to create some fake data. The Forgery documentation is here. We’ll use the LoremIpsumForgery to create some basic data. We can define our own rake tasks by creating a .rake file in /lib/tasks. So create a new file /lib/tasks/populate.rake:

begin namespace :db do desc "Populate the development database with some fake data" task :populate => :environment do 5.times do Book.create! :title => Forgery::LoremIpsum.sentence, :thoughts => Forgery::LoremIpsum.paragraphs(3) end end end rescue LoadError puts "Please run: sudo gem install forgery" end

This rake task will create five fake books. Notice I added a begin/rescue. When you run a rake task it looks at all possible tasks in the rake initialization. If you were to run any rake task before you installed the gem, rake would blow up. Wrapping it in an begin/rescue stop rake from aborting. Run the task to populate our db:

bookshelf $ rake db:populate bookshelf $ ./script/console # let's enter the console to verify it all worked >> Book.all => [#<Book id: 1, title: "Rails is awesome!", thoughts: "Some sentence from a super long paragraph", created_at: "2009-12-02 06:05:38", updated_at: "2009-12-02 06:05:38">, #<Book id: 2, title: "Lorem ipsum dolor sit amet, consectetuer adipiscing...", thoughts: "Lorem ipsum dolor sit amet, consectetuer adipiscing...", created_at: "2009-12-02 07:28:05", updated_at: "2009-12-02 07:28:05">, #<Book id: 3, title: "Lorem ipsum dolor sit amet, consectetuer adipiscing...", thoughts: "Lorem ipsum dolor sit amet, consectetuer adipiscing...", created_at: "2009-12-02 07:28:05", updated_at: "2009-12-02 07:28:05">, #<Book id: 4, title: "Lorem ipsum dolor sit amet, consectetuer adipiscing...", thoughts: "Lorem ipsum dolor sit amet, consectetuer adipiscing...", created_at: "2009-12-02 07:28:05", updated_at: "2009-12-02 07:28:05">, #<Book id: 5, title: "Lorem ipsum dolor sit amet, consectetuer adipiscing...", thoughts: "Lorem ipsum dolor sit amet, consectetuer adipiscing...", created_at: "2009-12-02 07:28:05", updated_at: "2009-12-02 07:28:05">, #<Book id: 6, title: "Lorem ipsum dolor sit amet, consectetuer adipiscing...", thoughts: "Lorem ipsum dolor sit amet, consectetuer adipiscing...", created_at: "2009-12-02 07:28:05", updated_at: "2009-12-02 07:28:05">] >> exit

Start the server again and head back to the /books pages. You should see:

Book Index

Now we have a listing of more than one book. What if we have a lot of books? We need to paginate the results. There’s another gem for this. The gem is ‘will_paginate.’ Following the same steps as before, let’s add a gem dependency for ‘will_paginate’ and rake gems:install:

# in environment.rb config.gem 'will_paginate' # from terminal bookshelf $ sudo rake gems:install # then let's add more books to our db bookshelf $ rake db:populate # run this a few times to get a large sample, or change the number in rake file

Head back to your /books page and you should be bombarded by books at this point. It’s time to add pagination. Pagination works at two levels. The controller decides which books should be in @books, and the view should display the pagination links. The will_paginate helper makes this very easy. We’ll use the .paginate method and the will_paginate view helper to render page links. All it takes is two lines of code.

# books_controller.rb, change the previous line to: @books = Books.paginate :page => params[:page], :per_page => 10 # index.html.erb, add this line after the loop: <%= will_paginate @books %>

Head back to your /makes page and you should see some pagination links (given you have more than 10 books)

Paginated Books

Sweet! We are movin’ through this app. It’s time to spruce up our page a little bit. One key Rails principle is DRY (Do Not Repeat Yourself). We could work through the exercise of doing some basic CSS to get a page looking OK, or we could keep things DRY and use some code to do it for us. We are going to use Ryan Bate’s nifty-generators gem to generate a layout for the site. A layout is a template your views can fill in. For example we can use a layout to determine the over all structure of a page, then define where the views fill it in. Since this isn’t a project dependency, we don’t have to add it to environment.rb. We can just install it regularly.

# console $ sudo gem install nifty-generators

Run the generator to create a layout file and stylesheets.

$ ./script/generate nifty_layout exists app/views/layouts exists public/stylesheets exists app/helpers create app/views/layouts/application.html.erb # this is our layout file create public/stylesheets/application.css # css file that styles the layout create app/helpers/layout_helper.rb # view helpers needed in the generator

Take a look at the application.html.erb file and see what’s inside:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html> <head> <title><%= h(yield(:title) || "Untitled") %></title> <%= stylesheet_link_tag 'application' %> <%= yield(:head) %> </head> <body> <div id="container"> <%- flash.each do |name, msg| -%> <%= content_tag :div, msg, :id => "flash_#{name}" %> <%- end -%> <%- if show_title? -%> <h1><%=h yield(:title) %></h1> <%- end -%> <%= yield %> </div> </body> </html>

See those yields? That’s where a view fills in the layout. The last yield has no argument. Default content goes there. Yields with an argument must have content defined using content_for. Fix up index.html.erb view to go with the new layout:

<% title 'My Books' %> <% for book in @books do %> <h2><%=h book.title %></h2> <p><%= book.thoughts %></p> <% end %> <%= will_paginate @books %>

All we did was add the title method which sets the title for a page. The title helper calls content_for :title and sets it to the argument. Our view fills in the last yield. Check out the results!

Nifty Layout Forty

Now that our application is looking better, let’s add some interaction. In typical Web 2.0 style we’re going to allow users to comment on our content, but we aren’t going to require the user to register. We need to create new model called Comment. A comment is going to have some text, an author, and an associated Book. How do we link these two models together? Associations. Rails provides these associations: belongs_to, has_many, has_one, and has_and_belongs_to many. It should be easy to see that a book has many comments, and a comment belongs_to a book. So we’ll use a generator to create the comment model and migration:

$ ./script/generate model Comment text:text author:string exists app/models/ exists test/unit/ exists test/fixtures/ create app/models/comment.rb create test/unit/comment_test.rb create test/fixtures/comments.yml exists db/migrate create db/migrate/20091202081421_create_comments.rb

Astute readers will notice that this migration is lacking the foreign key column. We’ll have to add that ourselves. Open up your create_comments.rb migration:

class CreateComments < ActiveRecord::Migration def self.up create_table :comments do |t| t.text :text t.string :author t.belongs_to :book # creates a new integer column named book_id t.timestamps end end def self.down drop_table :comments end end # now migrate your database $ rake db:migrate rake db:migrate (in /Users/adam/Code/bookshelf) == CreateComments: migrating ================================================= -- create_table(:comments) -> 0.0021s == CreateComments: migrated (0.0022s) ========================================

Now it’s time to associate our models using the Rails associations. We’ll call the method inside the model’s class body. Rails uses metaprogramming to generate the methods needed to make our association work. We’ll edit our comment.rb and book.rb files:

# book.rb class Book < ActiveRecord::Base has_many :comments end # comment.rb class Comment < ActiveRecord::Base belongs_to :book end

Now Book instances have a method .comments with returns an array of all its comments. Comment instances have a method called .book that return the associated book. Use the << operator to add objects to arrays. Let’s see how it works in the console:

$ ./script/console >> book = Book.find(1) => #<Book id: 1, title: "Rails is awesome!", thoughts: "Some sentence from a super long paragraph", created_at: "2009-12-02 06:05:38", updated_at: "2009-12-02 06:05:38"> >> comment = Comment.new :text => "This is an comment", :author => "Adam" => #<Comment id: nil, text: "This is an comment", author: "Adam", book_id: nil, created_at: nil, updated_at: nil> >> book.comments << comment => [#<Comment id: 1, text: "This is an comment", author: "Adam", book_id: 1, created_at: "2009-12-02 08:25:47", updated_at: "2009-12-02 08:25:47">] >> book.save => true >> book.comments => [#<Comment id: 1, text: "This is an comment", author: "Adam", book_id: 1, created_at: "2009-12-02 08:25:47", updated_at: "2009-12-02 08:25:47">] >> comment.book => #<Book id: 1, title: "Rails is awesome!", thoughts: "Some sentence from a super long paragraph", created_at: "2009-12-02 06:05:38", updated_at: "2009-12-02 06:05:38"> >> exit

In the console session I found one of the existing books, then created a new comment. Next I added it to the book.comments. Then I save book. The book must be saved for the association to be stored. What’s next? We need to create a page where the user can view a book and all it comments. That page should also have a form where the user can add their comment. Create a new action in the books controller to show the details for a specified book. The book is found by id. Pop into books_controller.rb and add this:

def show @book = Book.find params[:id] end

Make a new template for this action at /app/views/books/show.html.erb and paste this:

<% title @book.title %> <h2><%=link_to(h(@book.title), book_path(@book)) %></h2> <p><%= @book.thoughts %></p>

Now let’s add some links for the index actions to link the show action:

# update index.html.erb contents to: <% title 'My Books' %> <% for book in @books do %> <h2><%=link_to(h(book.title), book_path(book)) %></h2> <p><%= book.thoughts %></p> <% end %> <%= will_paginate @books %>

Remember our url helpers from rake routes? We’re using book_path to generate a url to the book controller’s show actions. If you don’t remember check rake routes again. link_to is a helper to generate a link tag. Now let’s fire up our server and click through the app. Now you should have some ugly blue links. Click on your book title and it should go to /books/:id aka BooksController#show:

Links!

Time to display some comments. Remember that console session we did a little bit back? One of our books has some comments. let’s update our controller to find the comments and our show.html.erb to display them.

# books_controller.rb def show @book = Book.find(params[:id]) @comments = @book.comments end # show.html.erb <% title @book.title %> <h2><%=link_to(h(@book.title), book_path(@book)) %></h2> <p><%= @book.thoughts %></p> <% if @comments %> <h3>Comments</h3> <% for comment in @comments do %> <p><strong><%=h(comment.author) %></strong>: <%=h comment.text %> <% end %> <% end %>

So we assign @comments in the controller to be all the book’s comments, then do a loop in the view to display them. Head over to /books/1 (1 came from Book.find(1) in the console session). Check this out:

Comments

Now we need the form to create a new comment. We need two things. 1, A comments controller to save the comment, and 2 a route to that action. let’s tackle #1 first.

bookshelf $ ./script/generate controller Comments

Create action that instantiates a new comment, sets its attributes (text/author) from the submitted form data, and saves it.

class CommentsController < ApplicationController def create book = Book.find params[:book_id] comment = book.comments.new params[:comment] comment.save flash[:notice] = 'Comment saved' redirect_to book_path(book) end end

First the code finds the book, then creates a new comment form the form data, saves it, sets a message, then redirects back to that book’s page. params holds a hash of all GET/POST data with a request. Now we need to create a route to the controller’s action. Open up routes.rb:

ActionController::Routing::Routes.draw do |map| map.resources :books do |book| book.resources :comments, :o nly => :create end end bookshelf $ rake routes # We needed to add a route to create a new comment for a book. We need to know what book we are creating a comment for, so we need a book_id in the route. Look at the book_comment line. # book_comment is tied to our CommentsController#create book_comments POST /books/:book_id/comments(.:format) {:controller=>"comments", :action=>"create"} books GET /books(.:format) {:controller=>"books", :action=>"index"} POST /books(.:format) {:controller=>"books", :action=>"create"} new_book GET /books/new(.:format) {:controller=>"books", :action=>"new"} edit_book GET /books/:id/edit(.:format) {:controller=>"books", :action=>"edit"} book GET /books/:id(.:format) {:controller=>"books", :action=>"show"} PUT /books/:id(.:format) {:controller=>"books", :action=>"update"} DELETE /books/:id(.:format) {:controller=>"books", :action=>"destroy"} # every time you modify routes.rb you'll need to restart the server # kill the server process you have running with ^c (ctrl + c) and start it again

Head back to the /books page and make sure nothing has blown up. Everything should be fine and dandy. Now for constructing the form. We need a form that submits POST data to /book/:book_id/comments. Luckily Rails has the perfect helper for this: form_for. form_for takes some models and generates a route for them. We pass form_for a block to create form inputs. Go ahead and paste this into the bottom of your show.html.erb:

<h3>Post Your Comment</h3> <% form_for([@book, Comment.new]) do |form| %> <p><%= form.label :author %></p> <p><%= form.text_field :author %></p> <p><%= form.label :text, 'Comment' %></p> <p><%= form.text_area :text %></p> <%= form.submit 'Save' %> <% end %>

We call form_for to create a new form for the book’s comment, then use the text_field/text_area to create inputs for attributes. At this point we can go ahead and make a comment. Fill in the form and viola you now have comments!

We can comment!

See that green thing? That’s the flash. The flash is a way to store messages between actions. It’s perfect for storing little messages like this. But what do we do if a book has too many comments? We paginate them just like did before. So let’s make some changes to the controller and view:

# books_controller.rb def show @book = Book.find(params[:id]) @comments = @book.comments.paginate :page => params[:page], :per_page => 10, :o rder => 'created_at ASC' end # show.html.erb <% title @book.title %> <h2><%=link_to(h(@book.title), book_path(@book)) %></h2> <p><%= @book.thoughts %></p> <% if @comments %> <h3>Comments</h3> <% for comment in @comments do %> <p><strong><%=h(comment.author) %></strong>: <%=h comment.text %> <% end %> <%= will_paginate @comments %> <% end %> <h3>Post Your Comment</h3> <% form_for([@book, Comment.new]) do |form| %> <p><%= form.label :author %></p> <p><%= form.text_field :author %></p> <p><%= form.label :text, 'Comment' %></p> <p><%= form.text_area :text %></p> <%= form.submit 'Save' %> <% end %>

Start commenting on your books and you should see some pagination.

Paginated Comments

Now people can comment, and everything is paginated but we’re missing something. We have no web interface for creating books. We need to create a form for that. Also we are the admin so only I should be allowed to create books. This means we need to create a user, login, and check to see if they can do an action.

Fifty

Now we’re going to implement CRUD functionality for admins. First we’ll implement actions to create, edit, and delete books. Then we’ll create an admin login. Finally we’ll make sure only admins can do those actions.

Creating a new books requires two new actions. One action that renders a form for a new book. This action is named ‘new’. The second is named ‘create.’ This action takes the form parameters and saves them in the database. Open up your books_controller.rb and add these actions:

def new @book = Book.new end def create @book = Book.new params[:book] if @book.save flash[:notice] = "#{@book.title} saved." redirect_to @book else render :new end end

We also need a new view that shows a form. Create a new file /apps/views/books/new.html.erb and paste this:

<% form_for(@book) do |form| %> <p> <%= form.label :title %><br/> <%= form.text_field :title %> </p> <p> <%= form.label :thoughts %><br/> <%= form.text_area :thoughts %> </p> <%= form.submit %> <% end %>

Now we’re ready to create a new book. Point your browser to /books/new and you should see this form. Go a head and create a new book. After you fill in your form you should see your new book.

New Book Form Saved book

Get rid of the double header in /app/views/books/show.html.erb and add some links to actions an admin can do on that book. Open up that file and set it’s contents to:

<% title @book.title %> <p><%= @book.thoughts %></p> <% if @comments %> <h3>Comments</h3> <% for comment in @comments do %> <p><strong><%=h(comment.author) %></strong>: <%=h comment.text %> <% end %> <%= will_paginate @comments %> <% end %> <h3>Post Your Comment</h3> <% form_for([@book, Comment.new]) do |form| %> <p><%= form.label :author %></p> <p><%= form.text_field :author %></p> <p><%= form.label :text, 'Comment' %></p> <p><%= form.text_area :text %></p> <%= form.submit 'Save' %> <% end %> <p> Admin Actions: <%= link_to 'Edit', edit_book_path(@book) %> | <%= link_to 'Delete', book_path(@book), :method => :delete, :confirm => "Are you sure?" %> </p>

Head over to a book’s page and you should see:

Updated book's page

Now that we have some links to edit and delete, you can implement them. Editing a book works just about the same as creating a new one. We need an action that shows an edit form, and one to save the changes. Delete is just one action that deletes the record from the database. Open up books_controller.rb and add these actions:

def edit @book = Book.find params[:id] end def update @book = Book.find params[:id] if @book.update_attributes(params[:book]) flash[:notice] = "#{@book.title} saved." redirect_to @book else render :edit end end def destroy book = Book.find params[:id] book.destroy flash[:notice] = "#{book.title} deleted." redirect_to books_path end

The edit action finds the requested book from the id in the url. The update action finds the book from the id and uses the update_attributes method to set the new values from the form. Delete finds the book by id and deletes it. Then it redirects you back to the books listing.

Next we have to create an edit form. This form is exactly the same as the create form. We can just about duplicate the show.html.erb to edit.html.erb. All we are going to do is change the title. Create a new file in /app/views/books/edit.html.erb and paste this:

<% title "Editing #{@book.title}" %> <% form_for(@book) do |form| %> <p> <%= form.label :title %><br/> <%= form.text_field :title %> </p> <p> <%= form.label :thoughts %><br/> <%= form.text_area :thoughts %> </p> <%= form.submit %> <% end %>

Now from one of the book’s pages, click the edit link. You should see a familiar form:

Editing a book

Notice how Rails filled in the inputs with the saved values? Nice huh. Go ahead and save some changes to a book. When you’re done you should see this:

Viewing an edited book.

Now delete that book. You should get a confirmation dialog then be redirected back to /books.

Are you sure? Book deleted.

Add a link to create a new book on the index page. Open up /app/views/books/index.html.erb and add this to the bottom:

<p> Admin actions: <%= link_to 'New Book', new_book_path %> </p>

Now that we have CRUD functionality. We need to create our admin user.

Fifty Five

Maintaing user logins is a solved problem in Rails. You rarely have to write your own authentication system. We’re going to use the authlogic gem. Authlogic provides simple mechanics to authenticate users and store sessions. This is prefect for our app. We need an admin to login so he can create/edit/delete books. First let’s start by installing the authlogic gem.

# add config.gem 'authlogic' in environment.rb bookshelf $ sudo rake gems:install

Create a new model to hold the admins. Since our users are only admins, we’ll name the model Admin. For now the model only needs a login attribute. Generate the model using script/generate model:

bookshelf $ ./script/generate model Admin login:string exists app/models/ exists test/unit/ exists test/fixtures/ create app/models/admin.rb create test/unit/admin_test.rb create test/fixtures/admins.yml exists db/migrate create db/migrate/20091204202129_create_admins.rb

Now add authlogic specific columns to our admin model. Open up the migration you just created and paste this into it:

class CreateAdmins < ActiveRecord::Migration def self.up create_table :admins do |t| t.string :login t.string :crypted_password, :null => false t.string :password_salt, :null => false t.string :persistence_token, :null => false t.timestamps end end def self.down drop_table :admins end end

Now migrate your database.

bookshelf $ rake db:migrate == CreateAdmins: migrating =================================================== -- create_table(:admins) -> 0.0025s == CreateAdmins: migrated (0.0026s) ==========================================

Now the admin model is created. Next we need to create an authlogic session for that admin. Authlogic includes a generator for this:

bookshelf $ ./script/generate session admin_session exists app/models/ create app/models/admin_session.rb

Next we need to create some routes for logging in and out. Open up routes.rb and add this line:

map.resource :admin_session

Now we need a controller to handle the logging in and out. Generate this controller using the generator:

bookshelf $ ./script/generate controller AdminSessions exists app/controllers/ exists app/helpers/ create app/views/admin_sessions exists test/functional/ exists test/unit/helpers/ create app/controllers/admin_sessions_controller.rb create test/functional/admin_sessions_controller_test.rb create app/helpers/admin_sessions_helper.rb create test/unit/helpers/admin_sessions_helper_test.rb

Now open up /app/controllers/admin_sessions_controller.rb and paste this into it:

class AdminSessionsController < ApplicationController def new @admin_session = AdminSession.new end def create @admin_session = AdminSession.new(params[:admin_session]) if @admin_session.save flash[:notice] = "Login successful!" redirect_to books_path else render :action => :new end end def destroy current_admin_session.destroy flash[:notice] = "Logout successful!" redirect_to books_path end end

Wow! It seems like we just did a lot, but we haven’t. We’ve just created 2 new models. One model to hold our admins, and the other to hold admin session information. Finally we created a controller to handle the logging in and out. Now we need a view to show a login form. Create a new file at /app/views/admin_sessions/new.html.erb and paste this into it:

<% title 'Login' %> <% form_for @admin_session, :url => admin_session_path do |f| %> <%= f.error_messages %> <p> <%= f.label :login %><br /> <%= f.text_field :login %> </p> <p> <%= f.label :password %><br /> <%= f.password_field :password %> </p> <%= f.submit "Login" %> <% end %>

We’re almost done. We still need to tell our Admin model that it uses authlogic and add some logic to our application controller to maintain session information. All controller inherit from application_controller, so it’s a good way to share methods between controllers. Open up /app/controllers/application_controller.rb and paste this:

class ApplicationController < ActionController::Base helper :all # include all helpers, all the time protect_from_forgery # See ActionController::RequestForgeryProtection for details # Scrub sensitive parameters from your log # filter_parameter_logging :password filter_parameter_logging :password, :password_confirmation helper_method :current_admin_session, :current_admin private def current_admin_session return @current_admin_session if defined?(@current_admin_session) @current_admin_session = AdminSession.find end def current_admin return @current_admin if defined?(@current_admin) @current_admin = current_admin_session && current_admin_session.user end end

Now in /app/models/admin.rb add this line inside the class:

# /app/models/admin.rb acts_as_authentic

We’re finally ready to do some logging in and out. All of the stuff we did was almost purely from the authlogic documentation examples. This is a standard setup for many applications. If you want to find out more about how authlogic works you can here. Here’s a run down of what we did.

  1. Install the authlogic gem
  2. Create an Admin model to hold the basic information like login/password
  3. Add authlogic specific columns to the Admin table
  4. Generated an authlogic admin session
  5. Created routes for logging in and out
  6. Generated an AdminSession controller to do all the work
  7. Created a view that shows a login form
  8. Added methods to ApplicationController for persisting sessions
  9. Told the Admin model that it uses authlogic

It’s time to create the admin account. Our application is simple and only has one admin. Since we only have one admin, we can easily use the console. Since we’ll need to recreate that user later when we deploy, it doesn’t make sense to do the same thing twice. Rails now has a functionality for seeding the database. This is perfect for creating the initial records. There is a file /db/seeds.rb where you can write ruby code to create your initial models. Then you can run this file through rake db:seed. In order to create our admin model we’ll need a login, password, and password confirmation. Open up /db/seeds.rb and paste this. Fill in the login with the name you want.

Admin.create! :login => 'Adam', :password => 'nettuts', :password_confirmation => 'nettuts'

We use the create! method because it will throw an exception if the record can’t be saved. Go ahead and run the rake task to seed the database:

bookshelf $ rake db:seed

Now we should be able to login. Restart the server to get the new routes. Head to /admin_session/new. You should see:

Login Form

Go ahead and fill it in and now you should be logged in!

ZOMG logged in!

Now that admins can login, we can give them access to the new/edit/delete functionality. Rails has these awesome things called filters. Filters are things you can do at points in the request lifecycle. The most popular filter is a before_filter. This filter gets executed before an action in the controller. We can create a before filter in the books controller that checks to see if we have a logged in admin. The filter will redirect users who aren’t logged in, therefore preventing unauthorized access. Open up books_controller.rb and add these lines:

# first line inside the class: before_filter :login_required, :except => [:index, :show] # after all the actions private def login_required unless current_admin flash[:error] = 'Only logged in admins an access this page.' redirect_to books_path end end

Now we need to update our views to show the admin links only if there’s an admin logged in. That’s easy enough. All we need to do is wrap it in an if.

# show.html.erb <% if current_admin %> <p> Admin Actions: <%= link_to 'Edit', edit_book_path(@book) %> | <%= link_to 'Delete', book_path(@book), :method => :delete, :confirm => "Are you sure?" %> </p> <% end %> # index.html.erb <% if current_admin %> <p> Admin actions: <%= link_to 'New Book', new_book_path %> </p> <% end %>

We still need to add a login/logout link. That should go on every page. An easy way to put something on every page is add it to the layout.

# /app/views/layouts/application.erb <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html> <head> <title><%= h(yield(:title) || "Untitled") %></title> <%= stylesheet_link_tag 'application' %> <%= yield(:head) %> </head> <body> <div id="container"> <%- flash.each do |name, msg| -%> <%= content_tag :div, msg, :id => "flash_#{name}" %> <%- end -%> <%- if show_title? -%> <h1><%=h yield(:title) %></h1> <%- end -%> <%= yield %> <% if current_admin %> <p><%= link_to 'Logout', admin_session_path(current_admin_session), :method => :delete %></p> <% else %> <p><%= link_to 'Login', new_admin_session_path %></p> <% end %> </div> </body> </html>

Now you should have login/logout links on pages depending if your logged in and logged out. Go ahead and click the through the app. Try access the new book page after you’ve logged out. You should see an error message.

Access Denied

Click through the app. You should be able to login and out, and edit/create/delete books. Time for the final step. Let’s add some formatting to your thoughts and user comments. Rails has a helper method that will change new lines to line breaks and that sorta thing. Add that show.html.erb:

# <p><%= @book.thoughts %></p> becomes <%= simple_format @book.thoughts %> # do the same thing for comments # <p><strong><%=h(comment.author) %></strong>: <%=h comment.text %> becomes <p><strong><%=h(comment.author) %></strong>:</p> <%= simple_format comment.text %>

It doesn’t make sense to put the thoughts in the index, so let’s replace that with a preview instead of the entire text.

# index.html.erb # <p><%= book.thoughts %></p> becomes <%= simple_format(truncate(book.thoughts, 100)) %>

Now our final index page should look like this:

Easier index.

Finally we need to set up a route for our root page. Open up routes.rb and add this line:

map.root :controller => 'books', :action => 'index'

Now when you go to / you’ll see the book listing.

Sixty

Now we are going to deploy this app in a few steps. You don’t need your own server or anything like that. All you need is an account on Heroku. Heroku is a cloud Rails hosting service. If you have a small app, you can use their service for free. Once you’ve signed up for an account, install the heroku gem:

$ sudo gem install heroku

Heroku works with git. Git is a distributed source control management system. In order to deploy to heroku all you need to do is create your app then push your code to it’s server. If you haven’t already install git, instructions can be found here. Once you have heroku and git installed you are ready to deploy. First thing we need to do is create a new git repo out of your project:

bookshelf $ git init Initialized empty Git repository in /Users/adam/Code/bookshelf/.git/

It’s time to do some preparation for heroku deployment. In order to get your application’s gems installed, create a .gems file in the root project directory. Each line has the name of the gem on it. When you push your code to heroku it will read the .gems file and install the gems for you. So create a .gems file and paste this into it:

forgery will_paginate authlogic

There is a problem with authlogic on heroku, so we need to create an initializer to require the gem for us. Create a new file in /config/initializers/authlogic.rb and put this line in there:

require 'authlogic'

Now we should be ready to deploy. First thing you’re going to do is run heroku create. This will create a new heroku app for you. If you’re a first time user, it will guide you through the setup process.

bookshelf $ heroku create Git remote heroku added

No we are ready to deploy. Here are the steps

  1. Add all files in the project to a commit
  2. Commit the files
  3. Push are code to heroku
  4. Migrate the database on heroku
  5. Seed the database on heroku
  6. Restart the heroku server
  7. Open your running application
bookshelf $ git add -A bookshelf $ git commit -m 'Initial commit' bookshelf $ git push heroku master bookshelf $ heroku rake db:migrate bookshelf $ heroku rake db:seed bookshelf $ heroku restart bookshelf $ heroku open

Here is the finally app running on the world wide web:

Deployed Hit the Brakes

We’ve covered a lot of ground in this article, so where do we go from here? There are few things we didn’t do in this app. We didn’t add any validations to the models. We didn’t use partials. We didn’t do any administration for the comments. These are things you should look into next. Here are some links to help you with the next steps.

Links to gems used in this project.

Ready to take your skills to the next level, and start profiting from your scripts and components? Check out our sister marketplace, CodeCanyon.

CodeCanyonWrite a Plus Tutorial

Did you know that you can earn up to $600 for writing a PLUS tutorial and/or screencast for us? We’re looking for in depth and well-written tutorials on HTML, CSS, PHP, and JavaScript. If you’re of the ability, please contact Jeffrey at nettuts@tutsplus.com.

Please note that actual compensation will be dependent upon the quality of the final tutorial and screencast.

Write a PLUS tutorial


Categories: How-to's & Tutorials

An Interview with Brandon Jones

NETTUTS - Fri, 2010-01-01 10:16

Brandon Jones is an extraordinary web designer from California. He first appeared on ThemeForest in February of this year, and immediately soared to being one of our top selling authors, amassing over 5,000 sales.

His work is impeccable, and, more importantly, his work ethic is even more impressive. Today, we’ll talk with him about his work-flow, strategies for selling templates, and what makes him stand out among the rest.

“From sunny Southern California, Brandon Jones has been designing, drawing, photographing, and coding the world around him for the past several years. Not content to pick one media and stick with it has left Brandon with a broad range of talents that have allowed him to work on projects ranging from grungy digital art kits to Fortune 500 software prototyping.”

#1 – How long have you been in the web design business?

Over 7 years and counting. I’ve been designing as a freelancer working from my home in Southern California since 2002. I began by working on websites for local bands and musicians, then moved up to working with local businesses and small agencies in the Los Angeles area. In 2007, I jumped from working with Jetpack Studio (Los Angeles) to working with Shane&Peter Inc. (Santa Cruz) as my primary freelance team.


ShapeShifter

Shane&Peter Inc. is owned by two guys (named Shane and Peter, go figure), but the team is composed of over 50 designers, developers, projects managers, and other specialists from all around the world. I’m actually the designer with the most tenure at the moment, but we’ve got a number of great contractors that have been teaming up with us for years. We’re all freelance contractors, so there are no employees here – we all work when we want to and where we want to, which is a huge perk. We call it “remote team contracting”, which means that we more or less act as a team, but we’re all working as individual contractors rather than as hired employees.

In 2007 we won a Webby award for work on Blip.tv, and since then we’ve worked on countless projects for everyone from surf magazines to tech startups to Fortune 500 companies. Aside from working on several large scale websites (that I can’t mention because of non-disclosure agreements), I’ve also had the privilege to design a bunch of well known iPhone apps, software prototypes, and high intensity internal projects for some of the biggest companies in the world.

In 2009, I began contributing to Graphic River and Theme Forest as an adventurous little side project. 7 months later, I still freelance about 70% of the time, but I’m slowly allotting more and more time into ThemeForest. Right now I’m working on several major freelance projects in addition to planning on a few awesome new releases for ThemeForest over the next couple months.

#2 – When/How did you first come across the Envato marketplaces – specifically ThemeForest?

I first came across the Envato marketplaces in early 2009. I’d been a longtime fan of all of the Envato sites, but never really dug into the marketplaces until early this year when I started becoming more interested in using micro-stock and other stock templates in my own freelance work to save time. At the time, there were a number of sites out there releasing similar content, but none with the diversity and quality that Envato authors were publishing – the price points on the products were also ridiculously affordable to use on most of my freelance projects.

I released a few of my own web-related products to GraphicRiver in Feburary to test the waters and see what kind of reception I would get. Next, I released my first PSD template to ThemeForest in March, which is what really motivated me to begin releasing full website templates using the skills that I’d been honing over the past 7 years.

#3 – You have been particularly successful when it comes to WordPress theme sales. When developing a new item, do you try to plan for PSD, Site Template, and WordPress submissions? Or do you prefer to focus only on one category per design?

Yes and no. I definitely don’t allow the traditional understanding of a WordPress theme to limit my goals for any design. That said, starting in 2007 I began producing most of my website designs with WordPress in mind because my clients loved the backend and there weren’t many limitations on what you could in terms of design… so it only seemed natural to keep this mentality when I began releasing stock templates at ThemeForest.

That said, it usually takes more time to release a product to WordPress as it does to code into HTML. As you can see from my portfolio, most of my designs start as HTML, then move up to WordPress when I’ve had the time to code them properly.

There’s a huge difference between releasing a WordPress theme to ThemeForest and building a WordPress theme for a client though. Because of the nature of an individual client, most solutions that you come up with only have to work for them specifically. When I release a theme at ThemeForest, I have to account for countless possible usage scenarios. One buyer might want to take my theme and create a photography portfolio, another might want to use it as a site for a charitable organization. This kind of diverse usage really forces me to think through as many different types of users as possible because I actually hit the submit button.

Lately, I’ve been trying to push the envelope for WordPress themes. This means incorporating more and more design elements that you normally wouldn’t consider for a traditional blog theme. With the broad range of resources available for WordPress right now on the web, it’s easy to get inspired and want to try out some of the new tricks that are out there. Having these resources available also means that I can really let loose during the design phase and trust that there’ll be some way of making it work in WordPress.

#4 – Though encouraged, we do not require that authors provide support for their items. Do you? Considering how well your items sell, how do you manage to provide quality support to so many different buyers? Any tricks/short-cuts?

Yes, absolutely! Answering custom support emails is the easiest way to learn how to release better products and documentation in the future. Hearing what roadblocks users run into, what requests they have for customizations, and what they like best about my themes gives me an inside track to understanding how buyers are putting my products to use. This kind of information is like gold if you ask me, as it grants you a continuing education on the do’s and don’ts of stock templates.


New Portfolio Theme

As far as the practicality of responding to every user question goes: I used to answer all emails within 24 hours like clockwork… lately though, I’ve been doing my best to answer everyone within a week at the most simply because my current freelance work keeps me busy during most days of the week. It takes time to give every email a genuine response, but it ultimately pays off with happy buyers and lessons learned. Without tapping into the kind of feedback that comes from buyer-questions, I’d have a difficult time improving the quality of work as I’ve been doing over the past 6 months.

As far as shortcuts go – there’s no magic formula. However, proper documenting your product will significantly cut down on the number of questions that buyers have. After that, maintaining a FAQ page on your products that answers the most commonly asked questions will help out a lot as well.

#5 – Do you, or have you considered selling on the different Envato marketplaces as well?

Yep – I’ve worked in Flash for several years as well, and I’m hoping to begin releasing Flash templates in early 2010. I also release products now and then at GraphicRiver.

#6 – Many successful authors have noted that the biggest advantage to selling their designs through us is that it takes the client completely out of the design process, and, instead, allows the author full control. Would you agree? Any other advantages?

I totally agree! Not having to cater a design concept to one particular customer is incredibly liberating. If a user doesn’t like what I’ve done with a design, they can keep browsing until they find what’s right for them… but for those users who do like what I’m doing – they get an entire website for under $30! That’s not bad for either side of the transaction if you ask me.

The other major advantage that I’ve seen with releasing work at ThemeForest is the lack of deadlines. I live and die by deadlines in my freelance work. At ThemeForest, you can take as long as you want to get a product “just right”, and there’s no client breathing down your neck waiting to see the final product. I have a number of people that follow my work now and always want to see the latest work that I’m releasing, but it’s a totally different phenomena from “needing” to hit a deadline or risk getting into trouble with a client.

#7 – Do you take advantage of our referral program, or promote your items in any additional ways to increase sales?

I use as many ways to promote my products and I can think of. I release blog posts whenever I release a new product. I try to write for Envato as often as I can. I make twitter posts. I let people know on Facebook (politely, I hate spammers). I also join contests on other sites to garner extra exposure. I don’t really focus on getting a referral cut for any of those links, but it definitely doesn’t hurt when it happens.

#8 – Without giving away any of your key techniques, if you could only make one choice, what would be the most important thing to keep in mind when selling with us?

Think of the end-user! This is my mantra. It doesn’t matter how gorgeous your design is if a buyer can’t use the final product. Organize your files and coding as if your grandmother were going to have to use it. Document it as thoroughly and simply as you possibly can. Include images, instructions, links, FAQ’s, and any other sort of resource that might help out a buyer in the end. Good design is only one piece of a complex mechanism that creates a truly great stock product. Keep this in mind and you’ll do just fine.

I usually have my fiance’ read my documentation and instructions before I release a file. She has almost no experience at all with coding or web design, so it gives me a totally different response than when I read it. Things that make sense to me won’t to her – so it allows me the unique opportunity to re-write certain aspects of my documentation to help the most users possible. It helps that she has a degree in publicity and marketing though ;)

#9 – A new member signs up with ThemeForest, and is hoping to upload his or her first item. Any advice?

Do your homework! Look around to see what’s selling the best. Follow your favorite authors to see what they do and how they work. Read the forums and product threads to see how authors and buyers interact. Read the blogs to see what’s new at the marketplace. All of these are crucial to really diving in and understanding what it takes to become successful at ThemeForest.

#10 – Final question. What’s next for you? Any special items planned before the end of the year?

I actually have a batch of new products coming out in November and December, including at least 4 new WordPress themes, a handful of new HTML templates, even more PSD templates, and a few other surprises up my sleeve.

Here’s a sneak peek of my upcoming Reverb theme:

Thanks for reading!

If you Enjoyed this Interview…

…and would like to sty up to date on the latest from Brandon:

Write a Plus Tutorial

Did you know that you can earn up to $600 for writing a PLUS tutorial and/or screencast for us? We’re looking for in depth and well-written tutorials on HTML, CSS, PHP, and JavaScript. If you’re of the ability, please contact Jeffrey at nettuts@tutsplus.com.

Please note that actual compensation will be dependent upon the quality of the final tutorial and screencast.

Write a PLUS tutorial


Categories: How-to's & Tutorials

Get Your Free Wallpaper + Win a Unique 2010 Poster – 12 for 2010, Day Twelve

NETTUTS - Thu, 2009-12-31 23:00

To welcome in the new year and finish up our 12 for 2010 celebrations we’re releasing a wonderful unique 2010 desktop wallpaper created by James White, free to all Tuts+ readers and available in a number of different resolutions and also for the iPhone.

We’re also giving away one unique wall poster of the design, part of a limited run of only 12 and surely a collectors item! Entering to win a poster only takes a few seconds, since all you’ve got to do is leave a comment. Too easy! And everybody is welcome to download a free wallpaper.

Meet Aaron & Jordan – Support Ninjas Aaron & Jordan - Support Ninjas

Aaron and Jordan radiate Zen and calm in the office. There’s not a question they can’t handle, or a problem they can’t solve – usually in under 4 minutes (and those are ninja minutes, which are swift and cool; and by cool, I mean totally sweet). These two have been friends for a long time, allowing them to communicate almost telepathically as they sweep aside problems with ease – kind of like Dr. Phil before he sold-out.

Free Wallpaper by James White

Start the new year off right with some colorful inspiration on your desktop from James White.

sample

Download this Free WallpaperConceptual Development of this Design

Read a a bit about what James has to say about the development of this design below.

I wanted to design something bright and striking for the team at the Tuts+ Network which had me diving into sci-fi material from the 70s and 80s, most notably were old Omni Magazine covers. Omni had a way of creating interesting imagery for their covers that were somehow vague enough to leave a lot to your imagination. This is the chord I wanted to strike with the poster, I even modeled the type after Omni’s title.

After several different concepts and hours of development, one version involved a colorful exploding astronaut, the poster finally settled on the version seen here . . . the space medusa I lovingly nicknamed ‘La Femme II’ after my previous poster.

How to Enter in 10 Seconds or Less

For the chance to win a free James White 2010 poster, all you need to do is comment. Make sure to include your correct email address with your comment so that we can contact you. This giveaway is open worldwide, but make sure to get your comment in before midnight on January 2nd, Pacific Eastern Standard Time.

To increase your chances of winning, make sure to enter again once on each Tuts+ site. There are 8 poser giveaways going on right now, each with another chance to win.

Please note: Envato staff and people who have written more than two tutorials/articles for a Tuts+ site are not eligible to enter.


Categories: How-to's & Tutorials

Happy New Year!

NETTUTS - Thu, 2009-12-31 15:37

Where I live, it’ll be 2010 in six hours. Wow – 2010! And that means, in five years, we’ll be in flying cars where the postal service isn’t as efficient as the weather. (First person who names that movie reference will be sent a web dev book.)

As the year comes to a close, I want to take a moment to thank all of you so much. While my job is to edit and create tutorials, I can promise that I’ve learned far more from the comments of these postings, and I’m grateful for that. I also would like to go over what I have planned for 2010…

More Design

Especially in the last few months, we’ve managed to focus almost exclusively on development-style tutorials. I can only post what is submitted, and we truthfully haven’t received many articles focused on design. That’ll be changing in 2010 – and I encourage you to submit your ideas to me at nettuts@tutsplus.com.

  • Advanced CSS tutorials
  • The process of designing a website
  • How to build advanced menus
  • Combining CSS and JavaScript
  • Etc.

If you’re a successful ThemeForest author, or have some amazing design skills, please do contact me about writing tutorials, and even recording a screencast for our site. Please remember that compensation for Plus tutorials can go as high as $600, depending on the quality of the final product.

Broader Topics

Additionally, we’ll be focusing more on Air apps, as well as Ruby/Rails tutorials. If there’s anything that we don’t cover on Nettuts+, be sure to let me know within the comments. I promise to consider it!

Redesign

I can’t say much, but I’m fairly certain that the design of all the Tuts site will receive another evolutionary “remodeling,” so to speak. Going with the great design that we currently have, Collis and Derek will refine the layout further, as well as add some neat new features/categories.

Happy New Year!

So let’s meet back here in the next decade! Allie is giving me the evil eye right now – which means I have to close the computer for the night. Bye!

Jeffrey WayWrite a Plus Tutorial

Did you know that you can earn up to $600 for writing a PLUS tutorial and/or screencast for us? We’re looking for in depth and well-written tutorials on HTML, CSS, PHP, and JavaScript. If you’re of the ability, please contact Jeffrey at nettuts@tutsplus.com.

Please note that actual compensation will be dependent upon the quality of the final tutorial and screencast.

Write a PLUS tutorial


Categories: How-to's & Tutorials

Create an In-Place Editing System: One Step Further

NETTUTS - Thu, 2009-12-31 09:46

A few months ago, you learned how to create an in-place editing system. Today, we’ll take things a step further as we create a simple backend, which will allow our website to remember the changes that we’ve made.

A Word From the Author

With all the buzz around Web 2.0, ease of use is now much more important than ever. Being able to edit some content without having to go to another page is something a lot of users really crave. A lot of big names are already using this pattern to great effect. If you’ve used Flickr, you’ve probably seen this in action.

Today, we are going to improve on the earlier version: weeding out some bugs, adding some features, and, more importantly, saving all the data to an actual database for retention. Interested? Let’s get started right away!

Prepping the Database

First up, we need a database to pull in the information from and then, when required, update the data it holds. For the sake of this exercise, let us setup a table with some random data.

I already had a database named inplace with a table called data on my development server. For our use we’ll add another table.

Tutorial Image

I typically prefer using phpMyAdmin for running my SQL queries. Click on the SQL tab and paste in the following query:

CREATE TABLE IF NOT EXISTS `inplace` ( `field` varchar(120) NOT NULL, `value` text NOT NULL, PRIMARY KEY (`field`) ) ENGINE=MyISAM; INSERT INTO `inplace` (`field`, `value`) VALUES ('name', 'am Siddharth'), ('passion', 'love working with the web'), ('profession', 'am a freelancer'), ('work', 'write for Net Tuts'), ('url', 'can be found at www.ssiddharth.com'), ('punch', 'will never let you down or give you up :) '), ('design', 'Get design approval from Yusuf'), ('invoice', 'Send an invoice to Drew'), ('research', 'Start research on Pallav\'s project'), ('discuss', 'Speak with Harnish about new ideas'), ('debug', 'Check Aditya\'s site for rendering bugs'), ('meet', 'Meet with Clintson to discuss new project'); Tutorial Image

If everything worked out as it should you should get the following screen:

Tutorial Image

A closer look at the table:

Tutorial Image

Since I explicitly wanted to keep the simplicity of the demo and just add the back end people requested, I am keeping the table structure very simple. Feel free to modify and extend it in your projects.

Now that the sample table has been created and pre populated with some test data, we can move on to the actual back end.

Setting up a Database Config File

Since we’ll be accessing the database often either to read data or to update the data it contains, it’s prudent to create a config file which holds the relevant data. Create a file called db.php and paste the following in it.

<?php DEFINE ('DB_USER', 'sid'); DEFINE ('DB_PASSWORD', 'somerandompassword'); DEFINE ('DB_HOST', 'localhost'); DEFINE ('DB_NAME', inplace); $connection = mysql_connect(DB_HOST, DB_USER, DB_PASSWORD) or die('Connection to the specified database couldn\'t be established'); mysql_select_db(DB_NAME) or die ('Specified database couldn\'t be selected'); ?>

Nothing special here. We define all the relevant details, connect to the host using the given username/password combination and then select the relevant database for manipulation down the road.

The EditorTutorial Image

The editor takes care of reading from the database and outputting the data in a specific format so it is easy for us to send relevant details back to the server informing which record to update. We’ll talk about it more in a second.

The code doesn’t change significantly from the static HTML only code from the earlier version. We do, however, need to make the data dynamic. So in the original HTML code, this:

<li class="editable">am Siddharth</li> <li class="editable">love working with the web</li> <li class="editable">am a freelancer</li> <li class="editable">write for Net Tuts</li> <li class="editable">can be found at <a href="http://www.ssiddharth.com">www.ssiddharth.com</a></li> <li class="editable">will never let you down or give you up :) </li> <li class="editable">Get design approval from Deacon</li> <li class="editable">Send an invoice to Albert </li> <li class="editable">Start work on Dwight's project</li> <li class="editable">Talk with Sarah about new ideas</li> <li class="editable">Check Seth's site for rendering bugs</li> <li class="editable">Meet with Clintson to discuss project</li>

is replaced by:

<?php $query = "SELECT * FROM inplace LIMIT 0, 6"; $result = mysql_query($query) or die ('Query couldn\'t be executed'); while ($row = mysql_fetch_assoc($result)) { echo '<li class="editable" id="'.$row['field'].'">'.$row['value'].'</li>'; } ?> <?php $query = "SELECT * FROM inplace LIMIT 6, 6"; $result = mysql_query($query) or die ('Query couldn\'t be executed'); while ($row = mysql_fetch_assoc($result)) { echo '<li class="editable" id="'.$row['field'].'">'.$row['value'].'</li>'; } ?>

Since the table is small, we’ll just select everything from the table but ask it to return only the first 6 elements. Next, I just iterate through and print out the li elements. Take special note of the fact that each li elements gets its id attribute set to the name of field it obtains its value from. This will be used later in the data sent back to the server to denote which record needs to be updated.

I am aware exposing the name of the field like this may pose a security threat but on a properly secured environment, I don’t think this will instigate any troubles. Else you could just use aliases here and do a reverse lookup on the server side. Let your creative juices flow there. For a very straight forward demo, it seemed rather overkill.

Also, don’t forget to include the db.php file we created earlier to the editor. This line will take care of that.

<?php require("db.php"); ?>

After making the edits, remember to save the file with a .php extension.

The Handler

The handler is where the page posts the details to. This takes care of checking whether data was actually sent to the page, and if so, sanitizes the sent data and then updates the relevant values.

Create a file named handler.php and paste in the following:

<?php require("db.php"); if (isset($_POST['field']) && isset($_POST['value'])) { $value = mysql_real_escape_string($_POST['value']); $field = mysql_real_escape_string($_POST['field']); $query = "UPDATE inplace SET value ='$value' WHERE field='$field'"; $result = mysql_query($query) or die ('Query couldn\'t be executed'); if ($result) {echo 1;} } ?>

A pretty straightforward affair. Let me explain each step in detail.

Since we’ll need to manipulate the database, we first include the db.php file we created earlier.

Next, we check whether both our required variables, field- value which tells us which field to update and value – the value to update to, are sent as POST variables to the handler. If so, we can proceed to the actual work. If not, nothing happens.

Once, we’ve verified that the variables were sent, we can go on sanitizing the data for insertion into the database. To keep it as simple as possible, we’ll use the mysql_real_escape_string function to sanitize our data. This function escapes the special characters present in the passed string. If passed in un sanitized, our code is subject to SQL injection attacks.

Now that we’ve made sure the data is safe, we can update the relevant record. I am assuming this part needs no explanation since it is very simple SQL. In layman’s terms, in the inplace table, change field’s corresponding value to value.

If everything goes on according to plan, return a value of 1 which’ll be captured by our script to determine the outcome of the transaction so it can proceed accordingly. I’ll elaborate more later below. Please do note that in this case, I merely report whether the attempt succeeded or failed. In your project, you may want to return much more detailed info in case any error occurs. You are not limited to my extremely simple implementation.

The JavaScript

Now that the back end has been constructed, it’s time to edit the front end part of the project to let it communicate with the server. We’ll also look at implementing a new feature along the way.

Cleaning up the Old Code

One of the complaints of the old version was data corruption when certain actions were performed in a specific order. This was due to my extreme need for simplicity and conciseness which ultimately led me to overlook that specific scenario. Never the less, we’ll rectify that today.

I’m assuming you have the old JavaScript code nearby to compare with and edit.

Getting Rid of Global Variables

The first version used global variables to hold the original data which led to unexpected results in certain case. We’ll rectify this first.

The easiest way to rectify this would be to just add a hidden input next to the original input and use it as a buffer. Since it is created and destroyed on the fly and is specific to that element alone, we can edit/save/discard as many elements as possible as many time as possible without any hiccups.

The old replaceHTML function gets updated to:

function replaceHTML() { var buffer = $(this).html() .replace(/"/g, """); $(this).addClass("noPad") .html("") .html("<form class=\"editor\"> <input type=\"text\" name=\"value\" class=\"editBox\" value=\"" + buffer + "\" /> <input type=\"hidden\" name=\"buffer\" class=\"buffer\" value=\"" + buffer + "\" /> </form> <a href=\"#\" class=\"btnSave\">Save changes</a> <a href=\"#\" class=\"btnDiscard\">Discard changes</a>") .unbind('dblclick', replaceHTML); }

Not a big edit here. First we create an internal variable called buffer to hold the original value. We then purge the HTML content of the parent element and inject our own. In addition to the original snippet, we add a hidden text box which retains the original value. Nothing else is changed here.

Creating a Unified Handler

The earlier iteration binded similar but separate functions for each of the functional links. We’ll unify them here.

function handler() { var selector; if ($(this).hasClass("btnSave")) { selector = "editBox" } else { selector = "buffer" } $(this).parent() .html($(this).siblings("form") .children("."+selector) .val()) .removeClass("noPad editHover") .bind("dblclick", replaceHTML); return false; }

Instead of using anonymous functions like last time, we are going to use a normal function. We are only going to edit small parts of the function to make it handle both save and discard requests.

We first declare a variable named selector which holds the selector to use whilst updating the li elements. editBox is the class assigned the visible text box and buffer is the class assigned to the hidden text box which holds the original value.

Since we are unifying the event handlers, we need to check which link was clicked. We first see whether the clicked link has a class of btnSave. If so, then the user wants to save the edits and so we assign the value of editBox to the selector variable. If not, buffer is assigned.

The rest of the handler remains the same as the old version except that the selector is injected dynamically based on the action instead of it being hard coded into the function. If you seem lost here, look at the first part of the series to understand what the last block does. Essentially, we inject the selected text box’s value into the parent li element and rebind the original event handler.

Don’t forget to update the event handlers for each link. The following one liner takes care of that:

$(".btnSave, .btnDiscard").live("click", handler);

If you are wondering why I used the live function here, please refer to the earlier article.

Adding AJAX Capabilities

With all the bugs squashed out and the code generally tightened up a little, we can start working on implementing the actual functionality.

Prepping the HTML

Before we can send the data to the server, we need to find a way to send relevant details back to the server. In this case, we need 2 details to make a successful edit.

  • The value itself
  • The name of the field to be updated

The first part is rather straightforward since we have an actual text box holding the values to be sent to the server. The second part needs a little work.

Whilst creating the editor, remember that we used the primary ID of the table as id attributes to each li element? We are going to make use of it here. We’ll just create another hidden text box which’ll hold the value which can be then posted back to the server.

function replaceHTML() { var buffer = $(this).html() .replace(/"/g, """); $(this).addClass("noPad") .html("") .html("<form class=\"editor\"> <input type=\"text\" name=\"value\" class=\"editBox\" value=\"" + buffer + "\" /> <input type=\"hidden\" name=\"buffer\" class=\"buffer\" value=\"" + buffer + "\" /> <input type=\"hidden\" name=\"field\" class=\"record\" value=\"" + $(this).attr("id") + "\" /> </form> <a href=\"#\" class=\"btnSave\">Save changes</a> <a href=\"#\" class=\"btnDiscard\">Discard changes</a>") .unbind('dblclick', replaceHTML); }

The replaceHTML function has to be updated like so. The only difference is the addition of hidden text box with the name field. We use jQuery’s attr function to access the li element’s ID attribute and use it as the text box’s value.

The AJAX Implementation

On to the AJAX implementation then. We are going to use jQuery’s standard ajax function here.

function handler() { // Previous code if ($(this).hasClass("btnSave")) { var selector = "editBox"; var str = $(this).siblings("form").serialize(); $.ajax({ type: "POST", async: false, timeout: 100, url: "handler.php", data: str, success: function(msg){code = msg;}, }); if(code == 1) { alert ("Success"); } else { alert ("Failure"); } } // Rest of the code }

Since we only need to send the data to the server when the user has clicked the relevant link, we encapsulate all the code within the if block we created earlier to check which link was clicked.

I make use of the ajax function since I find it to be the most robust. First up, I serialize the data the parent form holds so it can be posted to the server. Next, I call the ajax function setting all relevant details as necessary which includes the type of request to make – POST and the URL to post to. We also specify that the data we serialized earlier should be sent to the server.

Usually, you’d use the inbuilt success and error callbacks to make further changes but I’ve chosen not to do so here. Instead, I am just capturing the text sent back by the server. If it returns 1, a value we configured our handler to return if everything happened correctly, we alert the user to let him know.

Implementing a Status BarTutorial Image

Alerts are a pretty rudimentary way to update the user with the status of the action. With that in mind, we are going to scrap the alert system and instead implement a simple status bar at the bottom which reflect these changes.

The Markup

We don’t need anything special here. We just need a simple div element which we can manipulate. We are just going to have to add that directly in the editor.

<div id="status"></div>

Make note of the id attribute. We’ll use it later.

The Helper Function

In the interest of code reusability, we’ll create a helper function which updates the status bar as needed.

function UI(state) { var status = {}; status.Ready = "Ready"; status.Post = "Saving your data. Please wait..."; status.Success = "Success! Your edits have been saved."; status.Failure = "Attempts to save data failed. Please retry."; var background = {}; background.Ready = "#E8F3FF"; background.Post = "#FAD054"; background.Success = "#B6FF6C"; background.Failure = "#FF5353"; $("#status").animate({opacity: 0}, 200, function (){$("#status").html(status[state]).css({background: background[state]}).animate({opacity: 1}, 200)}); }

The function, which we’ve named, UI, takes the state of the status bar as its parameter. Inside the function, we create two objects: status holds the relevant text and background holds the background colors of the status bar.

We could just directly update the status bar’s text and background color but here at Net Tuts, that’s not how we roll. :)

We are going to make use of jQuery’s animate function to gracefully animate the status bar. First, we animate its opacity to zero. Next, we update its text and background color and then animate it back to full visibility.

Take special note of the fact that the logic used to update the values are enclosed within an anonymous function and passed as the callback to the original animation. This way the bar will animate to zero opacity and then everything is updated. If the animations are chained, the text and background colors will be updated just after the initial animation starts which leads to a very jarring effect.

Adding it to UI

Adding it to the UI and updating the status bar now is a piece of cake. Instead of the alerts we used earlier, we need to use the UI function.

The earlier block which succeeded the ajax call can now be replaced by:

if(code == 1) { UI("Success"); } else { UI("Failure"); }

Also, don’t forget to add UI(“Ready”); when the page loads so the user knows the system is ready for manipulation and UI(“Post”); when the data is being posted to the server.

When you are adding your own states to the task bar, make special note of the fact that the string we send as the parameter to the function maps directly to the property of the object.

Proper Data Persistence

The last thing we need to look at is the fact that if the attempt to save the data failed, the updated text is still retained. This seems rather counter intuitive. If the attempt to save the data fails, we need to make sure the original text is placed back so the user knows the data hasn’t been saved.

In order to rectify this, we’ll need to change the selector variable in case we encounter an error.

if(code == 1) { UI("Success"); selector = "editBox"; } else { UI("Failure"); selector = "buffer"; }

If the value was edited successfully, we change the the relevant variable’s value to editBox. But if the attempt ended in failure, we need to swap out the new value with the old value. So we assign buffer to the variable so that value will revert back to its original value.

Conclusion

And there you have it. How to add a user friendly functionality to your projects. Hopefully you’ve found this tutorial interesting and this has been useful to you. Feel free to reuse this code elsewhere in your projects and chime in here if you are running into difficulties.

Please do keep in mind that this system was designed with the primary intention of teaching the techniques associated with this, not as a production system designed to drop in into existing systems. This is more of a foundation that I encourage people to build upon and improve.

Questions? Nice things to say? Criticisms? Hit the comments section and leave me a comment. Happy coding!

Write a Plus Tutorial

Did you know that you can earn up to $600 for writing a PLUS tutorial and/or screencast for us? We’re looking for in depth and well-written tutorials on HTML, CSS, PHP, and JavaScript. If you’re of the ability, please contact Jeffrey at nettuts@tutsplus.com.

Please note that actual compensation will be dependent upon the quality of the final tutorial and screencast.

Write a PLUS tutorial


Categories: How-to's & Tutorials

One Page Résumé Site

CSS-Tricks - Thu, 2009-12-31 06:17

A friend of mine recently sent me her résumé to look over. I’m definitely not a professional job hunter but I think in these situations any extra set of eyes can help fine tune the final product. As it was, the résumé was a Microsoft Word document, which in itself is fine, but it wasn’t particularly well designed. I thought, we’re going on 2010 here, we might as well take this thing to the web!

I created a really simple design. Then I replaced all her content with good ol’ C’thulu so I have a generic template I can give out to you folks.

View Demo   Download Files

Really nothing to amazing here, just a clean layout. But also:

  • Contact information using microformats
  • Main resume area using what I think is the semantically correct definition list (<dl>)
  • Literally one page (just an index.html file, with optional images)
  • Prints nicely
Print Preview

Of course feel free to do whatever you want with it.

Categories: How-to's & Tutorials

Win a Three-month Plus Membership – 12 for 2010, Day Eleven

NETTUTS - Wed, 2009-12-30 23:00

As part of the eleventh day of 12 for 2010, we’re giving away a free 3-month membership to Plus;. Entries are open to everybody – Plus members and non-Plus members alike. Entering only takes a few seconds, since all you’ve got to do is leave a comment. Too easy.

Meet Derek Herman Derek Herman

Derek is the Tuts+ WordPress master and the guy who turns Collis’s designs into a reality. He makes WordPress do things Matt Mullenweg has never even dreamed of. In fact, we’re pretty sure Matt could learn a thing or two from Derek!

Meet Fred Wu Fred Wu

Fred is Envato’s only PHP developer, valiantly holding the fort against the Ruby dev team who consider their development language to be superior. We couldn’t have picked a better representative for the PHP community. In his spare time, Fred indulges in his country-specific domain name addiction while continuing to amass a vast knowledge of anything and everything to do with technology. Whether you want to know which Mac app will help you categorize your taxidermy collection, or which Firefox plug-in you should use to initiate global thermonuclear war in the guise of a computer game, Fred has you covered.

Win a 3-Month Membership to Plus

3-month Plus Membership

Joining Plus; gives you access to an exclusive members area chocked-full of our best tutorials, plus hundreds of source files. You get the best teaching on Photoshop and graphic design, motion graphics, vector illustration, web development and audio production and mixing.

For the chance to win the 3-month Plus membership, all you need to do is comment. Make sure to include your correct email address with your comment so that we can contact you. This giveaway is open worldwide, but make sure to get your comment in before midnight on New Year’s Eve, Pacific Eastern Standard Time.

To increase your chances of winning, make sure to enter again once on each Tuts+ site. There are 8 giveaways going on right now, each with another chance to win.

If you don’t want to leave it to chance, you can begin your Plus membership for only $9.

Please note: Envato staff and people who have written more than two tutorials/articles for a Tuts+ site are not eligible to enter.


Categories: How-to's & Tutorials

A Video Crash-Course in Raphael: New Plus Tutorial

NETTUTS - Wed, 2009-12-30 17:52

Raphaël is a JavaScript library that provides you with extreme flexibility when working with animations. If you’re unfamiliar with this library, you’ll be amazed when you view some of the demos, and find that no Flash has been used.

Perhaps the reason why this library hasn’t become as widely used as it should be is because there aren’t enough quality tutorials available around the web. Hopefully, this Plus tutorial will help! Join Plus!

Join Tuts PlusNETTUTS+ Screencasts and Bonus Tutorials

For those unfamiliar, the family of TUTS sites runs a premium membership service called “TUTSPLUS”. For $9 per month, you gain access to exclusive premium tutorials, screencasts, and freebies from Nettuts+, Psdtuts+, Aetuts+, Audiotuts+, and Vectortuts+! For the price of a pizza, you’ll learn from some of the best minds in the business. Join Plus!

Write a Plus Tutorial

Did you know that you can earn up to $600 for writing a PLUS tutorial and/or screencast for us? We’re looking for in depth and well-written tutorials on HTML, CSS, PHP, and JavaScript. If you’re of the ability, please contact Jeffrey at nettuts@tutsplus.com.

Please note that actual compensation will be dependent upon the quality of the final tutorial and screencast.

Write a PLUS tutorial


Categories: How-to's & Tutorials

Seeing The Details

CSS-Tricks - Wed, 2009-12-30 05:17

When a great musician hears a great song, what do they hear? I am not a great musician so I can only speculate. I would guess that they listen for the emotion behind the song. Great songs are great because of their delivery of honest emotion, in any genre. I also think they listen for the intangibles. They try and put their fingers on the elusive qualities and little details that make a great song great.

I think this is very similar to how great designers see great designs.

But let’s get back to the musician for a second.

To an outsider observing this great musician analyzing a great song, they might oooh-and-ahhh at their ability to quickly dissect the key, melody, or chord changes. But to the musician, these things likely feel trivial. That is no problem for them. That is their trade. Of course they know what key it’s in. What is more important is that emotion, that atmosphere, those little details that took the song to greatness.

Now what about a great designer looking at a great design? I think the designer looks for the same things the musician does. The emotion. The details. I think they try to put their finger on the sometimes elusive qualities that make a great design great. The outside observer watching this designer break down the design might oooh-and-ahhh at the designers ability to replicate it, or analyze the technology used to make it happen. But the designer likely feels those things are trivial. Of course they can replicate it. Of course they can figure out the fancy CSS tricks or Photoshop effects that made it happen. That is their trade. More importantly though, is the designers ability to understand the details that took the design to greatness.

 

Do you think this is true? Or is perhaps the opposite true? Great creators are great because of their ability to see their creations as an untrained layperson would.

Categories: How-to's & Tutorials

JavaScript from Null: Chapter 4

NETTUTS - Wed, 2009-12-30 02:30

JavaScript University continues today as we learn about methods of the Array object, how to return values from functions, scope, and even your first animation.

Remember – though each new chapter builds upon the previous ones, you can still follow along perfectly well if you haven’t watched the other entries in the series!

Catch UpIn this Screencast, you’ll Learn:
  • Methods of the Array object: push, pop, unshift, shift
  • Pull values outside of functions
  • Reducing your “global footprint” by creating an object
  • SetInterval
  • Create your first animation
  • Methods of the String object.
Chapter 4: Arrays, Functions, and your First Animation Other Viewing Options

Ready to take your skills to the next level, and start profiting from your scripts and components? Check out our sister marketplace, CodeCanyon.

CodeCanyon

digg_url = "post permalink (not digg url)";


Categories: How-to's & Tutorials

Win a $50 Amazon Gift Card – 12 for 2010, Day Ten

NETTUTS - Tue, 2009-12-29 23:00

As part of the tenth day of 12 for 2010, we’re giving away a $50 Amazon.com Gift Card to spend on whatever you like! Entering only takes a few seconds, since all you’ve got to do is leave a comment. Too easy.

Meet David Appleyard David Appleyard

David loves photography and Macs – and especially loves doing photography with the help of Macs! As such, he’s the editor of Phototuts+ and Mac.AppStorm. Aside from these sites, he manages four other design related websites. When we create Multitaskingtuts+ David will probably edit that too.

Win a $50 Amazon Gift Card

$50 Amazon Gift Card Example

About the Prize

3D artists all over the world need to stay inspired. This gift card will help you to buy whatever it takes to keep the creative juices on tap – whether that’s great music, DVDs, books, art supplies, electronics… the choice is yours!

How to Enter in 10 Seconds or Less

For the chance to win a $50 Amazon Gift Card, all you need to do is comment. Make sure to include your correct email address with your comment so that we can contact you. This giveaway is open worldwide, but make sure to get your comment in before midnight on New Year’s Eve, Pacific Eastern Standard Time.

To increase your chances of winning something, make sure to enter again once on each Tuts+ site. There are 8 giveaways going on right now, each with another chance to win.

Please note: Envato staff and people who have written more than two tutorials/articles for a Tuts+ site are not eligible to enter.


Categories: How-to's & Tutorials

Testing, Performance Analysis, and jQuery 1.4

NETTUTS - Tue, 2009-12-29 19:10

John Resig recently gave a three-part lecture on JavaScript for YUI Theater, which I found to be extremely informative. Over the course of ninety minutes or so, he goes over unit testing, as well as some of the changes the jQuery team have made in the upcoming jQuery 1.4. It’s well worth the watch!

Ready to take your skills to the next level, and start profiting from your scripts and components? Check out our sister marketplace, CodeCanyon.

CodeCanyon

digg_url = "post permalink (not digg url)";


Categories: How-to's & Tutorials

6 CodeIgniter Hacks for the Masters

NETTUTS - Tue, 2009-12-29 08:50

CodeIgniter is a simple and powerful open source web application framework for PHP. Today, we’ll do some core “hacks” to this framework to change and improve its functionality. In the process, you’ll gain a better understanding of the intricacies of CodeIgniter.

Disclaimer
  1. It is not recommended to apply these hacks to an existing project. Since they change some of CodeIgniter’s core functionality, it can break the existing code.
  2. As of this writing, CodeIgniter 1.7.2 is the latest stable release. These hacks are not guaranteed to work for future (or past) releases.
  3. Even though CodeIgniter is designed to be PHP 4 compatible, some of these hacks are not. So you will need a server with PHP 5 installed.
  4. When you make any changes to the files inside the system folder, you should document it somewhere for future reference. Next time you upgrade CodeIgniter (even though they do not release updates very often), you may need to reapply those changes.
1. Autoloading Models PHP 5 StyleThe Goal

On the left side, you see the regular way of loading a model in CodeIgniter, from within a Controller. After this hack, we will be able to create objects directly. The code is cleaner, and your IDE will be able to recognize the object types. This enables IDE features such as auto-complete, or previewing documentation.

There are two more side effects of this hack. First, you are no longer required to extend the Model class:

And you no longer have to add a require_once call before you do model class inheritance.

The Hack

All we need to do is add a PHP 5 style autoloader function.

Add this code to the bottom of system/application/config/config.php:

<?php // ... function __autoload($class) { if (file_exists(APPPATH."models/".strtolower($class).EXT)) { include_once(APPPATH."models/".strtolower($class).EXT); } } ?>

If you are interested in applying this hack for controllers too, you can use this code instead:

<?php // ... function __autoload($class) { if (file_exists(APPPATH."models/".strtolower($class).EXT)) { include_once(APPPATH."models/".strtolower($class).EXT); } else if (file_exists(APPPATH."controllers/".strtolower($class).EXT)) { include_once(APPPATH."controllers/".strtolower($class).EXT); } } ?>

Any time you try to use a class that is not defined, this __autoload function is called first. It takes care of loading the class file.

2. Prevent Model-Controller Name CollisionThe Goal

Normally, you can not have the same class name for a Model and a Controller. Let’s say you created a model name Post:

class Post extends Model { // ... }

Now you can not have a URL like this:

http://www.mysite.com/post/display/13

The reason is because that would require you to also have a Controller class named ‘Post.’ Creating such a class would result in a fatal PHP error.

But with this hack, it will become possible. And the Controller for that URL will look like this:

// application/controllers/post.php class Post_controller extends Controller { // ... }

Note the ‘_controller’ suffix.

The Hack

To get around this issue, normally most people add the ‘_model’ suffix to the Model class names (eg. Post_model). Model objects are created and referenced all over the application, so it might seem a bit silly to have all of these names with ‘_model’ floating around. I think it is better to add a suffix to the Controllers instead, since they are almost never referenced by their class names in your code.

First we need to extend the Router class. Create this file: “application/libraries/MY_Router.php”

class MY_Router extends CI_Router { var $suffix = '_controller'; function MY_Router() { parent::CI_Router(); } function set_class($class) { $this->class = $class . $this->suffix; } function controller_name() { if (strstr($this->class, $this->suffix)) { return str_replace($this->suffix, '', $this->class); } else { return $this->class; } } }

Now edit “system/codeigniter/CodeIgniter.php” line 153:

if ( ! file_exists(APPPATH.'controllers/'.$RTR->fetch_directory().$RTR->controller_name().EXT))

Same file, line 158:

include(APPPATH.'controllers/'.$RTR->fetch_directory().$RTR->controller_name().EXT);

Next, edit: “system/libraries/Profiler.php”, line 323:

$output .= " ".$this->CI->router->controller_name()."/".$this->CI->router->fetch_method()." ";

That is all. Keep in mind that with this hack you are required to put the ‘_controller’ suffix on all of your controller class names. But not in the file names or the URL’s.

3. Form Validation for Unique ValuesThe Goal

CodeIgniter has a nice Form_validation class. It comes with several validation rules:

These are useful, but there is an important one missing from this list: to check for unique values. For example, most user registration forms need to check that the username is not already taken, or the e-mail address is not already in the system.

With this hack, you will be able add this validation rule to your form submission handler very easily:

$this->form_validation->set_rules('username', 'Username', 'required|alpha_numeric|min_length[6]|unique[User.username]');

Note the last part that says “unique[User.username].” This new validation rule is just called “unique,” and takes a parameter inside the square brackets, which is “tablename.fieldname”. So it will check the “username” column of the “User” table to make sure the submitted value does not already exist.

Similarly, you can check for duplicate e-mails:

$this->form_validation->set_rules('email', 'E-mail', 'required|valid_email|unique[User.email]');

And your application can respond with proper error messages:

The Hack

This might be considered more of an extension than a hack. Nevertheless, we are going to take a core CodeIgniter library and improve it.

Create: “application/libraries/MY_Form_validation.php”

class MY_Form_validation extends CI_Form_validation { function unique($value, $params) { $CI =& get_instance(); $CI->load->database(); $CI->form_validation->set_message('unique', 'The %s is already being used.'); list($table, $field) = explode(".", $params, 2); $query = $CI->db->select($field)->from($table) ->where($field, $value)->limit(1)->get(); if ($query->row()) { return false; } else { return true; } } }

Now you can use the unique validation rule.

4. Running CodeIgniter from the Command LineThe Goal

Just like the title says, our goal is to be able to run CodeIgniter applications from the command line. This is necessary for building cron jobs, or running more intensive operations so you don’t have the resource limitations of a web script, such as maximum execution time.

This is what it looks like on my local Windows machine:

The above code would be like calling this URL:

http://www.mysite.com/hello/world/foo The Hack

Create a “cli.php” file at the root of your CodeIgniter folder:

if (isset($_SERVER['REMOTE_ADDR'])) { die('Command Line Only!'); } set_time_limit(0); $_SERVER['PATH_INFO'] = $_SERVER['REQUEST_URI'] = $argv[1]; require dirname(__FILE__) . '/index.php';

If you are on a Linux environment and want to make this script self executable, you can add this as the first line in cli.php:

#!/usr/bin/php

If you want a specific controller to be command line only, you can block web calls at the controller constructor:

class Hello extends Controller { function __construct() { if (isset($_SERVER['REMOTE_ADDR'])) { die('Command Line Only!'); } parent::Controller(); } // ... } 5. Adding Doctrine ORM to CodeIgniterThe Goal

Doctrine is a popular Object Relational Mapper for PHP. By adding it to CodeIgniter, you can have a very powerful Model layer in your framework.

The Hack

Just installing Doctrine is not very “hacky” per se, as we can just add it as a plug-in. However, once added, your Model classes will need to extend the Doctrine base classes, instead of the CodeIgniter Model class. This will completely change the way the Model layer works in the framework. The objects you create will have database persistence and also will able to have database relationships with other objects.

Follow these steps:

  1. Create folder: application/plugins
  2. Create folder: application/plugins/doctrine
  3. Download Doctrine (1.2 as of this article)
  4. Copy the “lib” folder from Doctrine to: “application/plugins/doctrine”
  5. Create “application/plugins/doctrine_pi.php”
// system/application/plugins/doctrine_pi.php // load Doctrine library require_once APPPATH.'/plugins/doctrine/lib/Doctrine.php'; // load database configuration from CodeIgniter require_once APPPATH.'/config/database.php'; // this will allow Doctrine to load Model classes automatically spl_autoload_register(array('Doctrine', 'autoload')); // we load our database connections into Doctrine_Manager // this loop allows us to use multiple connections later on foreach ($db as $connection_name => $db_values) { // first we must convert to dsn format $dsn = $db[$connection_name]['dbdriver'] . '://' . $db[$connection_name]['username'] . ':' . $db[$connection_name]['password']. '@' . $db[$connection_name]['hostname'] . '/' . $db[$connection_name]['database']; Doctrine_Manager::connection($dsn,$connection_name); } // CodeIgniter's Model class needs to be loaded require_once BASEPATH.'/libraries/Model.php'; // telling Doctrine where our models are located Doctrine::loadModels(APPPATH.'/models');

Next, edit “application/config/autoload.php” to autoload this Doctrine plugin

$autoload['plugin'] = array('doctrine');

Also make sure you have your database configuration in “application/config/database.php”.

That is all. Now you can create Doctrine Models within your CodeIgniter application. Read my tutorials on this subject for more information.

6. Running Multiple SitesThe Goal

This hack will make it possible for you to run multiple sites from a single install of CodeIgniter. Each website will have its own application folder, but they will all share the same system folder.

The Hack

Install CodeIgniter anywhere on the server. It doesn’t need to be under a website folder. Then take the application folder out of the system folder. And make additional copies of it, as seen in the image above, for every website you want to run. You can place those application folders anywhere, like under each separate website folders.

Now copy the index.php file to the root of each website folder, and edit it as follows:

At line 26, put the full path to the system folder:

$system_folder = dirname(__FILE__) . '../codeigniter/system';

At line 43, put the full path to the application folder:

$application_folder = dirname(__FILE__) . '../application_site1';

Now you can have independent websites using separate application folders, but sharing the same system folder.

There is a similar implementation in the CodeIgniter User Guide you can read also.

7. Allowing All File Types for UploadsThe Goal

When using the Upload library in CodeIgniter, you must specify which file types are allowed.

$this->load->library('upload'); $this->upload->set_allowed_types('jpg|jpeg|gif|png|zip');

If you do not specify any file types, you will receive an error message from CodeIgniter: “You have not specified any allowed file types.”

So, by default, there is no way to allow all file types to be uploaded. We need to do small hack to get around this limitation. After that we will be able to allow all file types by setting it to ‘*’.

$this->load->library('upload'); $this->upload->set_allowed_types('*'); The Hack

For this hack we are going to modify the Upload class behavior.

Create file: application/libraries/My_Upload.php

class MY_Upload extends CI_Upload { function is_allowed_filetype() { if (count($this->allowed_types) == 0 OR ! is_array($this->allowed_types)) { $this->set_error('upload_no_file_types'); return FALSE; } if (in_array("*", $this->allowed_types)) { return TRUE; } $image_types = array('gif', 'jpg', 'jpeg', 'png', 'jpe'); foreach ($this->allowed_types as $val) { $mime = $this->mimes_types(strtolower($val)); // Images get some additional checks if (in_array($val, $image_types)) { if (getimagesize($this->file_temp) === FALSE) { return FALSE; } } if (is_array($mime)) { if (in_array($this->file_type, $mime, TRUE)) { return TRUE; } } else { if ($mime == $this->file_type) { return TRUE; } } } return FALSE; } } Conclusion

I hope some of these are useful to you. If not, they are still interesting to know and can help you learn more about the internal workings of a framework and some of the core PHP language features.

If you know any other cool hacks or modifications, let us know in the comments. Thank you!

Ready to take your skills to the next level, and start profiting from your scripts and components? Check out our sister marketplace, CodeCanyon.

CodeCanyon

digg_url = "post permalink (not digg url)";


Categories: How-to's & Tutorials

Digging Into Wordpress – in Print!

CSS-Tricks - Tue, 2009-12-29 05:12

The print version of Digging Into WordPress the book is now available here. It’s been an awesome journey, taking this from idea to final product and having 100% control over everything. That is a story for another post, for now let’s take a look at the book!

How much is it?

It’s $67, and automatically comes with the PDF version (more on that later). We realize that is toward the high end of the scale. There are a variety of reasons for this.

  • This is 400 pages of full-color printing.
  • We aren’t printing in massive quantities. In printing, the less you run the more you pay per-piece.
  • It has been a ton of work getting this all together. Aside from some very helpful folks who helped us find errors after the PDF went out, every inch of it has been done by myself, Jeff, or our friends and family. We don’t have the efficiency of a company that does this day in and day out.

Believe me, our margins are fairly tight on this.

We are shipping internationally. But as you might suspect, it costs a good bit more. I asked the post office for the cheapest possible way to get it overseas, and places like the United Kingdom and Germany were around $28, so that’s what we need to charge.

In the United States, shipping is closer to $5.00. Illinois sales are subject to sales tax; international sales get VAT.

Already have the PDF?

First things first, as promised, if you already purchased the PDF and want a print copy of the book, you are entitled to a discount. Here is what you do:

  1. Email us at sales@digwp.com (or use the contact form). Either forward us your receipt or just provide us with the full name and email used when buying the book.
  2. We’ll send you a discount code you can use during checkout.
  3. The code will be valued at whatever you paid for the book plus $5 off. So if you paid the full $27 for the PDF, we’ll send you a code worth $32. So you’ll end up paying $35. This helps us keep our margins intact and not lose money on these print books.
Features

My favorite… it’s spiral bound! For once, a tech book that will lay flat next to your keyboard.

Every page is full color!

And of course the slew of stuff we talk about. Perhaps most importantly, free updates to the PDF version for life. We already have plans in the works for updates. As a buyer, you’ll be emailed a free download link when those new versions go out.

How it affects the affiliate program

Our affiliate program for the book pays (a rocking) 50% for sales of the PDF. That part doesn’t change. But we obviously can’t afford to pay 50% for book sales. For book sales we are going to give 20%. What that works out to is basically the same exact payout for either purchase: $13.50 for a PDF sale, $13.40 for a book sale. Pretty damn good if you ask me. I’ve literally PayPal’d out thousands of dollars to affiliates.

Much love

Look at my family hard at work!

… and in case you missed it at the top, it’s available here.

Categories: How-to's & Tutorials

Win a Three-month Plus Membership – 12 for 2010, Day Nine

NETTUTS - Mon, 2009-12-28 23:00

As part of the ninth day of 12 for 2010, we’re giving away a free 3-month membership to Plus;. Entries are open to everybody – Plus members and non-Plus members alike. Entering only takes a few seconds, since all you’ve got to do is leave a comment. Too easy.

Meet Kaleb Aylsworth Kaleb Aylsworth

Kaleb is the Cgtuts+ editor and a new dad – both challenging jobs, but nothing scares Kaleb. That’s probably because he spends most of his days modeling creepy and atmospheric environments for blockbuster video games, then shares his expertise on Cgtuts+. And we’re glad of it!

Win a 3-Month Membership to Plus

3-month Plus Membership

Joining Plus; gives you access to an exclusive members area chocked-full of our best tutorials, plus hundreds of source files. You get the best teaching on Photoshop and graphic design, motion graphics, vector illustration, web development and audio production and mixing.

For the chance to win the 3-month Plus membership, all you need to do is comment. Make sure to include your correct email address with your comment so that we can contact you. This giveaway is open worldwide, but make sure to get your comment in before midnight on New Year’s Eve, Pacific Eastern Standard Time.

To increase your chances of winning, make sure to enter again once on each Tuts+ site. There are 8 giveaways going on right now, each with another chance to win.

If you don’t want to leave it to chance, you can begin your Plus membership for only $9.

Please note: Envato staff and people who have written more than two tutorials/articles for a Tuts+ site are not eligible to enter.


Categories: How-to's & Tutorials
Syndicate content