10.2 DOM manipulation skills [JavaScript advanced programming]

Many times, DOM manipulation is relatively straightforward, so it's not a hassle to use JavaScript to generate content that would normally be generated from HTML code. However, there are times when manipulating the DOM is not as simple as it seems. Because browsers are full of hidden pitfalls and incompatibilities, dealing with some parts of the DOM with JavaScript code is a bit more complicated than dealing with others.

10.2.1 Dynamic scripts

Use the <script> element to insert JavaScript code into a page, either by including external files through its src attribute, or by including the code within the element itself. The dynamic script to be discussed in this section refers to the script that does not exist when the page is loaded, but is dynamically added by modifying the DOM at some point in the future. Like manipulating HTML elements, there are two ways to create dynamic scripts: inserting external files and directly inserting JavaScript code.

Dynamically loaded external JavaScript files can run immediately, such as the following <script> element:

<script type="text/javascript" src="client.js"></script>

This <script> element contains the client-side detection script from Chapter 9. And the DOM code to create this node looks like this:

var script = document.createElement("script");
script.type = "text/javascript";
script.src = "client.js";
document.body.appendChild(script);

Obviously, the DOM code here faithfully reflects the corresponding HTML code. However, the external file will not be downloaded until the last line of code is executed to add the <script> element to the page. You can also add this element to the <head> element with the same effect.

The whole process can be encapsulated with the following function:

function loadScript(url) {
	var script = document.createElement("script");
	script.type = "text/javascript";
	script.src = url;
	document.body.appendChild(script);
}

Then, you can load external JavaScript files by calling this function:

loadScript("client.js");

Once loaded, the script can be used elsewhere in the page. There is only one problem: how do you know that the script is loaded? Unfortunately, there is no standard way to find out. However, some events related to this can come in handy, depending on the browser used, as discussed in Chapter 13.

Another way to specify JavaScript code is inline, as shown in the following example:

<script type="text/javascript">
	function sayHi() {
		alert("hi");
	}
</script>

Logically, the following DOM code is valid:

var script = document.createElement("script");
script.type = "text/javascript";
script.appendChild(document.createTextNode("function sayHi(){alert('hi');}"));
document.body.appendChild(script);

In Firefox, Safari, Chrome, and Opera, these DOM codes work fine. But in IE, it causes an error.
IE treats <script> as a special element and does not allow DOM access to its children. However, JavaScript code can be specified using the text attribute of the <script> element, as in the following example:

var script = document.createElement("script");
script.type = "text/javascript";
script.text = "function sayHi(){alert('hi');}";
document.body.appendChild(script);

run it

The code after this modification will work in IE, Firefox, Opera and Safari 3 and later. Versions of Safari prior to 3.0 did not properly support the text property, but allowed the use of text node technology to specify code. If you need compatibility with earlier versions of Safari, you can use the following code:

var script = document.createElement("script");
script.type = "text/javascript";
var code = "function sayHi(){alert('hi');}";
try {
    script.appendChild(document.createTextNode("code"));
} catch(ex) {
    script.text = "code";
}
document.body.appendChild(script);

Here, the standard DOM text node approach is tried first, since it is supported by all browsers except IE (where it causes an error to be thrown). If this line of code throws an error, then it's IE, so the text attribute must be used. The whole process can be represented by the following function:

function loadScriptString(code) {
	var script = document.createElement("script");
	script.type = "text/javascript";
	try {
		script.appendChild(document.createTextNode(code));
	} catch(ex) {
		script.text = code;
	}
	document.body.appendChild(script);
}

Here is an example of calling this function:

loadScriptString("function sayHi(){alert('hi');}");

Run it
. Code loaded in this way will be executed in the global scope and will be available as soon as the script executes. In effect, executing the code this way is the same as passing the same string to eval() in the global scope.

10.2.2 Dynamic styles

There are two elements that can include CSS styles into an HTML page. Among them, the <link> element is used to include files from outside, and the <style> element is used to specify embedded styles. Similar to dynamic scripts, the so-called dynamic styles refer to styles that do not exist when the page is first loaded; dynamic styles are dynamically added to the page after the page is loaded.

Let's take the following typical <link> element as an example:

<link rel="stylesheet" type="text/css" href="styles.css">

This element can easily be created dynamically using DOM code:

var link = document.createElement("link");
link.rel = "stylesheet";
link.type = "text/css";
link.href = "style.css";
var head = document.getElementsByTagName("head")[0];
head.appendChild(link);

The above code works fine in all major browsers. Note that the <link> element must be added to the <head> and not the <body> element for consistent behavior across browsers. The whole process can be represented by the following function:

function loadStyles(url) {
	var link = document.createElement("link");
	link.rel = "stylesheet";
	link.type = "text/css";
	link.href = url;
	var head = document.getElementsByTagName("head")[0];
	head.appendChild(link);
}

