Tải bản đầy đủ (.pdf) (6 trang)

Practical prototype and scipt.aculo.us part 7 potx

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (93.03 KB, 6 trang )

if (person.country == "USA") {
Object.extend(data, {
socialSecurityNumber: "456-78-9012",
stateOfResidence: "TX",
standardTaxDeduction: true,
zipCode: 78701
});
}
Since objects are passed by reference, not value, the source object is modified
in place.
Object.extend also solves our typing woes when extending built-ins:
Object.extend(String.prototype, {
strip: function() {
//
},
gsub: function() {
//
},
times: function() {
//
},
toQueryParams: function() {
//
}
});
for (var i in String.prototype)
console.log(i);
//-> "strip", "gsub", "times", "toQueryParams"
That’s one annoyance out of the way. This construct cuts down on redundancy,
making code both smaller and easier to read. Prototype uses
Object.extend all over


the place internally: extending built-ins, “mixing in” interfaces, and merging default
options with user-defined options.
CHAPTER 2 ■ PROTOTYPE BASICS 23
WHY NOT USE OBJECT.PROTOTYPE.EXTEND?
If we were steadfastly abiding by JavaScript’s object orientation, we’d define Object.prototype.
extend, so that we could say the following:
var data = { height: "5ft 10in", hair: "brown" };
data.extend({
socialSecurityNumber: "456-78-9012",
stateOfResidence: "TX"
});
This may appear to make things easier for us, but it will make things much harder elsewhere.
Because properties defined on the prototypes of objects are enumerated in a
for in loop, augment-
ing
Object.prototype would “break” hashes:
for (var property in data)
console.log(property);
//-> "height", "hair", "socialSecurityNumber", "stateOfResidence", "extend"
There are ways around this, but they all involve changing the way we enumerate over objects.
And we’d be breaking a convention that’s relied upon by many other scripts that could conceivably
exist in the same environment as Prototype. In the interest of “playing well with others,” nearly all
modern JavaScript libraries abide by a gentleman’s agreement not to touch
Object.prototype.
$A: Coercing Collections into Arrays
Oftentimes in JavaScript, you’ll have to work with a collection that seems like an array but
really isn’t. The two major culprits are DOM
NodeLists (returned by getElementsByTagName
and other DOM methods) and the magic arguments variable within functions (which con-
tains a collection of all the arguments passed to the function).

Both types of collections have numeric indices and a
length property, just like
arrays—but because they don’t inherit from
Array, they don’t have the same methods
that arrays have. For most developers, this discovery is sudden and confusing.
$A provides a quick way to get a true array from any collection. It iterates through the
collection, pushes each item into an array, and returns that array.
CHAPTER 2 ■ PROTOTYPE BASICS24
The arguments Variable
When referenced within a function, arguments holds a collection of all the arguments
passed to the function. It has numeric indices just like an array:
function printFirstArgument() {
console.log(arguments[0]);
}
printFirstArgument('pancakes');
//-> "pancakes"
It isn’t an array, though, as you’ll learn when you try to use array methods on it.
function joinArguments() {
return arguments.join(', ');
}
joinArguments('foo', 'bar', 'baz');
//-> Error: arguments.join is not a function
To use the join method, we first need to convert the arguments variable to an array:
function joinArguments() {
return $A(arguments).join(', ');
}
joinArguments('foo', 'bar', 'baz');
//-> "foo, bar, baz"
DOM NodeLists
A DOM NodeList is the return value of any DOM method that fetches a collection of

elements (most notably
getElementsByTagName). Sadly, DOM NodeLists are nearly use-
less. They can’t be constructed manually by the user. They can’t be made to inherit
from
Array. And the same cross-browser issues that make it hard to extend HTMLElement
also make it hard to extend NodeList.
Any Prototype method that returns a collection of DOM nodes will use an array.
But native methods (like
getElementsByTagName) and properties (like childNodes) will
return a
NodeList. Be sure to convert it into an array before you attempt to use array
methods on it.
// WRONG:
var items = document.getElementsByTagName('li');
items = paragraphs.slice(1);
//-> Error: items.slice is not a function
CHAPTER 2 ■ PROTOTYPE BASICS 25
// RIGHT:
var items = $A(document.getElementsByTagName('li'));
items = items.slice(1);
//-> (returns all list items except the first)
$$: Complex Node Queries
The richness of an HTML document is far beyond the querying capabilities of the basic
DOM methods. What happens when we need to go beyond tag name queries and fetch
elements by class name, attribute, or position in the document?
Cascading Style Sheets (CSS) got this right. CSS, for those of you who don’t have
design experience, is a declarative language for defining how elements look on a page.
The structure of a CSS file consists of selectors, each with a certain number of rules
(i.e., “The elements that match this selector should have these style rules.”). A CSS file,
if it were obsessively commented, might look like this:

