Tag Archiv: php
These days you can do pretty much anything with JavaScript and HTML. Thanks to Node-WebKit, we can even create desktop applications that feel native, and have full access to every part of the operating system. In this short tutorial, we will show you how to create a simple desktop application using Node-WebKit, which combines jQuery and a few Node.js modules.
Node-WebKit is a combination of Node.js and an embedded WebKit browser. The JavaScript code that you write is executed in a special environment and has access to both standard browser APIs and Node.js. Sounds interesting? Keep reading!
Update (15th Jan 2015) – Just after a week of publishing this tutorial, node-webkit was renamed to NW.js. For now, this tutorial is perfectly compatible with it. In the future we might update it if there are any changes.
Installing Node-WebKit
For developing applications, you will need to download the node-webkit executable, and call it from your terminal when you want to run your code. (Later you can package everything in a single program so your users can only click an icon to start it).
Head over to the project page and download the executable that is built for your operating system. Extract the archive somewhere on your computer. To start it, you need to do this in your terminal:
# If you are on linux/osx
/path/to/node-webkit/nw /your/project/folder
# If you are on windows
C:pathtonode-webkitnw.exe C:yourprojectfolder
# (the paths are only for illustrative purposes, any folder will do)
This will open a new node-webkit window and print a bunch of debug messages in your terminal.
You can optionally add the extracted node-webkit folder to your PATH, so that it is available as the nw
command from your terminal.
Your First Application
There is a Download button near the top of this article. Click it and get a zip with a sample app that we prepared for you. It fetches the most recent articles on Tutorialzine from our RSS feed and turns them into a cool looking 3D carousel using jQuery Flipster.
Directory Structure
Once you extract it, you will see the files above. From here this looks like a standard static website. However, it won’t work if you simply double click index.html – it requires Node.js modules, which is invalid in a web browser. To run it, CD into this folder, and try running the app with this command:
/path/to/node-webkit/nw .
This will show our glorious desktop app.
Our node-webkit app
How it was made
It all starts with the package.json file, which node-webkit looks up when starting. It describes what node-webkit should load and various parameters of the window.
package.json
{
"name": "nw-app",
"version": "1.0.0",
"description": "",
"main": "index.html",
"scripts": {
"test": "echo "Error: no test specified" && exit 1"
},
"author": "",
"window": {
"toolbar": false,
"width": 800,
"height": 500
},
"license": "ISC",
"dependencies": {
"pretty-bytes": "^1.0.2"
}
}
The window
property in this file tells node-webkit to open a new window 800 by 500px and hide the toolbar. The file pointed to by the main
property will be loaded. In our case this is index.html:
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Tutorialzine Node-Webkit Experiment</title>
<link rel="stylesheet" href="./css/jquery.flipster.min.css">
<link rel="stylesheet" href="./css/styles.css">
</head>
<body>
<div class="flipster">
<ul>
<!-- Tutorialzine's latest articles will show here -->
</ul>
</div>
<p class="stats"></p>
<script src="./js/jquery.min.js"></script>
<script src="./js/jquery.flipster.min.js"></script>
<script src="./js/script.js"></script>
</body>
</html>
And finally, here is our JavaScript file. This is where it gets interesting!
js/script.js
// Mixing jQuery and Node.js code in the same file? Yes please!
$(function(){
// Display some statistic about this computer, using node's os module.
var os = require('os');
var prettyBytes = require('pretty-bytes');
$('.stats').append('Number of cpu cores: <span>' + os.cpus().length + '</span>');
$('.stats').append('Free memory: <span>' + prettyBytes(os.freemem())+ '</span>');
// Node webkit's native UI library. We will need it for later
var gui = require('nw.gui');
// Fetch the recent posts on Tutorialzine
var ul = $('.flipster ul');
// The same-origin security policy doesn't apply to node-webkit, so we can
// send ajax request to other sites. Let's fetch Tutorialzine's rss feed:
$.get('http://feeds.feedburner.com/Tutorialzine', function(response){
var rss = $(response);
// Find all articles in the RSS feed:
rss.find('item').each(function(){
var item = $(this);
var content = item.find('encoded').html().split('</a></div>')[0]+'</a></div>';
var urlRegex = /(http|ftp|https)://[w-_]+(.[w-_]+)+([w-.,@?^=%&:/~+#]*[w-@?^=%&/~+#])?/g;
// Fetch the first image of the article
var imageSource = content.match(urlRegex)[1];
// Create a li item for every article, and append it to the unordered list
var li = $('<li><img /><a target="_blank"></a></li>');
li.find('a')
.attr('href', item.find('link').text())
.text(item.find("title").text());
li.find('img').attr('src', imageSource);
li.appendTo(ul);
});
// Initialize the flipster plugin
$('.flipster').flipster({
style: 'carousel'
});
// When an article is clicked, open the page in the system default browser.
// Otherwise it would open it in the node-webkit window which is not what we want.
$('.flipster').on('click', 'a', function (e) {
e.preventDefault();
// Open URL with default browser.
gui.Shell.openExternal(e.target.href);
});
});
});
Notice that we are accessing Tutorialzine’s RSS feed directly with jQuery, even though it is on a different domain. This is not possible in a browser, but Node-WebKit removes this limitation to make development of desktop applications easier.
Here are the node modules we’ve used:
- Shell – A node webkit module that provides a collection of APIs that do desktop related jobs.
- OS – The built-in Node.js OS module, which has a method that returns the amount of free system memory in bytes.
- Pretty Bytes – Convert bytes to a human readable string: 1337 → 1.34 kB.
Our project also includes jQuery and the jQuery-flipster plugin, and that’s pretty much it!
Packaging and Distribution
You most certainly don’t want your users to go through the same steps in order to run you application. You wan’t to package it in a standalone program, and open it by simply double clicking it.
Packaging node-webkit apps for multiple operating systems takes a lot of work to do manually. But there are libraries that do this for you. We tried this npm module – https://github.com/mllrsohn/node-webkit-builder, and it worked pretty well.
The only disadvantage is that the executable files have a large size (they can easily hit 40-50mb) , because they pack a stripped down webkit browser and node.js together with your code and assets. This makes it rather impractical for small desktop apps (such as ours), but for larger apps it is worth a look.
Conclusion
Node-webkit is a powerful tool that opens a lot of doors to web developers. With it, you can easily create companion apps for your web services and build desktop clients which have full access to the users’s computer.
You can read more about node-webkit on their wiki.
When you collect information from people through a form, applying some kind of validation is a must. Failing to do so could lead to lost customers, junk data in your database or even security exploits of your website. Historically, building form validation has been a pain. On the server side, this is made easier by full stack frameworks which handle it for you, but on the client you often end up with JavaScript libraries that take a lot of effort to integrate.
Thankfully, HTML5 gives us a number of features that can handle most of your validation needs. Forms in HTML5 now have built-in support for validation through the use of special attributes and new input types, and give you a lot of control over styling with CSS.
See an online demo here and read a quick overview of the basics behind HTML5 form validation below.
1. Specialized Input Types
HTML5 introduced several new input types. They can be used to create input boxes, which will accept only a specified kind of data.
The new input types are as follows:
color
date
datetime
datetime-local
email
month
number
range
search
tel
time
url
week
To use one of the new types, include them as the value of the type attribute:
<input type="email"/>
If the browser does not support the given input type, the field will behave as a regular text input. Also, it’s helpful to know that some of the input fields (such as “email” and “tel”) will cause a mobile devices to open specialized keyboards with a limited set of keys, and not the full QUERTY layout.
For more details on all the input types, head out to this MDN wiki – here.
2. Required Fields
By simply adding the “required” attribute to a <input>, <select> or <textarea>, you tell the browser that a value must be provided in this field. Think of this as the red asterisk* we see in most registration forms.
<input type="checkbox" name="terms" required >
The problem here is that nearly any data will fulfill this requirement – for example you can bypass it with an empty space (we’ll show you how to prevent this in a moment).
When you set the required attribute on email or url fields, the browser expects a certain pattern to be followed, but it’s very permissive and emails such as “z@zz” are considered valid (read on to see how to work around this).
3. Limits
We can set some basic limitations like max length and minimum and maximum values for number fields. To limit the length of input fields and textareas, use the “maxlength” attribute. What this does is to forbid any string longer than the field’s “maxlength” value to be entered at all. If you try and paste a string witch exceeds this limit, the form will simply clip it.
<input type="text" name="name" required maxlength="15">
The <input type=”number”> fields use “max” and “min” attributes to create a range of possible values – in our example we’ve made the minimum allowed age to be 18 (too bad you can be whatever age you want on the internet).
<input type="number" name="age" min="18" required>
4. Styling
CSS3 pseudo classes let us style any form field depending on its state. They are:
:valid
:invalid
:required
:optional
:in-range
:out-of-range
:read-only
:read-write
This means that you can have required fields look one way, optional ones another and so on.
In our demo we’ve combined the “valid” and “invalid” selectors with the “focus” pseudo class to color form fields red or green when the user selects them and starts typing.
input:focus:invalid,
textarea:focus:invalid{
border:solid 2px #F5192F;
}
input:focus:valid,
textarea:focus:valid{
border:solid 2px #18E109;
background-color:#fff;
}
5. Tooltips
As you’ve probably noticed, when you try to submit a form that’s not correctly filled, a pop-up appears. By setting the “title” attribute of our fields, we can add additional hints on what values our rules validation expects.
Note that different browsers display the pop-ups in differently. In Chrome, the title attribute value will appear under the main error-message text in smaller font. In Firefox it doesn’t show your custom tooltip text at all, unless you’ve used the “pattern” attribute as well, which then is taken as a pattern info.
<input type="text" name="name" title="Please enter your user name.">
The error boxes or their default content can’t be changed that simply and require the help of JavaScript, but that’s a whole other tutorial.
6. Patterns
The “pattern” attribute lets developers set a Regular Expression, which the browser will match against the user supplied input, before allowing the form to be submitted. This gives us great control over the data entries, since RegEx patterns can be quite complex and precise. To learn more about Regular Expressions, check out our article – Learn Regular Expressions in 20 Minutes.
Now, with the ability to filter out input values, our example form accepts only full email addresses and a password with a minimum length of 8 characters, including at least one number.
Here’s how to use it:
<input type="email" name="email" required pattern="^S+@S+.S+$" title="example@mail.com">
We hope that this quick tip helped you get up to speed with HTML5’s validation features. Thanks for reading!
For a long time we have been forced to create the interfaces of our web apps using CSS layout modes which were originally designed for laying down documents. No wonder it is such a pain for beginners! Luckily, things are changing – we now have the flexbox layout mode. If you can afford to ignore IE < 10 (which is a big if), you can use it today! The spec is finalized, and most browsers already support it unprefixed.
In this quick tutorial, I will show you how to use flexbox to create something basic that has been surprisingly tricky to do until now – a sidebar that has equal height to your main content. The tricky part lies in that you want both your sidebar and main page element to have content with varying lengths, but at the same time they should have equal heights. With flex box, this is very easy. Here is how to do it.
Say hello to Flexbox
There are plenty of guides to give you an overview of flexbox. Here, we will take a more practical approach, and jump right into using it. First, here is the HTML:
<section id="page">
<div id="main">
<!-- The content of the page will go here -->
</div>
<aside>
<!-- This is the sidebar -->
</aside>
</section>
We want to make the #main div and the aside show next to each other, and be of equal heights, regardless of the content that they hold. We also want to make the page responsive, so that the sidebar has a fixed width, but the main element shrinks/expands to fill the available space. Lastly, on small screens, we want the sidebar to go beneath the main content.
Equal Height Sidebar with Flexbox
Here is how to do it. First, we will activate flexbox:
#page {
display:flex;
}
This will turn the #page element into a flex container. It will be displayed as a block level element (it will take the full width of the page), and will turn all the elements inside it into flex items. This is the important bit – we want our main content and sidebar to be flex items, so they take the full height of the page.
But we also want to make the #page element 1200px wide as a maximum, and we want it to be centered. It is still a normal block element, so we can center it as such:
#page {
display:flex;
/* Centering the page */
max-width:1200px;
margin:0 auto;
}
Perfect! Now we have to give widths to the #main element and the sidebar:
#main {
/* This makes the element grow and take all available space, not taken by the sidebar */
flex-grow:1;
}
aside {
/* Give the sidebar a default width, and prevent it from shrinking */
flex-shrink:0;
width:280px;
}
Done! Our layout is nearly finished. When the content of the #main element causes it to become longer, it will enlarge the #page, which in turn would also grow the aside (and the other way around). The last thing to do is make the sidebar go beneath the main content on small screens, which can be done with a simple media query:
@media all and (max-width: 800px) {
#page {
flex-flow:column;
}
/* Make the sidebar take the entire width of the screen */
aside {
width:auto;
}
}
By default flex containers have a flex-flow
value of row, which shows elements side-by-side. On small screens we are switching it to vertical orientation, which pushes the sidebar below the main content.
I’ve omitted some of the CSS that does not pertain to the layout for brevity. You can download the entire example from the button near the top of this article. It is worth mentioning that I have not provided fallbacks for IE 10, which implements a slightly older version of the flexbox spec, so it only works in Firefox, Chrome, Safari, Opera and IE 11.
With this our sidebar is ready!
Conclusion
There is more to learn about flexbox, but I hope that this article gives you a quick start. Flexbox is by no means the only way to create this layout, but it is the way forward for building web interfaces. If you can afford to drop support for older IE, you can use it today.
If you are looking for a way to convert your PHP script into a Windows .EXE file, the latest addition to the
Free PHP Compilers: native code, .NET
and Java bytecode compilers page might interest you. As the title suggests, the page also lists compilers that can change
your PHP program into Java and .NET executables. These compilers may be useful if you (say) want to write a program for
the desktop but only know the PHP computer language.
Listening to what your visitors have to say, is always beneficial when planning new features or changes in your website. For a long time we’ve been limited to just setting up a contact form and hoping that quality feedback will follow, which unfortunately is not always the case.
Today we are taking things up a notch – we are applying the same social principles that have brought success to sharing sites such as Digg and delicious, and encourage visitors to suggest and vote on features that they want implemented on your website.
The XHTML
Starting with the new HTML5 doctype, we define the opening and closing head and title tags, and include the main stylesheet of the app – styles.css, in the document.
suggestions.php
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Feature Suggest w/ PHP, jQuery & MySQL | Tutorialzine Demo</title>
<link rel="stylesheet" type="text/css" href="styles.css" />
</head>
<body>
<div id="page">
<div id="heading" class="rounded">
<h1>Feature Suggest<i>for Tutorialzine.com</i></h1>
</div>
<!-- The generated suggestion list comes here -->
<form id="suggest" action="" method="post">
<p>
<input type="text" id="suggestionText" class="rounded" />
<input type="submit" value="Submit" id="submitSuggestion" />
</p>
</form>
</div>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script src="script.js"></script>
</body>
</html>
After this comes the body tag and the #page div, which is the main container element. It holds the heading, the unordered list with all the suggestions (which is generated by PHP, as you will see in a moment), and the submit form.
Lastly we include the jQuery library from Google’s AJAX Library CDN, and our own script.js file, which is discussed in detail in the last section of this tutorial.
Feature Suggest App w/ jQuery, PHP, MySQL
The Table Schema
The app uses two MySQL tables to store data. Suggestions and Suggestions_votes. The first table contains the text of the suggestion and data such as rating and the number of votes the item has received. The second table keeps record of the IPs of the voters and prevents more than one vote to be cast in a single day per IP.
Suggestion Table Schema
To speed up the selection queries, an index is defined on the rating field. This helps when showing the suggestions ordered by popularity.
The suggestion votes table has a primary key consisting of three fields – the suggestion_id, the IP of the voter, and the date of the vote. And because primary keys do not allow for duplicate rows, we can be sure that users can vote only once per day by just checking the value of the affected_rows variable after the insert.
Suggestions_Votes Schema
The PHP
Before delving into the generation of the suggestion items and the AJAX interactions, first we have to take a look at the suggestion PHP class. It uses two PHP magic methods (apart from the constructor) to provide rich functionality to our code. When generating the front page, PHP runs a MySQL select query against the database, and creates an object of this class for every table row. The columns of the row are added as properties to the object.
suggestion.class.php
class Suggestion
{
private $data = array();
public function __construct($arr = array())
{
if(!empty($arr)){
// The $arr array is passed only when we manually
// create an object of this class in ajax.php
$this->data = $arr;
}
}
public function __get($property){
// This is a magic method that is called if we
// access a property that does not exist.
if(array_key_exists($property,$this->data)){
return $this->data[$property];
}
return NULL;
}
public function __toString()
{
// This is a magic method which is called when
// converting the object to string:
return '
<li id="s'.$this->id.'">
<div class="vote '.($this->have_voted ? 'inactive' : 'active').'">
<span class="up"></span>
<span class="down"></span>
</div>
<div class="text">'.$this->suggestion.'</div>
<div class="rating">'.(int)$this->rating.'</div>
</li>';
}
}
The __toString() method is used to create a string representation of the object. With its help we can build the HTML markup, complete with the suggestion title and number of votes.
The __get() method is used to route the access to undefined properties of the class to the $data array. This means that if we access $obj->suggestion, and this property is undefined, it is going to be fetched from the $data array, and returned to us as if it existed. This way we can just pass an array to the constructor, instead of setting up all the properties. We are using this when creating an object in ajax.php.
Now lets proceed with the generation of the unordered list on the front page.
suggestions.php
require "connect.php";
require "suggestion.class.php";
// Converting the IP to a number. This is a more effective way
// to store it in the database:
$ip = sprintf('%u',ip2long($_SERVER['REMOTE_ADDR']));
// The following query uses a left join to select
// all the suggestions and in the same time determine
// whether the user has voted on them.
$result = $mysqli->query("
SELECT s.*, if (v.ip IS NULL,0,1) AS have_voted
FROM suggestions AS s
LEFT JOIN suggestions_votes AS v
ON(
s.id = v.suggestion_id
AND v.day = CURRENT_DATE
AND v.ip = $ip
)
ORDER BY s.rating DESC, s.id DESC
");
$str = '';
if(!$mysqli->error)
{
// Generating the UL
$str = '<ul class="suggestions">';
// Using MySQLi's fetch_object method to create a new
// object and populate it with the columns of the result query:
while($suggestion = $result->fetch_object('Suggestion')){
$str.= $suggestion; // Uses the __toString() magic method.
}
$str .='</ul>';
}
After running the query, we use the fetch_object() method of the $result object. This method creates an object of the given class for every row in the result, and assigns the columns of that row to the object as public properties.
PHP also manages the AJAX requests sent by jQuery. This is done in ajax.php. To distinguish one AJAX action from another, the script takes a $_GET['action'] parameter, which can have one of two values – ‘vote‘ or ‘submit‘.
ajax.php
require "connect.php";
require "suggestion.class.php";
// If the request did not come from AJAX, exit:
if($_SERVER['HTTP_X_REQUESTED_WITH'] !='XMLHttpRequest'){
exit;
}
// Converting the IP to a number. This is a more effective way
// to store it in the database:
$ip = sprintf('%u',ip2long($_SERVER['REMOTE_ADDR']));
if($_GET['action'] == 'vote'){
$v = (int)$_GET['vote'];
$id = (int)$_GET['id'];
if($v != -1 && $v != 1){
exit;
}
// Checking to see whether such a suggest item id exists:
if(!$mysqli->query("SELECT 1 FROM suggestions WHERE id = $id")->num_rows){
exit;
}
// The id, ip and day fields are set as a primary key.
// The query will fail if we try to insert a duplicate key,
// which means that a visitor can vote only once per day.
$mysqli->query("
INSERT INTO suggestions_votes (suggestion_id,ip,day,vote)
VALUES (
$id,
$ip,
CURRENT_DATE,
$v
)
");
if($mysqli->affected_rows == 1)
{
$mysqli->query("
UPDATE suggestions SET
".($v == 1 ? 'votes_up = votes_up + 1' : 'votes_down = votes_down + 1').",
rating = rating + $v
WHERE id = $id
");
}
}
else if($_GET['action'] == 'submit'){
// Stripping the content
$_GET['content'] = htmlspecialchars(strip_tags($_GET['content']));
if(mb_strlen($_GET['content'],'utf-8')<3){
exit;
}
$mysqli->query("INSERT INTO suggestions SET suggestion = '".$mysqli->real_escape_string($_GET['content'])."'");
// Outputting the HTML of the newly created suggestion in a JSON format.
// We are using (string) to trigger the magic __toString() method.
echo json_encode(array(
'html' => (string)(new Suggestion(array(
'id' => $mysqli->insert_id,
'suggestion' => $_GET['content']
)))
));
}
When jQuery fires the ‘vote‘ request, it does not expect any return values, so the script does not output any. In the ‘submit‘ action, however, jQuery expects a JSON object to be returned, containing the HTML markup of the suggestion that was just inserted. This is where we create a new Suggestion object for the sole purpose of using its __toString() magic method and converting it with the inbuilt json_encode() function.
Suggest & Vote on Features
The jQuery
All of the jQuery code resides in script.js. It listens for click events on the green and red arrows. But as suggestions can be inserted at any point, we are using the live() jQuery method, so we can listen for the event even on elements that are not yet created.
script.js
$(document).ready(function(){
var ul = $('ul.suggestions');
// Listening of a click on a UP or DOWN arrow:
$('div.vote span').live('click',function(){
var elem = $(this),
parent = elem.parent(),
li = elem.closest('li'),
ratingDiv = li.find('.rating'),
id = li.attr('id').replace('s',''),
v = 1;
// If the user's already voted:
if(parent.hasClass('inactive')){
return false;
}
parent.removeClass('active').addClass('inactive');
if(elem.hasClass('down')){
v = -1;
}
// Incrementing the counter on the right:
ratingDiv.text(v + +ratingDiv.text());
// Turning all the LI elements into an array
// and sorting it on the number of votes:
var arr = $.makeArray(ul.find('li')).sort(function(l,r){
return +$('.rating',r).text() - +$('.rating',l).text();
});
// Adding the sorted LIs to the UL
ul.html(arr);
// Sending an AJAX request
$.get('ajax.php',{action:'vote',vote:v,'id':id});
});
$('#suggest').submit(function(){
var form = $(this),
textField = $('#suggestionText');
// Preventing double submits:
if(form.hasClass('working') || textField.val().length<3){
return false;
}
form.addClass('working');
$.getJSON('ajax.php',{action:'submit',content:textField.val()},function(msg){
textField.val('');
form.removeClass('working');
if(msg.html){
// Appending the markup of the newly created LI to the page:
$(msg.html).hide().appendTo(ul).slideDown();
}
});
return false;
});
});
When a click on one of those arrows occurs, jQuery determines whether the ‘inactive’ class is present on the LI element. This class is only assigned to the suggestion, if the user has voted during the last day, and, if present, the script will ignore any click events.
Notice how $.makeArray is used to turn the jQuery objects, containing the LI elements, into a true array. This is done, so we can use the array.sort() method and pass it a custom sort function, which takes two LIs at the same time and outputs a negative integer, zero or a positive integer depending on which of the two elements has a grater rating. This array is later inserted into the unordered list.
The CSS
Now that we have all the markup generated, we can move on with the styling. As the styling is pretty much trivial, I only want to show you the class that rounds the top-left and bottom-right corners of the elements that it is applied to. You can see the rest of the CSS rules in styles.css.
styles.css
.rounded,
#suggest,
.suggestions li{
-moz-border-radius-topleft:12px;
-moz-border-radius-bottomright:12px;
-webkit-border-top-left-radius:12px;
-webkit-border-bottom-right-radius:12px;
border-top-left-radius:12px;
border-bottom-right-radius:12px;
}
Notice that the Mozilla syntax differs from the standard in the way it targets the different corners of the element. Keeping that in mind, we can apply this class to pretty much every element, as you can see from the demonstration.
With this our Feature Suggest App is complete!
Conclusion
If you plan to set up this script on your own server, you would need to create the two suggestion tables by running the code found in tables.sql in the SQL tab of phpMyAdmin. Also remember to fill in your database connection details in connect.php.
You can use this script to gather precious feedback from your visitors. You can also disable the option for users to add new suggestions, and use it as a kind of an advanced poll system.
Be sure to share your thoughts in your comment section below.
In this tutorial, we are making a dynamic FAQ section. The script, with the help of jQuery & YQL, will pull the contents of a shared spreadsheet in your Google Docs account, and use the data to populate the FAQ section with questions and answers.
The best aspect of this solution, is that you can change the contents of the FAQ section from within Google Docs – just edit the spreadsheet. You can even leverage the rest of Google Docs’ features, such as collaborative editing. This way, a small team can support the FAQ section without the need of a dedicated CMS solution.
Thanks to Chris Ivarson for designing the Google Docs icon, used in the featured illustration of this tutorial.
Google Docs
Before starting work on the FAQ section, we first need to create a new Google Docs Spreadsheet. As you probably already have an account with Google (if not, create one), we’ll move straight to the interesting part.
In a blank spreadsheet, start filling in two columns of data. The first column should contain the questions, and the second one the answers, that are going to become entries in your FAQ section. Each FAQ goes on a separate line. You can see the one that I created here.
After this, click Share > Publish as webpage and choose CSV from the dropdown list. This will generate a link to your spreadsheet in the form of a regular CSV file, which we will use later.
The HTML
The first step in developing the script is the markup. The #page div is the main container element. It is the only div with an explicit width. It is also centered in the middle of the page with a margin:auto, as you will see in the CSS part of the tut.
faq.html
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link rel="stylesheet" type="text/css" href="styles.css" />
</head>
<body>
<div id="page">
<div id="headingSection">
<h1>Frequently Asked Questions</h1>
<a class="button expand" href="#">Expand</a>
</div>
<div id="faqSection">
<!-- The FAQs are inserted here -->
</div>
</div>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script src="script.js"></script>
</body>
</html>
The stylesheet is included in the head of the document, and the jQuery library and our script.js are included at the bottom. All of these are discussed in detail in the next sections of this tutorial.
The #headingSection contains the h1 heading, and the expand/collapse button. After it comes the #faqSection div, where the FAQ entries are inserted after jQuery fetched the contents of your Google Docs Spreadsheet.
The FAQ entries are organized as a definition list structure (dl). This is one of the least used HTML elements, but is perfect for our task. Here is how it looks once jQuery adds it to the page.
faq.html
<dl>
<dt><span class="icon"></span>How does this FAQ section work?</dt>
<dd>With the help of jQuery and YQL, this script pulls the latest data ..</dd>
<dt><span class="icon"></span>Can you modify it?</dt>
<dd>This is the best part of it - you can change the contents ..</dd>
</dl>
The dl element holds a dt for each question and a dd for each answer. The dd elements are hidden with display:none, and are only shown with a slideDown animation once the respective dt is clicked.
Dynamic FAQ Section
The CSS
The styles, (held in styles.css) are pretty straightforward and self-explanatory. As mentioned above, only the #page div, which acts as the main container, is explicitly assigned a width. It is also centered in the middle of the page with an auto value for the left/right margins.
styles.css – Part 1
#page{
width:753px;
margin:50px auto;
}
#headingSection{
background-color:#7b8b98;
padding:40px;
padding-left:60px;
position:relative;
border:1px solid #8b9ba7;
border-bottom:none;
}
#faqSection{
background:url('img/faq_bg.jpg') repeat-y #fff;
padding:20px 90px 60px 60px;
border:1px solid white;
text-shadow:1px 1px 0 white;
}
h1{
color:#fff;
font-size:36px;
font-weight:normal;
}
/* The expand / collapse button */
a.button{
background:url('img/buttons.png') no-repeat;
width:80px;
height:38px;
position:absolute;
right:50px;
top:45px;
text-indent:-9999px;
overflow:hidden;
border:none !important;
}
a.button.expand:hover{ background-position:0 -38px;}
a.button.collapse{ background-position:0 -76px;}
a.button.collapse:hover{ background-position:0 bottom;}
We are using a single anchor tag for both the expand and the collapse button, by assigning it either the expand or the collapse CSS class. These classes determine which parts of the background image are offset into view. The background image itself is four times the height of the button, and contains a normal and a hover state for both the expand and collapse button versions.
styles.css – Part 2
/* Definition Lists */
dt{
color:#8F9AA3;
font-size:25px;
margin-top:30px;
padding-left:25px;
position:relative;
cursor:pointer;
border:1px solid transparent;
}
dt:hover{ color:#5f6a73;}
dt .icon{
background:url('img/bullets.png') no-repeat;
height:12px;
left:0;
position:absolute;
top:11px;
width:12px;
}
dt.opened .icon{ background-position:left bottom;}
dd{
font-size:14px;
color:#717f89;
line-height:1.5;
padding:20px 0 0 25px;
width:580px;
display:none;
}
When a definition title (dt) is clicked, its respective dd is expanded into view (as you will see in the next section). With this, the dt is also assigned an opened class. This class helps jQuery determine which FAQ’s are opened, and in the same time affects the styling of the small bullet on the left of the title.
FAQ expanded
The jQuery
Moving to probably the most interesting part of the tutorial. If you’ve been following the tutorials on this site, you’ve probably noticed that YQL finds its way into a lot of the tutorials here. The main reason behind this, is that YQL makes possible for developers to use it as a proxy for a wide range of APIs, and implement a diverse functionality entirely in JavaScript.
Today we are using YQL to fetch our Google Spreadsheet as CSV and parse it, so that it is available as a regular JSON object. This way, we end up with a free and easy to update data storage for our simple app.
script.js
$(document).ready(function(){
// The published URL of your Google Docs spreadsheet as CSV:
var csvURL = 'https://spreadsheets.google.com/pub?key='+
'0Ahe1-YRnPKQ_dEI0STVPX05NVTJuNENhVlhKZklNUlE&hl=en&output=csv';
// The YQL address:
var yqlURL = "http://query.yahooapis.com/v1/public/yql?q="+
"select%20*%20from%20csv%20where%20url%3D'"+encodeURIComponent(csvURL)+
"'%20and%20columns%3D'question%2Canswer'&format=json&callback=?";
$.getJSON(yqlURL,function(msg){
var dl = $('<dl>');
// Looping through all the entries in the CSV file:
$.each(msg.query.results.row,function(){
// Sometimes the entries are surrounded by double quotes. This is why
// we strip them first with the replace method:
var answer = this.answer.replace(/""/g,'"').replace(/^"|"$/g,'');
var question = this.question.replace(/""/g,'"').replace(/^"|"$/g,'');
// Formatting the FAQ as a definition list: dt for the question
// and a dd for the answer.
dl.append('<dt><span class="icon"></span>'+
question+'</dt><dd>'+answer+'</dd>');
});
// Appending the definition list:
$('#faqSection').append(dl);
$('dt').live('click',function(){
var dd = $(this).next();
// If the title is clicked and the dd is not currently animated,
// start an animation with the slideToggle() method.
if(!dd.is(':animated')){
dd.slideToggle();
$(this).toggleClass('opened');
}
});
$('a.button').click(function(){
// To expand/collapse all of the FAQs simultaneously,
// just trigger the click event on the DTs
if($(this).hasClass('collapse')){
$('dt.opened').click();
}
else $('dt:not(.opened)').click();
$(this).toggleClass('expand collapse');
return false;
});
});
});
It may not be clear from the code above, but jQuery sends a JSONP request to YQL’s servers with the following YQL query:
SELECT * FROM csv
WHERE url='https://spreadsheets.google.com/...'
AND columns='question,answer'
CSV is a YQL table, which takes the URL of a csv file and a list of column names. It returns a JSON object with the column names as its properties. The script then filters them (stripping off unnecessary double quotes) and inserts them as a definition list (DL) into the page.
With this our dynamic FAQ section is complete!
Customization
To use this script with your own spreadsheet, you only need to edit the csvURL variable in script.js, and replace it with the CSV URL of your spreadsheet. You can obtain this address from Share > Publish as webpage > CSV dropdown. Also, be aware that when you make changes to the spreadsheet, it could take a few minutes for the changes to take effect. You can speed this up by clicking the Republish now button, found in the same overlay window.
Obtaining the CSV URL
Final Words
You can use the same technique to power different kinds of dynamic pages. However, this implementation does have its shortcomings. All the content is generated with JavaScript, which means that it will not be visible to search engines.
To guarantee that your data will be crawled, you can take a different route. You could use PHP, or other back-end language, to fetch and display the data from YQL in a fixed interval of time – say 30 minutes (or even less frequently, if you don’t plan to update the data often).
Be sure to share your suggestions in the comment section below.
In this tutorial, we are using jQuery UI’s autocomplete widget, to build a simple AJAX movie search form. The script is going to use TheMovieDatabase.org‘s free API, to provide auto suggestions against a vast database of movie titles.
For those of you who might not be familiar with TMDb.org, this is an open, community driven movie database. It is similar to IMDb, which you’ve probably heard about, but also provides a number of useful API’s for developers.
Prerequisites
Before being able to use the API, you need to get a free developer key from TMDb after a quick registration. After this, remember to copy your key to movieInfo.php from the download archive.
Step 1 – XHTML
The markup consists of the two main div containers – #logo and #holder. The former holds the icon and the logo text in the form of transparent PNG images (defined as backgrounds to the respective divs), while the latter contains the search form and the submit button.
movieApp.html
<div id="page">
<div id="logo">
<div id="icon"></div>
<div id="movieAppLabel"></div>
</div>
<div id="holder">
<form action="http://www.themoviedb.org/search" method="post" target="_blank">
<fieldset>
<input type="text" id="movieName" name="search" />
</fieldset>
</form>
<a href="#" class="button">Submit</a>
</div>
</div>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.2/jquery-ui.min.js"></script>
<script src="script.js"></script>
Notice that the action attribute of the form is pointed at TMDB’s search page. The search terms are passed via POST with the #movieName text field. You can test it by filling in a movie name and submitting the form.
Lastly in the page are included jQuery, jQuery UI and our own script file. We are using jQuery UI’s autocomplete widget to display a drop down list of movie titles which are fetched from TMDb’s API. You can see the markup that is generated by the widget below.
<input class="ui-autocomplete-input"/>
<ul class="ui-autocomplete ui-menu ui-widget ui-widget-content ui-corner-all">
<li class="ui-menu-item">
<a class="ui-corner-all">item 1</a>
</li>
<li class="ui-menu-item">
<a class="ui-corner-all">item 2</a>
</li>
<li class="ui-menu-item">
<a class="ui-corner-all">item 3</a>
</li>
</ul>
This code is generated automatically by the widget and appended before the closing body tag.
Simple Movie Search App
Step 2 – PHP
When you start typing a movie title in the text box of the form, an AJAX request is sent to moveInfo.php. This script sends a search request to TMDb’s API, with our developer key. The service returns a JSON object with suitable movie titles. The script processes them and outputs them back as a response to the AJAX request.
Lets take a closer look at how this works.
movieInfo.php
/**
* Define your API key below. To obtain one, visit
* http://www.themoviedb.org/account/signup
*/
$api_key = '...';
// If the request was not issued by AJAX, or
// the search term is missing, exit:
if(!$_SERVER["HTTP_X_REQUESTED_WITH"] || !$_GET['term']){
exit;
}
include 'tmdbAPI/TMDb.php';
$tmdb = new TMDb($api_key);
// Send a search API request to TMDb,
// and parse the returned JSON data:
$json = json_decode($tmdb->searchMovie($_GET['term']));
$response = array();
$i=0;
foreach($json as $movie){
// Only movies existing in the IMDB catalog (and are not adult) are shown
if(!$movie->imdb_id || $movie->adult) continue;
if($i >= 8 ) break;
// The jQuery autocomplete widget shows the label in the drop down,
// and adds the value property to the text box.
$response[$i]['value'] = $movie->name;
$response[$i]['label'] = $movie->name . ' <small>(' . date('Y',strtotime($movie->released)).')</small>';
$i++;
}
// Presenting the response as a JSON object:
echo json_encode($response);
Luckily for us, there is a PHP class available, that handles all communication with the TMDb API. We just need to include it into the page, and provide the developer API key we received from TMDb. The search terms, that the user has entered into the search box, are available in $_GET['term']. Calling the searchMovie() method with these terms, will yield a JSON object, containing all kinds of information about the movies that match our search criteria. You can see a sample response below.
[{
"score": 8.750235,
"popularity": 3,
"translated": true,
"adult": false,
"language": "en",
"name": "The Hitchhiker's Guide to the Galaxy",
"alternative_name": "The Hitchhikers Guide to the Galaxy",
"movie_type": "movie",
"id": 7453,
"imdb_id": "tt0371724",
"url": "http://www.themoviedb.org/movie/7453",
"rating": 6.8,
"certification": "PG",
"overview": "Mere seconds before the Earth is to be demolished by an alien construction crew, Arthur Dent is swept off the planet by his friend Ford Prefect, a researcher penning a new edition of \"The Hitchhiker's Guide to the Galaxy.\"",
"released": "2005-04-20",
"posters": [{
"image": {
"type": "poster",
"size": "original",
"height": 1000,
"width": 675,
"url": "http://hwcdn.themoviedb.org/posters/16e/4bcc96cd017a3c0f2600016e/the-hitchhiker-s-guide-to-the-galaxy-original.jpg",
"id": "4bcc96cd017a3c0f2600016e"
}
}],
"version": 22,
"last_modified_at": "2010-07-19 22:59:02"
}]
The response contains the title of the movie, an overview, release date, a corresponding IMDB id, and even posters and fan art. We do not need most of this information, so PHP reduces it only to a title and a release year, after which outputs it in the form of a JSON object, ready to be used by the autocomplete. This brings us to the next step.
Step 3 – jQuery
As you know, jQuery comes with a lot of useful functionality in the form of plugins. There is also a dedicated extension of the library, for building user interfaces, known as jQuery UI. It gives developers widgets, which are ready for use and easy to customize. One of these widgets is the new autocomplete widget, introduced in the newer versions of the library.
Lets take a look at how it is used.
script.js
$(document).ready(function(){
// Caching the movieName textbox:
var movieName = $('#movieName');
// Defining a placeholder text:
movieName.defaultText('Type a Move Title');
// Using jQuery UI's autocomplete widget:
movieName.autocomplete({
minLength : 5,
source : 'movieInfo.php'
});
$('#holder .button').click(function(){
if(movieName.val().length && movieName.data('defaultText') != movieName.val()){
$('#holder form').submit();
}
});
});
// A custom jQuery method for placeholder text:
$.fn.defaultText = function(value){
var element = this.eq(0);
element.data('defaultText',value);
element.focus(function(){
if(element.val() == value){
element.val('').removeClass('defaultText');
}
}).blur(function(){
if(element.val() == '' || element.val() == value){
element.addClass('defaultText').val(value);
}
});
return element.blur();
}
To create an autocomplete, we just need to call the autocomplete() method. It takes a number of optional parameters. The most important ones are minLength (which prevents request to the server from being fired before a certain number of characters has been typed) and source, which determines the data that is shown in the drop down list.
Source can take either an array with strings, a URL (to which an AJAX request will be sent) or a callback function. In our case, the URL of movieInfo.php will suffice.
Here is a sample response, which is returned by movieInfo.php (this JSON object was compiled after a request to TMDb’s API for “Hitchhiker’s guide“).
[{
"value": "Hachiko: A Dog's Story",
"label": "Hachiko: A Dog's Story <small>(2009)<\/small>"
},
{
"value": "Teenage Hitch-hikers",
"label": "Teenage Hitch-hikers <small>(1975)<\/small>"
},
{
"value": "The Hitchhiker's Guide to the Galaxy",
"label": "The Hitchhiker's Guide to the Galaxy <small>(2005)<\/small>"
},
{
"value": "The Hitch-Hiker",
"label": "The Hitch-Hiker <small>(1953)<\/small>"
}]
Each object in the array contains a value and a label property. The label is only shown in the dropdown list, while the value is inserted into the textbox once the item is selected.
Custom Styled jQuery UI Autocomplete
Step 4 – CSS
Now that all the markup is generated and is in place, it is time to start beautifying.
styles.css – Part 1
#page{
width:600px;
margin:150px auto 0;
}
/* Logo */
#logo{
width:380px;
position:relative;
height:90px;
margin:0 auto;
}
#icon{
width:80px;
height:86px;
background:url('img/icon.png') no-repeat;
float:left;
}
#movieAppLabel{
width:268px;
height:58px;
background:url('img/logo_txt.png') no-repeat;
position:absolute;
right:0;
top:18px;
}
/* The Search Box & Holder */
#holder{
width:530px;
height:145px;
background:url('img/holder.png') no-repeat center center;
margin:30px auto;
position:relative;
}
#holder fieldset{
position:absolute;
top:52px;
left:40px;
border-bottom:1px solid #fff;
}
#holder input{
font-family:'Myriad Pro',Arial,Helvetica,sans-serif;
border:none;
border-bottom:1px solid #bbb;
background:none;
color:#8D8D8D;
font-size:20px;
padding:4px 0;
width:250px;
text-shadow:1px 1px #fff;
outline:none;
}
In the first part of the code, we style the #logo, and the #holder divs. The shutter icon and the logo text are defined as backgrounds to the #icon and #movieAppLabel divs respectively. Relative positioning is applied to the #holder so that it is easier to position the input box and the submit button.
styles.css – Part 2
fieldset{
border:none;
}
/* The Blue Button */
a.button{
background:url('img/buttons.png') no-repeat;
width:105px;
height:37px;
position:absolute;
top:52px;
right:42px;
text-indent:-9999px;
overflow:hidden;
border:none !important;
}
a.button:hover{
background-position:left bottom;
}
/* Styling the markup generated by the autocomplete jQuery UI widget */
ul.ui-autocomplete{
width:250px;
background-color:#f5f5f5;
border:1px solid #fff;
outline:1px solid #ccc;
}
ul.ui-autocomplete li{
list-style:none;
border-bottom:1px solid #e0e0e0;
border-top:1px solid #fff;
}
ul.ui-autocomplete li:first-child{
border-top:none;
}
ul.ui-autocomplete li:last-child{
border-bottom:none;
}
ul.ui-autocomplete li a{
color:#999;
border:none !important;
text-decoration:none !important;
padding:10px 17px;
display:block;
}
#ui-active-menuitem{
background-color:#fff;
color:#666;
cursor:pointer;
}
jQuery UI does come with its own styles, however they are rather clunky and do not fit well into the current design. This is why we are applying a number of rules (starting from line 23), which apply a custom design to the autocomplete widget. The structure of the widget is basically an unordered list, with each of the suggested items being a hyperlink in a li element. With this in mind (and after looking up the appropriate class names from the code in step one) we can safely style the drop down list and perfectly blend it with the rest of the design.
With this our Simple Movie Search App is complete!
To Wrap it Up
You can modify this script to use any kind of api and data. This can be a powerful tool, as it might assist users in typing search terms that they may not normally think of themselves. For example providing your product names as search suggestions, may be an effective tactic to expose more of your merchandise and improve sales.
What do you think? How would you improve this app?
When you publish something online, there are not that many ways to determine whether people like what you have to say. Comments, the cornerstone of blogging, are too demanding, and users often prefer not to post one. If you’ve dropped by Behance, you’ve probably noticed their appreciate badge, which is a neat solution to this exact problem. With it people share their appreciation for somebody’s work.
Today we are implementing such a badge, which you can include in every page of your website with a bit of jQuery magic. So go ahead and download the zip from the button above (PSD included!) and continue with the tutorial.
The Database Schema
The script we are doing today uses two tables. The first holds one record for each of the pages which have the appreciate button enabled. The second one stores the IP of the person that voted along the unique ID of the page. This way we can easily determine whether the person has previously voted for the page and display the appropriate version of the button (active or disabled).
Table Schema appreciate_pages
The hash field holds an MD5 sum of the URL of the page. This way we add an UNIQUE index which will speed up the selects we run on the records, as well ensure there are no duplicate records in the table. The appreciated column holds the number of appreciations of the pages.
Table Schema appreciate_votes
The appreciate_votes table contains the IP of the person that has voted (in the form of an integer), and the id of the page from the appreciate_pages table. The timestamp is automatically updated to the current time when an insert occurs.
You can create these two tables by running the code from tables.sql in the SQL section of phpMyAdmin from the downloadable archive, part of this tutorial.
Step 1 – XHTML
Lets start with the XHTML part of the tutorial. The markup of the page is extremely simple. To have the appreciate button functioning, you just need to provide a container in which the button is inserted, and an optional element, which holds the total number of clicks on the button. You can safely omit the latter one, leaving you with only one div to code.
page.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>An AJAX Click To Appreciate Badge</title>
<link rel="stylesheet" type="text/css" href="styles.css" />
<link rel="stylesheet" type="text/css" href="appreciateMe/appreciate.css"/>
</head>
<body>
<div id="countDiv"></div>
<div id="main"></div>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js" type="text/javascript"></script>
<script src="appreciateMe/plugin.js" type="text/javascript"></script>
<script src="script.js" type="text/javascript"></script>
</body>
</html>
In the page above, you can see that I am including two stylesheet files. The first is styles.css, which is used to style the page, and appreciate.css, which is located in the plugin directory, and is responsible for the styling of the appreciate button.
Before the closing body tag, you can see that I also include the jQuery library from Google’s CDN repository, the plugin.js file and script.js, which uses the plugin to create the button on the page. You will only need to change the contents of script.js to make the script working on your pages.
Click To Appreciate - looks good on both dark and light backgrounds
Step 2 – PHP
PHP handles the database interactions and is on the backend of the AJAX requests. Most of the script logic is located in c script.php which you can see below. But first lets take a look at connect.php, which handles the database connection.
appreciateMe/connect.php
$db_host = 'localhost';
$db_user = 'YourUsername';
$db_pass = 'YouPassword';
$db_name = 'NameOfDB';
@$mysqli = new mysqli($db_host, $db_user, $db_pass, $db_name);
if (mysqli_connect_errno()) {
die('<h1>Could not connect to the database</h1>');
}
$mysqli->set_charset("utf8");
Up until now, we’ve always used the old mysql extension for database connections under PHP, as it is a bit easier to use and I wanted to keep the code compatible with PHP 4. However, with the recent announcement that WordPress (our favorite blogging engine) will be dropping support for that version of PHP, I decided that it is time to also make the switch to the new version – MySQLi (MySQL improved).
As you can see from the code above, the only major difference with the old way we connected to a database, is that here we create a MySQLi object instead of using the mysql_ functions. Also, as you will see in a moment, when we query the database a MySQL resource object is returned, which in turn has its own set of methods. This might sound intimidating, but it will become perfectly clear once you see it in action.
appreciateMe/script.php
/* Setting the error reporting level */
error_reporting(E_ALL ^ E_NOTICE);
include 'connect.php';
if(!$_GET['url'] || !filter_input(INPUT_GET,'url',FILTER_VALIDATE_URL)){
exit;
}
$pageID = 0;
$appreciated = 0;
$jsonArray = array();
$hash = md5($_GET['url']);
$ip = sprintf('%u',ip2long($_SERVER['REMOTE_ADDR']));
// $result is an object:
$result = $mysqli->query("SELECT id,appreciated FROM appreciate_pages WHERE hash='".$hash."'");
if($result)
{
list($pageID,$appreciated) = $result->fetch_row();
// fetch_row() is a method of result
}
// The submit parameter denotes that we need to write to the database
if($_GET['submit'])
{
if(!$pageID)
{
// If the page has not been appreciated yet, insert a new
// record to the database.
$mysqli->query("
INSERT INTO appreciate_pages
SET
hash='".$hash."',
url='".$mysqli->real_escape_string($_GET['url'])."'"
);
if($mysqli->affected_rows){
// The insert_id property contains the value of
// the primary key. In our case this is also the pageID.
$pageID = $mysqli->insert_id;
}
}
// Write the vote to the DB, so the user can vote only once
$mysqli->query("
INSERT INTO appreciate_votes
SET
ip = ".$ip.",
pageid = ".$pageID
);
if($mysqli->affected_rows){
$mysqli->query("
UPDATE appreciate_pages
SET appreciated=appreciated+1 WHERE id=".$pageID
);
// Increment the appreciated field
}
$jsonArray = array('status'=>1);
}
else
{
// Only print the stats
$voted = 0;
// Has the user voted?
$res = $mysqli->query("
SELECT 1 FROM appreciate_votes
WHERE ip=".$ip." AND pageid=".$pageID
);
if($res->num_rows){
$voted = 1;
}
$jsonArray = array('status'=>1,'voted'=>$voted,'appreciated'=>$appreciated);
}
// Telling the browser to interpret the response as JSON:
header('Content-type: application/json');
echo json_encode($jsonArray);
The script handles two different types of AJAX requests – read only request (which returns a JSON object with information about the number of appreciations of the page, and whether the current user has clicked the button), and write requests (which save the visitor’s vote to the database, and if necessary, save the page URL and hash as well).
As you an see in the code snippet above, one of the first things that the script does is to calulate the MD5 hash of the page. This is used as a unique key in the database, as URLs have unlimited length which is incompatible with MySQL’s UNIQUE keys. As an MD5 hash is unique for most practical purposes, we can safely use it in our selects and inserts, instead of the long URL addresses.
In the last line of the code, we convert the $jsonArray array into a valid JSON object with the inbuilt json_encode PHP function, and output it with a applicatoin/json content type.
Click To Appreciate - Inactive
Step 3 – jQuery
Inside the appreciateMe directory you can find the plugin.js file. You must include it in the page you wish to show the Appreciate button on. It uses AJAX to request data from the PHP backend and uses the response it receives to create the markup of the button.
appreciateMe/plugin.js
function(){
$.appreciateButton = function(options){
// The options object must contain a URL and a Holder property
// These are the URL of the Appreciate php script, and the
// div in which the badge is inserted
if(!'url' in options || !'holder' in options){
return false;
}
var element = $(options.holder);
// Forming the url of the current page:
var currentURL = window.location.protocol+'//'+
window.location.host+window.location.pathname;
// Issuing a GET request. A rand parameter is passed
// to prevent the request from being cached in IE
$.get(options.url,{url:currentURL,rand:Math.random()},function(response){
// Creating the appreciate button:
var button = $('<a>',{
href:'',className:'appreciateBadge',
html:'Appreciate Me'
});
if(!response.voted){
// If the user has not voted previously,
// make the button active / clickable.
button.addClass('active');
}
else button.addClass('inactive');
button.click(function(){
if(button.hasClass('active')){
button.removeClass('active').addClass('inactive');
if(options.count){
// Incremented the total count
$(options.count).html(1 + parseInt(response.appreciated));
}
// Sending a GET request with a submit parameter.
// This will save the appreciation to the MySQL DB.
$.getJSON(options.url,{url:currentURL,submit:1});
}
return false;
});
element.append(button);
if(options.count){
$(options.count).html(response.appreciated);
}
},'json');
return element;
}
})(jQuery);
The script basically creates a new method in the main jQuery object. This differs from the plugins that we usually do, in that this type of plugins are not called on a set of elements (no need to select elements). You can just call $.appreciateButton() while passing a configuration object as a parameter. This is exactly what we’ve done in script.js add a button to the page:
script.js
$(document).ready(function(){
// Creating an appreciate button.
$.appreciateButton({
url : 'appreciateMe/script.php', // URL to the PHP script.
holder : '#main', // The button will be inserted here.
count : '#countDiv' // Optional. Will show the total count.
});
});
The configuration object, which is passed as a parameter, has to contain a url and a holder properties, whereas count is optional. Notice that I’ve specified the path to script.php relatively, as appreciateMe is a child directory of the one the page is currently in.
However, if you plan to add the script to a site with a variable path structure, you should probably specify an absolute path. Either add a leading slash, or provide a complete URL with http://.
Step 4 – CSS
Now that we have all the markup and code in place, it is time to turn to the styling. The CSS rules that style the appreciate badge are located in appreciate.css. You could optionally copy these rules to your main stylesheet file, if you’d like to avoid the extra request, but beware that you will need to change the paths to the background images.
appreciateMe/appreciate.css
.appreciateBadge{
width:129px;
height:129px;
display:block;
text-indent:-9999px;
overflow:hidden;
background:url('sprite.png') no-repeat;
text-decoration:none;
border:none;
}
.appreciateBadge.active{
background-position:left top;
}
.appreciateBadge.active:hover{
background-position:0 -129px;
}
.appreciateBadge.inactive{
background-position:left bottom;
cursor:default;
}
There are three versions of the appreciate badge image. A default one, a hover one, and an inactive one. All three of these reside in the same file – sprite.png, one below the other. With this technique you can switch between the versions instantaneously by offsetting the background image of the hyperlink.
styles.css
#main{
margin:80px auto;
width:130px;
}
#countDiv{
color:#eee;
font-size:35px;
margin-right:120px;
position:absolute;
right:50%;
top:265px;
}
You can find the rest of the styles, which refine the looks of page.html, in styles.css. Only two sets of styles affect the appreciate button directly. The #main div, which contains the button and centers it on the page, and #countDiv in which the total number of appreciations is inserted.
With this our Click to Appreciate Badge is complete!
Conclusion
Before being able to run this script on your server, you first have to replace the MySQL credentials in connect.php with your own. Also, you will need to run the contents of tables.sql in the SQL tab of phpMyAdmin, so the two tables are created. Lastly, depending on your URL paths, you may have to change the URL property of appreciateMe/script.php in the script.js JavaScript File.
Video presentations are a great addition to any product page. With a presentation you can showcase your product’s features without making the visitor read through long paragraphs of text. But apart from producing the video, you still need to manually convert it and find (or code) some sort of flash player that will display it on your site.
The other possible path is that you upload it to a video sharing site such as youtube, but you are going to have a rough time trying to incorporate the player into your design.
Luckily for us, YouTube does provide a solution to this problem – their chromeless player (a stripped down version of the regular embeddable player), which allow you to build and style your own custom controls. This way you have both a quick and secure way to include videos in your pages, and the freedom to customize any way you might want to.
The Idea
Today we are going to make a jQuery plugin which uses YouTube’s chromeless player, and creates our own set of minimalistic controls, which allows for perfect integration with your designs. The supported controls include a Play/Pause/Replay button, and a clickable progress bar.
The plugin is going to use YouTube’s gdata api to determine whether embedding has been allowed for the video, and fetch extensive information about it, such as title, description, tags, screenshots & more, which you can use to improve the plugin.
Using the plugin to embed videos is extremely easy:
// Embed a video into the #player div:
$('#player').youTubeEmbed('http://www.youtube.com/watch?v=u1zgFlCw8Aw');
// Chaining is also supported:
$('#player').youTubeEmbed('http://www.youtube.com/watch?v=u1zgFlCw8Aw');
.youTubeEmbed('http://www.youtube.com/watch?v=AsdfFdwlzdAw');
You can also specify a width for the embedded video (the height will be calculated automatically depending on the aspect ratio), and choose to disable the progress bar:
$('#player').youTubeEmbed({
video : 'http://www.youtube.com/watch?v=u1zgFlCw8Aw',
width : 600, // Height is calculated automatically
progressBar : false // Hide the progress bar
});
You can grab the plugin from the download button above, and start with the first step.
Step 1 – XHTML
Our plugin depends on jQuery SWFObject to embed the SWF files in the page. Below you can see the combined markup that is generated by both of the plugins.
youtube-player.html
<div class="flashContainer" style="width: 640px; height: 360px;">
<object height="360" width="640" id="video_26ELpS3Wc4Q" type="application/x-shockwave-flash"
data="http://www.youtube.com/apiplayer?enablejsapi=1&version=3">
<param value="always" name="allowScriptAccess">
<param value="transparent" name="wmode">
<param value="video_id=26ELpS3Wc4Q&playerapiid=26ELpS3Wc4Q"
name="flashvars">
<param value="http://www.youtube.com/apiplayer?enablejsapi=1&version=3"
name="movie">
</object>
<div class="controlDiv play"></div>
<div class="progressBar">
<div class="elapsed"></div>
</div>
</div>
The .flashContainerDiv is dynamically created by the plugin for each video on the page. It is populated with the embed code generated by SWFObject, the .controlDiv (which acts as a play/pause button) and the progress bar.
As mentioned above, the insertion of the player itself is handled by the SWFObject plugin. Depending on the browser, it can output either an object element, or a non-standard embed element for IE. This lifts the burden from us and allows us to concentrate on tasks such as querying YouTube’s APIs and building the player controls.
Custom YouTube Player
Step 2 – jQuery
The plugin’s code is located in the youTubeEmbed-jquery-1.0.js file. However, before being able to use it, you need to include the latest version of the jQuery library in the page, along with the jQuery SWFObject plugin and lastly script.js, which inserts two videos in the demonstration page and handles the submissions of the preview form.
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script src="jquery.swfobject.1-1-1.min.js"></script>
<script src="youTubeEmbed/youTubeEmbed-jquery-1.0.js"></script>
<script src="script.js"></script>
Before we start digging into the player plugin’s code, lets take a look at a sample response from YouTube’s gdata api. It can give you a lot of useful information about a video, including duration, access control (both of which used by the plugin) and all sorts of additional data such as title, description, tags, screenshots and more.
Sample JSON response
{
"id": "u1zgFlCw8Aw",
"uploaded": "2008-03-05T01:22:17.000Z",
"updated": "2010-07-23T01:02:42.000Z",
"uploader": "GoogleDevelopers",
"category": "People",
"title": "The YouTube API: Upload, Player APIs and more!",
"description": "Listen to the YouTube APIs and Tools team talk about...",
"tags": ["youtube", "launch", "api", "engineering"],
"thumbnail": {
"sqDefault": "http://i.ytimg.com/vi/u1zgFlCw8Aw/default.jpg",
"hqDefault": "http://i.ytimg.com/vi/u1zgFlCw8Aw/hqdefault.jpg"
},
"player": {
"default": "http://www.youtube.com/watch?v=u1zgFlCw8Aw",
"mobile": "http://m.youtube.com/details?v=u1zgFlCw8Aw"
},
"content": {
"1": "rtsp://v4.cache5.c.youtube.com/CiILE..",
"5": "http://www.youtube.com/v/u1zgFlCw8Aw?f..",
"6": "rtsp://v3.cache4.c.youtube.com/CiILENy73.."
},
"duration": 259,
"location": "san bruno, ca",
"rating": 4.3,
"likeCount": "119",
"ratingCount": 144,
"viewCount": 251024,
"favoriteCount": 164,
"commentCount": 118,
"accessControl": {
"syndicate": "allowed",
"commentVote": "allowed",
"rate": "allowed",
"list": "allowed",
"comment": "allowed",
"embed": "allowed",
"videoRespond": "allowed"
}
}
All the fields of this response objects are available as properties in the data variable (data.fieldname). You could potentially modify the plugin to show the title with a link to the video page on youtube, or show the rating of the video.
Now lets dive directly into the script’s source code.
youTubeEmbed-jquery-1.0.js – Part 1
(function($){
$.fn.youTubeEmbed = function(settings){
// Settings can be either a URL string,
// or an object
if(typeof settings == 'string'){
settings = {'video' : settings}
}
// Default values
var def = {
width : 640,
progressBar : true
};
settings = $.extend(def,settings);
var elements = {
originalDIV : this, // The "this" of the plugin
container : null, // A container div, inserted by the plugin
control : null, // The control play/pause button
player : null, // The flash player
progress : null, // Progress bar
elapsed : null // The light blue elapsed bar
};
try{
settings.videoID = settings.video.match(/v=(\w+)/)[1];
// safeID is a stripped version of videoID,
// ready for use as a JavaScript function name
settings.safeID = settings.videoID.replace(/[^a-z0-9]/ig,'');
} catch (e){
// If the url was invalid, just return the "this"
return elements.originalDIV;
}
// Fetch data about the video from YouTube's API
var youtubeAPI = 'http://gdata.youtube.com/feeds/api/videos?v=2&alt=jsonc';
$.get(youtubeAPI,{'q':settings.videoID},function(response){
var data = response.data;
if(!data.totalItems || data.items[0].accessControl.embed!="allowed"){
// If the video was not found, or embedding is not allowed;
return elements.originalDIV;
}
// data holds API info about the video:
data = data.items[0];
settings.ratio = 3/4;
if(data.aspectRatio == "widescreen"){
settings.ratio = 9/16;
}
settings.height = Math.round(settings.width*settings.ratio);
We start by defining our script as a jQuery plugin by adding it as a function to the $.fn object. To make the code easier to follow and read, I put all the elements of the page, such as the control and the progressBar divs in a structure called elements.
After extracting the id of the video (a unique 11 character sequence after the ?v= parameter), we send a JSONP request to youtube’s gdata API. Depending on whether such a video exists, and on whether embedding is allowed on it, we proceed with calculating the aspect ratio. The height of the video is calculated by using this ratio and multiplying it to the width.
youTubeEmbed-jquery-1.0.js – Part 2
// Creating a container inside the original div, which will
// hold the object/embed code of the video
elements.container = $('<div>',{className:'flashContainer',css:{
width : settings.width,
height : settings.height
}}).appendTo(elements.originalDIV);
// Embedding the YouTube chromeless player
// and loading the video inside it:
elements.container.flash({
swf : 'http://www.youtube.com/apiplayer?enablejsapi=1&version=3',
id : 'video_'+settings.safeID,
height : settings.height,
width : settings.width,
allowScriptAccess:'always',
wmode : 'transparent',
flashvars : {
"video_id" : settings.videoID,
"playerapiid" : settings.safeID
}
});
// We use get, because we need the DOM element
// itself, and not a jquery object:
elements.player = elements.container.flash().get(0);
// Creating the control Div. It will act as a ply/pause button
elements.control = $('<div>',{className:'controlDiv play'})
.appendTo(elements.container);
// If the user wants to show the progress bar:
if(settings.progressBar){
elements.progress = $('<div>',{className:'progressBar'})
.appendTo(elements.container);
elements.elapsed = $('<div>',{className:'elapsed'})
.appendTo(elements.progress);
elements.progress.click(function(e){
// When a click occurs on the progress bar, seek to the
// appropriate moment of the video.
var ratio = (e.pageX-elements.progress.offset().left)/elements.progress.outerWidth();
elements.elapsed.width(ratio*100+'%');
elements.player.seekTo(Math.round(data.duration*ratio), true);
return false;
});
}
In the second part of the code, we use the SWFObject plugin to generate the embed code of the youtube chromeless player. Notice that the id of the video is passed as a flashvar so the player can load it directly. The safeID variable (a JavaScript safe version of the videoid) becomes the value of the id parameter of the to-be generated object element. This way we can later fetch the DOM element by running document.getElementById(‘video_’+settings.safeID) and get access to the methods which control the youtube player (play, pause etc).
youTubeEmbed-jquery-1.0.js – Part 3
var initialized = false;
// Creating a global event listening function for the video
// (required by YouTube's player API):
window['eventListener_'+settings.safeID] = function(status){
var interval;
if(status==-1) // video is loaded
{
if(!initialized)
{
// Listen for a click on the control button:
elements.control.click(function(){
if(!elements.container.hasClass('playing')){
// If the video is not currently playing, start it:
elements.control.removeClass('play replay').addClass('pause');
elements.container.addClass('playing');
elements.player.playVideo();
if(settings.progressBar){
interval = window.setInterval(function(){
elements.elapsed.width(
((elements.player.getCurrentTime()/data.duration)*100)+'%'
);
},1000);
}
} else {
// If the video is currently playing, pause it:
elements.control.removeClass('pause').addClass('play');
elements.container.removeClass('playing');
elements.player.pauseVideo();
if(settings.progressBar){
window.clearInterval(interval);
}
}
});
initialized = true;
}
else{
// This will happen if the user has clicked on the
// YouTube logo and has been redirected to youtube.com
if(elements.container.hasClass('playing'))
{
elements.control.click();
}
}
}
In order to be able to control the video player, we need to be notified when certain events (like playback stopped, video ready etc) occur. Normally, this would mean that we need to pass a callback function, which is executed by the player every time such an event happens.
Unfortunately, flash can only execute functions if they are defined in the global scope and cannot see the functions which are defined inside the plugin. However, by creating functions with unique names (with the safeID) and explicitly adding them to the window object we can make this happen. If it weren’t for this little trick, it would be impossible for the plugin to work.
youTubeEmbed-jquery-1.0.js – Part 4
if(status==0){ // video has ended
elements.control.removeClass('pause').addClass('replay');
elements.container.removeClass('playing');
}
}
// This global function is called when the player is loaded.
// It is shared by all the videos on the page:
if(!window.onYouTubePlayerReady)
{
window.onYouTubePlayerReady = function(playerID){
document.getElementById('video_'+playerID).addEventListener('onStateChange','eventListener_'+playerID);
}
}
},'jsonp');
return elements.originalDIV;
}
})(jQuery);
The event listening function we created in the previous section of the code, is attached to the player with the addEventListener method. It is called every time when a “stateChange” occurs (playback start, playback pause, end of playback etc). A numeric code is passed to the event listening function as a parameter, corresponding to the event.
Now lets take a look at how our plugin is used.
script.js
$(document).ready(function(){
$('#player').youTubeEmbed('http://www.youtube.com/watch?v=u1zgFlCw8Aw');
/*
//You can alternatively pass an object:
$('#player').youTubeEmbed({
video : 'http://www.youtube.com/watch?v=u1zgFlCw8Aw',
width : 600, // Height is calculated automatically
progressBar : false // Hide the progress bar
});
*/
});
You just need to call the youTubeEmbed() method and pass it either a string or a configuration object. If a string is passed, it is assumed to be the URL of a YouTube video. If you are passing an object make sure that you are passing the video property with a correct video URL.
Video Playing with a Progress Bar
Step 3 – CSS
Finally we are left with applying a few CSS styles to the player. They will change the design of the player controls and define the way they are positioned in the player window.
youTubeEmbed-jquery-1.0.css
.flashContainer{
/* Setting the container to relative positioning
so we can center the control div */
position:relative;
overflow:hidden;
}
.progressBar{
display:none;
position:absolute;
width:auto;
height:8px;
left:20px;
right:105px;
bottom:20px;
background-color:#141414;
overflow:hidden;
cursor:pointer;
/* A light CSS3 bottom highlight */
-moz-box-shadow:0 1px 0 rgba(255, 255, 255, 0.3);
-webkit-box-shadow:0 1px 0 rgba(255, 255, 255, 0.3);
box-shadow:0 1px 0 rgba(255, 255, 255, 0.3);
}
.progressBar .elapsed{
position:absolute;
width:0;
height:100%;
background-color:#1fa2f6;
border-right:1px solid #49AFF0;
}
.controlDiv{
/* Centering the control div */
position:absolute;
width:120px;
height:120px;
cursor:pointer;
top:50%;
left:50%;
margin:-60px 0 0 -60px;
}
.controlDiv.play{
background:url('img/play.png') no-repeat center center;
}
.controlDiv.replay{
background:url('img/replay.png') no-repeat center center;
}
.controlDiv.pause{
background:url('img/pause.png') no-repeat -99999px;
}
.flashContainer:hover .controlDiv.pause{
background-position:center center;
}
/* Only show the progress bar when the video is playing */
.flashContainer.playing:hover .progressBar{
display:block;
}
To customize the look of the player you just need to change the color values above. Also you can edit the png files with the play/pause buttons. Clearly this is much easier than modifying the looks of the default youtube player. It also strips all of the unnecessary chrome and leaves you with what matters most – your video.
With this our Custom YouTube Player plugin is complete!
Did you like this tutorial? Share your thoughts in the comment section below.
In this tutorial we are going to write a simple jQuery tooltip plugin. It is going to convert the title attributes of elements withing your page, into a series of colorful tooltips. Six color themes are available, so you can easily match it with the rest of your design.
Step 1 – XHTML
The plugin we are about to write today, works by converting the title of an element on the page to a structure of three spans, which form a tooltip, displayed on hover. So if you start with a regular link like this one:
<a href="http://tutorialzine.com/" class="blue" title="Go to Tutorialzine">Tutorialzine</a>
jQuery will convert it to the markup you can see below.
<a class="blue colorTipContainer" href="http://tutorialzine.com/">Tutorialzine
<span class="colorTip" style="margin-left: -60px;">Go to Tutorialzine
<span class="pointyTipShadow"></span>
<span class="pointyTip"></span>
</span>
</a>
Notice that the first block of code above specifies a “blue” class name. This is because we are overriding the default color of the tip (yellow). You can choose from red, blue, green, yellow, white and black, or you can create your own colors in the plugin stylesheet.
Users which have JavaScript disabled are still going to see the regular title tooltips on the page, so the script degrades gracefully.
Step 2 – CSS
To display the colorful tips on your page, you first need to include the plugin stylesheet file in the head section of the html document.
<link rel="stylesheet" type="text/css" href="colortip-1.0/colortip-1.0-jquery.css"/>
You could also just copy the styles from the file and paste them directly in your main stylesheet, if you do not want to keep a separate include file. This stylesheet defines both the positioning and the design of the tooltips. It comes with six color themes, and you can easily add more.
colortip-1.0-jquery.css – Part 1
.colorTipContainer{
position:relative;
text-decoration:none !important;
}
.colorTip{
/* This class is assigned to the color tip span by jQuery */
display:none;
position:absolute;
left:50%;
top:-30px;
padding:6px;
background-color:white;
font-family:Arial,Helvetica,sans-serif;
font-size:11px;
font-style:normal;
line-height:1;
text-decoration:none;
text-align:center;
text-shadow:0 0 1px white;
white-space:nowrap;
-moz-border-radius:4px;
-webkit-border-radius:4px;
border-radius:4px;
}
.pointyTip,.pointyTipShadow{
/* Setting a thick transparent border on a 0x0 div to create a triangle */
border:6px solid transparent;
bottom:-12px;
height:0;
left:50%;
margin-left:-6px;
position:absolute;
width:0;
}
.pointyTipShadow{
/* The shadow tip is 1px larger, so it acts as a border to the tip */
border-width:7px;
bottom:-14px;
margin-left:-7px;
}
The .colorTipContainer class is assigned to the elements to which the color tips are added. It sets their positioning to relative, so that the tips can be centered above them.
A neat CSS trick is used to create the triangular pointy tips. As you know, divs and spans can only take a rectangular shape (or an ellipse / rounded rectangle if you apply border-radius). However, if you apply a thick border to a 0 by 0 element, the only way the borders are going to fit is to take up a triangular space on each side. Then, by setting the default border color to transparent, you can make visible any of the four triangular shapes.
Making a triangular shape with a div
This is actually done on both the .pointyTip and the .pointyTipShadow spans, in order to give an impression that the pointy tip has a border applied so it matches the rest of the colortip design. Now lets take a closer look at the six color themes.
colortip-1.0-jquery.css – Part 2
/* 6 Available Color Themes */
.white .pointyTip{ border-top-color:white;}
.white .pointyTipShadow{ border-top-color:#ddd;}
.white .colorTip{
background-color:white;
border:1px solid #DDDDDD;
color:#555555;
}
.yellow .pointyTip{ border-top-color:#f9f2ba;}
.yellow .pointyTipShadow{ border-top-color:#e9d315;}
.yellow .colorTip{
background-color:#f9f2ba;
border:1px solid #e9d315;
color:#5b5316;
}
.blue .pointyTip{ border-top-color:#d9f1fb;}
.blue .pointyTipShadow{ border-top-color:#7fcdee;}
.blue .colorTip{
background-color:#d9f1fb;
border:1px solid #7fcdee;
color:#1b475a;
}
.green .pointyTip{ border-top-color:#f2fdf1;}
.green .pointyTipShadow{ border-top-color:#b6e184;}
.green .colorTip{
background-color:#f2fdf1;
border:1px solid #b6e184;
color:#558221;
}
.red .pointyTip{ border-top-color:#bb3b1d;}
.red .pointyTipShadow{ border-top-color:#8f2a0f;}
.red .colorTip{
background-color:#bb3b1d;
border:1px solid #8f2a0f;
color:#fcfcfc;
text-shadow:none;
}
.black .pointyTip{ border-top-color:#333;}
.black .pointyTipShadow{ border-top-color:#111;}
.black .colorTip{
background-color:#333;
border:1px solid #111;
color:#fcfcfc;
text-shadow:none;
}
Remember that only the borders are the only visible part of the pointy tips. You can add your own color themes by following the same structure.
Step 3 – jQuery
First you need to include the jquery library and the plugin files to the page, after which our script.js file, which is going to utilize the plugin and add colortips to the links on the page.
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script type="text/javascript" src="colortip-1.0/colortip-1.0-jquery.js"></script>
<script type="text/javascript" src="script.js"></script>
For performance reasons, I’ve put this code at the bottom of the colortips.html file. This allow for the page design to be rendered before the browser blocks for the loading of the js files.
Now lets take a look at the colortip the plugin.
colortip-1.0-jquery.js – Part 1
(function($){
$.fn.colorTip = function(settings){
var defaultSettings = {
color : 'yellow',
timeout : 500
}
var supportedColors = ['red','green','blue','white','yellow','black'];
/* Combining the default settings object with the supplied one */
settings = $.extend(defaultSettings,settings);
/*
* Looping through all the elements and returning them afterwards.
* This will add chainability to the plugin.
*/
return this.each(function(){
var elem = $(this);
// If the title attribute is empty, continue with the next element
if(!elem.attr('title')) return true;
// Creating new eventScheduler and Tip objects for this element.
// (See the class definition at the bottom).
var scheduleEvent = new eventScheduler();
var tip = new Tip(elem.attr('title'));
// Adding the tooltip markup to the element and
// applying a special class:
elem.append(tip.generate()).addClass('colorTipContainer');
// Checking to see whether a supported color has been
// set as a classname on the element.
var hasClass = false;
for(var i=0;i<supportedColors.length;i++)
{
if(elem.hasClass(supportedColors[i])){
hasClass = true;
break;
}
}
// If it has been set, it will override the default color
if(!hasClass){
elem.addClass(settings.color);
}
When you call the plugin, you can pass a configuration object, which holds the default color and the timeout after which the tooltips disappear on mouseleave.
You can choose from six possible colors and assign them as a class name to the element. The plugin would check if a color class is present, and if it is not, it will apply the default one you’ve passed in the config object. If you’ve not passed a config object, yellow will be used instead.
Colortip jQuery Plugin
colortip-1.0-jquery.js – Part 2
// On mouseenter, show the tip, on mouseleave set the
// tip to be hidden in half a second.
elem.hover(function(){
tip.show();
// If the user moves away and hovers over the tip again,
// clear the previously set event:
scheduleEvent.clear();
},function(){
// Schedule event actualy sets a timeout (as you can
// see from the class definition below).
scheduleEvent.set(function(){
tip.hide();
},settings.timeout);
});
// Removing the title attribute, so the regular OS titles are
// not shown along with the tooltips.
elem.removeAttr('title');
});
}
/*
/ Event Scheduler Class Definition
*/
function eventScheduler(){}
eventScheduler.prototype = {
set : function (func,timeout){
// The set method takes a function and a time period (ms) as
// parameters, and sets a timeout
this.timer = setTimeout(func,timeout);
},
clear: function(){
// The clear method clears the timeout
clearTimeout(this.timer);
}
}
In the second part of the plugin code, we bind event listeners to the hover event (combination of a mouseenter and a mouseleave event).
Notice the event scheduler class. It is used to set a function to be executed after a predetermined period of time. At its heart lays a call to setTimeout. A clear() method is also provided as a part of the class, so previously scheduled events can be destroyed (useful when you move your mouse away from a tooltip and then move it back over before it has disappeared).
colortip-1.0-jquery.js – Part 3
/*
/ Tip Class Definition
*/
function Tip(txt){
this.content = txt;
this.shown = false;
}
Tip.prototype = {
generate: function(){
// The generate() method returns either a previously generated element
// stored in the tip variable, or generates and saves it in tip for
// later use, after which returns it.
return this.tip || (this.tip = $('<span class="colorTip">'+this.content+
'<span class="pointyTipShadow"></span><span class="pointyTip"></span></span>'));
},
show: function(){
if(this.shown) return;
// Center the tip and start a fadeIn animation
this.tip.css('margin-left',-this.tip.outerWidth()/2).fadeIn('fast');
this.shown = true;
},
hide: function(){
this.tip.fadeOut();
this.shown = false;
}
}
})(jQuery);
In the last part of the code we define the tip class. It has a generate, show and hide methods. An object from this class is created for each of the tooltips. The generate method is called first, and the tooltip is stored in the local this.tip variable. The show and hide methods operate on this variable to run fade in/out animations.
Now we are only left with calling the plugin and adding colortips to all the links on the page.
script.js
$(document).ready(function(){
/* Adding a colortip to any tag with a title attribute: */
$('[title]').colorTip({color:'yellow'});
});
The configuration object here might as well be omitted, because yellow is the default value anyway. But you could specify a different color for the tooltips. If you apply red, green, blue, white, yellow or black as a class name of the element, the design of the tooltip will be overridden.
With this our Colortip plugin is complete!
Wrapping it up
Using the plugin we wrote today is really easy, as it does not require any specific configuration. It just takes a series of elements and replaces their title attributes with colorful tooltips. You can also easily create your own tooltip designs by just including three additional style classes to your stylesheet and adding an element to the supported colors array in colortip-1.0-jquery.js.
What do you think? How would you improve this plugin?