79
HTML attributes are not case sensitive, but JavaScript property names are. To convert
an attribute name to the JavaScript property, write it in lowercase. If the attribute is
more than one word long, however, put the first letter of each word after the first in
uppercase:
defaultChecked
and
tabIndex
, for example.
Some HTML attribute names are reserved words in JavaScript. For these, the general
rule is to prefix the property name with “html”. The HTML
for
attribute (of the
<label>
element), for example, becomes the JavaScript
htmlFor
property. “class” is a
reserved (but unused) word in JavaScript, and the very important HTML
class
attribute
is an exception to the rule above: it becomes
className
in JavaScript code. We’ll see
the
className
property again in Chapter 16.
The properties that represent HTML attributes usually have string value. When the
attribute is a boolean or numeric value (the
defaultChecked
and
maxLength
attributes of
an
<input>
element, for example), the properties values are booleans or numbers instead
of strings. Event handler attributes always have Function objects (or
null
) as their val-
ues. The HTML5 specification defines a few attributes (such as the
form
attribute of
<input>
and related elements) that convert element IDs to actual Element objects. Fi-
nally, the value of the
style
property of any HTML element is a CSSStyleDeclaration
object rather than a string. We’ll see much more about this important property in
Chapter 16.
Note that this property-based API for getting and setting attribute values does not define
any way to remove an attribute from an element. In particular, the
delete
operator
cannot be used for this purpose. The section that follows describes a method that you
can use to accomplish this.
15.4.2 Getting and Setting Non-HTML Attributes
As described above, HTMLElement and its subtypes define properties that correspond
to the standard attributes of HTML elements. The Element type also defines
getAttribute()
and
setAttribute()
methods that you can use to query and set non-
standard HTML attributes and to query and set attributes on the elements of an XML
document:
var image = document.images[0];
var width = parseInt(image.getAttribute("WIDTH"));
image.setAttribute("class", "thumbnail");
The code above highlights two important differences between these methods and the
property-based API described above. First, attribute values are all treated as strings.
getAttribute()
never returns a number, boolean, or object. Second, these methods use
standard attribute names, even when those names are reserved words in JavaScript. For
HTML elements, the attribute names are case insensitive.
Element also defines two related methods,
hasAttribute()
and
removeAttribute()
,
which check for the presence of a named attribute and remove an attribute entirely.
These methods are particularly useful with boolean attributes: these are attributes (such
376 | Chapter 15: Scripting Documents
How to C#: Basic SDK Concept of XDoc.PDF for .NET You may add PDF document protection functionality into your C# program. Hyperlink Edit. XDoc.PDF for .NET allows C# developers to edit hyperlink of PDF document
adding links to pdf document; add links to pdf
73
as the
disabled
attribute of HTML form elements) whose presence or absence from an
element matters but whose value is not relevant.
If you are working with XML documents that include attributes from other namespa-
ces, you can use the namespaced variants of these four methods:
getAttributeNS()
,
setAttributeNS()
,
hasAttributeNS()
, and
removeAttributeNS()
. Instead of taking a sin-
gle attribute name string, these methods take two. The first is the URI that identifies
the namespace. The second is usually the unqualified local name of the attribute within
the namespace. For
setAttributeNS()
only, however, the second argument is the
qualified name of the attribute and includes the namespace prefix. You can read more
about these namespace-aware attribute methods in Part IV.
15.4.3 Dataset Attributes
It is sometimes useful to attach additional information to HTML elements, typically
when JavaScript code will be selecting those elements and manipulating them in some
way. Sometimes this can be done by adding special identifiers to the
class
attribute.
Other times, for more complex data, client-side programmers resort to the use of
nonstandard attributes. As noted above, you can use the
getAttribute()
and
setAttribute()
methods to read and write the values of nonstandard attributes. The
price you pay, however, is that your document will not be valid HTML.
HTML5 provides a solution. In an HTML5 document, any attribute whose name is
lowercase and begins with the prefix “data-” is considered valid. These “dataset at-
tributes” will not affect the presentation of the elements on which they appear and they
define a standard way to attach additional data without compromising document
validity.
HTML5 also defines a
dataset
property on Element objects. This property refers to an
object, which has properties that correspond to the
data-
attributes with their prefix
removed. Thus
dataset.x
would hold the value of the
data-x
attribute. Hyphenated
attributes map to camel-case property names: the attribute
data-jquery-test
becomes
the property
dataset.jqueryTest
.
As a more concrete example, suppose that a document contains the following markup:
<span class="sparkline" data-ymin="0" data-ymax="10">
1 1 1 2 2 3 4 5 5 4 3 5 6 7 7 4 2 1
</span>
A sparkline is a small graphic—often a line plot—designed to be displayed within the
flow of text. In order to generate a sparkline, you might extract the value of the dataset
attributes above with code like this:
// Assumes the ES5 Array.map() method (or a work-alike) is defined
var sparklines = document.getElementsByClassName("sparkline");
for(var i = 0; i < sparklines.length; i++) {
var dataset = sparklines[i].dataset;
var ymin = parseFloat(dataset.ymin);
var ymax = parseFloat(dataset.ymax);
var data = sparklines[i].textContent.split(" ").map(parseFloat);
15.4 Attributes | 377
Client-Side
JavaScript
65
drawSparkline(sparklines[i], ymin, ymax, data); // Not yet implemented
}
At the time of this writing, the
dataset
property is not implemented in current browsers,
and the code above would have to be written like this:
var sparklines = document.getElementsByClassName("sparkline");
for(var i = 0; i < sparklines.length; i++) {
var elt = sparklines[i];
var ymin = parseFloat(elt.getAttribute("data-ymin"));
var ymin = parseFloat(elt.getAttribute("data-ymax"));
var points = elt.getAttribute("data-points");
var data = elt.textContent.split(" ").map(parseFloat);
drawSparkline(elt, ymin, ymax, data); // Not yet implemented
}
Note that the
dataset
property is (or will be, when implemented) a live, two-way in-
terface to the
data-
attributes of an element. Setting or deleting a property of
dataset
sets or removes the corresponding
data-
attribute of the element.
The
drawSparkline()
function in the above examples is fictitious, but Example 21-13
draws sparklines marked up like this using the
<canvas>
element.
15.4.4 Attributes As Attr Nodes
There is one more way to work with the attributes of an Element. The Node type defines
an
attributes
property. This property is
null
for any nodes that are not Element ob-
jects. For Element objects,
attributes
is a read-only array-like object that represents
all the attributes of the element. The attributes object is live in the way that NodeLists
are. It can be indexed numerically, which means that you can enumerate all the attrib-
utes of an element. And it can also be indexed by attribute name:
document.body.attributes[0] // The first attribute of the <body> elt
document.body.attributes.bgcolor // The bgcolor attribute of the <body> elt
document.body.attributes["ONLOAD"] // The onload attribute of the <body> elt
The values obtained when you index the
attributes
object are Attr objects. Attr objects
are a specialized kind of Node but are never really used like one. The
name
and
value
properties of an Attr return the name and value of the attribute.
15.5 Element Content
Take a look again at Figure 15-1, and ask yourself what the “content” of the
<p>
element
is. There are three ways we might answer this question:
• The content is the HTML string “This is a <i>simple</i> document.”
• The content is the plain-text string “This is a simple document.”
• The content is a Text node, an Element node that has a Text node child, and
another Text node.
378 | Chapter 15: Scripting Documents
67
Each of these are valid answers, and each answer is useful in its own way. The sections
that follow explain how to work with the HTML representation, the plain-text repre-
sentation, and the tree representation of element content.
15.5.1 Element Content As HTML
Reading the
innerHTML
property of an Element returns the content of that element as a
string of markup. Setting this property on an element invokes the web browser’s parser
and replaces the element’s current content with a parsed representation of the new
string. (Despite its name,
innerHTML
can be used with XML elements as well as HTML
elements.)
Web browsers are very good at parsing HTML and setting
innerHTML
is usually fairly
efficient, even though the value you specify must be parsed. Note, however, that re-
peatedly appending bits of text to the
innerHTML
property with the
+=
operator is usually
not efficient because it requires both a serialization step and a parsing step.
innerHTML
was introduced in IE4. Although it has long been supported by all browsers,
it has only become standardized with the advent of HTML5. HTML5 says that
innerHTML
should work on Document nodes as well as Element nodes, but this is not
universally supported yet.
HTML5 also standardizes a property named
outerHTML
. When you query
outerHTML
,
the string of HTML or XML markup that is returned includes the opening and closing
tags of the element on which you queried it. When you set
outerHTML
on an element,
the new content replaces the element itself.
outerHTML
is defined only for Element nodes,
not Documents. At the time of this writing,
outerHTML
is supported by all current
browsers except Firefox. (See Example 15-5, later in this chapter, for an
outerHTML
implementation based on
innerHTML
.)
Another feature introduced by IE and standardized by HTML5 is the
insertAdjacentHTML()
method, which allows you to insert a string of arbitrary HTML
markup “adjacent” to the specified element. The markup is passed as the second ar-
gument to this method, and the precise meaning of “adjacent” depends on the value
of the first argument. This first argument should be a string with one of the values
“beforebegin”, “afterbegin”, “beforeend” or “afterend”. These values correspond to
insertion points that are illustrated in Figure 15-3.
Figure 15-3. Insertion points for insertAdjacentHTML()
insertAdjacentHTML()
is not supported by current versions of Firefox. Later in this
chapter, Example 15-6 shows how to implement
insertAdjacentHTML()
using the
15.5 Element Content | 379
Client-Side
JavaScript
Download from Wow! eBook <www.wowebook.com>
86
innerHTML
property and also demonstrates how to write HTML insertion methods that
do not require the insertion position to be specified with a string argument.
15.5.2 Element Content As Plain Text
Sometimes you want to query the content of an element as plain text, or to insert plain-
text into a document (without having to escape the angle brackets and ampersands
used in HTML markup). The standard way to do this is with the
textContent
property
of Node:
var para = document.getElementsByTagName("p")[0]; // First <p> in the document
var text = para.textContent; // Text is "This is a simple document."
para.textContent = "Hello World!"; // Alter paragraph content
The
textContent
property is supported by all current browsers except IE. In IE, you can
use the Element property
innerText
instead. Microsoft introduced
innerText
in IE4,
and it is supported by all current browsers except Firefox.
The
textContent
and
innerText
properties are similar enough that you can usually use
them interchangeably. Be careful though to distinguish empty elements (the string “”
is falsy in JavaScript) from undefined properties:
/**
* With one argument, return the textContent or innerText of the element.
* With two arguments, set the textContent or innerText of element to value.
*/
function textContent(element, value) {
var content = element.textContent; // Check if textContent is defined
if (value === undefined) { // No value passed, so return current text
if (content !== undefined) return content;
else return element.innerText;
}
else { // A value was passed, so set text
if (content !== undefined) element.textContent = value;
else element.innerText = value;
}
}
The
textContent
property is a straightforward concatenation of all Text node descend-
ants of the specified element.
innerText
does not have a clearly specified behavior, but
differs from
textContent
in a few ways.
innerText
does not return the content of
<script>
elements. It omits extraneous whitespace and attempts to preserve table for-
matting. Also,
innerText
is treated as a read-only property for certain table elements
such as
<table>
,
<tbody>
, and
<tr>
.
Text in <script> elements
Inline
<script>
elements (i.e., those that do not have a
src
attribute) have a
text
prop-
erty that you can use to retrieve their text. The content of a
<script>
element is never
displayed by the browser, and the HTML parser ignores angle brackets and ampersands
within a script. This makes a
<script>
element an ideal place to embed arbitrary textual
data for use by your application. Simply set the
type
attribute of the element to some
380 | Chapter 15: Scripting Documents
60
value (such as “text/x-custom-data”) that makes it clear that the script is not executable
JavaScript code. If you do this, the JavaScript interpreter will ignore the script, but the
element will exist in the document tree and its
text
property will return the data to you.
15.5.3 Element Content As Text Nodes
Another way to work with the content of an element is as a list of child nodes, each of
which may have its own set of children. When thinking about element content, it is
usually the Text nodes that are of interest. In XML documents, you must also be pre-
pared to handle CDATASection nodes—they are a subtype of Text and represent the
content of CDATA sections.
Example 15-3 shows a
textContent()
function that recursively traverses the children
of an element and concatenates the text of all the Text node descendants. In order to
understand the code, recall that the
nodeValue
property (defined by the Node type)
holds the content of a Text node.
Example 15-3. Finding all Text node descendants of an element
// Return the plain-text content of element e, recursing into child elements.
// This method works like the textContent property
function textContent(e) {
var child, type, s = ""; // s holds the text of all children
for(child = e.firstChild; child != null; child = child.nextSibling) {
type = child.nodeType;
if (type === 3 || type === 4) // Text and CDATASection nodes
s += child.nodeValue;
else if (type === 1) // Recurse for Element nodes
s += textContent(child);
}
return s;
}
The
nodeValue
property is read/write and you can set it to change the content displayed
by a Text or CDATASection node. Both Text and CDATASection are subtypes of
CharacterData, which you can look up in Part IV. CharacterData defines a
data
prop-
erty, which is the same text as
nodeValue
. The following function converts the content
of Text nodes to uppercase by setting the
data
property:
// Recursively convert all Text node descendants of n to uppercase.
function upcase(n) {
if (n.nodeType == 3 || n.nodeTyep == 4) // If n is Text or CDATA
n.data = n.data.toUpperCase(); // ...convert content to uppercase.
else // Otherwise, recurse on child nodes
for(var i = 0; i < n.childNodes.length; i++)
upcase(n.childNodes[i]);
}
CharacterData also defines infrequently used methods for appending, deleting, insert-
ing, and replacing text within a Text or CDATASection node. Instead of altering the
content of existing Text nodes, it is also possible to insert brand-new Text nodes into
15.5 Element Content | 381
Client-Side
JavaScript
54
an Element or to replace existing nodes with new Text nodes. Creating, inserting, and
deleting nodes is the topic of the next section.
15.6 Creating, Inserting, and Deleting Nodes
We’ve seen how to query and alter document content using strings of HTML and of
plain text. And we’ve also seen that we can traverse a Document to examine the indi-
vidual Element and Text nodes that it is made of. It is also possible to alter a document
at the level of individual nodes. The Document type defines methods for creating Ele-
ment and Text objects, and the Node type defines methods for inserting, deleting, and
replacing nodes in the tree. Example 13-4 demonstrated both node creation and node
insertion, and that short example is duplicated here:
// Asynchronously load and execute a script from a specified URL
function loadasync(url) {
var head = document.getElementsByTagName("head")[0]; // Find document <head>
var s = document.createElement("script"); // Create a <script> element
s.src = url; // Set its src attribute
head.appendChild(s); // Insert the <script> into head
}
The subsections that follow include more details and examples of node creation, of the
insertion and deletion of nodes, and also of the use of DocumentFragment as a shortcut
when working with multiple nodes.
15.6.1 Creating Nodes
As shown in the code above, you can create new Element nodes with the
createElement()
method of the Document object. Pass the tag name of the element as
the method argument: this name is case-insensitive for HTML documents and case-
sensitive for XML documents.
Text nodes are created with a similar method:
var newnode = document.createTextNode("text node content");
Document defines other factory methods, such as the infrequently used
createComment()
, as well. We’ll use the
createDocumentFragment()
method in §15.6.4.
When working with documents that use XML namespaces, you can use
createElementNS()
to specify both the namespace URI and the tag name of the Element
to be created.
Another way to create new document nodes is to make copies of existing ones. Every
node has a
cloneNode()
method that returns a new copy of the node. Pass
true
to
recursively copy all descendants as well, or
false
to only make a shallow copy. In
browsers other than IE, the Document object also defines a similar method named
importNode()
. If you pass it a node from another document, it returns a copy suitable
for insertion into this document. Pass
true
as the second argument to recursively import
all descendants.
382 | Chapter 15: Scripting Documents
Documents you may be interested
Documents you may be interested