This article, as well as a follow-up article coming next week, are
excerpts from Chapter 2 of the new SitePoint book, jQuery: Novice
to Ninja, by Earle Castledine and Craig Sharkie. You can grab the
entirety of Chapter 2, as well as Chapters 1 and 7 and the complete code
archive for the entire book for free here. Together, these two
articles constitute an introduction to jQuery for designers who’ve only ever
worked with CSS and HTML.
If you’ve been wanting to learn the basics of jQuery and start adding
some dynamic interactions to your website, this is the place to start. If
you’d like to follow along with the code in this article, download the
sample, which includes all of the code examples from the book.
“In phase two, we are going to want to harness the social and enable
Web 2.0 community-based, crowd-sourced, Ajax, um, interactions,” says our
new client. “But for now we just need some basic stuff changed on our
site.
Our client is launching a startup called StarTrackr! It uses GPS and RFID technology to track popular celebrities’ exact physical location—then sells that information to fans. It’s been going great guns operating out of a friend’s local store, but now they’re taking the venture online.
“Can you do it? Here’s a list that needs to be live by Friday, close of business.”
You survey the list. By amazing coincidence you notice that all of the requests can be implemented using jQuery. You look at your calendar. It’s Friday morning. Let’s get started!
The first task on the list is to add a simple JavaScript alert when the existing site loads. This is to let visitors know that StarTrackr! is not currently being sued for invasion of privacy (which was recently implied in a local newspaper).
Sure, we could use plain old JavaScript to do it, but we know that using jQuery will make our lives a lot easier—plus we can learn a new technology as we go along! We already saw the anatomy of a jQuery statement in Chapter 1; now let’s look at the steps required to put jQuery into action: we wait until the page is ready, select our target, and then change it.
You may have probably guessed that jQuery can be more complicated than this—but only a little! Even advanced effects will rely on this basic formula, with multiple iterations of the last two steps, and perhaps a bit of JavaScript know-how. For now, let’s start nice and easy.
Our client is launching a startup called StarTrackr! It uses GPS and RFID technology to track popular celebrities’ exact physical location—then sells that information to fans. It’s been going great guns operating out of a friend’s local store, but now they’re taking the venture online.
“Can you do it? Here’s a list that needs to be live by Friday, close of business.”
You survey the list. By amazing coincidence you notice that all of the requests can be implemented using jQuery. You look at your calendar. It’s Friday morning. Let’s get started!
The first task on the list is to add a simple JavaScript alert when the existing site loads. This is to let visitors know that StarTrackr! is not currently being sued for invasion of privacy (which was recently implied in a local newspaper).
Sure, we could use plain old JavaScript to do it, but we know that using jQuery will make our lives a lot easier—plus we can learn a new technology as we go along! We already saw the anatomy of a jQuery statement in Chapter 1; now let’s look at the steps required to put jQuery into action: we wait until the page is ready, select our target, and then change it.
You may have probably guessed that jQuery can be more complicated than this—but only a little! Even advanced effects will rely on this basic formula, with multiple iterations of the last two steps, and perhaps a bit of JavaScript know-how. For now, let’s start nice and easy.
Before we can interact with HTML elements on a page, those
elements need to have been loaded: we can only change them once they’re
already there. In the old days of JavaScript, the only reliable way to do
this was to wait for the entire page (including images) to finish loading
before we ran any scripts.
Fortunately for us, jQuery has a very cool built-in event that executes our magic as soon as possible. Because of this, our pages and applications appear to load much faster to the end user:
The important bits here (highlighted in bold) say, “When our document is ready, run our function.” This is one of the most common snippets of jQuery you’re likely to see. It’s usually a good idea to do a simple alert test like this to ensure you’ve properly included the jQuery library—and that nothing funny is going on.
The document-ready idiom is so common, in fact, that there’s a
shortcut version of it:
At a cursory glance, the document-ready event looks much removed from the structure we encountered back in our jQuery anatomy class, but on closer inspection we can see that the requisite parts are all accounted for: the selector is
We have jQuery ready to do our bidding—it just needs us to choose a target for it. Selecting the elements you want to modify on the page is really the art of jQuery. One of the biggest differences between being a novice and ninja is the amount of time it takes you to grab the elements you want to play with!
You might remember from our jQuery anatomy class that all of our selectors are wrapped in the
Fortunately for us, jQuery has a very cool built-in event that executes our magic as soon as possible. Because of this, our pages and applications appear to load much faster to the end user:
Example 1.
chapter_02/01_document_ready/script.js
$(document).ready(function() { alert('Welcome to StarTrackr! Now no longer under police …'); });
The important bits here (highlighted in bold) say, “When our document is ready, run our function.” This is one of the most common snippets of jQuery you’re likely to see. It’s usually a good idea to do a simple alert test like this to ensure you’ve properly included the jQuery library—and that nothing funny is going on.
important: You’ll Be Seeing $(document).ready() a
Lot!
Almost everything you do in jQuery will need to be done
after the document is ready—so we’ll be using this
action a lot. It will be referred to as the document-ready event from
now on. Every example that follows in this book, unless otherwise
stated, needs to be run from inside the document-ready event. You should
only need to declare it once per page though.$(function() { alert('Ready to do your bidding!'); });
If you’d like to use the shortcut method, go ahead! The expanded
version is arguably a better example of self-documenting code; it’s much
easier to see at a glance exactly what’s going on—especially if it’s
buried in a page of another developer’s JavaScript!At a cursory glance, the document-ready event looks much removed from the structure we encountered back in our jQuery anatomy class, but on closer inspection we can see that the requisite parts are all accounted for: the selector is
document
; the action is
ready
; and the parameter is a function that runs some
code (our alert
).Selecting: The Core of jQuery
Time is ticking, and deadlines wait for no one. The client has noted that people have been having quoting incorrect celebrity IDs from the web site. This is because the celebrities’ names are all laid out in one big table and it’s difficult for users to line up a celebrity with the correct reference ID. Our client tells us that he wants every other row to be a light gray color so the users can easily find their favorite celebrity.We have jQuery ready to do our bidding—it just needs us to choose a target for it. Selecting the elements you want to modify on the page is really the art of jQuery. One of the biggest differences between being a novice and ninja is the amount of time it takes you to grab the elements you want to play with!
You might remember from our jQuery anatomy class that all of our selectors are wrapped in the
jQuery
function:
jQuery()
Or the alias:
$()
We’ll be using the shortcut alias for the remainder of the book—it’s
much more convenient. As we mentioned earlier, there’s no real reason to
use the full jQuery name unless you’re having conflict issues with other
libraries (see ???).
Our task is to select alternate table rows on the
celebrity table. How do we do this? When selecting with jQuery, your
goal should be to be only as specific as required: you want to find out
the most concise selector that returns exactly what you want to change.
Let’s start by taking a look at the markup of the Celebrities table,
shown in Figure 1, “class and id attributes in the HTML page”.
We could start by selecting every table row element on the entire page. To select by element type, you simply pass the element’s HTML name as a string parameter to the
Similarly, if we wanted to select every paragraph,
jQuery borrows the conventions from CSS for referring to
Similarly, we can use a CSS
We could start by selecting every table row element on the entire page. To select by element type, you simply pass the element’s HTML name as a string parameter to the
$
function. To select
all table row elements (which are marked up with the
tag), you would simply write:$('tr')
important: Nothing Happens!
If you run this command, nothing will happen on the page. This
is expected—after all, we’re just selecting elements. But there’s no
need to worry; soon enough we’ll be modifying our selections in all
sorts of weird and wonderful ways.div
element, h1
heading, or input
box on the page, we would use these
selectors accordingly:
$('p')
$('div')
$('h1')
$('input')
But we don’t want to change every table row
on the celebrity page: just the rows in the table that have the
celebrity data. We need to be a bit more specific, and select first the
containing element that holds the list of celebrities. If you have a
look at the HTML and at Figure 1, “class and id attributes in the HTML page”, you can see
that the div
that contains our
celebrity table
has an id
of celebs
, while the table
itself has a class
of data
. We could use either of these to select
the table
.jQuery borrows the conventions from CSS for referring to
id
and class
names. To select by id
, use the hash symbol (#) followed by the element’s id
, and pass this as a string to the jQuery
function:$('#celebs')
You should note that the string we pass to the jQuery
function is exactly the same format as a CSS id
id
s should be unique, we expect this to only
return one element. jQuery now holds a reference to this element. selector. Because Similarly, we can use a CSS
class
selector to select by class
. We pass a string consisting of a
period (.
) followed by the element’s class
name to the jQuery function:$('.data')
Both of these statements will select the table but, as mentioned
earlier when we talked about the DOM, a class
can be shared by multiple elements—and
jQuery will happily select as many elements as we point it to. If there
were multiple tables (or any other elements for that matter) that also
had the class
data
, they’d all be selected. For that
reason, we’ll stick to using the id
for this one!
tip: Can You Be More Specific?
Just like with CSS, we can select either
$('.data')
or the more specific
$('table.data')
. By specifying an element type
in addition to the class
, the selector will only return
table
elements with the class
data
—rather than all
elements with the class data
.
Also, like CSS, you can add parent container selectors to narrow your selection even further.
We’ve selected the table successfully, though the table
itself is of no interest to us—we want every other
rowinside the containing
Let’s take this idea a step further. Say we wanted to select all
table
. To do this, we put a space between the
ancestor and the descendant: inside it. We’ve selected the containing element, and
from that containing element we want to pick out all the descendants
that are table rows: that is, we want to specify all table rows
$('#celebs tr')
You can use this construct to drill down to the elements that
you’re looking for, but for clarity’s sake try to keep your selectors as
succinct as possible.Let’s take this idea a step further. Say we wanted to select all
span
elements inside of p
elements, which are themselves inside
div
elements—but only if those
div
s happen to have a class of
fancy
. We would use the
selector:
$('div.fancy p span')
If you can follow this, you’re ready to select just about
anything!
Right, back to our task at hand. It feels like we’re
getting closer, but so far we’ve just been selecting blindly with no way
of knowing if we’re on the right path. We need a way of confirming that
we’re selecting the correct elements. A simple way to achieve this is to
take advantage of the length property. length returns the number of
elements currently matched by the selector. We can combine this with the
good ol’ trusty
This will alert the length of the selection—7 elements—for the celebrity
This will alert the correct length of 6 elements—the jQuery object is now holding our six celebrity table row elements.
If the alert shows 0, you’ll know there’s a mistake in your selector. A good way to troubleshoot this sort of issue is to reduce your selector to the smallest, simplest one possible.
In our example, we could simply write
alert
statement to ensure that our
elements have been selected:
Example 2.
chapter_02/02_selecting/script.js
$(document).ready(function() { alert($('#celebs tr').length + ' elements!'); });
This will alert the length of the selection—7 elements—for the celebrity
table
. This result might be
different from what you’d expect, as there are only six celebrities in
the table! If you have a look at the HTML, you’ll see where our problem
lies: the table
header is also a
tr
, so there are seven rows in total.
A quick fix involves narrowing down our selector to find only table rows
that lie inside the tbody
element:
Example 3.
chapter_02/03_narrowing_selection/script.js
$(document).ready(function() { alert($('#celebs tbody tr').length + ' elements!'); });
This will alert the correct length of 6 elements—the jQuery object is now holding our six celebrity table row elements.
If the alert shows 0, you’ll know there’s a mistake in your selector. A good way to troubleshoot this sort of issue is to reduce your selector to the smallest, simplest one possible.
In our example, we could simply write
$('#celebs')
, which would select just the table
element and alert a length of 1. From here you can make your selectors
more specific, and check that you’re selecting the correct number of
elements as you go.
With the knowledge that we’ve successfully selected all of
the table rows, narrowing our selection down to every other row is
simple—because jQuery has a filter to do it. A
filter removes certain items, and keeps only the ones we want. You’ll
acquire a feel for what can be filtered as we work through some more
examples, but for now we’ll just jump straight to the filter we need for
our zebra stripes:
Filters are attached to the item you want to filter (in this case, the table rows) and are defined by a colon, followed by the filter name. The
Example 4.
chapter_02/04_filters/script.js
$(document).ready(function() { alert($('#celebs tbody tr:even').length + ' elements!'); });
Filters are attached to the item you want to filter (in this case, the table rows) and are defined by a colon, followed by the filter name. The
:even
filter used here keeps every even-indexed element in the
selection and removes the rest, which is what we want. When we alert the
selection length now, we see 3, as expected. All of our odd-numbered
rows have been filtered out of the selection. There is a wide array of
jQuery selector filters available to us: :odd
(as you
might expect), :first
, :last
,
:eq()
(for selecting, for example, the third
element), and more. We’ll look at each of these in more detail as we
need them throughout the book.
One last trick for basic selecting is the ability to
select multiple elements in a single statement. This is very useful, as
we’ll often want to apply the same action to several elements in
unrelated parts of the page. Separating the selector strings with commas
allows you to do this. For example, if we wanted to select every
paragraph,
div
element, h1
heading, and input
box on the page, we’d use this
selector:
$('p,div,h1,input')
Learning how to use all these different selectors together to
access exactly the page elements you want is a big part of mastering
jQuery. It’s also one of the most satisfying parts of using jQuery,
since you can pack some fairly complex selection logic into a single
short line of code!
Selecting may seem quite easy and, up to a point, it is. But what
we’ve covered so far has only just scratched the surface of selecting.
In most cases the basics are all you’ll need: if you’re simply trying to
target an element or a bunch of related elements, the element name,
When moving around the DOM from a given element, the situation becomes a little trickier. jQuery provides a myriad of selectors and actions for traversing the DOM. Traversing means traveling up and down the page hierarchy, through parent and child elements. You can add and remove elements as you go, applying different actions at each step—which lets you perform some mind-bogglingly complex actions in a single jQuery statement!
If you’re a wiz at CSS, you’ll already be familiar with a lot of the statements; they’re mostly borrowed directly from the CSS specification. But there are probably a few that you’re unfamiliar with, especially if you’ve yet to spend much time learning CSS3 selectors. Of course, we’ll be covering and learning advanced selection techniques as we implement them in our examples and demos. For this reason, any time you want to find out more about all the jQuery selectors available, you can just head over to the online documentation and browse away!
id
, and class
are the most efficient and easiest
ways to achieve this.When moving around the DOM from a given element, the situation becomes a little trickier. jQuery provides a myriad of selectors and actions for traversing the DOM. Traversing means traveling up and down the page hierarchy, through parent and child elements. You can add and remove elements as you go, applying different actions at each step—which lets you perform some mind-bogglingly complex actions in a single jQuery statement!
If you’re a wiz at CSS, you’ll already be familiar with a lot of the statements; they’re mostly borrowed directly from the CSS specification. But there are probably a few that you’re unfamiliar with, especially if you’ve yet to spend much time learning CSS3 selectors. Of course, we’ll be covering and learning advanced selection techniques as we implement them in our examples and demos. For this reason, any time you want to find out more about all the jQuery selectors available, you can just head over to the online documentation and browse away!
Decorating: CSS with jQuery
Selecting elements in jQuery is the hard part. Everything else is both easy and fun. After we have selected our targets, we are able to manipulate them to build effects or interfaces. In this section we will cover a series of jQuery actions relating to CSS: adding and removing styles, classes, and more. The actions we execute will be applied individually to every element we’ve selected, letting us bend the page to our will!
Before we try changing CSS properties, let’s look first
into how we can simply access them. jQuery lets us do this with the
This code will alert the font size of the first element matched by the selector (as you’ve likely guessed, the
The nifty aspect about retrieving CSS properties with this method
is that jQuery gives you the element’s calculated
style. This means that you’ll receive the value that’s been
rendered in the user’s browser, rather than the value entered in the CSS
definition. So, if you gave a
We’ll see why that’s really important when we come to implement some funky tricks a bit later.
css
function. Try this:
Example 5.
chapter_02/05_reading_css_properties/script.js
$(document).ready(function() { var fontSize = $('#celebs tbody tr:first').css('font-size'); alert(fontSize); });
This code will alert the font size of the first element matched by the selector (as you’ve likely guessed, the
:first
filter will return the first element among those matched by the
selector).
tip: CSS Properties of Multiple Elements
You can ask for a CSS property
after selecting multiple elements, but this is almost always a bad
idea: a function can only return a single result, so you’ll still only
obtain the property for the first matched element.div
a
height of, say, 200 pixels in the CSS file, but the content inside it
pushed the height over 200 pixels, jQuery would provide you with the
actual height of the element, rather than the 200 pixels you’d
specified.We’ll see why that’s really important when we come to implement some funky tricks a bit later.
So far we’ve yet to see jQuery actually do
anything, and it’s high time to remedy that. We know the page is ready
(since we popped up an alert), and we’re fairly sure we’ve selected the
elements we’re interested in. Let’s check that we really have:
You probably saw that coming! This is the same
This is true for all of the examples that follow, so remember to wrap your code in the document-ready function.
It’s looking good! But perhaps we should add a little extra to
it—after all, more is more! What about a shade lighter font color to
really define our stripes? There are a few ways we could add a second
CSS property. The simplest way is to repeat the entire jQuery statement
with our new values:
These lines are executed one after the other. Though the end result is correct, it will become quite messy and inefficient if we have to change a whole slew of properties. Thankfully, jQuery provides us with a nice way to set multiple properties at the same time, using an object literal. Object literals are a JavaScript concept beyond the scope of this book, but for our purposes, all you need to know is that they provide an easy way of grouping together key/value pairs. For CSS, object literals allow us to match up our CSS properties (the keys) with the matching CSS values (the values) in a neat package:
The object literal is wrapped in curly braces, with each key separated from its corresponding value by a colon, and each key/value pair separated by a comma. It’s passed as a single parameter to the
Additionally, any key that’s already a keyword in the JavaScript language (such as
It can be confusing trying to remember which keys need to be quoted and which don’t, so it’s to be recommended that you just put all object keys in quotes each time.
Example 6.
chapter_02/06_zebra_striping/script.js
$(document).ready(function() { $('#celebs tbody tr:even').css('background-color','#dddddd'); });
You probably saw that coming! This is the same
css
function we used to read a CSS property, but
now it’s being passed an extra parameter: the value we wish to set for
that property. We’ve used the action to set the
background-color to the value
#dddddd
(a light gray). Open the file from the code
archive in your browser and test that it’s working correctly. You can
see the result in Figure 2, “Zebra striping implemented with jQuery”.
important: Were You Ready?
As mentioned previously, this command must be issued
from within our document-ready function. If we run the command before
the DOM is ready, the selector will go looking for the
#celebs
element, but will find nothing that
matches. At this point it will give up; it won’t even look for the
tr
elements, let alone change the
background style.This is true for all of the examples that follow, so remember to wrap your code in the document-ready function.
Example 7.
chapter_02/07_multiple_properties_1/script.js
(excerpt)$('#celebs tbody tr:even').css('background-color','#dddddd'); $('#celebs tbody tr:even').css('color', '#666666');
These lines are executed one after the other. Though the end result is correct, it will become quite messy and inefficient if we have to change a whole slew of properties. Thankfully, jQuery provides us with a nice way to set multiple properties at the same time, using an object literal. Object literals are a JavaScript concept beyond the scope of this book, but for our purposes, all you need to know is that they provide an easy way of grouping together key/value pairs. For CSS, object literals allow us to match up our CSS properties (the keys) with the matching CSS values (the values) in a neat package:
Example 8.
chapter_02/08_multiple_properties_2/script.js
(excerpt)$('#celebs tbody tr:even').css( {'background-color': '#dddddd', 'color': '#666666'} );
The object literal is wrapped in curly braces, with each key separated from its corresponding value by a colon, and each key/value pair separated by a comma. It’s passed as a single parameter to the
css
function. Using this method you can specify as
many key/value pairs as you like—just separate them with commas. It’s a
good idea to lay out your key/value pairs in a readable manner so you
can easily see what’s going on when you come back to your code later.
This is especially helpful if you need to set a larger number of
properties. As an example:
Example 9.
chapter_02/09_multiple_properties_3/script.js
(excerpt)$('#celebs tbody tr:even').css({ 'background-color': '#dddddd', 'color': '#666666', 'font-size': '11pt', 'line-height': '2.5em' });
note: To Quote or Not to Quote
In general, when dealing with JavaScript
objects, it’s unnecessary for the keys to be in quotes. However,
for jQuery to work properly, any key that contains a hyphen (as our
background-color and
font-size examples do) must be placed in quotes,
or written in camel case (like
backgroundColor
).Additionally, any key that’s already a keyword in the JavaScript language (such as
float
and class
) must also be
written in quotes.It can be confusing trying to remember which keys need to be quoted and which don’t, so it’s to be recommended that you just put all object keys in quotes each time.
Excellent! We’ve already struck two tasks off the client’s
list, and we have some funky jQuery happening. But if you stop and have
a look at our last solution, you might notice something a little fishy.
If you were to inspect the zebra-striped rows in a development tool such
as Firebug, you’d notice that the CSS properties have been added to the
paragraphs inline, as illustrated in Figure 3, “Inline styles viewed with Firebug”.
Inline styles are a big no-no in HTML/CSS best practice, right?
That’s quite true, and this also applies in jQuery: to keep your code
clear and maintainable, it makes more sense for all the styling
information to be in the same place, in your CSS files. Then, as we’ll
soon see, you can simply toggle those styles by attaching or removing
There are times when it is a good idea to use the
tip: Firebug
Firebug is a particularly useful tool for examining the DOM
in your browser, as well as monitoring and editing CSS,
HTML, and JavaScript (including jQuery). A debugger’s Swiss Army knife
for the Web, it will save you hours by helping you see exactly what
your browser thinks is going on. It’s available as a Mozilla Firefox
extension, or as a stand-alone JavaScript file that you can include in
your projects if you develop using another browser.class
attributes to your HTML
tags.There are times when it is a good idea to use the
css
jQuery method in the way we’ve just
seen. The most common application is when quickly debugging code: if you
just want to outline an element in red to make sure you’ve selected it
correctly, switching to your CSS file to add a new rule seems like a
waste of time.
If we need to remove the CSS from inline style rules,
where should we put it? In a separate style sheet, of course! We can
put the styles we want in a rule in our CSS that’s targeted to a given
The
Then, back in our JavaScript file, we’ll modify the selector to use jQuery’s
The result is exactly the same, but now when we inspect the table in Firebug, we’ll see that the inline styles are gone—replaced by our new class definition. This is shown in Figure 4, “Adding classes to table rows”.
That’s much better. Now, if we want to change the appearance of the zebra stripes in the future, we can simply modify the CSS file; this will save us hunting through our jQuery code (potentially in multiple locations) to change the values.
There’ll also be times when we want to remove class names from elements (we’ll see an example of when this is necessary very soon). The action to remove a class is conveniently known as
class
, and use jQuery to add or
remove that class from targeted elements in the HTML. Perhaps
unsurprisingly, jQuery provides some handy methods for manipulating
the class
attributes of DOM
elements. We’ll use the most common of these,
addClass
, to move our zebra stripe styles into the CSS file
where they belong.The
addClass
function accepts a string
containing a class
name as a
parameter. You can also add multiple class
es at the same time by separating the
class
names with a space, just as
you do when writing HTML:$('div').addClass('class_name');
$('div').addClass('class_name1 class_name2 class_name3');
We only want to add one class
name, though, which we’ll call
zebra
. First, we’ll add the rule
to a new CSS file (including it with a link
tag in our HTML page):
Example 10.
chapter_02/10_adding_classes/zebra.css
.zebra {
background-color: #dddddd;
color: #666666;
}
Then, back in our JavaScript file, we’ll modify the selector to use jQuery’s
addClass
method rather than
css
:The result is exactly the same, but now when we inspect the table in Firebug, we’ll see that the inline styles are gone—replaced by our new class definition. This is shown in Figure 4, “Adding classes to table rows”.
That’s much better. Now, if we want to change the appearance of the zebra stripes in the future, we can simply modify the CSS file; this will save us hunting through our jQuery code (potentially in multiple locations) to change the values.
There’ll also be times when we want to remove class names from elements (we’ll see an example of when this is necessary very soon). The action to remove a class is conveniently known as
removeClass
. This function is used in exactly the same way as
addClass
; we just pass the (un)desired class
name as a parameter:$('#celebs tr.zebra').removeClass('zebra');
It’s also possible to manipulate the id
attribute, or any other attribute for
that matter, using jQuery’s attr
method.
We’ll cover this method in more detail later in the book.
Next week we’ll bring you another introductory jQuery article. We’ll
be looking at modifying the DOM itself using jQuery: adding, removing, and
changing elements. We’ll also learn some of the basics of jQuery animation
and the theory behind the concept of progressive enhancement.
If you can’t wait till next week, you can read the full contents of both articles, as well as over 100 more pages of jQuery content, by downloading the sample PDF of jQuery: Novice to Ninja. It also includes the full code archive for the entire book.
If you can’t wait till next week, you can read the full contents of both articles, as well as over 100 more pages of jQuery content, by downloading the sample PDF of jQuery: Novice to Ninja. It also includes the full code archive for the entire book.
No comments:
Post a Comment