Author Archive

Ten Oddities And Secrets About JavaScript

Advertisement in Ten Oddities And Secrets About JavaScript
 in Ten Oddities And Secrets About JavaScript  in Ten Oddities And Secrets About JavaScript  in Ten Oddities And Secrets About JavaScript

JavaScript. At once bizarre and yet beautiful, it is surely the programming language that Pablo Picasso would have invented. Null is apparently an object, an empty array is apparently equal to false, and functions are bandied around as though they were tennis balls.

This article is aimed at intermediate developers who are curious about more advanced JavaScript. It is a collection of JavaScript’s oddities and well-kept secrets. Some sections will hopefully give you insight into how these curiosities can be useful to your code, while other sections are pure WTF material. So, let’s get started.

Data Types And Definitions

1. Null is an Object

Let’s start with everyone’s favorite JavaScript oddity, as well known as it is. Null is apparently an object, which, as far as contradictions go, is right up there with the best of them. Null? An object? “Surely, the definition of null is the total absence of meaningful value,� you say. You’d be right. But that’s the way it is. Here’s the proof:

alert(typeof null); //alerts 'object'

Despite this, null is not considered an instance of an object. (In case you didn’t know, values in JavaScript are instances of base objects. So, every number is an instance of the Number object, every object is an instance of the Object object, and so on.) This brings us back to sanity, because if null is the absence of value, then it obviously can’t be an instance of anything. Hence, the following evaluates to false:

alert(null instanceof Object); //evaluates false

2. NaN is a Number

You thought null being an object was ridiculous? Try dealing with the idea of NaN — “not a numberâ€� — being a number! Moreover, NaN is not considered equal to itself! Does your head hurt yet?

alert(typeof NaN); //alerts 'Number'
alert(NaN === NaN); //evaluates false

In fact NaN is not equal to anything. The only way to confirm that something is NaN is via the function isNaN().

3. An Array With No Keys == False (About Truthy and Falsy)

Here’s another much-loved JavaScript oddity:

alert(new Array() == false); //evaluates true

To understand what’s happening here, you need to understand the concepts of truthy and falsy. These are sort of true/false-lite, which will anger you somewhat if you majored in logic or philosophy.

I’ve read many explanations of what truthy and falsy are, and I feel the easiest one to understand is this: in JavaScript, every non-boolean value has a built-in boolean flag that is called on when the value is asked to behave like a boolean; like, for example, when you compare it to a boolean.

Because apples cannot be compared to pears, when JavaScript is asked to compare values of differing data types, it first “coercesâ€� them into a common data type. False, zero, null, undefined, empty strings and NaN all end up becoming false — not permanently, just for the given expression. An example to the rescue:

var someVar = 0;
alert(someVar == false); //evaluates true

Here, we’re attempting to compare the number 0 to the boolean false. Because these data types are incompatible, JavaScript secretly coerces our variable into its truthy or falsy equivalent, which in the case of 0 (as I said above) is falsy.

You may have noticed that I didn’t include empty arrays in the list of falsies above. Empty arrays are curious things: they actually evaluate to truthy but, when compared against a boolean, behave like a falsy. Confused yet? With good cause. Another example perhaps?

var someVar = []; //empty array
alert(someVar == false); //evaluates true
if (someVar) alert('hello'); //alert runs, so someVar evaluates to true

To avoid coercion, you can use the value and type comparison operator, ===, (as opposed to ==, which compares only by value). So:

var someVar = 0;
alert(someVar == false); //evaluates true – zero is a falsy
alert(someVar === false); //evaluates false – zero is a number, not a boolean

Phew. As you’ve probably gathered, this is a broad topic, and I recommend reading up more on it — particularly on data coercion, which, while not uniquely a JavaScript concept, is nonetheless prominent in JavaScript.

I discuss the concept of truthy and falsy and data coercion more over here. And if you really want to sink your teeth into what happens internally when JavaScript is asked to compare two values, then check out section 11.9.3 of the ECMA-262 document specification.

Regular Expressions

4. replace() Can Accept a Callback Function

This is one of JavaScript’s best-kept secrets and arrived in v1.3. Most usages of replace() look something like this:

alert('10 13 21 48 52'.replace(/\d+/g, '*')); //replace all numbers with *

This is a simple replacement: a string, an asterisk. But what if we wanted more control over how and when our replacements take place? What if we wanted to replace only numbers under 30? This can’t be achieved with regular expressions alone (they’re all about strings, after all, not maths). We need to jump into a callback function to evaluate each match.

alert('10 13 21 48 52'.replace(/\d+/g, function(match) {
	return parseInt(match) < 30 ? '*' : match;
}));

For every match made, JavaScript calls our function, passing the match into our match argument. Then, we return either the asterisk (if the number matched is under 30) or the match itself (i.e. no match should take place).

5. Regular Expressions: More Than Just Match and Replace

Many intermediate JavaScript developers get by just on match and replace with regular expressions. But JavaScript defines more methods than these two.

Of particular interest is test(), which works like match except that it doesn’t return matches: it simply confirms whether a pattern matches. In this sense, it is computationally lighter.

alert(/\w{3,}/.test('Hello')); //alerts 'true'

The above looks for a pattern of three or more alphanumeric characters, and because the string Hello meets that requirement, we get true. We don’t get the actual match, just the result.

Also of note is the RegExp object, by which you can create dynamic regular expressions, as opposed to static ones. The majority of regular expressions are declared using short form (i.e. enclosed in forward slashes, as we did above). That way, though, you can’t reference variables, so making dynamic patterns is impossible. With RegExp(), though, you can.

function findWord(word, string) {
	var instancesOfWord = string.match(new RegExp('\\b'+word+'\\b', 'ig'));
	alert(instancesOfWord);
}
findWord('car', 'Carl went to buy a car but had forgotten his credit card.');

Here, we’re making a dynamic pattern based on the value of the argument word. The function returns the number of times that word appears in string as a word in its own right (i.e. not as a part of other words). So, our example returns car once, ignoring the car tokens in the words Carl and card. It forces this by checking for a word boundary (\b) on either side of the word that we’re looking for.

Because RegExp are specified as strings, not via forward-slash syntax, we can use variables in building the pattern. This also means, however, that we must double-escape any special characters, as we did with our word boundary character.

Functions And Scope

6. You Can Fake Scope

The scope in which something executes defines what variables are accessible. Free-standing JavaScript (i.e. JavaScript that does not run inside a function) operates within the global scope of the window object, to which everything has access; whereas local variables declared inside functions are accessible only within that function, not outside.

var animal = 'dog';
function getAnimal(adjective) { alert(adjective+' '+this.animal); }
getAnimal('lovely'); //alerts 'lovely dog';

Here, our variable and function are both declared in the global scope (i.e. on window). Because this always points to the current scope, in this example it points to window. Therefore, the function looks for window.animal, which it finds. So far, so normal. But we can actually con our function into thinking that it’s running in a different scope, regardless of its own natural scope. We do this by calling its built-in call() method, rather than the function itself:

var animal = 'dog';
function getAnimal(adjective) { alert(adjective+' '+this.animal); };
var myObj = {animal: 'camel'};
getAnimal.call(myObj, 'lovely'); //alerts 'lovely camel'

Here, our function runs not on window but on myObj — specified as the first argument of the call method. Essentially, call() pretends that our function is a method of myObj (if this doesn’t make sense, you might want to read up on JavaScript’s system of prototypal inheritance). Note also that any arguments we pass to call() after the first will be passed on to our function — hence we’re passing in lovely as our adjective argument.

I’ve heard JavaScript developers say that they’ve gone years without ever needing to use this, not least because good code design ensures that you don’t need this smoke and mirrors. Nonetheless, it’s certainly an interesting one.