body { /* the BODY tag */
margin: 0; /* no space outside the BODY */
padding: 0; /* no space inside the BODY */
}
a { /* all A tags (links) */
color: red; /* links are red instead of the default blue */
text-decoration: none; /* links won't be underlined */
}
ul li { /* all LIs inside a UL */
background-color: green;
}
ul#menu { /* the UL with the ID of "menu" */
border: 1px dotted black; /* a dotted, 1-pixel black line around the UL */
}
ul li.current {
/* all LIs with a class name of "current" inside a UL */
background-color: red;
}
CHAPTER 2 ■ PROTOTYPE BASICS26
To put this another way, one side of our problem is already solved: in CSS, there
exists a syntax for describing specific groups of nodes to retrieve. Prototype solves the
other side of the problem: writing the code to parse these selectors in JavaScript and
turn them into collections of nodes.
The
$$ function can be used when simple ID or tag name querying is not powerful
enough. Given any number of CSS selectors as arguments,
$$ will search the document
for all nodes that match those selectors.
$$('li'); // (all LI elements)
//-> [<li class="current" id="nav_home">, <li id="nav_archives">,

<li id="nav_contact">, <li id="nav_google">]
$$('li.current'); // (all LI elements with a class name of "current")
//-> [<li class="current" id="nav_home">]
$$('#menu a'); // (all A elements within something with an ID of "menu")
//-> [<a href="/">, <a href="/archives">, <a href="/contact">,
<a href="" rel="external">]
There are two crucial advantages $$ has over ordinary DOM methods. The first is
brevity: using
$$ cuts down on keystrokes, even for the simplest of queries.
// BEFORE:
var items = document.getElementsByTagName('li');
// AFTER:
var items = $$('li');
As the complexity of your query increases, so does the savings in lines of code. $$
can be used to fetch node sets that would take many lines of code to fetch otherwise:
// find all LI children of a UL with a class of "current"
// BEFORE:
var nodes = document.getElementsByTagName('li');
var results = [];
for (var i = 0, node; node = nodes[i]; i++) {
if (node.parentNode.tagName.toUpperCase() == 'UL' &&
node.className.match(/(?:\s*|^)current(?:\s*|$)) {
results.push(node);
}
}
// AFTER:
var results = $$('ul > li.current');
CHAPTER 2 ■ PROTOTYPE BASICS 27
The second advantage is something we’ve talked about already: the nodes returned
by

$$ are already “extended” with Prototype’s node instance methods.
If you’re a web designer, you’re likely familiar with CSS, but the power of
$$ goes
far beyond the sorts of selectors you’re likely accustomed to.
$$ supports virtually all of
CSS3 syntax, including some types of selectors that you may not have encountered:
• Querying by attribute:

$('input[type="text"]') will select all text boxes.

$$('a[rel]') will select all anchor tags with a rel attribute.

$$('a[rel~=external]) will select all a elements with the word “external” in the
rel attribute.
• Querying by adjacency:

$$('ul#menu > li') li') selector> will select all li elements that are direct
children of
ul#menu.

$$('li.current + li') will select any li sibling that directly follows a
li.current in the markup.

$$('li.current ~ li') will select all the following siblings of a li.current
element that are li elements themselves.
• Negation:

$$('ul#menu li:not(.current)') will select all li elements that don’t have a
class name of
current.


$$('ul#menu a:not([rel])') will select all a elements that don’t have a rel
attribute.
These are just some of the complex selectors you can use in
$$. For more information
on what’s possible, consult the Prototype API reference online (
/>api/). We’ll encounter other complex selectors in some of the code we’ll write later in this
book.
CHAPTER 2 ■ PROTOTYPE BASICS28

×