Then we’ll add in a new element before the category title that contains a click event handler—this will open and close its branch:... Delegating events If we added a click event handler
Trang 1Licensed to JamesCarlson@aol.com
⋮
The tree can nest as far as is needed—just repeat the structure inside the appropriate
child list item Because it’s nice and consistent, you can easily generate the HTML
on the server
With the list on the page, the next step is to pretty it up with some CSS That’s in
your court, but our code will add a few extra classes you can use to customize the
display The handle class will be assigned to the element we’ll insert to act as the
toggle handle When a branch of the tree is opened, it’ll receive the opened class;
otherwise it’ll have the closed class We’ve used these classes below to add a CSS
sprite, which will change between a plus sign and a minus sign:
chapter_08/04_expandable_tree/script.js (excerpt)
The code for the tree is remarkably simple, thanks to the recursive nature of a tree:
we just have to do one small piece of code, and attach it to each subcategory Our
plan of attack for creating the expanding/collapsing tree effect is to first hide all of
the nested ul categories Then we’ll add in a new element before the category title
that contains a click event handler—this will open and close its branch:
Trang 2Licensed to JamesCarlson@aol.com
chapter_08/04_expandable_tree/script.js (excerpt)
Six chained actions Bet you’re feeling some of that jQuery power coursing through
your veins right about now! Here we see where consistent markup helps us out: in
each subcategory list we look for the previous spanelement—that’s the subcategory
title Then we insert a new span element right before the title
Because our handle was added before the title, we need to move back to it with the
prev action We add the handle and closed (it’s closed by default because of the
hide action) classes to it, and an event handler for when it’s clicked
At this stage the tree will be fully collapsed, with our brand-new handle prepended
to the titles All that’s left to do is toggle the branches when we click on it:
chapter_08/04_expandable_tree/script.js (excerpt)
When the handle is clicked, we toggle the closed and opened classes with
toggleClass If you specify multiple classnames with toggleClass, any specified
classthat exists on the element is removed, and any that are absent from the element
are added
Trang 3Licensed to JamesCarlson@aol.com
Advanced toggleClass
Another neat trick of toggleClass is that it accepts a second parameter: a Boolean value that specifies whether the class should be added or removed This might sound strange, but it’s a nice shortcut Consider this code:
With the toggleClass(class, switch) syntax, we can replace the if statement with the following concise syntax:
Finding the subcategory that we need to open and close is easy, thanks to the nextAll
action jQuery will check the next sibling, see that it’s a span element (the category
title), filter it out based on our expression, and move to the next sibling … which
is a ul item Bingo! We just toggle this and the tree swings open and closed
Event Delegation
Event delegation is a topic that’s applicable everywhere, but is particularly important
if you’re dealing with large trees The idea is that instead of applying individual
event handlers to every node in your tree, you apply a single event handler to inter
cept the click—and then figure out who the click was aimed at and run the appro
priate action
If you’ve been following along closely this might sound a bit familiar to you We
covered the live method in the section called “Prepare for the Future: live and
die” in Chapter 6 live handles event delegation for you—that’s the magic that
makes it possible—but it comes with a potential gotcha that you need to be aware
of
To acquire a better understanding of how event delegation works and why it is im
portant, let’s use it for real We’ll start with the following HTML that displays our
products in a categorized list:
Trang 4Licensed to JamesCarlson@aol.com
chapter_08/05_event_delegation/index.html (excerpt)
When users clicks on a celebrity, their selection should appear above the list in the
format “category > celebrity” as in Figure 8.4 So what’s the best way to capture this
information?
Figure 8.4 Delegating events
If we added a click event handler to every single list item—$('#picker
li').click(…)—we could end up with hundreds of handlers This would severely
impact performance, as the browser would need to keep track of them all and check
Trang 5Licensed to JamesCarlson@aol.com
them all whenever a click occurred on the page With event delegation, we add our
lone event handler to the parent of the list, and then access the target of the event
to determine the element that was actually clicked on The target property of an
event is the actual DOM element—so it needs to be wrapped in the jQuery selector
to obtain a jQuery object:
Our list acts as if each item has its own handler! Nice, but what was the gotcha
mentioned earlier? Event delegation works because of event bubbling (which we
looked at in the section called “Event Propagation” in Chapter 5)—the events will
bubble up until our parent handler catches them The problem occurs if a handler
catches the event before the parent and stops the event from propagating (with
e.stopPropagation, or "return false") If the event is stopped on its way up,
event delegation will fail! That’s why it’s important that you know how events are
being handled under the hood—it’ll save you a lot of headaches when dealing with
otherwise incomprehensible bugs
We’ve handled any clicks with a single handler, but we now need to find out a bit
more about where the element is located Specifically, how can we find out which
category the element is in? How about this:
(excerpt)
We’ve asked the closest method to find the closest element with the category
class If the element itself doesn’t have that class, closest will check its parent
… and so on until it finds a matching element This saves us having long strings of
parent().parent().parent(), and also lets us be more flexible in how we structure
our HTML
Trang 6Licensed to JamesCarlson@aol.com
If HTML lists are the unsung heroes of the new Web, tables are that bad kid who
ends up turning out good The tables themselves were never to blame—we misused
and abused them for years as a hack to lay out our web designs in a reasonable
cross-browser manner But that’s what CSS is for, not poor old tables And now that CSS
has come of age, we can finally return to using tables solely for their original purpose:
displaying tabular data
StarTrackr! has stacks of data to display So much so that it’s growing out of hand:
the tables are becoming overly large and unreadable, the information has no paging
or sorting mechanisms, and there’s no way to edit the information easily We saw
how easy it was to add zebra striping and row highlighting to tables in Chapter 2;
this will give us a few quick wins, but to address the more serious table issues, we’re
going to need some extra help from jQuery
Fixed Table Headers
The header row of a table is of paramount importance: without it, you’d be stuck
with rows of meaningless data But if you’re dealing with a large number of rows,
you’ll find that the headers become less and less helpful, as they scroll out of sight
and out of mind Paging the data generally takes care of the problem—but if you
need to have all the data on one page at the same time, you’ll have to think of another
option
Keeping the header row fixed at the top of the table is an effective way to keep track
of what our columns are about, and it also looks really cool! As the user scrolls the
table to reveal new data, the header follows along You can see this in action in
Figure 8.5
Figure 8.5 Fixed header row
Trang 7Licensed to JamesCarlson@aol.com
If your table is the only element on the page, position: fixed can be used to affix
the theadelement in place However, position: fixedcan only position an element
relative to the viewport, rather than its containing element This means that for
tables contained inside other elements (which will almost always be the case), we
need to turn to jQuery
Let’s have a look at how we can achieve this effect Our markup is the same
Celebrities table we added zebra-striping to back in Chapter 2:
chapter_08/06_fixed_table_headers/index.html (excerpt)
⋮
Moving the thead around is tricky Some browsers let you move it with impunity,
while in others it’s surprisingly resistant to styling So we’ll employ a trick: we’ll
duplicate the contents of the thead in the form of an unordered list, styled to look
exactly like the thead Then we’ll give that position: absolute;, and move it
around the screen as the user scrolls
We start by creating a TABLEwidget to hold our code, with a fixHeadermethod that
we’ll use for our fixed headers effect The method will expect to receive a selector
string pointing at a table on the page We start by storing a few selections inside
variables and in data, to speed up our subsequent code:
Trang 8Licensed to JamesCarlson@aol.com
chapter_08/06_fixed_table_headers/script.js (excerpt)
➥$thead.height());
⋮
We first declare a closure to hold on to our widget’s context Then we select any
tables that match the selector passed in to our method We loop over them with
each, and store a reference to the table itself ($table), the thead ($thead), and the
collection of thelements it contains ($ths) Finally, we store the left and top offsets
of the $thead, as well as the location of the bottom of the table (to avoid the header
moving past the bottom!)
Use each When Writing Selector-based Functions
When writing this sort of utility function, you should always anticipate the pos
sibility of your selector returning more than one element In this case, our page only has one table on it, so the method would work fine if we omitted the each loop But in the interests of preparing for the future, and making our code reusable, we’ve included the loop anyway; even if the table selector returns multiple tables, our function will handle them all with grace
Next, we create our faux header—a ul—and copy over the contents of the table
header:
Trang 9Licensed to JamesCarlson@aol.com
chapter_08/06_fixed_table_headers/script.js (excerpt)
With the real th elements collected in $ths, we use an each action to go through
each one and craft our mimics We copy the original elements’ class, HTML con
tents, width, and click event handlers Some of this is unnecessary in our simple
example, but it’s good to be prepared! After the list is fully populated, we hide it
and position it directly over our real thead before slipping it into the page
Append as Infrequently as Possible
You may wonder why we wait until the list is fully constructed before appending
it to the page While appending the list to the page first, and subsequently append
ing each item to it would have the same desired effect, the method we’ve chosen
to adopt executes much more quickly in the browser
Every time you insert a new element into the DOM, the browser needs to recalculate the position of every element on the page If you do this a lot (especially if you
do it in a loop!), your script can become very slow The method we’ve used above—storing the new elements in a variable, processing them as necessary, and then appending them all in one fell swoop—ensures optimal performance
With our thead mimic now nicely in place, we need to react to the scroll event and
position it appropriately:
Trang 10Licensed to JamesCarlson@aol.com
chapter_08/06_fixed_table_headers/script.js (excerpt)
➥$(document).scrollTop() < $tablẹdatắbottom')) {
We set the timeout for 100 milliseconds after the scrollevent fires; it’s a very short
time, but enough to ensure that we avoid constantly animating as the user scrolls
We check to see if we’ve scrolled the thead out of the viewport, but not past the
bottom of the table; if we have, we reveal our mimic and animate it to the correct
position If we’ve scrolled back high enough so that the original thead is visible, or
down past the bottom of the table, we fade out the impostor list (and position it
back at the top, so that it animates from the correct position when it reappears)
And there you have it! We can call our TABLẸfixHeader("#celebs") and scroll
the page, and the new “thead” follows along to keep the identifying labels visible
at all times
Repeating Header
Another approach to the disappearing header row problem is to simply repeat the
header at regular intervals This is particularly handy if the intention is for the data
to be printed out—as cool as it looks, our animated table header is unhelpful if you
need to sort through a dozen pages of printed tables! The goal would be to take the
first row of the table, and copy it every, say, ten rows
The result is shown in Figure 8.6
Trang 11Licensed to JamesCarlson@aol.com
Figure 8.6 Repeating the table header Copying the header row and putting it elsewhere should be old hat for you by now
But how exactly do we add the header every ten rows? Do we loop over every row
and check its index? Well, we could … but yet again jQuery comes to the rescue
with a powerful built-in filter:
chapter_08/07_repeating_table_header/script.js (excerpt)
This solution starts out simply enough—grabbing the first table row, then cloning
it with the clonemethod Then comes the clever bit: the nth-child selector is perfect
for adding the rows right where we want them You might be a little baffled by the
way we’ve used it, though, as its syntax differs a bit from the other filters we’ve
seen At its most basic, if you give it a simple integer, it will select that index For
example, if you selected :nth-child(2), you’d receive the third child element
Trang 12Licensed to JamesCarlson@aol.com
But the :nth-child selector also accepts other values, which cause it to select
multiple rows as the same time If you pass in the text values even or odd, you’ll
select all of the even or odd child elements Coolest of all, you can pass it an equation
to figure out which children to select!
You can use the letter “n” to indicate repetition; for example, :nth-child(10n)
will select every tenth row You can then augment this with a plus or minus to offset
which rows are selected For example, if you want to select the third row, and then
every tenth row after that, you could use :nth-child(10n+3) Finally, if you like
to think backwards, you could achieve the same result with :nth-child(10n-7)
That all works okay, but it does have a bug: if the last row of the table matches our
equation, the header row will be repeated as the final row—which looks a bit weird
Also, we want to apply the repeating header to a couple of tables, and want to avoid
having to copy/paste code Next chapter, we’ll look at making our code reusable
via plugins—but for now, we’ll keep it simple and stick with a trusty widget object:
chapter_08/07_repeating_table_header/script.js (excerpt)
We’ve created a function that accepts the selector string for our table, and a number
representing how many rows to leave between each repeat of the header We then
cycle through each element our selector finds (so our function can be applied to
more than one table on the same page) For each item, we set up a shortcut to the
$(this) element and grab the number of rows in the table It’s important to store