The code to call the loadStyles() function looks like this:

loadStyles("styles.css");

The process of loading external style files is asynchronous, that is, there is no fixed order in which styles are loaded and JavaScript code is executed.

In general, it doesn't matter whether or not a style has finished loading; however, there are several techniques for using events to detect when the process is complete, which are discussed in Chapter 13.

Another way to define styles is to use the <style> element to include embedded CSS, like this:

<style type="text/css">
	body { background-color: red; }
</style>

Following the same logic, the following DOM code should be valid:

var style = document.createElement("style");
style.type = "text/css";
style.appendChild(document.createTextNode("body{background-color:red}"));
var head = document.getElementsByTagName("head")[0];
head.appendChild(style);

run it

The above code will work in Firefox, Safari, Chrome and Opera, but will throw an error in IE. IE treats <style> as a special, <script>-like node, and does not allow access to its children. In fact, IE throws the same error at this point that it throws when adding a child node to the <script> element. The solution to this problem in IE is to access the element's styleSheet property, which in turn has a cssText property that accepts CSS code (both properties are discussed further in Chapter 13), as shown in the following example.

var style = document.createElement("style");
style.type = "text/css";
try {
	style.appendChild(document.createTextNode("body{background-color:red}"));
} catch(ex) {
	style.styleSheet.cssText = "body{background-color:red}";
}
var head = document.getElementsByTagName("head")[0];
head.appendChild(style);

Similar to adding embedded scripts dynamically, the rewritten code uses a try-catch statement to catch errors thrown by IE, and then style it in a special way for IE. So the general solution is as follows.

function loadStyleString(css) {
	var style = document.createElement("style");
	style.type = "text/css";
	try {
		style.appendChild(document.createTextNode(css));
	} catch(ex) {
		style.styleSheet.cssText = css;
	}
	var head = document.getElementsByTagName("head")[0];
	head.appendChild(style);
}

An example of calling this
function is as follows:

loadStyleString("body{background-color:red}");

This approach adds styles to the page in real time, so changes are immediately visible.

If you're writing code specifically for IE, use the styleSheet.cssText property with care. Reusing the same <style> element and setting this attribute again may crash the browser. Likewise, setting the cssText property to an empty string may also crash the browser. We hope this bug in IE will be fixed in the future.

10.2.3 Operation form

The <table> element is one of the most complex structures in HTML. To create a table, you generally have to involve labels that represent table rows, cells, headers, and so on. Creating and modifying tables using core DOM methods often involves writing a lot of code due to the number of tags involved. Suppose we want to use the DOM to create the following HTML table.

<table border="1" width="100%">
	<tbody>
		<tr>
			<td>
				Cell 1,1
			</td>
			<td>
				Cell 2,1
			</td>
		</tr>
		<tr>
			<td>
				Cell 1,2
			</td>
			<td>
				Cell 2,2
			</td>
		</tr>
	</tbody>
</table>

To create these elements using core DOM methods requires as much code as:

//create table
var table = document.createElement("table");
table.border = 1;
table.width = "100%";
//create tbody
var tbody = document.createElement("tbody");
table.appendChild(tbody);
//create the first row
var row1 = document.createElement("tr");
tbody.appendChild(row1);
var cell1_1 = document.createElement("td");
cell1_1.appendChild(document.createTextNode("Cell 1,1"));
row1.appendChild(cell1_1);
var cell2_1 = document.createElement("td");
cell2_1.appendChild(document.createTextNode("Cell 2,1"));
row1.appendChild(cell2_1);
//create second row
var row2 = document.createElement("tr");
tbody.appendChild(row2);
var cell1_2 = document.createElement("td");
cell1_2.appendChild(document.createTextNode("Cell 1,2"));
row2.appendChild(cell1_2);
var cell2_2 = document.createElement("td");
cell2_2.appendChild(document.createTextNode("Cell 2,2"));
row2.appendChild(cell2_2);
//Add the table to the document body
document.body.appendChild(table);

Run it
. Obviously, the DOM code is very long and a bit difficult to understand. To facilitate building tables, the HTML DOM also adds some attributes and methods to the <table>, <tbody>, and <tr> elements. The attributes and methods added to the <table> element are as follows.

  • caption: Holds a pointer to the <caption> element (if any).
  • tBodies: is an HTMLCollection of <tbody> elements.
  • tFoot: Holds a pointer to the <tfoot> element (if any).
  • tHead: Holds a pointer to the <thead> element (if any).
  • rows: is an HTMLCollection of all rows in a table.
  • createTHead(): Create a <thead> element, place it in the table, and return a reference.
  • createTFoot(): Create a <tfoot> element, place it in the table, and return a reference.
  • createCaption(): Create a <caption> element, place it in the table, and return a reference.
  • deleteTHead(): deletes the <thead> element.
  • deleteTFoot(): deletes the <tfoot> element.
  • deleteCaption(): deletes the <caption> element.
  • deleteRow(pos): Delete the row at the specified position.
  • insertRow(pos): Insert a row to the specified position in the rows collection.