As an aside, apply() does the same job as call(), except that arguments to the function are specified as an array, rather than as individual arguments. So, the above example using apply() would look like this:

getAnimal.apply(myObj, ['lovely']); //func args sent as array

7. Functions Can Execute Themselves

There’s no denying it:

(function() { alert('hello'); })(); //alerts 'hello'

The syntax is simple enough: we declare a function and immediately call it just as we call other functions, with () syntax. You might wonder why we would do this. It seems like a contradiction in terms: a function normally contains code that we want to execute later, not now, otherwise we wouldn’t have put the code in a function.

One good use of self-executing functions (SEFs) is to bind the current values of variables for use inside delayed code, such as callbacks to events, timeouts and intervals. Here is the problem:

var someVar = 'hello';
setTimeout(function() { alert(someVar); }, 1000);
var someVar = 'goodbye';

Newbies in forums invariably ask why the alert in the timeout says goodbye and not hello. The answer is that the timeout callback function is precisely that — a callback — so it doesn’t evaluate the value of someVar until it runs. And by then, someVar has long since been overwritten by goodbye.

SEFs provide a solution to this problem. Instead of specifying the timeout callback implicitly as we do above, we return it from an SEF, into which we pass the current value of someVar as arguments. Effectively, this means we pass in and isolate the current value of someVar, protecting it from whatever happens to the actual variable someVar thereafter. This is like taking a photo of a car before you respray it; the photo will not update with the resprayed color; it will forever show the color of the car at the time the photo was taken.

var someVar = 'hello';
setTimeout((function(someVar) {
	return function()  { alert(someVar); }
})(someVar), 1000);
var someVar = 'goodbye';

This time, it alerts hello, as desired, because it is alerting the isolated version of someVar (i.e. the function argument, not the outer variable).

The Browser

8. Firefox Reads and Returns Colors in RGB, Not Hex

I’ve never really understood why Mozilla does this. Surely it realizes that anyone interrogating computed colors via JavaScript is interested in hex format and not RGB. To clarify, here’s an example:


Hello, world! <script> var ie = navigator.appVersion.indexOf('MSIE') != -1; var p = document.getElementById('somePara'); alert(ie ? p.currentStyle.color : getComputedStyle(p, null).color); </script>

While most browsers will alert ff9900, Firefox returns rgb(255, 153, 0), the RGB equivalent. Plenty of JavaScript functions are out there for converting RGB to hex.

Note that when I say computed color, I’m referring to the current color, regardless of how it is applied to the element. Compare this to style, which reads only style properties that were implicitly set in an element’s style attribute. Also, as you’ll have noticed in the example above, IE has a different method of detecting computed styles from other browsers.

As an aside, jQuery’s css() method encompasses this sort of computed detection, and it returns styles however they were applied to an element: implicitly or through inheritance or whatever. Therefore, you would relatively rarely need the native getComputedStyle and currentStyle.

Miscellaneous

9. 0.1 + 0.2 !== 0.3

This one is an oddity not just in JavaScript; it’s actually a prevailing problem in computer science, and it affects many languages. The output of this is 0.30000000000000004.

This has to do with an issue called machine precision. When JavaScript tries to execute the line above, it converts the values to their binary equivalents.

This is where the problem starts. 0.1 is not really 0.1 but rather its binary equivalent, which is a near-ish (but not identical) value. In essence, as soon as you write the values, they are doomed to lose their precision. You might have just wanted two simple decimals, but what you get, as Chris Pine notes, is binary floating-point arithmetic. Sort of like wanting your text translated into Russian but getting Belorussian. Similar, but not the same.

More is going on here, but it’s beyond the scope of this article (not to mention the mathematical capabilities of this author).

Workarounds for this problem are a favorite on computer science and developer forums. Your choice, to a point, comes down to the sort of calculations you’re doing. The pros and cons of each are beyond the scope of this article, but the common choice is between the following:

  1. Converting to integers and calculating on those instead, then converting back to decimals afterward; or
  2. Tweaking your logic to allow for a range rather than a specific result.

So, for example, rather than…

var num1 = 0.1, num2 = 0.2, shouldEqual = 0.3;
alert(num1 + num2 == shouldEqual); //false

… we would do this:

alert(num1 + num2 > shouldEqual - 0.001 && num1 + num2 < shouldEqual + 0.001); //true

Translated, this says that because 0.1 + 0.2 is apparently not 0.3, check instead that it’s more or less 0.3 — specifically, within a range of 0.001 on either side of it. The obvious drawback is that, for very precise calculations, this will return inaccurate results.

10. Undefined Can Be Defined

OK, let’s end with a silly, rather inconsequential one. Strange as it might sound, undefined is not actually a reserved word in JavaScript, even though it has a special meaning and is the only way to determine whether a variable is undefined. So:

var someVar;
alert(someVar == undefined); //evaluates true

So far, so normal. But:

undefined = "I'm not undefined!";
var someVar;
alert(someVar == undefined); //evaluates false!

You can also check Mozilla's list of all reserved words in JavaScript for future reference.

Further Resources

(al) (il)


© Mitya for Smashing Magazine, 2011. | Permalink | Post a comment | Smashing Shop | Smashing Network | About Us
Post tags: , ,


Image Manipulation With jQuery and PHP GD

Advertisement in Image Manipulation With jQuery and PHP GD
 in Image Manipulation With jQuery and PHP GD  in Image Manipulation With jQuery and PHP GD  in Image Manipulation With jQuery and PHP GD

One of the numerous advantages brought about by the explosion of jQuery and other JavaScript libraries is the ease with which you can create interactive tools for your site. When combined with server-side technologies such as PHP, this puts a serious amount of power at your finger tips.

In this article, I’ll be looking at how to combine JavaScript/jQuery with PHP and, particularly, PHP’s GD library to create an image manipulation tool to upload an image, then crop it and finally save the revised version to the server. Sure, there are plugins out there that you can use to do this; but this article aims to show you what’s behind the process. You can download the source files (updated) for reference.

We’ve all seen this sort of Web application before — Facebook, Flickr, t-shirt-printing sites. The advantages are obvious; by including a functionality like this, you alleviate the need to edit pictures manually from your visitors, which has obvious drawbacks. They may not have access to or have the necessary skills to use Photoshop, and in any case why would you want to make the experience of your visitors more difficult?

Before You Start

For this article, you would ideally have had at least some experience working with PHP. Not necessarily GD — I’ll run you through that part, and GD is very friendly anyway. You should also be at least intermediate level in JavaScript, though if you’re a fast learning beginner, you should be fine as well.

A quick word about the technologies you’ll need to work through this article. You’ll need a PHP test server running the GD library, either on your hosting or, if working locally, through something like XAMPP. GD has come bundled with PHP as standard for some time, but you can confirm this by running the phpinfo() function and verifying that it’s available on your server. Client-side-wise you’ll need a text editor, some pictures and a copy of jQuery.

Setting Up The Files

And off we go, then. Set up a working folder and create four files in it: index.php, js.js, image_manipulation.php and css.css. index.php is the actual webpage, js.js and css.css should be obvious, while image_manipulation.php will store the code that handles the uploaded image and then, later, saves the manipulated version.

In index.php, first let’s add a line of PHP to start a PHP session and call in our image_manipulation.php file:

After that, add in the DOCTYPE and skeleton-structure of the page (header, body areas etc) and call in jQuery and the CSS sheet via script and link tags respectively.

Add a directory to your folder, called imgs, which will receive the uploaded files. If you’re working on a remote server, ensure you set the permissions on the directory such that the script will be able to save image files in it.

First, let’s set up and apply some basic styling to the upload facility.

The Upload Functionality

Now to some basic HTML. Let’s add a heading and a simple form to our page that will allow the user to upload an image and assign that image a name:

<h1>Image uploader and manipulator</h1>

Please note that we specify enctype=’multipart/form-data’ which is necessary whenever your form contains file upload fields.

As you can see, the form is pretty basic. It contains 3 fields: an upload field for the image itself, a text field, so the user can give it a name and a submit button. The submit button has a name so it can act as an identifier for our PHP handler script which will know that the form was submitted.

Let’s add a smattering of CSS to our stylesheet:

/* -----------------
| UPLOAD FORM
----------------- */
#imgForm { border: solid 4px #ddd; background: #eee; padding: 10px; margin: 30px; width: 600px; overflow:hidden;}
	#imgForm label { float: left; width: 200px; font-weight: bold; color: #666; clear:both; padding-bottom:10px; }
	#imgForm input { float: left; }
	#imgForm input[type="submit"] {clear: both; }
	#img_upload { width: 400px; }
	#img_name { width: 200px; }

Now we have the basic page set up and styled. Next we need to nip into image_manipulation.php and prepare it to receive the submitted form. Which leads nicely on to validation…

Validating The Form

Open up image_manipulation.php. Since we made a point above of including it into our HTML page, we can rest assured that when it’s called into action, it will be present in the environment.

Let’s set up a condition, so the PHP knows what task it is being asked to do. Remember we named our submit button upload_form_submitted? PHP can now check its existence, since the script knows that it should start handling the form.

This is important because, as I said above, the PHP script has two jobs to do: to handle the uploaded form and to save the manipulated image later on. It therefore needs a technique such as this to know which role it should be doing at any given time.

/* -----------------
| UPLOAD FORM - validate form and handle submission
----------------- */

if (isset($_POST['upload_form_submitted'])) {
	//code to validate and handle upload form submission here
}

So if the form was submitted, the condition resolves to true and whatever code we put inside, it will execute. That code will be validation code. Knowing that the form was submitted, there are now five possible obstacles to successfully saving the file: 1) the upload field was left blank; 2) the file name field was left blank; 3) both these fields were filled in, but the file being uploaded isn’t a valid image file; 4) an image with the desired name already exists; 5) everything is fine, but for some reason, the server fails to save the image, perhaps due to file permission issues. Let’s look at the code behind picking up each of these scenarios, should any occur, then we’ll put it all together to build our validation script.

Combined into a single validation script, the whole code looks as follows.

/* -----------------
| UPLOAD FORM - validate form and handle submission
----------------- */

if (isset($_POST['upload_form_submitted'])) {

	//error scenario 1
	if (!isset($_FILES['img_upload']) || empty($_FILES['img_upload']['name'])) {
		$error = "Error: You didn't upload a file";

	//error scenario 2
	} else if (!isset($_POST['img_name']) || empty($_FILES['img_upload'])) {
		$error = "Error: You didn't specify a file name";
	} else {

		$allowedExtensions = array('jpg', 'jpeg', 'gif', 'png');
		preg_match('/\.('.implode($allowedExtensions, '|').')$/', $_FILES['img_upload']['name'], $fileExt);
		$newPath = 'imgs/'.$_POST['img_name'].'.'.$fileExt[0];

		//error scenario 3
		if (file_exists($newPath)) {
			$error = "Error: A file with that name already exists";

		//error scenario 4
		} else if (!in_array(substr($fileExt[0], 1), $allowedExtensions)) {
			$error = 'Error: Invalid file format - please upload a picture file';

		//error scenario 5
		} else if (!copy($_FILES['img_upload']['tmp_name'], $newPath)) {
			$error = 'Error: Could not save file to server';

		//...all OK!
		} else {
			$_SESSION['newPath'] = $newPath;
			$_SESSION['fileExt'] = $fileExt;
		}
	}
}

There are a couple of things to note here.

$error & $_SESSION['newPath']

Firstly, note that I’m using a variable, $error, to log whether we hit any of the hurdles. If no error occurs and the image is saved, we set a session variable, $_SESSION['new_path'], to store the path to the saved image. This will be helpful in the next step where we need to display the image and, therefore, need to know its SRC.

I’m using a session variable rather than a simple variable, so when the time comes for our PHP script to crop the image, we don’t have to pass it a variable informing the script which image to use — the script will already know the context, because it will remember this session variable. Whilst this article doesn’t concern itself deeply with security, this is a simple precaution. Doing this means that the user can affect only the image he uploaded, rather than, potentially, someone else’s previously-saved image — the user is locked into manipulating only the image referenced in $error and has no ability to enforce the PHP script to affect another image.

The $_FILES superglobal

Note that even though the form was sent via POST, we access the file upload not via the $_POST superglobal (i.e. variables in PHP which are available in all scopes throughout a script), but via the special $_FILES superglobal. PHP automatically assigns file fields to that, provided the form was sent with the required enctype='multipart/form-data' attribute. Unlike the $_POST and $_GET superglobals, the $_FILES superglobal goes a little “deeper” and is actually a multi-dimensional array. Through this, you can access not only the file itself but also a variety of meta data related to it. You’ll see how we can use this information shortly. We use this meta data in the third stage of validation above, namely checking that the file was a valid image file. Let’s look at this code in a little more detail.

Confirming the upload is an image

It’s sensible that we don’t allow the user to proceed if the uploaded file is not an image. So we need to look out for this. First, we create an array of allowed file extensions:

$allowedExtensions = array('jpg', 'jpeg', 'gif', 'png');

We then check whether or not the extension of the uploaded file is in that array. To do this, we of course need to extract the file extension. Surprisingly, the superglobal doesn’t provide this directly, so instead we’ll extract it with a regular expression.

Regular expressions are typically considered one of the hardest parts of programming to master. This is definitely true, yet they are often extremely valuable. If you want to read up more on regular expressions, take a look at Smashing Magazine’s articles Crucial Concepts Behind Advanced Regular Expressions or the excellent Regular-Expressions.info. The concept is essentiall matching patterns within strings. We know that our extension is the final part of the final name, preceded by a dot, so that forms the basis of our pattern:

preg_match('/\.('.implode($allowedExtensions, '|').')$/', $_FILES['img_upload']['name'], $fileExt);

preg_match() is the preferred function in PHP to match via REGEXP. It takes three arguments: the pattern, the string to look in, and an array to save matches to. So if a match is found — and of course it should be — our file extension will live in $fileExt[0], i.e. the first and only key of the array of matches.

Patterns are expressed as strings, and inside forward slashes (usually, but not always), so please ignore these parts. Our actual pattern starts with the dot. It has a backslash before it as it needs escaping, because otherwise it would be read as a special character (unescaped dots denote wildcard characters in the regular expression syntax). This is no different to having to escape quotes when using them inside strings, e.g.

"...and then he said \"hello, there\"";

The next part says: match any ONE of our allowed extensions. Since these live in our array, we convert them into a string via the implode() function, separated by the pipe character. Finally, the dollar character forces the expression to match the end of the string — required in our case, since a file extension is always at the end of a filename. So by the time the PHP engine has evaluated this pattern, it looks as though we had specified this (which is much more readable):

'/\.(jpg|jpeg|gif|png)$/'

Saving the file

All uploaded files are assigned a temporary home by the server until such time as the session expires or they are moved. So saving the file means moving the file from its temporary location to a permanent home. This is done via the copy() function, which needs to know two rather obvious things: what’s the path to the temporary file, and what’s the path to where we want to put it.

The answer to the first question is read from the tmp_name part of the $_FILES superglobal. The answer to the second is the full path, including new filename, to where you want it to live. So it is formed of the name of the directory we set up to store images (/imgs), plus the new file name (i.e. the value entered into the img_name field) and the extension. Let’s assign it to its own variable, $newPath and then save the file:

$newPath = 'imgs/'.$_POST['img_name'].'.'.$fileExt;
...
copy($_FILES['img_upload']['tmp_name'],$newPath);

Reporting Back and Moving On

What happens next depends entirely on whether an error occurred, and we can find it out by looking up whether $error is set. If it is, we need to communicate this error back to the user. If it’s not set, it’s time to move on and show the image and let the user manipulate it. Add the following above your form:

<?php if (isset($error)) echo '<p id="error">'.$error.'</p>'; ?>

If there’s an error, we’d want to show the form again. But the form is currently set to show regardless of the situation. This needs to change, so that it shows only if no image has been uploaded yet, i.e. if the form hasn’t been submitted yet, or if it has but there was an error. We can check whether an uploaded image has been saved by interrogating the $_SESSION['newPath'] variable. Wrap your form HTML in the following two lines of code:

<?php if (!isset($_SESSION['newPath']) || isset($_GET['true'])) { ?>

<?php } else echo '<img src="'.$_SESSION['newPath'].'" />'; ?>

Now the form appears only if an uploaded image isn’t registered — i.e. $_SESSION['newPath'] isn’t set — or if new=true is found in the URL. (This latter part provides us with a means of letting the user start over with a new image upload should they wish so; we’ll add a link for this in a moment). Otherwise, the uploaded image displays (we know where it lives because we saved its path in $_SESSION['newPath']).

This is a good time to take stock of where we are, so try it out. Upload an image, and verify that that it displays. Assuming it does, it’s time for our JavaScript to provide some interactivity for image manipulation.

Adding Interactivity

First, let’s extend the line we just added so that we a) give the image an ID to reference it later on; b) call the JavaScript itself (along with jQuery); and c) we provide a “start again” link, so the user can start over with a new upload (if necessary). Here is the code snippet:

<?php } else { ?>
	<img id="uploaded_image" src="" />
	<p>start over with new image

	<script src="http://www.google.com/jsapi"></script>
	<script>google.load("jquery", "1.5");</script>
	<script src="js.js"></script>

Note that I defined an ID for the image, not a class, because it’s a unique element, and not one of the many (this sounds obvious, but many people fail to observe this distinction when assigning IDs and classes). Note also, in the image’s SRC, I’m appending a random string. This is done to force the browser not to cache the image once we’ve cropped it (since the SRC doesn’t change).

Open js.js and let’s add the obligatory document ready handler (DRH), required any time you’re using freestanding jQuery (i.e. not inside a custom function) to reference or manipulate the DOM. Put the following JavaScript inside this DRH:

$(function() {
	// all our JS code will go here
});

We’re providing the functionality to a user to crop the image, and it of course means allowing him to drag a box area on the image, denoting the part he wishes to keep. Therefore, the first step is to listen for a mousedown event on the image, the first of three events involved in a drag action (mouse down, mouse move and then, when the box is drawn, mouse up).

var dragInProgress = false;

$("#uploaded_image").mousedown(function(evt) {
	dragInProgress = true;
});

And in similar fashion, let’s listen to the final mouseup event.

$(window).mouseup(function() {
	dragInProgress = false;
});

Note that our mouseup event runs on window, not the image itself, since it’s possible that the user could release the mouse button anywhere on the page, not necessarily on the image.

Note also that the mousedown event handler is prepped to receive the event object. This object holds data about the event, and jQuery always passes it to your event handler, whether or not it’s set up to receive it. That object will be crucial later on in ascertaining where the mouse was when the event fired. The mouseup event doesn’t need this, because all we care about if is that the drag action is over and it doesn’t really matter where the mouse is.

We’re tracking whether or not the mouse button is currently depressed in a variable, . Why? Because, in a drag action, the middle event of the three (see above) only applies if the first happened. That is, in a drag action, you move the mouse whilst the mouse is down. If it’s not, our mousemove event handler should exit. And here it is:

$("#uploaded_image").mousemove(function(evt) {
	if (!dragInProgress) return;
});

So now our three event handlers are set up. As you can see, the mousemove event handler exits if it discovers that the mouse button is not currently down, as we decided above it should be.

Now let’s extend these event handlers.

This is a good time to explain how our JavaScript will be simulating the drag action being done by the user. The trick is to create a DIV on mousedown, and position it at the mouse cursor. Then, as the mouse moves, i.e. the user is drawing his box, that element should resize consistently to mimic that.

Let’s add, position and style our DIV. Before we add it, though, let’s remove any previous such DIV, i.e. from a previous drag attempt. This ensures there’s only ever one drag box, not several. Also, we want to log the mouse coordinates at the time of mouse down, as we’ll need to reference these later when it comes to drawing and resizing ourDIV. Extend the mousedown event handler to become:

$("#uploaded_image").mousedown(function(evt) {
	dragInProgress = true;
	$("#drag_box").remove();
	$("<div>").appendTo("body").attr("id", "drag_box").css({left: evt.clientX, top: evt.clientY});
	mouseDown_left = evt.clientX;
	mouseDown_top = evt.clientY;
});

Notice that we don’t prefix the three variables there with the 'var' keyword. That would make them accessible only within the mousedown handler, but we need to reference them later in our mousemove handler. Ideally, we’d avoid global variables (using a namespace would be better) but for the purpose of keeping the code in this tutorial concise, they’ll do for now.

Notice that we obtain the coordinates of where the event took place — i.e. where the mouse was when the mouse button was depressed — by reading the clientX and clientY properties of the event object, and it’s those we use to position our DIV.

Let’s style the DIV by adding the following CSS to your stylesheet.

#drag_box { position: absolute; border: solid 1px #333; background: #fff; opacity: .5; filter: alpha(opacity=50); z-index: 10; }

Now, if you upload an image and then click it, the DIV will be inserted at your mouse position. You won’t see it yet, as it’s got width and height zero; only when we start dragging should it become visible, but if you use Firebug or Dragonfly to inspect it, you will see it in the DOM.

So far, so good. Our drag box functionality is almost complete. Now we just need to make it respond to the user’s mouse movement. What’s involved here is very much what we did in the mousedown event handler when we referenced the mouse coordinates.

The key to this part is working out what properties should be updated, and with what values. We’ll need to change the box’s left, top, width and height.

Sounds pretty obvious. However, it’s not as simple as it sounds. Imagine that the box was created at coordinates 40×40 and then the user drags the mouse to coordinates 30×30. By updating the box’s left and top properties to 30 and 30, the position of the top left corner of the box would be correct, but the position of its bottom right corner would not be where the mousedown event happened. The bottom corner would be 10 pixels north west of where it should be!

To get around this, we need to compare the mousedown coordinates with the current mouse coordinates. That’s why in our mousedown handler, we logged the mouse coordinates at the time of mouse down. The box’s new CSS values will be as follows:

  • left: the lower of the two clientX coordinates
  • width: the difference between the two clientX coordinates
  • top: the lower of the two clientY coordinates
  • height: the difference between the two clientY coordinates

So let’s extend the mousemove event handler to become:

$("#uploaded_image").mousemove(function(evt) {
	if (!dragInProgress) return;
	var newLeft = mouseDown_left < evt.clientX ? mouseDown_left : evt.clientX;
	var newWidth = Math.abs(mouseDown_left - evt.clientX);
	var newTop = mouseDown_top < evt.clientY ? mouseDown_top : evt.clientY;
	var newHeight = Math.abs(mouseDown_top - evt.clientY);
	$('#drag_box').css({left: newLeft, top: newTop, width: newWidth, height: newHeight});
});

Notice also that, to establish the new width and height, we didn't have to do any comparison. Although we don't know, for example, which is lower out of the mousedown left and the current mouse left, we can subtract either from the other and counter any negative result by forcing the resultant number to be positive via Math.abs(), i.e.

result = 50 – 20; //30
result = Math.abs(20 – 50); //30 (-30 made positive)

One final, small but important thing. When Firefox and Internet Explorer detect drag attempts on images they assume the user is trying to drag out the image onto their desktop, or into Photoshop, or wherever. This has the potential to interfere with our creation. The solution is to stop the event from doing its default action. The easiest way is to return false. What's interesting, though, is that Firefox interprets drag attempts as beginning on mouse down, whilst IE interprets them as beginning on mouse move. So we need to append the following, simple line to the ends of both of these functions:

return false;

Try your application out now. You should have full drag box functionality.

Saving the Cropped Image

And so to the last part, saving the modified image. The plan here is simple: we need to grab the coordinates and dimensions of the drag box, and pass them to our PHP script which will use them to crop the image and save a new version.

Grabbing the drag box data

It makes sense to grab the drag box's coordinates and dimensions in our mouseup handler, since it denotes the end of the drag action. We could do that with the following:

var db = $("#drag_box");
var db_data = {left: db.offset().left, top: db.offset().top, width: db.width(), height: db.height()};

There's a problem, though, and it has to do with the drag box's coordinates. The coordinates we grab above are relative to the body, not the uploaded image. So to correct this, we need to subtract the position, relative to the body, of the image itself, from them. So let's add this instead:

var db = $("#drag_box");
if (db.width() == 0 || db.height() == 0 || db.length == 0) return;
var img_pos = $('#uploaded_image').offset();
var db_data = {
	left: db.offset().left – img_pos.left,
	top: db.offset().top - img_pos.top,
	width: db.width(),
	height: db.height()
};

What's happening there? We're first referencing the drag box in a local shortcut variable, db, and then store the four pieces of data we need to know about it, its left, top, width and height, in an object db_data. The object isn't essential: we could use separate variables, but this approach groups the data together under one roof and might be considered tidier.

Note the condition on the second line, which guards against simple, dragless clicks to the image being interpreted as crop attempts. In these cases, we return, i.e. do nothing.

Note also that we get the left and top coordinates via jQuery's offset() method. This returns the dimensions of an object relative to the document, rather than relative to any parent or ancestor with relative positioning, which is what position() or css('top/right/bottom/left') would return. However, since we appended our drag box directly to the body, all of these three techniques would work the same in our case. Equally, we get the width and height via the width() and height() methods, rather than via css('width/height'), as the former omits 'px' from the returned values. Since our PHP script will be using these coordinates in a mathematical fashion, this is the more suitable option.

For more information on the distinction between all these methods, see my previous article on SmashingMag, Commonly Confused Bits of jQuery.

Let's now throw out a confirm dialogue box to check that the user wishes to proceed in cropping the image using the drag box they've drawn. If so, time to pass the data to our PHP script. Add a bit more to your mouseup handler:

if (confirm("Crop the image using this drag box?")) {
	location.href = "index.php?crop_attempt=true&crop_l="+db_data.left+"&crop_t="+
db_data.top+"&crop_w="+db_data.width+"&crop_h="+db_data.height;
} else {
	db.remove();
}

So if the user clicks 'OK' on the dialogue box that pops up, we redirect to the same page we're on, but passing on the four pieces of data we need to give to our PHP script. We also pass it a flag crop_attempt, which our PHP script can detect, so it knows what action we'd like it to do. If the user clicks 'Cancel', we remove the drag box (since it's clearly unsuitable). Onto the PHP...

PHP: saving the modified file

Remember we said that our image_manipulation.php had two tasks — one to first save the uploaded image and another to save the cropped version of the image? It's time to extend the script to handle the latter request. Append the following to image_manipulation.php:

/* -----------------
| CROP saved image
----------------- */

if (isset($_GET["crop_attempt"])) {
	//cropping code here
}

So just like before, we condition-off the code area and make sure a flag is present before executing the code. As for the code itself, we need to go back into the land of GD. We need to create two image handles. Into one, we import the uploaded image; the second one will be where we paste the cropped portion of the uploaded image into, so we can essentially think of these two as source and destination. We copy from the source onto the destination canvas via the GD function imagecopy(). This needs to know 8 pieces of information:

  • destination, the destination image handle
  • source, the source image handle
  • destination X, the left position to paste TO on the destination image handle
  • destination Y, the top position “ “ “ “
  • source X, the left position to grab FROM on the source image handle
  • source Y, the top position “ “ “ “
  • source W, the width (counting from source X) of the portion to be copied over from the source image handle
  • source H, the height (counting from source Y) “ “ “ “

Fortunately, we already have the data necessary to pass to the final 6 arguments in the form of the JavaScript data we collected and passed back to the page in our mouseup event handler a few moments ago.

Let's create our first handle. As I said, we'll import the uploaded image into it. That means we need to know its file extension, and that's why we saved it as a session variable earlier.

switch($_SESSION["fileExt"][1]) {
	case "jpg": case "jpeg":
		var source_img = imagecreatefromjpeg($_SESSION["newPath"]);
		break;
	case "gif":
		var source_img = imagecreatefromgif($_SESSION["newPath"]);
		break;
	case "png":
		var source_img = imagecreatefrompng($_SESSION["newPath"]);
		break;
}

As you can see, the file type of the image determines which function we use to open it into an image handle. Now let's extend this switch statement to create the second image handle, the destination canvas. Just as the function for opening an existing image depends on image type, so too does the function used to create a blank image. Hence, let's extend our switch statement:

switch($_SESSION["fileExt"][1]) {
	case "jpg": case "jpeg":
		$source_img = imagecreatefromjpeg($_SESSION["newPath"]);
		$dest_ing = imagecreatetruecolor($_GET["crop_w"], $_GET["crop_h"]);
		break;
	case "gif":
		$source_img = imagecreatefromgif($_SESSION["newPath"]);
		$dest_ing = imagecreate($_GET["crop_w"], $_GET["crop_h"]);
		break;
	case "png":
		$source_img = imagecreatefrompng($_SESSION["newPath"]);
		$dest_ing = imagecreate($_GET["crop_w"], $_GET["crop_h"]);
		break;
}

You'll notice that the difference between opening a blank image and opening one from an existing or uploaded file is that, for the former, you must specify the dimensions. In our case, that's the width and height of the drag box, which we passed into the page via the $_GET['crop_w'] and $_GET['crop_h'] vars respectively.

So now we have our two canvases, it's time to do the copying. The following is one function call, but since it takes 8 arguments, I'm breaking it onto several lines to make it readable. Add it after your switch statement:

imagecopy(
	$dest_img,
	$source_img,
	0,
	0,
	$_GET["crop_l"],
	$_GET["crop_t"],
	$_GET["crop_w"],
	$_GET["crop_h"]
);

The final part is to save the cropped image. For this tutorial, we'll overwrite the original file, but you might like to extend this application, so the user has the option of saving the cropped image as a separate file, rather than losing the original.

Saving the image is easy. We just call a particular function based on (yes, you guessed it) the image's type. We pass in two arguments: the image handle we're saving, and the file name we want to save it as. So let's do that:

switch($_SESSION["fileExt"][1]) {
	case "jpg": case "jpeg":
		imagejpeg($dest_img, $_SESSION["newPath"]); break;
	case "gif":
		imagegif($dest_img, $_SESSION["newPath"]); break;
	case "png":
		imagepng($dest_img, $_SESSION["newPath"]); break;
}

It's always good to clean up after ourselves - in PHP terms that means freeing up memory, so let's destroy our image handlers now that we don't need them anymore.

imagedestroy($dest_img);
imagedestroy($source_img);

Lastly, we want to redirect to the index page. You might wonder why we'd do this, since we're on it already (and have been the whole time). The trick is that by redirecting, we can lose the arguments we passed in the URL. We don't want these hanging around because, if the user refreshes the page, he'll invoke the PHP crop script again (since it will detect the arguments). The arguments have done their job, so now they have to go, so we redirect to the index page without these arguments. Add the following line to force the redirect:

header("Location: index.php"); //bye bye arguments

Final Touches

So that's it. We now have a fully-working facility to first upload then crop an image, and save it to the server. Don't forget you can download the source files (updated) for your reference.

There's plenty of ways you could extend this simple application. Explore GD (and perhaps other image libraries for PHP); you can do wonders with images, resizing them, distorting them, changing them to greyscale and much more. Another thing to think about would be security; this tutorial does not aim to cover that here, but if you were working in a user control panel environment, you'd want to make sure the facility was secure and that the user could not edit other user's files.

With this in mind, you might make the saved file's path more complex, e.g. if the user named it pic.jpg, you might actually name it on the server 34iweshfjdshkj4r_pic.jpg. You could then hide this image path, e.g. by specifying the SRC attribute as 'getPic.php' instead of referencing the image directly inside an image's SRC attribute. That PHP script would then open and display the saved file (by reading its path in the session variable), and the user would never be aware of its path.

The possibilities are endless, but hopefully this tutorial has given you a starting point.

(vf)


© Mitya for Smashing Magazine, 2011. | Permalink | Post a comment | Smashing Shop | Smashing Network | About Us
Post tags: ,


Commonly Confused Bits Of jQuery

Smashing-magazine-advertisement in Commonly Confused Bits Of jQuerySpacer in Commonly Confused Bits Of jQuery
 in Commonly Confused Bits Of jQuery  in Commonly Confused Bits Of jQuery  in Commonly Confused Bits Of jQuery

The explosion of JavaScript libraries and frameworks such as jQuery onto the front-end development scene has opened up the power of JavaScript to a far wider audience than ever before. It was born of the need — expressed by a crescendo of screaming by front-end developers who were fast running out of hair to pull out — to improve JavaScript’s somewhat primitive API, to make up for the lack of unified implementation across browsers and to make it more compact in its syntax.

All of which means that, unless you have some odd grudge against jQuery, those days are gone — you can actually get stuff done now. A script to find all links of a certain CSS class in a document and bind an event to them now requires one line of code, not 10. To power this, jQuery brings to the party its own API, featuring a host of functions, methods and syntactical peculiarities. Some are confused or appear similar to each other but actually differ in some way. This article clears up some of these confusions.

[Offtopic: by the way, did you already get your copy of the Smashing Book?]

1. .parent() vs. .parents() vs. .closest()

All three of these methods are concerned with navigating upwards through the DOM, above the element(s) returned by the selector, and matching certain parents or, beyond them, ancestors. But they differ from each other in ways that make them each uniquely useful.

parent(selector)

This simply matches the one immediate parent of the element(s). It can take a selector, which can be useful for matching the parent only in certain situations. For example:

$('span#mySpan').parent().css('background', '#f90');
$('p').parent('div.large').css('background', '#f90');

The first line gives the parent of #mySpan. The second does the same for parents of all <p> tags, provided that the parent is a div and has the class large.

Tip: the ability to limit the reach of methods like the one in the second line is a common feature of jQuery. The majority of DOM manipulation methods allow you to specify a selector in this way, so it’s not unique to parent().

parents(selector)

This acts in much the same way as parent(), except that it is not restricted to just one level above the matched element(s). That is, it can return multiple ancestors. So, for example:

$('li.nav').parents('li'); //for each LI that has the class nav, go find all its parents/ancestors that are also LIs

This says that for each <li> that has the class nav, return all its parents/ancestors that are also <li>s. This could be useful in a multi-level navigation tree, like the following:

<ul id='nav'>
	<li>Link 1
		<ul>
			<li>Sub link 1.1</li>
			<li>Sub link 1.2</li>
			<li>Sub link 1.3</li>
		</ul>
	<li>Link 2
		<ul>
			<li>Sub link 2.1

			<li>Sub link 2.2

		</ul>
	</li>
</ul>

Imagine we wanted to color every third-generation <li> in that tree orange. Simple:

$('#nav li').each(function() {
	if ($(this).parents('#nav li').length == 2)
		$(this).css('color', '#f90');
});

This translates like so: for every <li> found in #nav (hence our each() loop), whether it’s a direct child or not, see how many <li> parents/ancestors are above it within #nav. If the number is two, then this <li> must be on level three, in which case color.

closest(selector)

This is a bit of a well-kept secret, but very useful. It works like parents(), except that it returns only one parent/ancestor. In my experience, you’ll normally want to check for the existence of one particular element in an element’s ancestry, not a whole bunch of them, so I tend to use this more than parent(). Say we wanted to know whether an element was a descendent of another, however deep in the family tree:

if ($('#element1').closest('#element2').length == 1)
	alert("yes - #element1 is a descendent of #element2!");
else
	alert("No - #element1 is not a descendent of #element2");

Tip: you can simulate closest() by using parents() and limiting it to one returned element.

$($('#element1').parents('#element2').get(0)).css('background', '#f90');

One quirk about closest() is that traversal starts from the element(s) matched by the selector, not from its parent. This means that if the selector that passed inside closest() matches the element(s) it is running on, it will return itself. For example:

$('div#div2').closest('div').css('background', '#f90');

This will turn #div2 itself orange, because closest() is looking for a <div>, and the nearest <div> to #div2 is itself.

2. .position() vs. .offset()

These two are both concerned with reading the position of an element — namely the first element returned by the selector. They both return an object containing two properties, left and top, but they differ in what the returned position is relative to.

position() calculates positioning relative to the offset parent — or, in more understandable terms, the nearest parent or ancestor of this element that has position: relative. If no such parent or ancestor is found, the position is calculated relative to the document (i.e. the top-left corner of the viewport).

offset(), in contrast, always calculates positioning relative to the document, regardless of the position attribute of the element’s parents and ancestors.

Consider the following two <div>s:

Hello – I’m outerDiv. I have position: relative and left: 100px

Hi – I’m #innerDiv. I have position absolute, left: 50px and top: 80px.

Querying (no pun intended) the offset() and position() of #innerDiv will return different results.

var position = $('#innerDiv').position();
var offset = $('#innerDiv').offset();
alert("Position: left = "+position.left+", top = "+position.top+"\n"+
      "Offset: left = "+offset.left+" and top = "+offset.top
)

Try it yourself to see the results: click here.

3. .css(‘width’) and .css(‘height’) vs. .width() and .height()

These three, you won’t be shocked to learn, are concerned with calculating the dimensions of an element in pixels. They both return the offset dimensions, which are the genuine dimensions of the element no matter how stretched it is by its inner content.

They differ in the data types they return: css('width') and css('height') return dimensions as strings, with px appended to the end, while width() and height() return dimensions as integers.

There’s actually another little-known difference that concerns IE (quelle surprise!), and it’s why you should avoid the css('width') and css('height') route. It has to do with the fact that IE, when asked to read “computedâ€� (i.e. not implicitly set) dimensions, unhelpfully returns auto. In jQuery core, width() and height() are based on the .offsetWidth and .offsetHeight properties resident in every element, which IE does read correctly.

But if you’re working on elements with dimensions implicitly set, you don’t need to worry about that. So, if you wanted to read the width of one element and set it on another element, you’d opt for css('width'), because setting dimensions, just like in CSS, requires specifying a unit of measurement.

But if you wanted to read an element’s width() with a view to performing a calculation on it, you’d be interested only in the figure; hence width() is better.

Note that each of these can simulate the other with the help of an extra line of JavaScript, like so:

var width = $('#someElement').width(); //returns integer
width = width+'px'; //now it's a string like css('width') returns
var width = $('#someElement').css('width'); //returns string
width = parseInt(width); //now it's an integer like width() returns

Lastly, width() and height() actually have another trick up their sleeves: they can return the dimensions of the window and document. If you try this using the css() method, you’ll get an error.

4. .click() (etc) vs. .bind() vs. .live() vs. .delegate

These are all concerned with binding events to elements. The differences lie in what elements they bind to and how much we can influence the event handler (or “callbackâ€�). If this sounds confusing, don’t worry. I’ll explain.

click() (etc)

It’s important to understand that bind() is the daddy of jQuery’s event-handling API. Most tutorials deal with events with simple-looking methods, such as click() and mouseover(), but behind the scenes these are just the lieutenants who report back to bind().

These lieutenants, or aliases, give you quick access to bind certain event types to the elements returned by the selector. They all take one argument: a callback function to be executed when the event fires. For example:

$('#table td ').click(function() {
	alert("The TD you clicked contains '"+$(this).text()+"'");
});

This simply says that whenever a <div> inside #table is clicked, alert its text content.

bind()

We can do the same thing with bind, like so:

$('#table td ').bind('click', function() {
	alert("The TD you clicked contains '"+$(this).text()+"'");
});

Note that this time, the event type is passed as the first argument to bind(), with the callback as the second argument. Why would you use bind() over the simpler alias functions?

Very often you wouldn’t. But bind() gives you more control over what happens in the event handler. It also allows you to bind more than one event at a time, by space-separating them as the first argument, like so:

$('#table td').bind('click contextmenu', function() {
	alert("The TD you clicked contains '"+$(this).text()+"'");
});

Now, our event fires whether we’ve clicked the <td> with the left or right button. I also mentioned that bind() gives you more control over the event handler. How does that work? It does it by passing three arguments rather than two, with argument two being a data object containing properties readable to the callback, like so:

$('#table td').bind('click contextmenu', {message: 'hello!'}, function(e) {
	alert(e.data.message);
});

As you can see, we’re passing into our callback a set of variables for it to have access to, in our case the variable message.

You might wonder why we would do this. Why not just specify any variables we want outside the callback and have our callback read those? The answer has to do with scope and closures. When asked to read a variable, JavaScript starts in the immediate scope and works outwards (this is a fundamentally different behavior to languages such as PHP). Consider the following:

var message = 'you left clicked a TD';
$('#table td').bind('click', function(e) {
	alert(message);
});
var message = 'you right clicked a TD';
$('#table td').bind('contextmenu', function(e) {
	alert(message);
});

No matter whether we click the <td> with the left or right mouse button, we will be told it was the right one. This is because the variable message is read by the alert() at the time of the event firing, not at the time the event was bound.

If we give each event its own “version� of message at the time of binding the events, we solve this problem.

$('#table td').bind('click', {message: 'You left clicked a TD'}, function(e) {
	alert(e.data.message);
});
$('#table td').bind('contextmenu', {message: 'You right clicked a TD'}, function(e) {
	alert(e.data.message);
});

Events bound with bind() and with the alias methods (.mouseover(), etc) are unbound with the unbind() method.

live()

This works almost exactly the same as bind() but with one crucial difference: events are bound both to current and future elements — that is, any elements that do not currently exist but which may be DOM-scripted after the document is loaded.

Side note: DOM-scripting entails creating and manipulating elements in JavaScript. Ever notice in your Facebook profile that when you “add another employerâ€� a field magically appears? That’s DOM-scripting, and while I won’t get into it here, it looks broadly like this:

var newDiv = document.createElement('div');
newDiv.appendChild(document.createTextNode('hello, world!'));
$(newDiv).css({width: 100, height: 100, background: '#f90'});
document.body.appendChild(newDiv);

delegate()

Another shortfall of live() is that, unlike the vast majority of jQuery methods, it cannot be used in chaining. That is, it must be used directly on a selector, like so:

$('#myDiv a').live('mouseover', function() {
	alert('hello');
});

But not…

$('#myDiv').children('a').live('mouseover', function() {
	alert('hello');
});

… which will fail, as it will if you pass direct DOM elements, such as $(document.body).

delegate(), which was developed as part of jQuery 1.4.2, goes some way to solving this problem by accepting as its first argument a context within the selector. For example:

$('#myDiv').delegate('a', 'mouseover', function() {
	alert('hello');
});

Like live(), delegate() binds events both to current and future elements. Handlers are unbound via the undelegate() method.

Real-Life Example

For a real-life example, I want to stick with DOM-scripting, because this is an important part of any RIA (rich Internet application) built in JavaScript.

Let’s imagine a flight-booking application. The user is asked to supply the names of all passengers travelling. Entered passengers appear as new rows in a table, #passengersTable, with two columns: “Nameâ€� (containing a text field for the passenger) and “Deleteâ€� (containing a button to remove the passenger’s row).

To add a new passenger (i.e. row), the user clicks a button, #addPassenger:

$('#addPassenger').click(function() {
	var tr = document.createElement('tr');
	var td1 = document.createElement('td');
	var input = document.createElement('input');
	input.type = 'text';
	$(td1).append(input);
	var td2 = document.createElement('td');
	var button = document.createElement('button');
	button.type = 'button';
	$(button).text('delete');
	$(td2).append(button);
	$(tr).append(td1);
	$(tr).append(td2);
	$('#passengersTable tbody').append(tr);
});

Notice that the event is applied to #addPassenger with click(), not live('click'), because we know this button will exist from the beginning.

What about the event code for the “Delete� buttons to delete a passenger?

$('#passengersTable td button').live('click', function() {
	if (confirm("Are you sure you want to delete this passenger?"))
	$(this).closest('tr').remove();
});

Here, we apply the event with live() because the element to which it is being bound (i.e. the button) did not exist at runtime; it was DOM-scripted later in the code to add a passenger.

Handlers bound with live() are unbound with the die() method.

The convenience of live() comes at a price: one of its drawbacks is that you cannot pass an object of multiple event handlers to it. Only one handler.

5. .children() vs. .find()

Remember how the differences between parent(), parents() and closest() really boiled down to a question of reach? So it is here.

children()

This returns the immediate children of an element or elements returned by a selector. As with most jQuery DOM-traversal methods, it is optionally filtered with a selector. So, if we wanted to turn all <td>s in a table that contained the word “dog� orange, we could use this:

$('#table tr').children('td:contains(dog)').css('background', '#f90');

find()

This works very similar to children(), only it looks at both children and more distant descendants. It is also often a safer bet than children().

Say it’s your last day on a project. You need to write some code to hide all <tr>s that have the class hideMe. But some developers omit <tbody> from their table mark-up, so we need to cover all bases for the future. It would be risky to target the <tr>s like this…

$('#table tbody tr.hideMe').hide();

… because that would fail if there’s no <tbody>. Instead, we use find():

$('#table').find('tr.hideMe').hide();

This says that wherever you find a <tr> in #table with .hideMe, of whatever descendancy, hide it.

6. .not() vs. !.is() vs. :not()

As you’d expect from functions named “notâ€� and “is,â€� these are opposites. But there’s more to it than that, and these two are not really equivalents.

.not()

not() returns elements that do not match its selector. For example:

$('p').not('.someclass').css('color', '#f90');

That turns all paragraphs that do not have the class someclass orange.

.is()

If, on the other hand, you want to target paragraphs that do have the class someclass, you could be forgiven for thinking that this would do it:

$('p').is('.someclass').css('color', '#f90');

In fact, this would cause an error, because is() does not return elements: it returns a boolean. It’s a testing function to see whether any of the chain elements match the selector.

So when is is useful? Well, it’s useful for querying elements about their properties. See the real-life example below.

:not()

:not() is the pseudo-selector equivalent of the method .not() It performs the same job; the only difference, as with all pseudo-selectors, is that you can use it in the middle of a selector string, and jQuery’s string parser will pick it up and act on it. The following example is equivalent to our .not() example above:

$('p:not(.someclass)').css('color', '#f90');

Real-Life Example

As we’ve seen, .is() is used to test, not filter, elements. Imagine we had the following sign-up form. Required fields have the class required.

<form id='myform' method='post' action='somewhere.htm'>
	<label>Forename *
	<input type='text' class='required' />
	<br />
	<label>Surname *
	<input type='text' class='required' />
	<br />
	<label>Phone number
	<input type='text' />
	<br />
	<label>Desired username *
	<input type='text' class='required' />
	<br />
	<input type='submit' value='GO' />
</form>

When submitted, our script should check that no required fields were left blank. If they were, the user should be notified and the submission halted.

$('#myform').submit(function() {
	if ($(this).find('input').is('.required[value=]')) {
		alert('Required fields were left blank! Please correct.');
		return false; //cancel submit event
	}
});

Here we’re not interested in returning elements to manipulate them, but rather just in querying their existence. Our is() part of the chain merely checks for the existence of fields within #myform that match its selector. It returns true if it finds any, which means required fields were left blank.

7. .filter() vs. .each()

These two are concerned with iteratively visiting each element returned by a selector and doing something to it.

.each()

each() loops over the elements, but it can be used in two ways. The first and most common involves passing a callback function as its only argument, which is also used to act on each element in succession. For example:

$('p').each(function() {
	alert($(this).text());
});

This visits every <p> in our document and alerts out its contents.

But each() is more than just a method for running on selectors: it can also be used to handle arrays and array-like objects. If you know PHP, think foreach(). It can do this either as a method or as a core function of jQuery. For example…

var myarray = ['one', 'two'];
$.each(myarray, function(key, val) {
	alert('The value at key '+key+' is '+val);
});

… is the same as:

var myarray = ['one', 'two'];
$(myarray).each(function(key, val) {
	alert('The value at key '+key+' is '+val);
});

That is, for each element in myarray, in our callback function its key and value will be available to read via the key and val variables, respectively.

One of the great things about this is that you can also iterate over objects — but only in the first way (i.e. $.each).

jQuery is known as a DOM-manipulation and effects framework, quite different in focus from other frameworks such as MooTools, but each() is an example of its occasional foray into extending JavaScript’s native API.

.filter()

filter(), like each(), visits each element in the chain, but this time to remove it from the chain if it doesn’t pass a certain test.

The most common application of filter() is to pass it a selector string, just like you would specify at the start of a chain. So, the following are equivalents:

$('p.someClass').css('color', '#f90');
$('p').filter('.someclass').css('color', '#f90');

In which case, why would you use the second example? The answer is, sometimes you want to affect element sets that you cannot (or don’t want to) change. For example:

var elements = $('#someElement div ul li a');
//hundreds of lines later...
elements.filter('.someclass').css('color', '#f90');

elements was set long ago, so we cannot — indeed may not wish to — change the elements that return, but we might later want to filter them.

filter() really comes into its own, though, when you pass it a filter function to which each element in the chain in turn is passed. Whether the function returns true or false determines whether the element stays in the chain. For example:

$('p').filter(function() {
	return $(this).text().indexOf('hello') != -1;
}).css('color', '#f90')

Here, for each <p> found in the document, if it contains the string hello, turn it orange. Otherwise, don’t affect it.

We saw above how is(), despite its name, was not the equivalent of not(), as you might expect. Rather, use filter() as the positive equivalent of not().

Note also that unlike each(), filter() cannot be used on arrays and objects.

Real-Life Example

You might be looking at the example above, where we turned <p>s starting with hello orange, and thinking, “But we could do that more simply.” You’d be right:

$('p:contains(hello)').css('color', '#f90')

For such a simple condition (i.e. contains hello), that’s fine. But filter() is all about letting us perform more complex or long-winded evaluations before deciding whether an element can stay in our chain.

Imagine we had a table of CD products with four columns: artist, title, genre and price. Using some controls at the top of the page, the user stipulates that they do not want to see products for which the genre is “Country� or the price is above $10. These are two filter conditions, so we need a filter function:

$('#productsTable tbody tr').filter(function() {
	var genre = $(this).children('td:nth-child(3)').text();
	var price = $(this).children('td:last').text().replace(/[^\d\.]+/g, '');
	return genre.toLowerCase() == 'country' || parseInt(price) >= 10;
}).hide();

So, for each <tr> inside the table, we evaluate columns 3 and 4 (genre and price), respectively. We know the table has four columns, so we can target column 4 with the :last pseudo-selector. For each product looked at, we assign the genre and price to their own variables, just to keep things tidy.

For the price, we replace any characters that might prevent us from using the value for mathematical calculation. If the column contained the value $14.99 and we tried to compute that by seeing whether it matched our condition of being below $10, we would be told that it’s not a number, because it contains the $ sign. Hence we strip away everything that is not number or dot.

Lastly, we return true (meaning the row will be hidden) if either of our conditions are met (i.e. the genre is country or the price is $10 or more).

filter()

8. .merge() vs. .extend()

Let’s finish with a foray into more advanced JavaScript and jQuery. We’ve looked at positioning, DOM manipulation and other common issues, but jQuery also provides some utilities for dealing with the native parts of JavaScript. This is not its main focus, mind you; libraries such as MooTools exist for this purpose.

.merge()

merge() allows you to merge the contents of two arrays into the first array. This entails permanent change for the first array. It does not make a new array; values from the second array are appended to the first:

var arr1 = ['one', 'two'];
var arr2 = ['three', 'four'];
$.merge(arr1, arr2);

After this code runs, the arr1 will contain four elements, namely one, two, three, four. arr2 is unchanged. (If you’re familiar with PHP, this function is equivalent to array_merge().)

.extend()

extend() does a similar thing, but for objects:

var obj1 = {one: 'un', two: 'deux'}
var obj2 = {three: 'trois', four: 'quatre'}
$.extend(obj1, obj2);

extend() has a little more power to it. For one thing, you can merge more than two objects — you can pass as many as you like. For another, it can merge recursively. That is, if properties of objects are themselves objects, you can ensure that they are merged, too. To do this, pass true as the first argument:

var obj1 = {one: 'un', two: 'deux'}
var obj2 = {three: 'trois', four: 'quatre'}
$.extend(true, obj1, obj2);

Covering everything about the behaviour of JavaScript objects (and how merge interacts with them) is beyond the scope of this article, but you can read more here.

The difference between merge() and extend() in jQuery is not the same as it is in MooTools. One is used to amend an existing object, the other creates a new copy.

There You Have It

We’ve seen some similarities, but more often than not intricate (and occasionally major) differences. jQuery is not a language, but it deserves to be learned as one, and by learning it you will make better decisions about what methods to use in what situation.

While there are strict rules these days for writing semantic and SEO-compliant mark-up, JavaScript is still very much the playground of the developer. No one will demand that you use click() instead of bind(), but that’s not to say one isn’t a better choice than the other. It’s all about the situation.

Related Posts

You may be interested in the following related posts:

We appreciate the feedback of our Twitter followers who reviewed the article before it was published.

(al)


© Andy Croxall for Smashing Magazine, 2010. | Permalink | Post a comment | Add to del.icio.us | Digg this | Stumble on StumbleUpon! | Tweet it! | Submit to Reddit | Forum Smashing Magazine
Post tags: , , ,


  •   
  • Copyright © 1996-2010 BlogmyQuery - BMQ. All rights reserved.
    iDream theme by Templates Next | Powered by WordPress