Current Feed Content
-
Building a Website with PHP, MySQL and jQuery Mobile, Part 2
Posted: Tue, 23 Aug 2011 12:34:01 +0000
This is the second part of a two-part tutorial, in which we use PHP, MySQL and jQuery mobile to build a simple computer web store. In the previous part we created the models and the controllers, and this time we will be writing our views.
jQuery mobile
First, lets say a few words about the library we will be using. jQuery mobile is a user interface library that sits on top of jQuery and provides support for a wide array of devices in the form of ready to use widgets and a touch-friendly development environment. It is still in beta, but upgrading to the official 1.0 release will be as simple as swapping a CDN URL.
The library is built around progressive enhancement. You, as the developer, only need to concern yourself with outputting the correct HTML, and the library will take care of the rest. jQuery mobile makes use of the HTML5 data- attributes and by adding them, you instruct the library how it should render your markup.
In this tutorial we will be using some of the interface components that this library gives us – lists, header and footer bars and buttons, all of which are defined using the data-role attributes, which you will see in use in the next section.
Rendering Views
The views are PHP files, or templates, that generate HTML code. They are printed by the controllers using the render() helper function. We have 7 views in use for this website – _category.php, _product.php, _header.php, _footer.php, category.php, home.php and error.php, which are discussed later on. First, here is render() function:
includes/helpers.php
/* These are helper functions */ function render($template,$vars = array()){ // This function takes the name of a template and // a list of variables, and renders it. // This will create variables from the array: extract($vars); // It can also take an array of objects // instead of a template name. if(is_array($template)){ // If an array was passed, it will loop // through it, and include a partial view foreach($template as $k){ // This will create a local variable // with the name of the object's class $cl = strtolower(get_class($k)); $$cl = $k; include "views/_$cl.php"; } } else { include "views/$template.php"; } }The first argument of this function is the name of the template file in the views/ folder (without the .php extension). The next is an array with arguments. These are extracted and form real variables which you can use in your template.
There is one more way this function can be called – instead of a template name, you can pass an array with objects. If you recall from last time, this is what is returned by using the find() method. So basically if you pass the result of
Category::find()to render, the function will loop through the array, get the class names of the objects inside it, and automatically include the _category.php template for each one. Some frameworks (Rails for example) call these partials.The Views
Lets start off with the first view – the header. You can see that this template is simply the top part of a regular HTML5 page with interleaved PHP code. This view is used in home.php and category.php to promote code reuse.
includes/views/_header.php
<!DOCTYPE html> <html> <head> <title><?php echo formatTitle($title)?></title> <meta name="viewport" content="width=device-width, initial-scale=1" /> <link rel="stylesheet" href="http://code.jquery.com/mobile/1.0b2/jquery.mobile-1.0b2.min.css" /> <link rel="stylesheet" href="assets/css/styles.css" /> <script type="text/javascript" src="http://code.jquery.com/jquery-1.6.2.min.js"></script> <script type="text/javascript" src="http://code.jquery.com/mobile/1.0b2/jquery.mobile-1.0b2.min.js"></script> </head> <body> <div data-role="page"> <div data-role="header" data-theme="b"> <a href="./" data-icon="home" data-iconpos="notext" data-transition="fade">Home</a> <h1><?php echo $title?></h1> </div> <div data-role="content">In the head section we include jQuery and jQuery mobile from jQuery’s CDN, and two stylesheets. The body section is where it gets interesting. We define a div with the data-role=”page” attribute. This, along with the data-role=”content” div, are the two elements required by the library to be present on every page.
The data-role=”header” div is transformed into a header bar. The data-theme attribute chooses one of the 5 standard themes. Inside it, we have a link that is assigned a home icon, and has its text hidden. jQuery Mobile comes with a set of icons you can choose from.
The closing tags (and the footer bar) reside in the _footer.php view:
includes/views/_footer.php
</div> <div data-role="footer" id="pageFooter"> <h4><?php echo $GLOBALS['defaultFooter']?></h4> </div> </div> </body> </html>
Nothing too fancy here. We only have a div with the data-role=”footer” attribute, and inside it we print the globally accessible $defaultFooter variable, defined in includes/config.php.
Neither of the above views are printed directly by our controllers. They are instead used by category.php and home.php:
includes/views/home.php
<?php render('_header',array('title'=>$title))?> <p>Welcome! This is a demo for a ...</p> <p>Remember to try browsing this ...</p> <ul data-role="listview" data-inset="true" data-theme="c" data-dividertheme="b"> <li data-role="list-divider">Choose a product category</li> <?php render($content) ?> </ul> <?php render('_footer')?>If you may recall, the home view was rendered in the home controller. There we passed an array with all the categories, which is available here as
$content. So what this view does, is to print the header, and footer, define a jQuery mobile listview (using the data-role attribute), and generate the markup of the categories passed by the controller, using this template (used implicitly by render()):index.php/views/_category.php
<li <?php echo ($active == $category->id ? 'data-theme="a"' : '') ?>> <a href="?category=<?php echo $category->id?>" data-transition="fade"> <?php echo $category->name ?> <span><?php echo $category->contains?></span></a> </li>Notice that we have a
$categoryPHP variable that points to the actual object this view is being generated for. This is done in lines 24/25 of the render function. When the user clicks one of the links generated by the above fragment, he will be taken to the /?category=someid url, which will show the category.php view, given below.<?php render('_header',array('title'=>$title))?> <div> <ul data-role="listview" data-inset="true" data-theme="c" data-dividertheme="c"> <?php render($products) ?> </ul> </div> <div> <ul data-role="listview" data-inset="true" data-theme="c" data-dividertheme="b"> <li data-role="list-divider">Categories</li> <?php render($categories,array('active'=>$_GET['category'])) ?> </ul> </div> <?php render('_footer')?>This file also uses the header, footer and _category views, but it also presents a column with products (passed by the category controller). The products are rendered using the _product.php partial:
<li> <img src="assets/img/<?php echo $product->id ?>.jpg" alt="<?php echo $product->name ?>" /> <?php echo $product->name ?> <i><?php echo $product->manufacturer?></i> <b>$<?php echo $product->price?></b> </li>
As we have an image as the first child of the li elements, it is automatically displayed as an 80px thumbnail by jQuery mobile.
One of the advantages to using the interface components defined in the library is that they are automatically scaled to the width of the device. But what about the columns we defined above? We will need to style them ourselves with some CSS3 magic:
assets/css/styles.css
media all and (min-width: 650px){ .rightColumn{ width:56%; float:right; margin-left:4%; } .leftColumn{ width:40%; float:left; } } .product i{ display:block; font-size:0.8em; font-weight:normal; font-style:normal; } .product img{ margin:10px; } .product b{ position: absolute; right: 15px; top: 15px; font-size: 0.9em; } .product{ height: 80px; }Using a media query, we tell the browser that if the view area is wider than 650px, it should display the columns side by side. If it is not (or if the browser does not support media queries) they will be displayed one on top of the other, the regular “block” behavior.
We’re done!
In the second and last part of this tutorial, we wrote our views to leverage the wonderful features of jQuery mobile. With minimal effort on our part, we were able to describe the roles of our markup and easily create a fully fledged mobile website.
-
Building a Website with PHP, MySQL and jQuery Mobile, Part 1
Posted: Fri, 19 Aug 2011 18:58:17 +0000
In this two-part tutorial, we will be building a simple website with PHP and MySQL, using the Model-View-Controller (MVC) pattern. Finally, with the help of the jQuery Mobile framework, we will turn it into a touch-friendly mobile website, that works on any device and screen size.
In this first part, we concentrate on the backend, discussing the database and MVC organization. In part two, we are writing the views and integrating jQuery Mobile.
The File Structure
As we will be implementing the MVC pattern (in effect writing a simple micro-framework), it is natural to split our site structure into different folders for the models, views and controllers. Don’t let the number of files scare you – although we are using a lot of files, the code is concise and easy to follow.
The Database Schema
Our simple application operates with two types of resources – categories and products. These are given their own tables – jqm_categories, and jqm_products. Each product has a category field, which assigns it to a category.
The categories table has an ID field, a name and a contains column, which shows how many products there are in each category.
The product table has a name, manufacturer, price and a category field. The latter holds the ID of the category the product is added to.
You can find the SQL code to create these tables in tables.sql in the download archive. Execute it in the SQL tab of phpMyAdmin to have a working copy of this database. Remember to also fill in your MySQL login details in config.php.
The Models
The models in our application will handle the communication with the database. We have two types of resources in our application – products and categories. The models will expose an easy to use method –
find()which will query the database behind the scenes and return an array with objects.Before starting work on the models, we will need to establish a database connection. I am using the PHP PDO class, which means that it would be easy to use a different database than MySQL, if you need to.
includes/connect.php
/* This file creates a new MySQL connection using the PDO class. The login details are taken from includes/config.php. */ try { $db = new PDO( "mysql:host=$db_host;dbname=$db_name;charset=UTF-8", $db_user, $db_pass ); $db->query("SET NAMES 'utf8'"); $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); } catch(PDOException $e) { error_log($e->getMessage()); die("A database error was encountered"); }This will put the $db connection object in the global scope, which we will use in our models. You can see them below.
includes/models/category.model.php
class Category{ /* The find static method selects categories from the database and returns them as an array of Category objects. */ public static function find($arr = array()){ global $db; if(empty($arr)){ $st = $db->prepare("SELECT * FROM jqm_categories"); } else if($arr['id']){ $st = $db->prepare("SELECT * FROM jqm_categories WHERE id=:id"); } else{ throw new Exception("Unsupported property!"); } // This will execute the query, binding the $arr values as query parameters $st->execute($arr); // Returns an array of Category objects: return $st->fetchAll(PDO::FETCH_CLASS, "Category"); } }Both models are simple class definitions with a single static method – find(). In the fragment above, this method takes an optional array as a parameter and executes different queries as prepared statements.
In the return declaration, we are using the fetchAll method passing it the PDO::FETCH_CLASS constant. What this does, is to loop though all the result rows, and create a new object of the Category class. The columns of each row will be added as public properties to the object.
This is also the case with the Product model:
includes/models/product.model.php
class Product{ // The find static method returns an array // with Product objects from the database. public static function find($arr){ global $db; if($arr['id']){ $st = $db->prepare("SELECT * FROM jqm_products WHERE id=:id"); } else if($arr['category']){ $st = $db->prepare("SELECT * FROM jqm_products WHERE category = :category"); } else{ throw new Exception("Unsupported property!"); } $st->execute($arr); return $st->fetchAll(PDO::FETCH_CLASS, "Product"); } }The return values of both find methods are arrays with instances of the class. We could possibly return an array of generic objects (or an array of arrays) in the find method, but creating specific instances will allow us to automatically style each object using the appropriate template in the views folder (the ones that start with an underscore). We will talk again about this in the next part of the tutorial.
There, now that we have our two models, lets move on with the controllers.
The controllers
The controllers use the find() methods of the models to fetch data, and render the appropriate views. We have two controllers in our application – one for the home page, and another one for the category pages.
includes/controllers/home.controller.php
/* This controller renders the home page */ class HomeController{ public function handleRequest(){ // Select all the categories: $content = Category::find(); render('home',array( 'title' => 'Welcome to our computer store', 'content' => $content )); } }Each controller defines a handleRequest() method. This method is called when a specific URL is visited. We will return to this in a second, when we discuss index.php.
In the case with the HomeController, handleRequest() just selects all the categories using the model’s find() method, and renders the home view (includes/views/home.php) using our render helper function (includes/helpers.php), passing a title and the selected categories. Things are a bit more complex in CategoryController:
includes/controllers/category.controller.php
/* This controller renders the category pages */ class CategoryController{ public function handleRequest(){ $cat = Category::find(array('id'=>$_GET['category'])); if(empty($cat)){ throw new Exception("There is no such category!"); } // Fetch all the categories: $categories = Category::find(); // Fetch all the products in this category: $products = Product::find(array('category'=>$_GET['category'])); // $categories and $products are both arrays with objects render('category',array( 'title' => 'Browsing '.$cat[0]->name, 'categories' => $categories, 'products' => $products )); } }The first thing this controller does, is to select the category by id (it is passed as part of the URL). If everything goes to plan, it fetches a list of categories, and a list of products associated with the current one. Finally, the category view is rendered.
Now lets see how all of these work together, by inspecting index.php:
index.php
/* This is the index file of our simple website. It routes requests to the appropriate controllers */ require_once "includes/main.php"; try { if($_GET['category']){ $c = new CategoryController(); } else if(empty($_GET)){ $c = new HomeController(); } else throw new Exception('Wrong page!'); $c->handleRequest(); } catch(Exception $e) { // Display the error page using the "render()" helper function: render('error',array('message'=>$e->getMessage())); }This is the first file that is called on a new request. Depending on the $_GET parameters, it creates a new controller object and executes its handleRequest() method. If something goes wrong anywhere in the application, an exception will be generated which will find its way to the catch clause, and then in the error template.
One more thing that is worth noting, is the very first line of this file, where we require main.php. You can see it below:
main.php
/* This is the main include file. It is only used in index.php and keeps it much cleaner. */ require_once "includes/config.php"; require_once "includes/connect.php"; require_once "includes/helpers.php"; require_once "includes/models/product.model.php"; require_once "includes/models/category.model.php"; require_once "includes/controllers/home.controller.php"; require_once "includes/controllers/category.controller.php"; // This will allow the browser to cache the pages of the store. header('Cache-Control: max-age=3600, public'); header('Pragma: cache'); header("Last-Modified: ".gmdate("D, d M Y H:i:s",time())." GMT"); header("Expires: ".gmdate("D, d M Y H:i:s",time()+3600)." GMT");This file holds the require_once declarations for all the models, controllers and helper files. It also defines a few headers to enable caching in the browser (PHP disables caching by default), which speeds up the performance of the jQuery mobile framework.
Continue to Part 2
With this the first part of the tutorial is complete! Continue to part 2, where we will be writing the views and incorporate jQuery Mobile. Feel free to share your thoughts and suggestions in the comment section below.
-
Display your Favorite Tweets using PHP and jQuery
Posted: Fri, 05 Aug 2011 14:22:01 +0000
If you have a twitter account, you oftentimes find yourself looking for a way to display your latest tweets on your website or blog. This is pretty much a solved problem. There are jQuery plugins, PHP classes and tutorials that show you how to do this.
However, what happens if you only want to display certain tweets, that you have explicitly marked to show? As minimalistic twitter’s feature set is, it does provide a solution to this problem – favorites.
In this tutorial, we will be writing a PHP class that will fetch, cache, and display your favorite tweets in a beautiful CSS3 interface.
HTML
You can see the markup of the page that we will be using as a foundation below. The #container div will hold the tweets (which we will be generating in the PHP section of the tutorial).
index.php
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Display your Favorite Tweets using PHP and jQuery | Tutorialzine Demo</title> <!-- Our CSS stylesheet file --> <link rel="stylesheet" href="assets/css/styles.css" /> <!--[if lt IE 9]> <script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script> <![endif]--> </head> <body> <div id="container"> <!-- The tweets will go here --> </div> <!-- JavaScript includes --> <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js"></script> <script src="assets/js/jquery.splitlines.js"></script> <script src="assets/js/script.js"></script> </body> </html>We will be using the splitLines plugin, which as its name suggest, will split the tweets into separate divs, one for each line of text. This is necessary as it is the only we can apply padding to the lines individually (as an illustration, view the demo with JS disabled). However, the demo will still keep most of its design without it.
As for the generation of the tweets, we will be creating a PHP class that will handle it for us. We will only need to call its generate method inside the #container div like this:
$tweets->generate(5), which will show the 5 most recent liked tweets. This method will output an unordered list with tweets:Tweet markup
<ul> <li> <p>The text of the tweet goes here</p> <div> <a title="Go to Tutorialzine's twitter page" href="http://twitter.com/Tutorialzine">Tutorialzine</a> <span title="Retweet Count">19</span> <a title="Shared 3 days ago" target="_blank" href="http://twitter.com/Tutorialzine/status/98439169621241856">3 days ago</a> </div> <div></div> </li> <!-- More tweets here .. --> </ul>
The text of the tweet will be held in a paragraph, with additional information available in the .info div. Now lets write the PHP class.
PHP
We will name our class FavoriteTweetsList. It will take a twitter username as a parameter, and expose a number of useful methods for fetching tweets and generating HTML markup.
The class will fetch the favorite tweets json feed, located at http://api.twitter.com/1/favorites/USERNAME.json (see Tutorialzine’s feed as an example). Additionally, it will include caching, so that a request is made only once every three hours, which will speed things up.
FavoriteTweetsList.class.php
class FavoriteTweetsList{ private $username; const cache = "cache_tweets.ser"; public function __construct($username){ $this->username = $username; } /* The get method returns an array of tweet objects */ public function get(){ $cache = self::cache; $tweets = array(); if(file_exists($cache) && time() - filemtime($cache) < 3*60*60){ // Use the cache if it exists and is less than three hours old $tweets = unserialize(file_get_contents($cache)); } else{ // Otherwise rebuild it $tweets = json_decode($this->fetch_feed()); file_put_contents($cache,serialize($tweets)); } if(!$tweets){ $tweets = array(); } return $tweets; } /* The generate method takes an array of tweets and build the markup */ public function generate($limit=10, $className = 'tweetFavList'){ echo "<ul class='$className'>"; // Limiting the number of shown tweets $tweets = array_slice($this->get(),0,$limit); foreach($tweets as $t){ $id = $t->id_str; $text = self::formatTweet($t->text); $time = self::relativeTime($t->created_at); $username = $t->user->screen_name; $retweets = $t->retweet_count; ?> <li> <p><?php echo $text ?></p> <div> <a href="http://twitter.com/<?php echo $username ?>" title="Go to <?php echo $username?>'s twitter page"> <?php echo $username ?></a> <?php if($retweets > 0):?> <span title="Retweet Count"> <?php echo $retweets ?></span> <?php endif;?> <a href="http://twitter.com/<?php echo $username,'/status/',$id?>" class="date" target="_blank" title="Shared <?php echo $time?>"> <?php echo $time?></a> </div> <div></div> </li> <?php } echo "</ul>"; } /* Helper methods and static functions */ private function fetch_feed(){ return file_get_contents("http://api.twitter.com/1/favorites/{$this->username}.json"); } private static function relativeTime($time){ $divisions = array(1,60,60,24,7,4.34,12); $names = array('second','minute','hour','day','week','month','year'); $time = time() - strtotime($time); $name = ""; if($time < 10){ return "just now"; } for($i=0; $i<count($divisions); $i++){ if($time < $divisions[$i]) break; $time = $time/$divisions[$i]; $name = $names[$i]; } $time = round($time); if($time != 1){ $name.= 's'; } return "$time $name ago"; } private static function formatTweet($str){ // Linkifying URLs, mentionds and topics. Notice that // each resultant anchor type has a unique class name. $str = preg_replace( '/((ftp|https?):\/\/([-\w\.]+)+(:\d+)?(\/([\w\/_\.]*(\?\S+)?)?)?)/i', '<a href="$1" target="_blank">$1</a>', $str ); $str = preg_replace( '/(\s|^)@([\w\-]+)/', '$1<a href="http://twitter.com/#!/$2" target="_blank">@$2</a>', $str ); $str = preg_replace( '/(\s|^)#([\w\-]+)/', '$1<a href="http://twitter.com/search?q=%23$2">#$2</a>', $str ); return $str; } }Of the methods above,
generate()is the one that you will most likely be working with directly. It takes the number of tweets to be displayed, and an optional class parameter, that overrides the default class attribute of the unordered list.Now that we have the FavoriteTweetsList class in place, we simply need to instantiate an object, passing it a twitter username, like this:
index.php
require "includes/FavoriteTweetsList.class.php"; $tweets = new FavoriteTweetsList('Tutorialzine');Calling the
$tweets->generate()will show that user’s latest faved tweets.jQuery
As we are using the splitLines jQuery plugin, we already have most of the work done for us. We simply have to loop through the paragraph elements holding the text of the tweets, and call the plugin.
script.js
$(function(){ var width = $('ul.tweetFavList p').outerWidth(); // Looping through the p elements // and calling the splitLines plugin $('ul.tweetFavList p').each(function(){ $(this).addClass('sliced').splitLines({width:width}); }); });This will split the contents of the paragraph into lines, each held in an individual div, which we can style.
CSS
First lets style the unordered list and the paragraph elements.
styles.css – 1
ul.tweetFavList{ margin:0 auto; width:600px; list-style:none; } ul.tweetFavList p{ background-color: #363636; color: #FFFFFF; display: inline; font-size: 28px; line-height: 2.25; padding: 10px; } /* Coloring the links differently */ ul.tweetFavList a.link { color:#aed080;} ul.tweetFavList a.mention { color:#6fc6d9;} ul.tweetFavList a.hash { color:#dd90e9;}If you take a closer look at the formatTweet() static method in the PHP class, you will see that we are adding a class name for each type of hyperlink – a regular link, a mention or a hash, so we can style them differently.
When the page loads, jQuery adds sliced as a class to each paragraph. This class undoes some of the styling applied to the paragraphs by default as a fallback, so we can display the individual lines properly.
styles.css – 2
/* The sliced class is assigned by jQuery */ ul.tweetFavList p.sliced{ background:none; display:block; padding:0; line-height:2; } /* Each div is a line generated by the splitLines plugin */ ul.tweetFavList li p div{ background-color: #363636; box-shadow: 2px 2px 2px rgba(33, 33, 33, 0.5); display: inline-block; margin-bottom: 6px; padding: 0 10px; white-space: nowrap; }Next we will style the colorful information boxes that hold the author username, publish date and retweet count.
styles.css – 3
ul.tweetFavList .info{ overflow: hidden; padding: 15px 0 5px; } /* The colorful info boxes */ ul.tweetFavList .user, ul.tweetFavList .retweet, ul.tweetFavList .date{ float:left; padding:4px 8px; color:#fff !important; text-decoration:none; font-size:11px; box-shadow: 1px 1px 1px rgba(33, 33, 33, 0.3); } ul.tweetFavList .user{ background-color:#6fc6d9; } ul.tweetFavList .retweet{ background-color:#dd90e9; cursor:default; } ul.tweetFavList .date{ background-color:#aed080; }And finally we will style the divider. This is a single div, but thanks to
:before/:afterpseudo elements, we add two more circles to the left and to the right of it.styles.css – 4
/* Styling the dotted divider */ ul.tweetFavList .divider, ul.tweetFavList .divider:before, ul.tweetFavList .divider:after{ background-color: #777777; border-radius: 50% 50% 50% 50%; height: 12px; margin: 60px auto 80px; width: 12px; position:relative; box-shadow: 1px 1px 0 rgba(255, 255, 255, 0.5); } ul.tweetFavList .divider:before, ul.tweetFavList .divider:after{ margin:0; position:absolute; content:''; top:0; left:-40px; } ul.tweetFavList .divider:after{ left:auto; right:-40px; } ul.tweetFavList li:last-child .divider{ display:none; }With this our favorited tweet list is complete!
Done
This example can be used to build a simple testimonials section, or to highlight tweets that you think your readers would find worthy. You can even see it implemented on the sidebar of this very site.
-
Bubble Slideshow Effect with jQuery
Posted: Thu, 28 Jul 2011 19:41:06 +0000
Today we will be building a jQuery-powered bubble animation effect. It will be a great way to present a set of images on your website as a interesting slideshow. And as the code will be completely modular, you will be able to easily use it and modify it.
The HTML
The slideshow effect we will be creating today, will take the form of an easy to use jQuery plugin. As most of the work is done by the plugin, there isn’t much to do in this section. However, to use the plugin you need to add an unordered list on your page. The individual slides of the slideshow will be added as LI elements.
index.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Bubble Slideshow Effect with jQuery | Tutorialzine Demo</title> <!-- Our CSS stylesheet file --> <link rel="stylesheet" href="assets/css/styles.css" /> <!-- The plugin stylehseet --> <link rel="stylesheet" href="assets/jquery.bubbleSlideshow/jquery.bubbleSlideshow.css" /> <!--[if lt IE 9]> <script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script> <![endif]--> </head> <body> <!-- The bubble slideshow holder --> <ul id="slideShow"></ul> <!-- JavaScript includes --> <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js"></script> <script src="assets/jquery.bubbleSlideshow/bgpos.js"></script> <script src="assets/jquery.bubbleSlideshow/jquery.bubbleSlideshow.js"></script> <script src="assets/js/script.js"></script> </body> </html>To be able to use the plugin, you will need to include jquery.bubbleSlideshow.css in the head of the page, bgpos.js and jquery.bubbleSlideshow.js before the closing body tag. bgpos.js is a jQuery CSS hooks plugin that will allow us to animate the background-position property (needed in the bubble animation), and jquery.bubbleSlideshow.js holds the code we will be writing today. Also remember to include the jQuery library as well.
You can see a simple explanation of the bubble effect below.
JavaScript and jQuery
First we will write a JavaScript class named Bubble. Every bubble in the slideshow is going to be an object of this class. It will have properties such as top and left (position offsets), size (diameter of the circle) and an elem property, which is a jQuery object containing the actual div. We will use this property later when we animate the bubble in the
flyFrom()method.jquery.bubbleSlideshow.js
// This is the Bubble class. It takes left and top // coordinates, size (diameter) and a image URL function Bubble( left, top, size, imgURL ){ this.top = top; this.left = left; this.size = size; // This places the center of the // circles on the specified position: top -= size/2; left-= size/2; this.elem = $('<div>',{ 'class':'bubble', 'css' : { 'width' : size, 'height' : size, 'top' : top, 'left' : left, 'background-position': (-left)+'px '+(-top)+'px', 'background-image': 'url('+imgURL+')' } }); } // The fly from method takes a starting position, time, // and a callback function, executed when the animation finishes. Bubble.prototype.flyFrom = function( startX, startY, time, callBack ){ time = time || 250; callBack = callBack || function(){}; startX -= this.size/2; startY -= this.size/2; // Offsetting the element this.elem.css({ 'display' : 'block', 'backgroundPositionX' : -startX, 'backgroundPositionY' : -startY, 'left' : startX, 'top' : startY }); // Animating it to where it should be this.elem.animate({ 'backgroundPositionX' : -this.left, 'backgroundPositionY' : -this.top, 'left' : this.left, 'top' : this.top }, time, 'easeOutCirc', callBack ); }; // Helper function for generating random // values in the [min,max] range function rand( min, max ){ return Math.floor( Math.random()*((max+1)-min) + min); }The
flyFrom()method takes a set of coordinates, that determine the position the bubble flies in from. It still ends up in the position that you specify when creating it. This method is defined on the Bubble function’s prototype, which automatically makes it available to all its instances. This is a more effective approach, as only one copy of this method exists at a time, instead of a copy of this method for each object. Also, notice therand()function defined at the bottom of the fragment. It mimics the PHP function of the same name and is used throughout the plugin code.Now that we have the class in place, lets write a function that creates an array with bubble objects, appends them to a new LI element, and animates them. The function takes three parameters:
- stage, which is a jQuery object that holds a UL element. This will hold the slideshow, with each slide being an individual LI;
- imgURL is the URL of the image that will be shown in the bubbles;
- func is a callback function that will be called once all bubble animations are complete. This is used to switch the slides and destroy the bubbles, as they will not be needed after the transition to the new slide is complete;
As you guessed, for every slide transition, a new random set of bubbles is created, and destroyed after the next slide is made visible.
jquery.bubbleSlideshow.js
function showBubbles( stage, imgURL, func ){ // This function appends a new LI element to the UL // and uses it to hold and animate the bubbles. var i = 0, bubbles = [], totalBubbles = 75, stageWidth = stage.outerWidth(), stageHeight = stage.outerHeight(), emptyFunc = function(){}; // This li holds the bubbles var li = $('<li>').appendTo(stage); // This function is passed to the flyFrom method call: var callBack = function(){ // Waiting for the func function to // finish and removing the li. $.when(func()).then(function(){ li.remove(); }); }; for( i=0; i<totalBubbles; i++ ){ var x = rand(0, stageWidth), y = rand(0,stageHeight), size = rand(30,150); var bubble = new Bubble( x, y, size, imgURL ); li.append(bubble.elem); bubbles.push(bubble); } // Sorting the bubbles so that the // bubbles closest to the top border of // the image come first: bubbles = bubbles.sort(function( b1, b2 ){ return b1.top+b1.size/2 > b2.top+b2.size/2; }); // Looping through all the bubbles, // and triggering their flyFrom methods for( i=0; i<bubbles.length; i++){ (function( bubble, i ){ setTimeout(function(){ bubble.flyFrom( stageWidth/2, stageHeight+200, 250, (i == bubbles.length-1) ? callBack : emptyFunc ); // This Math.floor schedules five bubbles // to be animated simultaneously }, Math.floor(i/5)*100); })( bubbles[i], i ); } }Great! So now we have a function that creates a set of bubbles in a new LI element and animates them. But these are only functions, they are not a plugin yet, so we will have to work on that. Also we are still missing the slides themselves. Lets write the missing pieces:
jquery.bubbleSlideshow.js
$.fn.bubbleSlideshow = function(photos){ if(!$.isArray(photos)){ throw new Error("You need to pass an array of photo URLs as a parameter!"); } photos = photos.reverse(); var ul = this.addClass('bubbleSlideshow'); $.each(photos,function(){ ul.append('<li><img src="'+this+'" /></li>'); }); // These methods are available externally and // can be used to control the bubble slideshow ul.showNext = function(){ showNext(ul); }; ul.showPrev = function(){ showPrev(ul); }; ul.autoAdvance = function(timeout){ timeout = timeout || 6000; autoAdvance(ul,timeout); }; ul.stopAutoAdvance = function(){ stopAutoAdvance(ul); }; return ul; };The code above defines a new plugin called
bubbleSlideshow(). It should be called on a UL element and takes an array of photo URLs as its parameter. These are added to the UL.Inside its body, the plugin creates a new LI element for each of the photos in the array, and adds showNext, showPrev, autoAdvance and stopAutoAdvance methods to the UL. These wrap around local functions with the same names, which you can see below:
jquery.bubbleSlideshow.js
function autoAdvance(stage,timeout){ stage.data('timeout',setTimeout(function(){ showNext(stage); autoAdvance(stage,timeout); },timeout)); } function stopAutoAdvance(stage){ clearTimeout(stage.data('timeout')); } function showNext(stage){ showFrame(stage, stage.find('li.bubbleImageFrame').first()); } function showPrev(stage){ showFrame(stage, stage.find('li.bubbleImageFrame').last().prev()); } function showFrame(stage, frame ){ // This function shows a frame, // passed as a jQuery object if(stage.data('working')){ // Prevents starting more than // one animation at a time: return false; } stage.data('working',true); var frame = frame.hide().detach(); // Using the showBubbles function, defined below. // The frame is showed after the bubble animation is over. showBubbles( stage, frame.find('img').attr('src'), function(){ stage.append(frame); stage.data('working',false); // This returns a jQuery Promise object. return frame.fadeIn('slow'); }); }I used “local” to describe these functions, because they are not available from outside the plugin. The showNext and showPrev functions above both call showFrame, passing it the UL and the LI slide that is to be shown. showFrame makes sure that there is only one animation running at a time, and calls the showBubbles function we already wrote.
The callback function that is passed along with the method call, displays the slide you want to show above all the others by appending it last in the UL (the slides are absolutely positioned, which means that the last element in the UL is shown on top). This function is called once the bubble animation completes.
Here is how you initialize the bubble slideshow:
script.js
$(function(){ var photos = [ 'http://farm6.static.flickr.com/5230/5822520546_dd2b6d7e24_z.jpg', 'http://farm5.static.flickr.com/4014/4341260799_b466a1dfe4_z.jpg', 'http://farm6.static.flickr.com/5138/5542165153_86e782382e_z.jpg', 'http://farm5.static.flickr.com/4040/4305139726_829be74e29_z.jpg', 'http://farm4.static.flickr.com/3071/5713923079_60f53b383f_z.jpg', 'http://farm5.static.flickr.com/4108/5047301420_621d8a7912_z.jpg' ]; var slideshow = $('#slideShow').bubbleSlideshow(photos); $(window).load(function(){ slideshow.autoAdvance(5000); }); // Other valid method calls: // slideshow.showNext(); // slideshow.showPrev(); // slideshow.stopAutoAdvance(); });All that is left is to define a few CSS rules that add properties such positioning, overflows and background-positions:
jquery.bubbleSlideshow.css
ul.bubbleSlideshow{ position:relative; list-style:none; overflow:hidden; } .bubbleSlideshow li{ position:absolute; top:0; left:0; } .bubbleSlideshow li img{ display:block; } .bubbleSlideshow li div.bubble{ -moz-border-radius:50%; -webkit-border-raidus:50%; border-radius:50%; background-repeat:no-repeat; display:none; position:absolute; }With this the bubble effect slideshow is complete!
Final words
The effect we made today may not be limited only to slideshows. It can be used to build unique website backgrounds, headers and presentations. The plugin is built to automatically resize to fit the UL, so you can easily change its size by tweaking a few CSS properties.
-
Having Fun With CSS3: Spinning Newspapers
Posted: Thu, 21 Jul 2011 18:11:40 +0000
Imagine a cop drama taking place in the 1930s. After a streak of bank heists, a young detective is given the case of his life. He accepts the challenge, and after grueling months of hard work and life-threatening situations, he manages to bring the bad guys to justice.
What follows is a classical device used by film makers of the period – newspapers flashing and spinning towards the camera, praising the heroic feats of our protagonist.
So lets have some fun and build this classical scene using the CSS3 animations capabilities of the new versions of Firefox, Chrome and Safari, picking useful techniques along the way.
The Idea
Using JavaScript, we will load a sliced up version of the newspaper (slices are independently encoded as PNG or JPG for smaller filesize), and combine them in a single canvas element. We will also load a custom font from Google WebFonts, which we use to write the article title to the canvas.
We also define a simple CSS3 keyframe animation, which uses transformations such as
scale()androtate()to animate the canvas elements. Appending the canvas to the page triggers the animation, which means we don’t need to write a single line of JavaScript for the effect itself.Currently, CSS3 keyframe animations are supported by Firefox, Safari and Chrome, so if you are using a recent version of one of these browsers, you will be able to enjoy the demo.
Here are some minor considerations that drove the decisions above:
- The image of the newspaper, encoded as PNG, weighs at over 250kb. Slicing it into independently encoded slices saves 200kb, as the center part does need transparency and is encoded as JPEG;
- Rotating a bunch of DOM elements is slower than a single canvas element. Also, rotated text in the browser generally does not look very good, as letters may lose their anti-aliasing (see a simple experiment here; it is most pronounced in Firefox). Painting the text and the newspaper background to a
canvaselement solves both of these problems;
The HTML
The markup of the page is minimal – everything is done using jQuery, so we only need to include our JS source files and stylesheets.
index.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Spinning Newspaper Effect | Tutorialzine Demo</title> <!-- Our CSS stylesheet file --> <link rel="stylesheet" href="assets/css/styles.css" /> <!-- Embedding the Anton font from Google Webfonts --> <link rel="stylesheet" href="http://fonts.googleapis.com/css?family=Anton&v2" /> <!--[if lt IE 9]> <script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script> <![endif]--> </head> <body> <h3 id="fin">That is all</h3> <!-- This div uses the "Anton" font, preloading it for the canvas element --> <div id="fontPreload">.</div> <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js"></script> <script src="assets/js/script.js"></script> </body> </html>In the head section, we include our styles.css file (discussed below) and a stylsheet, which embeds the Anton font from Google WebFonts. Near the end of the file, we include version 1.6.2 of the jQuery library and our script.js (discussed in detail later on).
The most important piece of markup in the code is also the most unassuming. The
#fontPreloaddiv is crucial for this example. What it does is use the embedded Anton web font. This is required so that browsers properly initializes the font before it is used in the canvas. Without it we would be staring at a blank newspaper cover.The jQuery
As we are using a custom web font, we need to be sure that the font is loaded before we use it to generate the newspaper titles. This is why we are binding a callback to the
$(window).load()event, which is called once everything is loaded:assets/js/script.js
$(window).load(function(){ var imgPath = "assets/img/"; // Define 6 paper covers: var papers = [ { line1:"The financial", line2:"chrisis is a hoax!", subtitle:"Economist admits practical joke" }, { line1:"Deeply fried now", line2:"considered healthy", subtitle:"Scientists change the definition of \"Healthy\"" }, { line1:"Apple announces", line2:"the new iphone 9", subtitle:"5, 6, 7 and 8 deemed \"not hip enough\"" }, { line1:"The world did end", line2:"on may 21st!", subtitle:"Priest argues we are actually dead" }, { line1:"France imposes an", line2:"internet kitten tax", subtitle:"Every cat picture on the internet will cost 3 €" }, { line1:"Thank you &", line2:"goodbye", subtitle:"The Zine Weekly takes its farewell" } ]; // Check whether canvas and CSS3 animations are supported: if(!$.support.canvas){ $('#fin').html('Sorry, your browser does not<br />support <canvas>').show(); return; } if(!$.support.css3Animation){ $('#fin').html('Sorry, your browser does not<br />support CSS3 Animations').show(); return; } // Use jQuery.Deferred to bind a callback when all // the images that comprise the paper are loaded: $.when( loadImage(imgPath+"paper_top.png"), loadImage(imgPath+"paper_left.png"), loadImage(imgPath+"paper_center.jpg"), loadImage(imgPath+"paper_right.png"), loadImage(imgPath+"paper_bottom.png") ).then(function( imgTop, imgLeft, imgCenter, imgRight, imgBottom ){ // Loop through the paper covers and // create a new canvas for each one: $.each(papers,function(i){ var canvas = document.createElement("canvas"), c = canvas.getContext("2d"); canvas.width = 717; canvas.height = 526; // Drawing the paper background slices: c.drawImage( imgTop, 0, 0 ); c.drawImage( imgLeft, 0, 12 ); c.drawImage( imgCenter, 14, 12 ); c.drawImage( imgRight, 711, 12 ); c.drawImage( imgBottom, 0, 516 ); // Drawing the text using our helper // function (see at the bottom): drawText( this.line1, this.line2, this.subtitle, c, 358, 250 ); // Appending the element to the page. // This triggers the CSS3 animation. setTimeout(function(){ $("body").append(canvas); },i*5800); }); // "This is all" $('#fin').delay(papers.length*5800).fadeIn(); }); /*------------------------ Helper functions ------------------------*/ // Load an image by URL and resolve a jQuery.Deferred: function loadImage(src){ var def = new $.Deferred(), img = new Image(); img.onload = function(){ // Resolve the deferred. The img parameter // will be available in the then function: def.resolve(img); } // Always set the src attribute // after the onload callback: img.src = src; return def.promise(); } // Draw two lines of text and a subtitle // on the canvas (passed as the c param): function drawText( line1, line2, subtitle, c, x, y ){ c.font = "65px Anton,Calibri"; c.textAlign = "center"; c.fillStyle = "#3e3e3e"; c.fillText(line1.toUpperCase(),x,y); c.fillText(line2.toUpperCase(),x,y+80); c.font = "italic 20px Georgia,serif"; c.fillStyle = "#737373"; c.fillText(subtitle,x,y+120); } }); (function(){ // Adding custom checks for canvas and css3 // animations support, to the jQuery.support object: $.support.canvas = 'getContext' in document.createElement('canvas'); $.support.css3Animation = (function(){ var sp = $('<span>'); return ( sp.css("-webkit-animation") !== undefined || sp.css("-moz-animation") !== undefined || sp.css("animation") !== undefined ); })(); })();To generate the newspapers, we need to first load the five slices that comprise the image. This sounds like the perfect place to use jQuery’s Deferred object, introduced in version 1.5. What it does is to notify us when a number of asynchronous events are completed. As you can see in the code above, we are using it in the
loadImage()function. Thethen()method on line 58 is called only when all five images are loaded.Using jQuery.Deferred is a convenient way to organize our code better. It is also used by jQuery’s internal AJAX and animation methods. To get a better idea of what you can do with it, read through the deferred object documentation.
Inside the
$.eachloop, we create a canvas element for each of the paper covers, and add them to the page after a delay introduced by thesetTimeout()call.Once we have the canvas on the page, we can continue with animating it.
The CSS
Canvas elements are treated as any other element. This means that you can safely style and transform them the same way as you would a regular image.
Once the canvas is added to the page, it will assume the styling you see below:
canvas{ position:fixed; width:717px; height:526px; top:50%; left:50%; margin:-263px 0 0 -358px; opacity:0; /* Configure the animation for Firefox */ -moz-animation-duration:6s; -moz-animation-name:spin; -moz-animation-timing-function:linear; /* Configure it for Chrome and Safari */ -webkit-animation-duration:6s; -webkit-animation-name:spin; -webkit-animation-timing-function:linear; }Nothing out of the ordinary here. We are centering the canvas in the page, and defining the different aspects of the animation like duration, name, and a timing function. “Linear” would make our animation run at a constant speed, instead of getting accelerated as is the case with “ease”, which is used by default.
After this, we have to use the @keyframes declaration to specify how our element would look at different key points during the animation:
@-moz-keyframes spin{ 0%{ opacity:0.2; -moz-transform:scale(0.2) rotate(0deg); } 15%{ opacity:1; margin:-263px 0 0 -358px; -moz-transform:scale(1) rotate(1090deg); } 90%{ opacity:1; top:50%; -moz-transform:scale(1) rotate(1090deg); } 100%{ top:500%; opacity:1; -moz-transform:scale(1) rotate(1090deg); } }When the canvas element is added to the page, we start off from the 0% position above. The element’s opacity is set to 0.2, and it is made 5 times smaller using a
scale()transformation. It is quickly animated to its full size (scale(1)) in and from 15% to 90% of the animation (or about four and a half seconds) it stays fixed on the screen, after which it quickly falls outside the bottom border of the window (top is increased to 500%).It is important to specify the properties that you want to persist in every percentage point of the animation. One example is the
-moz-transform:scale(1) rotate(1090deg)declaration, which is duplicated three times. Without it, Chrome and Safari (but not Firefox) will revert to the default rotation of 0 degrees mid animation.And, as this is still considered an experimental feature by browser vendors, we need to write the same code for webkit:
@-webkit-keyframes spin{ 0%{ opacity:0.2; -webkit-transform:scale(0.2) rotate(0deg); } 15%{ opacity:1; margin:-263px 0 0 -358px; -webkit-transform:scale(1) rotate(1090deg); } 90%{ opacity:1; top:50%; -webkit-transform:scale(1) rotate(1090deg); } 100%{ top:500%; opacity:1; -webkit-transform:scale(1) rotate(1090deg); } }With this our spinning newspaper effect is complete!
Conclusion
As with any cop drama from the 1930s, and the 1930s themselves for that matter, this tutorial has to come to an end. Hope you folks had as much fun following the tutorial as I had writing it. If you have any thoughts or suggestion be sure to share in the comment section. You can also download a PSD with the newspaper template, so you can make your own, below.
-
Creating a PHP and CSS3 Powered About Page
Posted: Tue, 12 Jul 2011 19:46:46 +0000
In this tutorial, we will be creating a simple about page that is powered by PHP, HTML5 and CSS3. It will present your contact information to your visitors, with an option for downloading it as a vCard (useful for importing it in third party applications).
You can use today’s example as a placeholder for your upcoming personal website, or as an actual about page.
HTML
As always, the first step is to write the HTML markup that will be powering our example. This is a simple page the main purpose of which is to present our contact details semantically. This will require adding appropriate meta tags and using the hCard microformat to embed data in the page.
index.php
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta name="description" content="Online info page of <?php echo $profile->fullName()?>. Learn more about me and download a vCard." /> <title>Creating a PHP and CSS Powered About Page | Tutorialzine Demo</title> <!-- Our CSS stylesheet file --> <link rel="stylesheet" href="assets/css/styles.css" /> <!--[if lt IE 9]> <script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script> <![endif]--> </head> <body> <section id="infoPage"> <img src="<?php echo $profile->photoURL()?>" alt="<?php echo $profile->fullName()?>" width="164" height="164" /> <header> <h1><?php echo $profile->fullName()?></h1> <h2><?php echo $profile->tags()?></h2> </header> <p><?php echo nl2br($profile->description())?></p> <a href="<?php echo $profile->facebook()?>">Find me on Facebook</a> <a href="<?php echo $profile->twitter()?>">Follow me on Twitter</a> <ul> <li><?php echo $profile->fullName()?></li> <li><?php echo $profile->company()?></li> <li><?php echo $profile->cellphone()?></li> <li><a href="<?php echo $profile->website()?>"><?php echo $profile->website()?></a></li> </ul> </section> <section id="links"> <a href="?vcard">Download as V-Card</a> <a href="?json">Get as a JSON feed</a> <p>In this tutorial: <a href="http://www.flickr.com/photos/levycarneiro/4144428707/">Self Portrait</a> by <a href="http://www.flickr.com/photos/levycarneiro/">Levy Carneiro Jr</a></p> </section> </body> </html>The
$profilevariable you see above, holds an object of the AboutPage PHP class that we will be writing in a moment. It holds our contact details and provides a number of useful methods for generating JSON and vCard files.As mentioned above, we are using the hCard microformat to embed contact details in the page. This is a simple standard with which we use the class names of regular HTML elements to specify data, easily recognizable by search engines. The hCard holds information about our full name, organization, phone and home page:
<ul> <li><?php echo $profile->fullName()?></li> <li><?php echo $profile->company()?></li> <li><?php echo $profile->cellphone()?></li> <li><a href="<?php echo $profile->website()?>"><?php echo $profile->website()?></a></li> </ul>You can also optionally specify a home / work address and other kinds of useful information.
PHP
One of the points of using a server side language is that we can leave some aspects of the page to be generated on the fly. This frees us from having to manually keep various parts of the page up to date.
In the case with our about page, we have a simple configuration file, which holds the data, used by the page. This same resource is used for the generation of the vCard file and the JSON feed.
config.php
$info = array( 'firstName' => 'John', 'middleName' => 'S.', 'lastName' => 'Smith', 'photoURL' => 'assets/img/photo.jpg', 'birthDay' => strtotime('22-03-1983'), 'city' => 'MyCity', 'country' => 'United States', 'street' => 'My Street 21', 'zip' => '12345', 'company' => 'Google Inc', 'website' => 'http://tutorialzine.com/', 'email' => 'email@example.com', 'cellphone' => '12345678910', 'description' => "I am a webdeveloper living in ...", 'tags' => 'Developer, Designer, Photographer', 'facebook' => 'http://www.facebook.com/', 'twitter' => 'http://twitter.com/Tutorialzine' );Not all of these properties are presented on the about page. Some of them (like the address fields, company, email and birthday) are only made available when the user downloads the profile as a vCard or as a JSON file. You can also add quite a few more properties to this array (a complete list is given as a comment in the config.php file).
So now that we have provided all the information we wanted, we need to build a class that will handle the task of presenting a complete about page.
aboutPage.class.php
class AboutPage{ private $info = array(); // The constructor: public function __construct(array $info){ $this->info = $info; } // A helper method that assembles the person's full name: public function fullName(){ return $this->firstName().' '.$this->middleName().' '.$this->lastName(); } // Using PHP's Magick __call method to make the // properties of $this->info available as method calls: public function __call($method,$args = array()){ if(!array_key_exists($method,$this->info)){ throw new Exception('Such a method does not exist!'); } if(!empty($args)){ $this->info[$method] = $args[0]; } else{ return $this->info[$method]; } } // This method generates a vcard from the $info // array, using the third party vCard class: public function downloadVcard(){ $vcard = new vCard; $methodCalls = array(); // Translating the properties of $info to method calls // understandable by the third party vCard class: $propertyMap = array( 'firstName' => 'setFirstName', 'middleName' => 'setMiddleName', 'lastName' => 'setLastName', 'birthDay' => 'setBirthday', 'city' => 'setHomeCity', 'zip' => 'setHomeZIP', 'country' => 'setHomeCountry', 'website' => 'setURLWork', 'email' => 'setEMail', 'description' => 'setNote', 'cellphone' => 'setCellphone'); // Looping though the properties in $info: foreach($this->info as $k=>$v){ // Mapping a property of the array to a recognized method: if($propertyMap[$k]){ $methodCalls[$propertyMap[$k]] = $v; } else { // If it does not exist, transform it to setPropertyName, // which might be recognized by the vCard class: $methodCalls['set'.ucfirst($k)] = $v; } } // Attempt to call these methods: foreach($methodCalls as $k=>$v){ if(method_exists($vcard,$k)){ $vcard->$k($v); } else error_log('Invalid property in your $info array: '.$k); } // Serving the vcard with a x-vcard Mime type: header('Content-Type: text/x-vcard; charset=utf-8'); header('Content-Disposition: attachment; filename="'.$this->fullName().'.vcf"'); echo $vcard->generateCardOutput(); } // This method generates and serves a JSON object from the data: public function generateJSON(){ header('Content-Type: application/json'); header('Content-Disposition: attachment; filename="'.$this->fullName().'.json"'); // If you wish to allow cross-domain AJAX requests, uncomment the following line: // header('Access-Control-Allow-Origin: *'); echo json_encode($this->info); } }As you can see from the code below, we are using a third party open source class for the actual generation of the vCard file (vcf). As this class uses its own set of method calls, we will need to transform our configuration file to something that it will understand. We are doing this with the
$propertyMaparray which maps properties found in our $info array to the names of method calls that will need to be executed on the vCard object. After we configure the$vcardobject, we set the content headers and call the object’sgenerateCardOutput()method. This causes the browser to display a file download dialog.We are doing basically the same thing in the generateJSON method, with the worthy exception that we are not using a third party PHP class, but the
json_encode()built-in. We are serving the JSON file with an application/json content type. You can also uncomment the access control header if you wish to be able to access your data via AJAX from other domains.Now lets see how we are using this class in index.php:
index.php
require 'includes/config.php'; require 'includes/aboutPage.class.php'; require 'includes/vcard.class.php'; $profile = new AboutPage($info); if(array_key_exists('json',$_GET)){ $profile->generateJSON(); exit; } else if(array_key_exists('vcard',$_GET)){ $profile->downloadVcard(); exit; }The fragment you see above is found at the top of index.php, before any of the HTML, as we have to be able to set headers. After including the appropriate PHP source files, we create a new
AboutPageobject with the configuration array as its parameter. After this we check whether the requested URL is ?json or ?vcard, and serve the appropriate data. Otherwise, the regular about page is displayed.CSS
Most of the design of the about page is pretty straightforward. However, a fair share of CSS3 is used to keep the number of images to a minimum. The two buttons – Find me on facebook, and Follow me on twitter, that are positioned below the text, are ordinary hyperlinks with a
.grayButtonclass name. You can see the definition of this class below:assets/css/styles.css
a.grayButton{ padding:6px 12px 6px 30px; position:relative; background-color:#fcfcfc; background:-moz-linear-gradient(left top -90deg, #fff, #ccc); background:-webkit-linear-gradient(left top -90deg, #fff, #ccc); background:linear-gradient(left top -90deg, #fff, #ccc); -moz-box-shadow: 1px 1px 1px #333; -webkit-box-shadow: 1px 1px 1px #333; box-shadow: 1px 1px 1px #333; -moz-border-radius:18px; -webkit-border-radius:18px; border-radius:18px; font-size:11px; color:#444; text-shadow:1px 1px 0 #fff; display:inline-block; margin-right:10px; -moz-transition:0.25s; -webkit-transition:0.25s; transition:0.25s; } a.grayButton:hover{ text-decoration:none !important; box-shadow:0 0 5px #2b99ff; } a.grayButton:before{ background:url('../img/icons.png') no-repeat; height: 18px; left: 4px; position: absolute; top: 6px; width: 20px; content: ''; } a.grayButton.twitter:before{ background-position:0 -20px; }The code above applies a CSS3 linear gradient to the button, text shadows and rounded corners. It also defines a 0.25 sec transition, that animates the glow that is applied on hover. We are also using the
:beforepseudo element to create the icon that goes with the button. As we are using a sprite, the only thing that differs between the two buttons is the offset of the background image.After this we have the “Download as vCard” and “Get as a JSON file” links:
assets/css/styles.css
#links{ text-align:center; padding-top: 20px; border-top:1px solid #4a4a4a; text-shadow: 1px 1px 0 #333333; width:655px; margin:0 auto; } #links a{ color: #ccc; position:relative; } #links > a{ display: inline-block; font-size: 11px; margin: 0 10px; padding-left:15px; } #links > a:before{ background: url("../img/icons.png") no-repeat -10px -60px; position:absolute; content:''; width:16px; height:16px; top:2px; left:-4px; } #links > a.vcard:before{ background-position: -10px -40px; top: 0; left: -8px; } #links p{ color: #888888; font-size: 10px; font-style: normal; padding: 30px; }As the
#linkssection element contains more than these links (it contains a paragraph with a link to a great portrait image by Levy Carneiro Jr.) , we have to limit the styling to the anchor elements that are direct children of the section.With this our PHP & CSS3 powered about page is complete!
To Wrap it up
You can use this about page as a simple placeholder for your personal website. You can also use an existing users database and create beautiful profiles for your users. Combined with some of our previous tutorials, you can display your latest posts on facebook, flickr images or tweets as a personalized home page.
-
15 Powerful jQuery Tips and Tricks for Developers
Posted: Wed, 29 Jun 2011 14:14:08 +0000
In this article we will take a look at 15 jQuery techniques which will be useful for your effective use of the library. We will start with a few tips about performance and continue with short introductions to some of the library’s more obscure features.
1) Use the Latest Version of jQuery
With all the innovation taking place in the jQuery project, one of the easiest ways to improve the performance of your web site is to simply use the latest version of jQuery. Every release of the library introduces optimizations and bug fixes, and most of the time upgrading involves only changing a script tag.
You can even include jQuery directly from Google’s servers, which provide free CDN hosting for a number of JavaScript libraries.
<!-- Include a specific version of jQuery --> <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js"></script> <!-- Include the latest version in the 1.6 branch --> <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.6/jquery.min.js"></script>
The latter example will include the latest 1.6.x version automatically as it becomes available, but as pointed out on css-tricks, it is cached only for an hour, so you better not use it in production environments.
2) Keep Selectors Simple
Up until recently, retrieving DOM elements with jQuery was a finely choreographed combination of parsing selector strings, JavaScript loops and inbuilt APIs like
getElementById(),getElementsByTagName()andgetElementsByClassName(). But now, all major browsers supportquerySelectorAll(), which understands CSS query selectors and brings a significant performance gain.However, you should still try to optimize the way you retrieve elements. Not to mention that a lot of users still use older browsers that force jQuery into traversing the DOM tree, which is slow.
$('li[data-selected="true"] a') // Fancy, but slow $('li.selected a') // Better $('#elem') // BestSelecting by id is the fastest. If you need to select by class name, prefix it with a tag –
$('li.selected'). These optimizations mainly affect older browsers and mobile devices.Accessing the DOM will always be the slowest part of every JavaScript application, so minimizing it is beneficial. One of the ways to do this, is to cache the results that jQuery gives you. The variable you choose will hold a jQuery object, which you can access later in your script.
var buttons = $('#navigation a.button'); // Some prefer prefixing their jQuery variables with $: var $buttons = $('#navigation a.button');Another thing worth noting, is that jQuery gives you a large number of additional selectors for convenience, such as
:visible,:hidden,:animatedand more, which are not valid CSS3 selectors. The result is that if you use them the library cannot utilizequerySelectorAll(). To remedy the situation, you can first select the elements you want to work with, and later filter them, like this:$('a.button:animated'); // Does not use querySelectorAll() $('a.button').filter(':animated'); // Uses itThe results of the above are the same, with the exception that the second example is faster.
3) jQuery Objects as Arrays
The result of running a selector is a jQuery object. However, the library makes it appear as if you are working with an array by defining index elements and a length.
// Selecting all the navigation buttons: var buttons = $('#navigation a.button'); // We can loop though the collection: for(var i=0;i<buttons.length;i++){ console.log(buttons[i]); // A DOM element, not a jQuery object } // We can even slice it: var firstFour = buttons.slice(0,4);If performance is what you are after, using a simple
for(or awhile) loop instead of$.each(), can make your code several times faster.Checking the length is also the only way to determine whether your collection contains any elements.
if(buttons){ // This is always true // Do something } if(buttons.length){ // True only if buttons contains elements // Do something }4) The Selector Property
jQuery provides a property which contains the selector that was used to start the chain.
$('#container li:first-child').selector // #container li:first-child $('#container li').filter(':first-child').selector // #container li.filter(:first-child)Although the examples above target the same element, the selectors are quite different. The second one is actually invalid – you can’t use it as the basis of a new jQuery object. It only shows that the filter method was used to narrow down the collection.
5) Create an Empty jQuery Object
Creating a new jQuery object can bring significant overhead. Sometimes, you might need to create an empty object, and fill it in with the add() method later.
var container = $([]); container.add(another_element);
This is also the basis for the quickEach() method that you can use as a faster alternative to the default
each().6) Select a Random Element
As I mentioned above, jQuery adds its own selection filters. As with everything else in the library, you can also create your own. To do this simply add a new function to the
$.expr[':']object. One awesome use case was presented by Waldek Mastykarz on his blog: creating a selector for retrieving a random element. You can see a slightly modified version of his code below:(function($){ var random = 0; $.expr[':'].random = function(a, i, m, r) { if (i == 0) { random = Math.floor(Math.random() * r.length); } return i == random; }; })(jQuery); // This is how you use it: $('li:random').addClass('glow');7) Use CSS Hooks
The CSS hooks API was introduced to give developers the ability to get and set particular CSS values. Using it, you can hide browser specific implementations and expose a unified interface for accessing particular properties.
$.cssHooks['borderRadius'] = { get: function(elem, computed, extra){ // Depending on the browser, read the value of // -moz-border-radius, -webkit-border-radius or border-radius }, set: function(elem, value){ // Set the appropriate CSS3 property } }; // Use it without worrying which property the browser actually understands: $('#rect').css('borderRadius',5);What is even better, is that people have already built a rich library of supported CSS hooks that you can use for free in your next project.
8) Use Custom Easing Functions
You have probably heard of the jQuery easing plugin by now – it allows you to add effects to your animations. The only shortcoming is that this is another JavaScript file your visitors have to load. Luckily enough, you can simply copy the effect you need from the plugin file, and add it to the
jQuery.easingobject:$.easing.easeInOutQuad = function (x, t, b, c, d) { if ((t/=d/2) < 1) return c/2*t*t + b; return -c/2 * ((--t)*(t-2) - 1) + b; }; // To use it: $('#elem').animate({width:200},'slow','easeInOutQuad');9) The $.proxy()
One of the drawbacks to using callback functions in jQuery has always been that when they are executed by a method of the library, the context is set to a different element. For example, if you have this markup:
<div id="panel" style="display:none"> <button>Close</button> </div>
And you try to execute this code:
$('#panel').fadeIn(function(){ // this points to #panel $('#panel button').click(function(){ // this points to the button $(this).fadeOut(); }); });You will run into a problem – the button will disappear, not the panel. With
$.proxy, you can write it like this:$('#panel').fadeIn(function(){ // Using $.proxy to bind this: $('#panel button').click($.proxy(function(){ // this points to #panel $(this).fadeOut(); },this)); });Which will do what you expect. The
$.proxyfunction takes two arguments – your original function, and a context. It returns a new function in which the value of this is always fixed to the context. You can read more about $.proxy in the docs.10) Determine the Weight of Your Page
A simple fact: the more content your page has, the more time it takes your browser to render it. You can get a quick count of the number of DOM elements on your page by running this in your console:
console.log( $('*').length );The smaller the number, the faster the website is rendered. You can optimize it by removing redundant markup and unnecessary wrapping elements.
11) Turn your Code into a jQuery Plugin
If you invest some time in writing a piece of jQuery code, consider turning it into a plugin. This promotes code reuse, limits dependencies and helps you organize your project’s code base. Most of the tutorials on Tutorialzine are organized as plugins, so that it is easy for people to simply drop them in their sites and use them.
Creating a jQuery plugin couldn’t be easier:
(function($){ $.fn.yourPluginName = function(){ // Your code goes here return this; }; })(jQuery);Read a detailed tutorial on turning jQuery code into a plugin.
12) Set Global AJAX Defaults
When triggering AJAX requests in your application, you often need to display some kind of indication that a request is in progress. This can be done by displaying a loading animation, or using a dark overlay. Managing this indicator in every single
$.getor$.postcall can quickly become tedious.The best solution is to set global AJAX defaults using one of jQuery’s methods.
// ajaxSetup is useful for setting general defaults: $.ajaxSetup({ url : '/ajax/', dataType : 'json' }); $.ajaxStart(function(){ showIndicator(); disableButtons(); }); $.ajaxComplete(function(){ hideIndicator(); enableButtons(); }); /* // Additional methods you can use: $.ajaxStop(); $.ajaxError(); $.ajaxSuccess(); $.ajaxSend(); */Read the docs about jQuery’s AJAX functionality.
13) Use delay() for Animations
Chaining animation effects is a powerful tool in every jQuery developer’s toolbox. One of the more overlooked features is that you can introduce delays between animations.
// This is wrong: $('#elem').animate({width:200},function(){ setTimeout(function(){ $('#elem').animate({marginTop:100}); },2000); }); // Do it like this: $('#elem').animate({width:200}).delay(2000).animate({marginTop:100});To appreciate how much time jQuery’s
animation()save us, just imagine if you had to manage everything yourself: you would need to set timeouts, parse property values, keep track of the animation progress, cancel when appropriate and update numerous variables on every step.Read the docs about jQuery animations.
14) Make Use of HTML5 Data Attributes
HTML5 data attributes are a simple means to embed data in a webpage. It is useful for exchanging data between the server and the front end, something that used to require outputting <script> blocks or hidden markup.
With the recent updates to the jQuery
data()method, HTML5 data attributes are pulled automatically and are available as entries, as you can see from the example below:<div id="d1" data-role="page" data-last-value="43" data-hidden="true" data-options='{"name":"John"}'> </div>To access the data attributes of this div, you would use code like the one below:
$("#d1").data("role"); // "page" $("#d1").data("lastValue"); // 43 $("#d1").data("hidden"); // true; $("#d1").data("options").name; // "John";Read more about data() in the jQuery docs.
15) Local Storage and jQuery
Local storage is a dead simple API for storing information on the client side. Simply add your data as a property of the global
localStorageobject:localStorage.someData = "This is going to be saved across page refreshes and browser restarts";
The bad news is that it is not supported in older browsers. This is where you can use one of the many jQuery plugins that provide different fallbacks if
localStorageis not available, which makes client-side storage work almost everywhere.Here is an example using the $.jStorage jQuery plugin:
// Check if "key" exists in the storage var value = $.jStorage.get("key"); if(!value){ // if not - load the data from the server value = load_data_from_server(); // and save it $.jStorage.set("key",value); } // Use valueTo Wrap it Up
The techniques presented here will give you a head start in effectively using the jQuery library. If you want something to be added to this list, or if you have any suggestions, use the comment section below.
-
Making a Beautiful HTML5 Portfolio
Posted: Fri, 17 Jun 2011 13:06:43 +0000
In today’s tutorial we will be making a beautiful HTML5 portfolio powered by jQuery and the Quicksand plugin. You can use it to showcase your latest work and it is fully customizable, so potentially you could expand it to do much more.
The HTML
The first step is to write down the markup of a new HTML5 document. In the head section, we will include the stylesheet for the page. The jQuery library, the Quicksand plugin and our script.js will go right before the closing body tag:
index.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Making a Beautiful HTML5 Portfolio | Tutorialzine Demo</title> <!-- Our CSS stylesheet file --> <link rel="stylesheet" href="assets/css/styles.css" /> <!-- Enabling HTML5 tags for older IE browsers --> <!--[if lt IE 9]> <script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script> <![endif]--> </head> <body> <header> <h1>My Portfolio</h1> </header> <nav id="filter"> <!-- The menu items will go here (generated by jQuery) --> </nav> <section id="container"> <ul id="stage"> <!-- Your portfolio items go here --> </ul> </section> <footer> </footer> <!-- Including jQuery, the Quicksand plugin, and our own script.js --> <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js"></script> <script src="assets/js/jquery.quicksand.js"></script> <script src="assets/js/script.js"></script> </body> </html>In the body, there are a number of the new HTML5 elements. The
headerholds our h1 heading (which is styled as the logo), thesectionelement holds the unordered list with the portfolio items (more lists are added by jQuery, as you will see later), and thenavelement, styled as a green bar, acts as a content filter.The
#stageunordered list holds our portfolio items. You can see what these items should look like below. Each of them has a HTML5dataattribute, which defines a series of comma-separated tags. Later, when we use jQuery to loop through this list, we will record the tags and create categories that can be selected from the green bar.<li data-tags="Print Design"> <img src="assets/img/shots/1.jpg" /> </li> <li data-tags="Logo Design,Print Design"> <img src="assets/img/shots/2.jpg" /> </li> <li data-tags="Web Design,Logo Design"> <img src="assets/img/shots/3.jpg" /> </li>
You can put whatever you want in these li items and customize the portfolio further. The Quicksand plugin will handle the animated transitions of this list, so you are free to experiment.
The jQuery
What the Quicksand plugin does, is compare two unordered lists of items, find the matching LIs inside them, and animate them to their new positions. The jQuery script we will be writing in this section, will loop through the portfolio items in the
#stagelist, and create a new (hidden) unordered list for each of the tags it finds. It will also create a new menu option, which will trigger the quicksand transition between the two lists.First we need to listen for the
readyevent (the earliest point in the loading of the page where we can access the DOM), and loop through all the li items detecting the associated tags.script.js – Part 1
$(document).ready(function(){ var items = $('#stage li'), itemsByTags = {}; // Looping though all the li items: items.each(function(i){ var elem = $(this), tags = elem.data('tags').split(','); // Adding a data-id attribute. Required by the Quicksand plugin: elem.attr('data-id',i); $.each(tags,function(key,value){ // Removing extra whitespace: value = $.trim(value); if(!(value in itemsByTags)){ // Create an empty array to hold this item: itemsByTags[value] = []; } // Each item is added to one array per tag: itemsByTags[value].push(elem); }); });Each tag is added to the
itemsByTagsobject as an array. This would mean thatitemsByTags['Web Design']would hold an array with all the items that have Web Design as one of their tags. We will use this object to create hidden unordered lists on the page for quicksand.It would be best to create a helper function that will handle it for us:
script.js – Part 2
function createList(text,items){ // This is a helper function that takes the // text of a menu button and array of li items // Creating an empty unordered list: var ul = $('<ul>',{'class':'hidden'}); $.each(items,function(){ // Creating a copy of each li item // and adding it to the list: $(this).clone().appendTo(ul); }); ul.appendTo('#container'); // Creating a menu item. The unordered list is added // as a data parameter (available via .data('list')): var a = $('<a>',{ html: text, href:'#', data: {list:ul} }).appendTo('#filter'); }This function takes the name of the group and an array with LI items as parameters. It then clones these items into a new UL and adds a link in the green bar.
Now we have to loop through all the groups and call the above function, and also listen for clicks on the menu items.
script.js – Part 3
// Creating the "Everything" option in the menu: createList('Everything',items); // Looping though the arrays in itemsByTags: $.each(itemsByTags,function(k,v){ createList(k,v); }); $('#filter a').live('click',function(e){ var link = $(this); link.addClass('active').siblings().removeClass('active'); // Using the Quicksand plugin to animate the li items. // It uses data('list') defined by our createList function: $('#stage').quicksand(link.data('list').find('li')); e.preventDefault(); }); // Selecting the first menu item by default: $('#filter a:first').click();Great! Now that we have everything in place we can move on to styling the page.
The CSS
Styling the page itself is not that interesting (you can see the CSS for this in assets/css/styles.css). However what is more interesting is the green bar (or the #filter bar), which uses the
:before / :afterpseudo elements to add attractive corners on the sides of the bar. As these are positioned absolutely, they also grow together with the bar.styles.css
#filter { background: url("../img/bar.png") repeat-x 0 -94px; display: block; height: 39px; margin: 55px auto; position: relative; width: 600px; text-align:center; -moz-box-shadow:0 4px 4px #000; -webkit-box-shadow:0 4px 4px #000; box-shadow:0 4px 4px #000; } #filter:before, #filter:after { background: url("../img/bar.png") no-repeat; height: 43px; position: absolute; top: 0; width: 78px; content: ''; -moz-box-shadow:0 2px 0 rgba(0,0,0,0.4); -webkit-box-shadow:0 2px 0 rgba(0,0,0,0.4); box-shadow:0 2px 0 rgba(0,0,0,0.4); } #filter:before { background-position: 0 -47px; left: -78px; } #filter:after { background-position: 0 0; right: -78px; } #filter a{ color: #FFFFFF; display: inline-block; height: 39px; line-height: 37px; padding: 0 15px; text-shadow:1px 1px 1px #315218; } #filter a:hover{ text-decoration:none; } #filter a.active{ background: url("../img/bar.png") repeat-x 0 -138px; box-shadow: 1px 0 0 rgba(255, 255, 255, 0.2), -1px 0 0 rgba(255, 255, 255, 0.2), 1px 0 1px rgba(0,0,0,0.2) inset, -1px 0 1px rgba(0,0,0,0.2) inset; }With this our beautiful HTML5 portfolio is complete!
Conclusion
You can use this template to present your work. The best thing about it is that it’s really easy to customize. You only need to change the contents of the initial LI items of the #stage list, and specify some new tags – the script will do the rest. If the visitor doesn’t have JavaScript enabled, they will still see all your portfolio items, which is also good for SEO purposes.
-
Making a Simple Tweet to Download System
Posted: Wed, 25 May 2011 14:28:05 +0000
Twitter is undoubtedly a hugely popular social network. One of the keys to its success is its simple and powerful API, which opens the doors to countless novel ways for you to use the service.
One of these uses is allowing your visitors to “pay with a tweet”. Namely you take something that you would otherwise offer for free (like an ebook, mp3 or other kind of digital media), and make it available to users only after they tweet about your website. It is a great way to promote your products and get noticed, and it doesn’t cost anything to your visitors.
Building such functionality is not as hard as you might think. Twitter made it even easier with their Web Intents – a dead simple way to integrate the platform in your website. In this tutorial we will build a jQuery plugin around that API, and activate a download button once the user tweets. So lets get started!
The HTML
First we will need a simple web page to hold the example together.
index.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Tweet to Download | Tutorialzine Demo</title> <!-- Our CSS stylesheet file --> <link rel="stylesheet" href="assets/css/styles.css" /> <!--[if lt IE 9]> <script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script> <![endif]--> </head> <body> <header> <h1>Tweet to Download</h1> <h2><a href="http://tutorialzine.com/2011/05/tweet-to-download-jquery/">« Back to Tutorialzine</a></h2> </header> <section id="container"> <p>The button below is activated<br />only* after you tweet. <a href="#" id="tweetLink">Try it.</a></p> <a href="#">Download</a> </section> <footer>*Not exactly. Read more in the tutorial..</footer> <img src="assets/img/twitter_bird.png" alt="Twitter Bird" /> <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js"></script> <script src="assets/js/jquery.tweetAction.js"></script> <script src="assets/js/script.js"></script> </body> </html>We are using some of the HTML5 tags – header, section and footer, to logically separate the page in three parts. Our #container section holds two anchor elements.
The first link – #tweetLink, is going to trigger the plugin and display a popup holding a Twitter submission form. The second – #downloadButton, is styled as a button and its href attribute is set to that of the file we are offering for download, once the user tweets.
At the bottom of the file, before the closing body tag, we have the usual set of JavaScript includes – version 1.6 of the jQuery library, the tweetAction.js plugin we will be writing in a moment, and script.js, which listens for clicks on the links and triggers the plugin.
Lets move to the jQuery section of this tutorial.
The jQuery
As you can see from the Web Intents documentation, it can be described as a popup based interface for interacting with Twitter. You just need to load a specific intent URL in a popup window and pass GET parameters with the text of the tweet, Twitter username and more, depending on the intent. This will produce a form which with which the user can publish a new tweet, reply or follow you.
Lets put this together in a jQuery plugin that handles it for us:
jquery.tweetAction.js
(function($){ var win = null; $.fn.tweetAction = function(options,callback){ // Default parameters of the tweet popup: options = $.extend({ url:window.location.href }, options); return this.click(function(e){ if(win){ // If a popup window is already shown, // do nothing; e.preventDefault(); return; } var width = 550, height = 350, top = (window.screen.height - height)/2, left = (window.screen.width - width)/2; var config = [ 'scrollbars=yes','resizable=yes','toolbar=no','location=yes', 'width='+width,'height='+height,'left='+left, 'top='+top ].join(','); // Opening a popup window pointing to the twitter intent API: win = window.open('http://twitter.com/intent/tweet?'+$.param(options), 'TweetWindow',config); // Checking whether the window is closed every 100 milliseconds. (function checkWindow(){ try{ // Opera raises a security exception, so we // need to put this code in a try/catch: if(!win || win.closed){ throw "Closed!"; } else { setTimeout(checkWindow,100); } } catch(e){ // Executing the callback, passed // as an argument to the plugin. win = null; callback(); } })(); e.preventDefault(); }); }; })(jQuery);To open a popup window with
window.open(), we need to pass a list of comma-delimited parameters. These include which address bars are to be shown, and the dimensions and position of the window.After we open
http://twitter.com/intent/tweetwe check theclosedattribute of the window every 100 ms by running thecheckWindow()function with asetTimeout(). This is the only way we can know that the popup has been closed, as browsers prevent any kind of cross-domain interactions. When the popup is closed, a callback function, passsed as the second argument of the function, is executed.You can see how we use this plugin below:
script.js
$(document).ready(function(){ // Using our tweetAction plugin. For a complete list with supported // parameters, refer to http://dev.twitter.com/pages/intents#tweet-intent $('#tweetLink').tweetAction({ text: 'How to make a simple Tweet to Download system', url: 'http://tutorialzine.com/2011/05/tweet-to-download-jquery/', via: 'tutorialzine', related: 'tutorialzine' },function(){ // Callback function. Triggered when the user closes the pop-up window: $('a.downloadButton') .addClass('active') .attr('href','tweet_to_download.zip'); }); });In the fragment above we trigger the tweetAction plugin on the #tweetLink anchor. When it clicked, we will display a popup window, which, when closed, will trigger the callback. This is where we enable the button and set its
hrefattribute to that of the file.The CSS
The only thing we are left to do, is throw in some fancy CSS styles. I am going to present only some of the more interesting declarations here. You can see the rest in assets/css/styles.css.
We are using multiple backgrounds for the html element. The background images are displayed one beneath the other, starting with the topmost image –
bg_gradient.jpg.html{ /* CSS3 Multiple backgrounds with a fallback */ background-color:#e4e4e4; background:url('../img/bg_gradient.jpg') no-repeat center center,url('../img/bg_tile.jpg'); } body{ color:#888; padding:10px; min-height:600px; font:14px/1.3 'Segoe UI',Arial, sans-serif; text-shadow: 1px 1px 1px rgba(255, 255, 255, 0.7); }Further down we have the styling of the twitter bird icon. I am using the > character to denote that this will affect only images that are direct children of body.
body > img{ /* The twitter illustration */ margin:50px auto 0; display:block; }Finally we have the #container section. With the help of the
:before/:afterpseudo elements, we display subtle shadows above and below the container.#container{ width:450px; height:300px; padding:10px; text-align:center; margin:0 auto; position:relative; background-color:#fff; display:block; -moz-border-radius:10px; -webkit-border-radius:10px; border-radius:10px; } #container:before, #container:after{ /* Adding subtle shadows with before/after elements */ content:'.'; text-indent:-99999px; overflow:hidden; display:block; height:12px; width:470px; background:url('../img/shadows.png') no-repeat center top; position:absolute; left:0; top:-12px; } #container:after{ top:auto; bottom:-12px; background-position:center bottom; }These two pseudo elements share almost all of their code, so I’ve defined them as a group. The
:afterelement is also styled separately, but only the styles that differ are redefined.With this our Pay with a Tweet experiment is complete!
But wait! This doesn’t work!
And you are entirely correct. As you can see from the code (and confirm from the demo), we assume that closing the popup window equals a published tweet. It does not.
As this is a cross domain interaction, there is no way to subscribe for when a tweet is actually published. The only way to do this would be to use Twitter’s more complex @Anywhere API, but even then people could just hotlink to your file.
Does it even matter? The real purpose of this technique is to give people an incentive to tweet about your product/service, something that a lot of folks would love to do just for the feeling of receiving your “members-only” download.
-
Generating Files with JavaScript
Posted: Tue, 17 May 2011 16:46:55 +0000
When building a web application, you oftentimes need to give users the ability to download a piece of data as a file. It could be a backup of configuration settings, reports, or other piece of information that is generated dynamically.
The usual solution to this problem would be to have a dedicated export script that selects from a database and builds the file you need. However, as we will be proving in this short tutorial, there is another way.
We will make a jQuery plugin which, combined with a simple php script, can generate every kind of textual file, and make it available for download. You will initiate the download from your JavaScript front end by only providing the file contents, and leave the rest to the plugin.
The HTML
We shall start by laying down a simple HTML page with a textarea and a download button, so we can demonstrate the plugin at work.
index.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Generating files with JS & PHP | Tutorialzine Demo</title> <!-- Our CSS stylesheet file --> <link rel="stylesheet" href="assets/css/styles.css" /> <!--[if lt IE 9]> <script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script> <![endif]--> </head> <body> <header> <h1>Generating Files with JavaScript</h1> <h2><a href="http://tutorialzine.com/2011/05/generating-files-javascript-php/">« Read and download on Tutorialzine</a></h2> </header> <form action="./" method="post"> <textarea></textarea> <a href="#" id="download">Download</a> </form> <footer>Another cool example: <a href="#" id="downloadPage">download this page.</a> <b>To download the source code, visit <a href="http://tutorialzine.com/2011/05/generating-files-javascript-php/">Tutorialzine.com</a></b></footer> <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.0/jquery.min.js"></script> <script src="assets/js/jquery.generateFile.js"></script> <script src="assets/js/script.js"></script> </body> </html>The page uses the HTML5 doctype, as we are using some of the tags defined by the standard. For it to work in IE, we also need to include the HTML5 enabling script in the head section.
Before the closing body tag, we are adding the jQuery library, the generateFile plugin we will be writing in a moment, and the script.js file that listens for events and triggers the file downloads.
The PHP
As you probably know, generating files is not possible with JavaScript alone. Different solutions exist (some of them even relying on Flash), but using a generic PHP script on the backend provides better control and ease of use (not to mention that it works in every major browser out there).
You can see the generic file generation script below:
download.php
if(empty($_POST['filename']) || empty($_POST['content'])){ exit; } // Sanitizing the filename: $filename = preg_replace('/[^a-z0-9\-\_\.]/i','',$_POST['filename']); // Outputting headers: header("Cache-Control: "); header("Content-type: text/plain"); header('Content-Disposition: attachment; filename="'.$filename.'"'); echo $_POST['content'];What this PHP script does is simply add some headers on top of an echo statement. The plugin we are building must pass two parameters along with the POST request: filename and content. The script will print the content of the file, while setting three headers that will force the file download box to appear (instead of your browser simply opening it).
To use the plugin you need to upload this file somewhere on your server and pass its URL to the plugin we will be coding next.
The jQuery
As you saw in the previous section, our plugin has to issue a POST request to download.php. The natural choice for making a request would be by using AJAX. However, there is a shortcoming to using this method – it does not trigger the file download dialog to appear.
So what we need is a bit more old school. We will be dynamically creating a hidden iframe and write a form to it, which we will later submit via POST. The action attribute of the form points to download.php, so the file download dialog will pop up, exactly as we need it to.
Now lets lay down the jQuery code that does this:
assets/jquery.generateFile.js
(function($){ // Creating a jQuery plugin: $.generateFile = function(options){ options = options || {}; if(!options.script || !options.filename || !options.content){ throw new Error("Please enter all the required config options!"); } // Creating a 1 by 1 px invisible iframe: var iframe = $('<iframe>',{ width:1, height:1, frameborder:0, css:{ display:'none' } }).appendTo('body'); var formHTML = '<form action="" method="post">'+ '<input type="hidden" name="filename" />'+ '<input type="hidden" name="content" />'+ '</form>'; // Giving IE a chance to build the DOM in // the iframe with a short timeout: setTimeout(function(){ // The body element of the iframe document: var body = (iframe.prop('contentDocument') !== undefined) ? iframe.prop('contentDocument').body : iframe.prop('document').body; // IE body = $(body); // Adding the form to the body: body.html(formHTML); var form = body.find('form'); form.attr('action',options.script); form.find('input[name=filename]').val(options.filename); form.find('input[name=content]').val(options.content); // Submitting the form to download.php. This will // cause the file download dialog box to appear. form.submit(); },50); }; })(jQuery);In less than 50 lines (with comments stripped) the above fragment does what we need. It creates a hidden iframe with a form inside it.
Notice the
setTimeout()function. Without it we cannot access the document element of the iframe in Internet Explorer. This way, we are giving it time to build the DOM and make it available to us.And here is how to use this plugin:
assets/script.js
$(document).ready(function(){ $('#download').click(function(e){ $.generateFile({ filename : 'export.txt', content : $('textarea').val(), script : 'download.php' }); e.preventDefault(); }); $('#downloadPage').click(function(e){ $.generateFile({ filename : 'page.html', content : $('html').html(), script : 'download.php' }); e.preventDefault(); }); });When calling
$.generateFile, you need to pass the name of the file (should be something descriptive), its text content, and the path to download.php. As you can see in the example above, we can generate any kind of file, as long as it is text.With this our simple plugin is complete!
Conclusion
You can use this code to add export features to your web app or enhance certain areas of your site with download functionality. It is even possible to generate doc files and spreadsheets if you follow Microsoft Office’s XML formats. The best part is that everything is done with JavaScript and you can easily combine different sources of data.
