The attributes and methods added to the <tbody> element are as follows.

  • rows: An HTMLCollection that holds the rows in the <tbody> element.
  • deleteRow(pos): Delete the row at the specified position.
  • insertRow(pos): Insert a row into the specified position in the rows collection, returning a reference to the newly inserted row.

The attributes and methods added to the <tr> element are as follows.

  • cells: An HTMLCollection that holds the cells in the <tr> element.
  • deleteCell(pos): Delete the cell at the specified position.
  • insertCell(pos): Inserts a cell at the specified position in the cells collection, returning a reference to the newly inserted cell.

Using these properties and methods, the amount of code required to create a table can be greatly reduced. For example, using these properties and methods, the preceding code can be rewritten as follows (the shaded part is the rewritten code).

//create table
var table = document.createElement("table");
table.border = 1;
table.width = "100%";
//create tbody
var tbody = document.createElement("tbody");
table.appendChild(tbody);
//create the first row
tbody.insertRow(0);
tbody.rows[0].insertCell(0);
tbody.rows[0].cells[0].appendChild(document.createTextNode("Cell 1,1"));
tbody.rows[0].insertCell(1);
tbody.rows[0].cells[1].appendChild(document.createTextNode("Cell 2,1"));
//create second row
tbody.insertRow(1);
tbody.rows[1].insertCell(0);
tbody.rows[1].cells[0].appendChild(document.createTextNode("Cell 1,2"));
tbody.rows[1].insertCell(1);
tbody.rows[1].cells[1].appendChild(document.createTextNode("Cell 2,2"));
//Add the table to the document body
document.body.appendChild(table);

run it

In this code, the code for creating <table> and <tbody> is unchanged. The difference is the part that creates the two lines, which uses the HTML DOM-defined table properties and methods. When creating the first row, the insertRow() method is called through the <tbody> element, passing in the parameter 0 - indicating where the inserted row should be placed. After executing this line of code, a row is automatically created and inserted into the <tbody> element at position 0, so the newly inserted row can be referenced immediately via tbody.rows[0].

The way to create a cell is also very similar, that is, call the insertCell() method through the <tr> element and pass in the position to place the cell. The newly inserted cell can then be referenced via tbody.rows[0].cells[0], since the newly created cell is inserted at position 0 in this row.

All in all, creating tables using these properties and methods is more logical and easier to understand, even though both sets of code are technically correct.

10.2.4 Using NodeList

Understanding NodeList and its "closer cousins" NamedNodeMap and HTMLCollection is the key to a thorough understanding of the DOM as a whole. All three collections are "dynamic"; in other words, they are updated whenever the document structure changes. Therefore, they always hold the latest and most accurate information. Essentially, all NodeList objects are queries that run in real-time as the DOM document is accessed. For example, the following code would cause an infinite loop:

var divs = document.getElementsByTagName("div"),
i,
div;
for (i = 0; i < divs.length; i++) {
	div = document.createElement("div");
	document.body.appendChild(div);
}

The first line of code gets an HTMLCollection of all <div> elements in the document. Since this collection is "dynamic", whenever a new <div> element is added to the page, this element will also be added to the collection. The browser does not keep all the collections created in a list, but updates the collection the next time the collection is accessed. As a result, an interesting problem arises when encountering the looping code shown in the example above. The condition i < divs.length is evaluated each time through the loop, meaning a query to get all <div> elements is run. Considering that the loop body creates a new <div> element each time and adds it to the document, the value of divs.length is incremented after each loop. Since i and divs.length are incremented at the same time each time, their values ​​will never be equal.

If you want to iterate over a NodeList, it's best to initialize a second variable with the length property, and then compare the iterator to that variable, as shown in the following example:

var divs = document.getElementsByTagName("div"),
i,
len,
div;
for (i = 0, len = divs.length; i < len; i++) {
	div = document.createElement("div");
	document.body.appendChild(div);
}

The second variable len is initialized in this example. Since len holds a snapshot of divs.length at the start of the loop, the infinite loop problem in the previous example is avoided. In the examples in this chapter that demonstrate iterating over NodeList objects, this more secure approach is used.

In general, the number of visits to the NodeList should be minimized. Because every time the NodeList is accessed, a document-based query is run. So, consider caching the values ​​obtained from the NodeList.

More chapter tutorials: http://www.shouce.ren/api/view/a/15218

 

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=326680238&siteId=291194637