JQuery: Novice to Ninja- P24

JQuery: Novice to Ninja- P24

JQuery: Novice to Ninja- P24:No matter what kind of ninja you are—a cooking ninja, a corporate lawyer ninja, or an actual ninja ninja—virtuosity lies in first mastering the basic tools of the trade. Once conquered, it’s then up to the full-fledged ninja to apply that knowledge in creative and inventive ways.

Nội dung Text: JQuery: Novice to Ninja- P24

322 jQuery: Novice to Ninja
to the wrapper div, then back down to the navigation section. This approach lets
you apply the same code to any tables that have been structured appropriately:
chapter_08/08_pagination/script.js (excerpt)
// 2. Set up the navigation controls
var $nav = $table
.parents('.table-wrapper')
.find('.wrapper-paging ul');
var $back = $nav.find('li:first-child a');
var $next = $nav.find('li:last-child a');
We then set the text in the display boxes for the current page and the total length
Licensed to JamesCarlson@aol.com
(adding one, because our counters are zero-based). Next, we attach the event handlers
for the Previous/Next buttons. When these buttons are clicked, we call our pagination
function with the direction we want to move:
chapter_08/08_pagination/script.js (excerpt)
$nav.find('a.paging-this b').text(current + 1);
$nav.find('a.paging-this span').text(numPages + 1);
$back
.addClass('paging-disabled')
.click(function() {
pagination('');
});
The last part of the setup is to limit how many rows the user sees to begin with.
The easiest way to do this is to hide all the table rows, and show only the rows
within the range we’re interested in. But how can we select a range of elements
with jQuery? We could use the :lt() and :gt() filters—but when it comes time to
show, say, rows 10 to 20, the selectors will get a bit messy. Luckily for us there’s
the slice action, which takes a start index and an end index as parameters, and
returns only the objects within that range:

Lists, Trees, and Tables 323
chapter_08/08_pagination/script.js (excerpt)
// 3. Show initial rows
$rows
.hide()
.slice(0, pageLength)
.show();
Everything looks in order now: our navigation controls are showing the correct page
and total, and the first page of data is displaying correctly. But our paging buttons
have no function yet. We’ll add some logic to move the current page, and work out
whether we should disable buttons (if we’re at either end of the table):
Licensed to JamesCarlson@aol.com
chapter_08/08_pagination/script.js (excerpt)
// 4. Move previous and next
if (direction == "

324 jQuery: Novice to Ninja
chapter_08/08_pagination/script.js (excerpt)
// 5. Reveal the correct rows
var reveal = function (current) {
$back.removeClass("paging-disabled");
$next.removeClass("paging-disabled");
$rows
.hide()
.slice(current * pageLength, current * pageLength + pageLength)
.show();
$nav.find("a.paging-this b").text(current + 1);
}
Licensed to JamesCarlson@aol.com
reveal starts by clearing the disabled classes, so that our buttons avoid becoming
stranded in a disabled state. We then use the slice method again to select the correct
rows to display.
Nested Functions
The structure we’ve used here is a little odd—but when you think about it, it’s
straightforward: we’ve nested a function declaration inside another function. All
this does is restrict the scope of the function (see the section called “Scope” in
Chapter 6), so it can only be called from within its parent function. Why is this a
good idea? In this case our function has no purpose outside its parent, and placing
it in a wider scope would only put it at greater risk of conflicting with other
function or variable names.
Editing a Row
We’ve already handled inline editing on a single element, but what if you want to
make an entire table editable? We’ll add editing functionality to the data grid by
inserting an Edit button at the end of each row, turning that entire row of cells into
input elements, as shown in Figure 8.8.

Lists, Trees, and Tables 325
Figure 8.8. Editable rows in action
Licensed to JamesCarlson@aol.com
The cells that contain your tabular data provide an excellent opportunity to store
information as we manipulate the markup; this is actually a trick we used in our
form controls. What we’ve yet to experience, though, is a row element to group our
work area. Let’s see how to use it:
chapter_08/09_editable_table_cells/index.html (excerpt)
ID
Name
Occupation
Approx. Location
Price
203A
Johny Stardust
Front-man
Los Angeles
$39.95
⋮
Looks quite normal, right? And so it should, as all the editable features will be added
progressively—the better to keep your visitors happy and coming back for more!

326 jQuery: Novice to Ninja
And there’s another payoff: by not including any of the editing controls in the table’s
HTML, we can easily add any number of rows to it, relying on jQuery to do our
heavy lifting.
We’ll start with a setup method, to initialize our table and add the required buttons:
chapter_08/09_editable_table_cells/script.js (excerpt)
TABLE.formwork = function(table) {
var $tables = $(table);
$tables.each(function () {
var _table = $(this);
_table
Licensed to JamesCarlson@aol.com
.find('thead tr')
.append($('&nbsp;'));
_table
.find('tbody tr')
.append($(''))
});
$tables.find('.edit :button').live('click', function(e) {
TABLE.editable(this);
e.preventDefault();
});
}
Our TABLE.formwork method looks for the selector we pass in, and then the rows
in the thead and tbody, adding an extra edit cell to each. The thead cell is empty,
but it’s there to retain the table’s column structure, and the tbody addition holds
the button that switches the row into edit mode. Even though we know that the
buttons exist prior to attaching our click event, event delegation through live is
the way of the future. This is why we’re using it to ensure that no matter how we
move our buttons around, remove them, or reinstate them, the click handlers will
stay in place.
The other point to remember here is that we’re applying the live method outside
the each loop. By applying it to $tables outside the loop, rather than _table inside,
we ensure that the event listener is only added once. We achieve the full effect, but
our code runs faster. And ninjas love fast code!

Lists, Trees, and Tables 327
Now comes the good part. We want our buttons to work in two states—an edit mode
and a save mode—and we want all the cells other than our button-containing cells
to become editable. Here’s how we’re going to do it:
chapter_08/09_editable_table_cells/script.js (excerpt)
TABLE.editable = function (button) {
var $button = $(button);
var $row = $button.parents('tbody tr');
var $cells = $row.children('td').not('.edit');
if ($row.data('flag')) { // in edit mode, move back to table
// cell methods
Licensed to JamesCarlson@aol.com
$row.data('flag', false);
$button.text('Edit');
}
else { // in table mode, move to edit mode
// cell methods
$row.data('flag', true);
$button.text('Save');
}
};
The live click event from TABLE.formwork passes in the button for the row we
want to edit, so we’ll use that to cache references to the parent row and the other
cells it contains. We use parents on the $button object to find the row, and children
on the $row—excluding the edit cell with not—to find all the other cells.
In the if statement, we set a data flag to let us know that the row is in edit mode,
and change the button text to reflect that. All that remains is to switch out the cell
contents.
We’ll look at the code in reverse order, to present the simpler code first. The easiest
part of what we need to do is exit edit mode:
chapter_08/09_editable_table_cells/script.js (excerpt)
$cells.each(function () {
var _cell = $(this);
_cell.html(_cell.find('input').val());
});

328 jQuery: Novice to Ninja
To move out of edit mode, we take the val of the input in the _cell, and add it as
the _cell’s html. Since we need to refer to the cell more than once (even if it’s only
twice!), we save it in a variable first to speed up performance.
Now for the code required to change into edit mode:
chapter_08/09_editable_table_cells/script.js (excerpt)
$cells.each(function () {
var _cell = $(this);
_cell.data('text',_cell.html())
.html('');
var $input = $('')
Licensed to JamesCarlson@aol.com
.val(_cell.data('text'))
.width(_cell.width() - 16);
_cell.append($input);
});
To make the cells editable is only slightly trickier. We’ll need to empty out the
current cell contents before we can add in the inputs, or we’ll break the layout.
Before we empty each cell, though, we store the current contents in data so we can
use them later.
We’re going to append an input we create, and as we have seen, it’s important to
manipulate anything we create before we add it to the DOM. The input’s width is
set smaller than the width of the cell to avoid any layout breakage, and we grab the
data we just saved to serve as the input element’s default value.
Try it out, and you’ll be impressed by how smoothly it works. Bet you’re starting
to feel like a ninja now!
DataTables Plugin
We’ve started down the path of creating a reusable data grid. There are infinite
number of ways to go from here; the table could do with being sortable by column,
or searchable—we could pack it with every crazy feature imaginable. But if you’re
pressed for time, and need a lot of features fast, you’ll want to research the plugin
options that are out there, such as the DataTables plugin.2
2
http://www.datatables.net/index

Lists, Trees, and Tables 329
DataTables is a truly impressive plugin for turning your HTML tables into fully
functional data grids, complete with pagination, column sorting, searching,
ThemeRoller support, Ajax loading, and more. As always, the decision to use a
plugin or build custom functionality comes down to how much time you have, how
many features you need, and how big a file you’re willing to serve to your visitors.
Oh, and also how much fun you think you’d have developing the functionality
yourself!
For the moment we’ve been able to add all the features required for our client’s
table-based needs on our own, and we’ve learned a lot about jQuery in doing it.
Before we move on to the next (and final) chapter, let’s revisit our check-all check-
boxes in the context of tables.
Licensed to JamesCarlson@aol.com
Selecting Rows with Checkboxes
Another feature that the public are becoming increasingly used to having is the
ability to select rows of a table (or any kind of list) by checking a checkbox associated
with the row. As well as selections being able to be made on an individual basis,
there are often shortcuts for selecting all items, selecting none, or inverting the
current selection of checkboxes. None of this is new to us: we’ve already built check-
all checkboxes and selection-inverting buttons.
But, dissatisfied with this, some users also want to be able to select continuous rows
by using the Shift key—just like in their desktop applications.
Selecting a Column of Checkboxes
Dealing with columns of data is much trickier than dealing with rows of data. They
may appear closely tied when viewed together onscreen, but when it comes to the
DOM they’re merely distant relatives. There are no handy next or previous functions
we can hook into.
Naturally, jQuery can help us out. Thanks to its sophisticated selector engine, we
can do whatever we want—it might just require a bit of thinking. To start with, we
know we need to grab the table; in our case, it’s good old #celebs.
Next, we want to retrieve the table rows: #celebs tr, and then the first columns of
each row #celebs tr td:nth-child(1). Finally, we make sure we’re only selecting
checkboxes: #celebs tr td:nth-child(1) :checkbox. That’s quite the selector!
How far we’ve come from our simple $('tr:odd').

330 jQuery: Novice to Ninja
We place another checkbox (with an id of checker) in the thead of our table. Because
it’s in a th rather than a td, our selector will ignore it. We can attach a click
handler to this to turn all the checkboxes on and off:
chapter_08/10_select_column_checkboxes/script.js (excerpt)
var chkSelector = 'tr td:nth-child(1) :checkbox';
$('#checker').click(function() {
$('#celebs ' + chkSelector)
.attr('checked', $(this).attr('checked'));
});
Licensed to JamesCarlson@aol.com
Shift-selecting Checkboxes
All on or all off is one thing … but our client now has high expectations of us. He
demands that he be able to Shift+Click on checkboxes to select a bunch at a time
within a range of rows. Just like in his webmail client.
Shift-selecting presents some fun problems. For starters, how do we know if the
user is pressing Shift? Luckily for us, jQuery events tell us! We can find out from
an event the state of the Shift key by checking the Boolean property e.shiftKey.
That was easy! How about finding out which row was clicked on? We can just jump
up the DOM and find the parent row of the checkbox, and fetch its index using
index. Another easy one.
Next, we have to know where the user last clicked, so we can have a start and end
point for checking boxes. The easiest way for us to do this is to store each click in
the table using the data method. That’s a lot of information we have, so let’s put it
into practice:

Lists, Trees, and Tables 331
chapter_08/10_select_column_checkboxes/script.js (excerpt)
$('#celebs ' + chkSelector).click(function(e) {
var $table = $(this).parents('table');
var lastRow = $table.data('lastRow');
var thisRow = $(this).parents('tr').index();
if (lastRow !== undefined && e.shiftKey) {
var numChecked = 0;
var start = lastRow < thisRow ? lastRow : thisRow;
var end = lastRow > thisRow ? lastRow : thisRow;
$table
.find(chkSelector)
.slice(start, end)
Licensed to JamesCarlson@aol.com
.attr('checked', true);
}
$table.data('lastRow', thisRow);
});
We spring into action only when there’s a lastRow value (it will be undefined the
first time through, before we’ve stored a click value on the table), and the user is
also holding down Shift.
With the source and destination rows in hand, we need to figure out which is further
down the table, so we can make sure that the starting point is before the end point.
Then we slice up our checkbox selection with jQuery’s slice method. With our
freshly sliced jQuery selection in hand, we can finish it off by checking all the re­
maining checkboxes in the selection. Now you can easily select ranges of rows in
a highly intuitive manner!

332 jQuery: Novice to Ninja
We’ve Made the A-list!
We’ve transformed the admin section of StarTrackr! from a horrible mess of poorly
formatted data into a usable desktop-style application—and in the process trans­
formed raw data into useful information. The ease with which jQuery lets us mold
standard HTML elements into powerful application-like controls means that the
holy grail of responsive, impressive, and accessible Rich Internet Applications is
well within our grasp.
What’s truly awesome to note at this stage, is that in terms of the core jQuery library,
almost all the functions we find ourselves using we’ve seen before at some earlier
stage. Surely this means we’re gaining a strong understanding of this powerful tool!
Licensed to JamesCarlson@aol.com
Now you just need to start trying out your own ideas—the time of automatically
reaching for the plugin should be well behind you. Try implementing a quick proof
of concept yourself, and you’ll be surprised how easy it is to reach the results you’re
looking for. Before long, you’ll find your code is as good as (if not better than!) what
you find in the plugin repository.
Speaking of the plugin repository—that’s one of the last legs in our jQuery journey.
In the next chapter, you’ll learn how to take all this fantastic functionality you’re
building and make it available to the whole world, in plugin form. We’ll also cover
a few other advanced topics to round out your jQuery ninja training nicely!

9
Chapter
Licensed to JamesCarlson@aol.com
Plugins, Themes, and Advanced Topics
jQuery, like the game of chess, or any version of Tetris, is simple to learn but difficult
to master. Thanks to its seamless integration with the Document Object Model,
jQuery feels natural and easy to use. But jQuery is a quiet achiever: it doesn’t like
to brag about it, but under the hood lies an extensible architecture, a powerful event
handling system, and a robust plugin framework.
Plugins
“Hey, now that everything’s in place—can you just go back and put that menu from
phase three into the admin section? And can you add the cool lists you made in the
last phase to those lists on the front end—and add the scroller effect you did … you
can just copy and paste the code, right?”
Ah, copy/paste: our good friend and worst enemy. Sure, it may seem like a quick
way to get a piece of functionality up and running—but we all know that this kind
of code reuse can so easily degenerate into our worst JavaScript nightmares. And
we can’t let that happen to our beautiful jQuery creations.

334 jQuery: Novice to Ninja
You’ve seen throughout the book how extremely useful the jQuery plugin architec­
ture is. We’ve made use of all manner of third-party creations—from styleable
scrollbars, to shuffling image galleries, to autocompleting form fields. The good
news is that it’s extremely easy to package your code as a plugin for reuse in your
other projects—and, if your code is really special, in other developers’ projects as
well!
Creating a Plugin
It will only be a short time into your jQuery-writing life before you have the urge
to turn some of your code into a plugin. There’s nothing better than seeing a bit of
your own code being called from the middle of a jQuery chain! The best part is that
Licensed to JamesCarlson@aol.com
it’s a very simple process to convert your existing jQuery into a plugin, and you can
make it as customizable as you like.
Setting Up
Before we start, we need an idea for our plugin. Some time ago the client mentioned
that he’d like to highlight all the text paragraphs on the page, so that when users
moved their mouse over the paragraphs, text would become unhighlighted to indicate
it had been read. While you’ll agree it’s far from being the best user interface idea,
it’s sufficiently simple to demonstrate how to make a plugin, without having to
concentrate on the effect’s code itself.
All we have to do to make a plugin callable like regular jQuery actions is attach a
function to the jQuery prototype. In JavaScript, the prototype property of any object
or built-in data type can be used to extend it with new methods or properties. For
our plugin, we’ll be using the prototype property of the core jQuery object itself
to add our new methods to it.
The safest (only!) way to do this is to create a private scope for the jQuery function.
This JavaScript trick ensures that your plugin will work nicely, even on pages where
a person is using the $ function for non-jQuery purposes:
(function($) {
// Shell for your plugin code
})(jQuery);

Plugins, Themes, and Advanced Topics 335
This code can go anywhere in your script, but standard practice is to put it in a
separate JavaScript file named jquery.pluginname.js, and include it as you’d include
any plugin. Now that you have a stand-alone file, you can easily use it in future
projects or share it with the world!
Inside this protective shell we can use the $ alias with impunity. That’s all there is
in the way of preliminaries—so it’s time to start to writing a plugin. First we need
to give it a name, highlightOnce, and attach it to the jQuery plugin hook, $.fn:
chapter_09/01_plugins/jquery.highlightonce.js (excerpt)
(function($) {
// Shell for your plugin code
Licensed to JamesCarlson@aol.com
$.fn.highlightOnce = function() {
// Plugin code
}
})(jQuery);
Internally $.fn is a shortcut to the jQuery.prototype JavaScript property—and it’s
the perfect place to put our plugins. This is where jQuery puts its actions, so now
that we’ve added our custom action we can call it as if it was built into jQuery.
At this point our code looks like a jQuery plugin, but it won’t act like one—there’s
still one more task left to do. If we were to perform some operations inside our
plugin code now, we would actually be working on the entire selection at once; for
example, if we ran $('p').highlightOnce(), we’d be operating on every paragraph
element as a single selection. What we need to do is work on each element, one at
a time, and return the element so the jQuery chain can continue. Here’s a fairly
standard construct for plugins:
chapter_09/01_plugins/jquery.highlightonce.js (excerpt)
// Plugin code
return this.each(function() {
// Do something to each item
});
So you now have a nice skeleton for creating simple plugins. Save this outline so
you can quickly create new plugins on a whim!

336 jQuery: Novice to Ninja
Adding the Plugin’s Functionality
Our highlightOnce plugin is ready to roll, so let’s give it a job to do. All the structure
we’ve added so far is just the scaffolding—now it’s time to create a building! The
type of code we can run in the guts of our plugin is exactly the same as the code
we’re used to writing; we can access the current object with the $(this) construct
and execute any jQuery or JavaScript code we need to.
The first function our plugin needs to accomplish is to highlight every selected
element, so we’ll just set the background to a bright yellow color. Next, we need to
handle when the user mouses over the element so we can remove the highlight. We
only want this to happen once, as soon as the element is faded back to the original
Licensed to JamesCarlson@aol.com
color (don’t forget, we need the jQuery UI Effects component to do that):
chapter_09/01_plugins/jquery.highlightonce.js (excerpt)
// Do something to each item
$(this)
.data('original-color', $(this).css('background-color'))
.css('background-color', '#fff47f')
.one('mouseenter', function() {
$(this).animate({
'background-color': $(this).data('original-color')
}, 'fast');
});
There just happens to be a jQuery action that fits our needs exactly: the one action.
Functionally, the one action is identical to the bind action we saw earlier, in that
it lets us attach an event handler to our element. The distinction with one is that
the event will only ever run once, after which the event will automatically unbind
itself.
For our code, we save the current background color in the element’s data store,
then bind the mouseover event to the DOM elements that are selected. When the
user mouses over the element, our code runs and the background color is animated
back to the original. And with that, our plugin is ready to be used: