Приглашаем посетить
Булгарин (bulgarin.lit-info.ru)

Dynamic Contents Properties

Dynamic Contents Properties

The Body element and all elements contained within it expose four properties for accessing and modifying the HTML contents: innerHTML, innerText, outerHTML, and outerText. An element's innerHTML property exposes its contents, including the HTML markup for any child elements. The innerText property exposes the contained text without any HTML tags. Assigning a new value to one of an element's inner properties replaces the contents of the element. The outerHTML and outerText properties resemble the innerHTML and innerText properties, but they reference the entire element rather than just its contents. Assigning a value to one of an element's outer properties replaces the entire element. In the following example, clicking a button replaces the button with the boldface text Blown Away!:

<HTML>
   <HEAD>
      <TITLE>Disappearing Button</TITLE>
   </HEAD>
   <BODY>
      <INPUT TYPE=BUTTON VALUE="Blow me away!"
         ONCLICK="this.outerHTML = `<B>Blown Away!</B>`">
   </BODY>
</HTML>

One limitation of these properties is that they can reference an element or its contents only in their entirety; they cannot reference just a portion of the contents. To use these properties to change the third character or word within an element, for example, you would have to reconstruct the string and reinsert it. The TextRange object provides an alternative technique that allows any portion of the document to be manipulated directly.

The dynamic contents properties use fairly strict rules for determining what HTML is valid. These rules are stricter than the rules used to originally parse the page, but not as rigid as the HTML DTD (document type definition). If you assign invalid HTML to one of these properties, an error can occur and the new contents might not be inserted. While the properties accept some invalid HTML, you should always supply syntactically valid HTML to ensure predictable results.

In addition to these properties, every element in the body of a document also exposes two methods for inserting contents before or after the begin or end tag: insertAdjacentHTML and insertAdjacentText. These two methods are useful for quickly inserting new paragraphs or list items into the document.

Figure 13-1 illustrates all the ways the contents of an element can be manipulated.

Dynamic Contents Properties

Figure 13-1. All the places HTML and text can be accessed and modified.

HTML vs. Text Properties

The primary distinction between the innerHTML and outerHTML properties on the one hand and the innerText and outerText properties on the other hand is that the HTML properties expose the entire markup while the text properties expose the contents without the markup. Consider the following HTML fragment:

<H1>Welcome to <EM>Scott's</EM> Home Page</H1>

For the H1 element in this fragment, the following table lists the values of each of the four properties.


Property Value
innerText Welcome to Scott's Home Page
innerHTML Welcome to <EM>Scott's</EM> Home Page
outerText Welcome to Scott's Home Page
outerHTML <H1>Welcome to <EM>Scott's</EM> Home Page</H1>

The innerText and outerText properties always return the same value but behave differently when you assign new values to them. Assigning a value to the innerText property replaces only the contents of the H1 element with new contents. Assigning a value to the outerText property replaces the H1 element and its contents with the new text. For example, assigning the value Thank you for visiting to each of these properties has different results: When you assign this value to the innerText property, the resulting HTML is <H1>Thank you for visiting</H1>. If you use the outerText property instead, the resulting HTML is Thank you for visiting; the <H1> tags are removed.

The markup in the values of the innerHTML and outerHTML properties does not necessarily match the markup in the source code. Instead, extraneous spaces are cleaned up and the attributes may be reordered. When you assign values to the HTML-related properties, be sure to use proper escape sequences for any entities. The < and > angle brackets are interpreted as tag delimiters; if the angle brackets are to be included in the contents and not parsed as HTML, they must be specified as entities by using &lt; and &gt;. When you assign values to the text properties, these brackets are automatically converted to their escape sequence equivalents.

Nonbreaking Spaces

Nonbreaking spaces (spaces at which line breaks are prohibited) and ordinary spaces are considered separate characters in the object model, where they are represented by the ASCII values 160 and 32, respectively. Comparing the two characters yields the value false, as in this example:

<SPAN ID="s1">&nbsp;</SPAN>
document.all.s1.innerText == " "  // false; not a space

To check whether a nonbreaking space is an element's contents, either check the ASCII value directly or compare the HTML property to the entity itself, as shown here:

document.all.s1.innerHTML == "&nbsp;" // true

Any specified entity that matches a built-in entity value is converted to the built-in name. The nonbreaking space entity can also be specified as &#160; instead of using its keyword identifier. Dynamic HTML recognizes that this value is a nonbreaking space and converts it to &nbsp;.

Using the Dynamic Contents Properties

The easiest way to learn the differences between the dynamic contents properties on an element is through examples. The following sections present two examples: the first is a review of the ticking clock example in Chapter 4, "The Browser Window," and the second is a tic-tac-toe game that demonstrates dynamically retrieving contents and assigning contents into a document.

A Ticking Clock

The ticking clock example in Chapter 4 uses the innerText property to update the time. A Span element with the ID clock contains the text with the current time. Every second, a script calls a function named buildTime to create a string with the current time, and then outputs the string into the Span element with ID clock using this statement:

document.all.clock.innerText = buildTime();

Tic-Tac-Toe

The tic-tac-toe example creates an interactive game using dynamic contents. A table provides the layout for the game board. Every time the user clicks in a cell, the cell's contents are replaced with an X or an O using the innerText property. The size of the game board can be dynamically changed by inserting a new table in the place of the existing one using the outerHTML property.

<HTML>
   <HEAD>
      <TITLE>Tic-Tac-Toe</TITLE>
      <STYLE TYPE="text/css">
         TD {font-weight:bold}
         #board TD {width:50px; height:50px; text-align:center;
            font-size:18pt; cursor:hand}
         .X {color:blue}
         .O {color:red}
         .draw {color:green}
      </STYLE>
      <SCRIPT LANGUAGE="JavaScript">
         function TicTac() {
            // Object for tracking the game
            this.lastMove = true;
            this.inProcess = true;
            this.scores = new Object();
            this.scores.xScore = 0; 
            this.scores.oScore = 0;
            this.scores.draws = 0;
            this.size = 3;
            this.drawBoard = initBoard;
         }

         function buildTable() {
            // Build the HTML table to be inserted into the document.
            var tb = "<TABLE BORDER ID=board
               ONCLICK=`doBoardClick();'>";
            for (var intRow = 0; intRow < game.size; intRow++) {
               tb += "<TR>";
               for (var intCell = 0; intCell < game.size; intCell++)
                  tb += "<TD>&nbsp;</TD>";
               tb += "</TR>";
            }
            tb += "</TABLE>";
            return tb;
         }

         function initBoard() {
            document.all.board.outerHTML = buildTable();
            game.inProcess = true;
            game.lastMove = true;
         }

         function checkWinner(xCount, oCount) {
            // Process results of the scan for a winner.
            if (game.size == xCount) {
               alert("X Wins!");
               game.scores.xScore++;
               return false;
            }
            if (game.size == oCount) { 
               alert("O Wins!");
               game.scores.oScore++;
               return false;
            }
            return true;
         }

         function checkGame() {
            // Tests all the directions for a winner.
            var xCount = 0, oCount = 0, total = 0;
            var el = document.all.board;
            // Check horizontal direction.
            for (var intRows = 0; intRows < el.rows.length;
                  intRows++) {
               xCount = 0, oCount = 0;
               for (var intCells = 0;
                     intCells < el.rows[intRows].cells.length;
                     intCells++) {
                  var strCell = el.rows[intRows].cells[intCells];
                  if ("X" == strCell.innerText)
                     xCount++;
                  if ("O" == strCell.innerText)
                     oCount++;
               }
               game.inProcess = checkWinner(xCount, oCount);
               if (!game.inProcess) 
                  return;
               total += xCount + oCount;
            }
            // Check vertical direction.
            for (var intCells = 0; intCells < el.rows.length;
                  intCells++)  {
               xCount = 0, oCount = 0;
               for (var intRows = 0;
                     intRows < el.rows[intCells].cells.length;
                     intRows++) {
                  var strCell = el.rows[intRows].cells[intCells];
                  if ("X" == strCell.innerText)
                     xCount++;
                  if ("O" == strCell.innerText)
                     oCount++;
               }
               game.inProcess = checkWinner(xCount, oCount);
               if (!game.inProcess) return;
            }

            // Check diagonal (upper left to lower right).
            xCount = 0, oCount = 0;
            for (var intRows = 0; intRows < el.rows.length;
                  intRows++) {
               var strCell = el.rows[intRows].cells[intRows];
               if ("X" == strCell.innerText)
                  xCount++;
               if ("O" == strCell.innerText)
                  oCount++;
            }
            game.inProcess = checkWinner(xCount, oCount);
            if (!game.inProcess) return;

            // Check diagonal (lower left to upper right).
            xCount = 0, oCount = 0;
            for (var intRows = 0; intRows < el.rows.length;
                  intRows++) {
               var strCell =
                  el.rows[game.size - intRows - 1].cells[intRows];
               if ("X" == strCell.innerText)
                  xCount++;
               if ("O" == strCell.innerText)
                  oCount++;
            }
            game.inProcess = checkWinner(xCount, oCount);
            if (!game.inProcess)
               return;
            if (total == game.size * game.size) {
               alert("draw");
               game.inProcess = false;
               game.scores.draws++;
               return
            }
         }

         function updateScore() {
            // Output new score.
            for (scores in game.scores) 
               document.all[scores].innerText = game.scores[scores];
         }
            
         function doBoardClick() {
            if (game.inProcess) {
               if ("TD" == event.srcElement.tagName) {
                  var strCell = event.srcElement;
                  // Check whether the cell is available.
                  if ("&nbsp;" == strCell.innerHTML) {
                     strCell.innerText = (game.lastMove ? "X" : "O");
                     event.srcElement.className = 
                        game.lastMove ? "X" : "O";
                     game.lastMove = !game.lastMove;
                  }
               }
               checkGame();
               if (!game.inProcess)
                  updateScore();
            }
         }

         // Manages the game variables
         var game = new TicTac;
      </SCRIPT>
      <SCRIPT FOR="size" EVENT="onclick()" LANGUAGE="JavaScript">
         // Shared event handler for the board-sizing radio buttons
         game.size = parseInt(this.value);
         game.drawBoard();
      </SCRIPT>
   </HEAD>
   <BODY>
      <H1>Tic-Tac-Toe</H1>
      <P><INPUT TYPE=BUTTON VALUE="New Game"
            ONCLICK="game.drawBoard();">
      <P><INPUT NAME=size TYPE=RADIO VALUE="3" ID="x3" checked>
         <LABEL FOR="x3">3 x 3</LABEL><BR>
         <INPUT NAME=size TYPE=RADIO VALUE="4" ID="x4">
         <LABEL FOR="x4">4 x 4</LABEL><BR>
         <INPUT NAME=size TYPE=RADIO VALUE="5" ID="x5">
         <LABEL FOR="x5">5 x 5</LABEL>
      <P>
      <SCRIPT LANGUAGE="JavaScript">
         document.write(buildTable());
      </SCRIPT>
      <TABLE>
         <TR class=x><TD>X Wins:</TD><TD ID=xScore>0</TD></TR>
         <TR class=o><TD>O Wins:</TD><TD ID=oScore>0</TD></TR>
         <TR class=draw><TD>Draws:</TD><TD ID=draws>0</TD></TR>
      </TABLE>
   </BODY>
</HTML>

Figure 13-2 shows the Tic-Tac-Toe program in action.

Dynamic Contents Properties

Figure 13-2. The Tic-Tac-Toe program game board.

Using the Adjacent Methods

An element's insertAdjacentHTML and insertAdjacentText methods insert HTML and text before or after the start tag, or before or after the end tag. Both methods take two arguments: the first argument represents where the contents are being inserted, and the second argument represents the actual contents.

The four valid values for the first argument represent each of the four insertion locations: beforeBegin, afterBegin, beforeEnd, and afterEnd, where Begin represents the begin tag and End represents the end tag. These methods are useful for insertions that do not affect any of the existing contents.

Generating Footnotes

This example demonstrates how to add pop-up footnotes to a page. The following code works by locating all elements that are specified as footnotes and inserting footnote numbers in the document. The author designates a footnote by adding a Span element with the class name footnote. The style sheet defines these Span elements as invisible. An alert containing the footnote text is displayed when the user clicks on the inserted footnote number.

<HTML>
   <HEAD>
      <TITLE>Dynamic Footnotes</TITLE>
      <STYLE TYPE="text/css">    
         SPAN {display:none}
         SUP.FNID {color:blue; cursor:hand}
      </STYLE>
      <SCRIPT LANGUAGE="JavaScript">
         function setupFootnotes() {
            // Get a collection of all the Span elements.
            var spans = document.all.tags("SPAN");
            for (var i = 0; i < spans.length; i++) {
               var el = spans[i];
               // If element is a footnote, process it.
               if ("footnote" == el.className) {
                  // Add a superscripted footnote number.
                  el.insertAdjacentHTML("beforeBegin",
                     "<SUP CLASS=FNID>" + (i + 1) + " </SUP>");
                  // Link the footnote number to the Span element.
                  document.all[el.sourceIndex - 1].linkFN = el;
               }
            }
         }

         function displayFN() {
            // If the number is clicked on, display the footnote.
            if ("FNID" == event.srcElement.className)
               if (null != event.srcElement.linkFN)
                  alert(event.srcElement.linkFN.innerText);
         }
      
         window.onload = setupFootnotes;
         document.onclick = displayFN;
      </SCRIPT>
   </HEAD>
   <BODY>
      <H1>Dynamic Footnotes
         <SPAN CLASS="footnote">
            Copyright (C) 1997 by Scott Isaacs.
         </SPAN>
      </H1>
      <P>Dynamic HTML is a "powerful way of creating Web pages"
         <SPAN CLASS="footnote">Scott Isaacs, "Inside Dynamic HTML."
         </SPAN>
         and "Soon Dynamic HTML will be used in most applications."
         <SPAN CLASS="footnote">
            Joe-Cool Developer, "The Future of the Web."
         </SPAN>
      <P>This page automatically generates and numbers the footnotes
         at load time. The footnotes are stored as hidden contents on
         the page.</P>
   </BODY>
</HTML>

You can display footnotes in ToolTips rather than in alert boxes by setting the TITLE attribute of each footnote number to the text of the footnote. Another alternative would be to display each footnote within the text when the user clicks the footnote number; for this technique, customize the displayFN function to change the display style attribute for the footnote text.

Creating Custom HTML List Boxes

This example uses HTML elements to simulate two list boxes whose items can be selected and copied between the lists. A single custom list box can also be used without the copying functionality to provide a rich selection list.

In the following code, two list boxes are created using scrolling DIV elements. Each item in the list boxes is a standard list item in a bulleted list. When the user clicks on an item to select it, the background color changes. When the user double-clicks on the item or clicks one of the arrow buttons, the item is removed from one list and inserted at the end of the other list using the insertAdjacentHTML method.

<HTML>
   <HEAD>
      <TITLE>Custom HTML List Boxes</TITLE>
      <STYLE TYPE="text/css">
         .list {cursor:hand; overflow:auto; height:75pt; width:150pt;
            border:1pt black solid}
         .list UL {list-style-type:none; margin-left:2pt;
            margin-top:0pt; margin-bottom:0pt}
         .list UL LI {margin-top:0pt; margin-bottom:0pt}
         .list UL LI.selected {background:navy; color:white}
      </STYLE>
      <SCRIPT LANGUAGE="JavaScript">
         function checkParent(src, tag) {
            while ("HTML" != src.tagName) {
               if (tag == src.tagName)
                  return src;
               src = src.parentElement;
            }
            return null;
         }

         function selectItem(list) {
            var el = checkParent(event.srcElement, "LI");
            if ("LI" == el.tagName) {
               if (null != list.selected)
                  list.selected.className = "";
               if (list.selected != el) {
                  el.className = "selected";
                  list.selected = el;
               }
               else
                  list.selected = null;
            }
         }

         function copy(src, dest) {
            var elSrc = document.all[src];
            var elDest = document.all[dest];
            if (elSrc.selected != null) {
               elSrc.selected.className = "";
               elDest.insertAdjacentHTML("beforeEnd",
                  elSrc.selected.outerHTML);
               elSrc.selected.outerHTML = "";
            }
         }
      </SCRIPT>
   </HEAD>
   <BODY>
      <H1>Custom HTML List Boxes</H1>
      <P>The bulleted lists simulate rich HTML selection lists.</P>
      <TABLE>
         <TR> 
            <TD>
               <DIV CLASS="list">
                  <UL ID="src" ONCLICK="selectItem(this);"
                        ONDBLCLICK="copy(`src', `dest');">
                     <LI>Scott's <EM>Home</EM> Page</LI>
                     <LI>Parents' Home Page</LI>
                     <LI><IMG SRC="foo.gif"></LI>
                     <LI>Inside Dynamic HTML Home Page</LI>
                     <LI>Microsoft Home Page</LI>
                     <LI>Item 6</LI>
                     <LI>Item 7</LI>
                  </UL>
               </DIV>
            </TD><TD>
               <P><INPUT TYPE=BUTTON VALUE="—>"
                  ONCLICK="copy(`src', `dest');">
               <P><INPUT TYPE=BUTTON VALUE="<—"
                  ONCLICK="copy(`dest', `src');">
            </TD><TD>
               <DIV class="list">
                  <UL ID="dest" ONCLICK="selectItem(this);"
                     ONDBLCLICK="copy(`dest','src');">
                  </UL>
               </DIV>
            </TD>
         </TR>
      </TABLE>
   </BODY>
</HTML>

Figure 13-3 illustrates these custom list boxes.

Dynamic Contents Properties

Figure 13-3. Two list boxes created from existing HTML elements.

Accessing the Contents

The contents of a document cannot be accessed or manipulated until the document is completely loaded. Therefore, be careful when a script or an event handler attempts to access and manipulate the contents. If the code might execute before the page is loaded, the code should first test the readyState property of the document:

if ("complete" == document.readyState) {
   // Manipulate the contents.
}
else {
   // Display a warning or perform alternative action.
}

Image Error Handling

The next example demonstrates how to queue document changes until the page is entirely loaded. This example replaces any images that fail to download with error messages and the images' titles. The trick here is to ensure that the document is loaded before accessing the contents because an image might fail and fire the onerror event before the page is completely loaded.

The following code builds a collection of all the images that failed before the page entirely loaded. Once the page is loaded, each image in the queue of bad images is replaced with the appropriate text. Any future errors are handled immediately.

<HTML>
   <HEAD>
      <TITLE>Image Error Handling</TITLE>
      <STYLE TYPE="text/css">
         SPAN.error {background:yellow; font-weight:bold}
      </STYLE>
      <SCRIPT LANGUAGE="JavaScript">
         var Errors = new Array();
         Errors[0] = 0;

         function badImage(el) {
            if (document.readyState != "complete") {
               Errors[0]++;
               Errors[Errors[0]] = el;
            }
            else  // The document is loaded; output error directly.
               el.outerHTML =
                  "<SPAN CLASS=`error'>Error Loading Image: " +
                     el.title + "</SPAN>";
         }
 
         function reviewErrors() {
            for (var i = 1; i <= Errors[0]; i++)
               Errors[i].outerHTML =
                  "<SPAN CLASS=`error'>Error Loading Image: " +
                     Errors[i].title + "</SPAN>";
         }
  
         window.onload = reviewErrors;
      </SCRIPT>
   </HEAD>
   <BODY>
      <P><IMG SRC="bad.gif" ONERROR="badImage(this);"
         TITLE="Cool Picture">
      <P><A HREF="http://www.insideDHTML.com">
         <IMG SRC="bad.gif" ONERROR="badImage(this);"
            TITLE="Inside Dynamic HTML Web Site"></A>
   </BODY>
</HTML>

This code also works if an anchor wraps the image. The new text that replaces the image is rendered within the anchor and properly jumps to the page when the user clicks on the element. This code can be expanded to output a message after an onabort event.

If replacing the image with text is considered too extreme, you can easily modify the title attribute by adding a message that says an error has occurred. (The title attribute in Internet Explorer 4.0 is displayed as a ToolTip.) This modification can be accomplished without all the hard work of creating the error queue because attributes of elements can be modified before the page is loaded. The following code demonstrates this feature added in line for an image; it works without any other code:

<IMG SRC="bad.gif" TITLE="Cool Picture"
   ONERROR="this.title = `Error Loading: ` + this.title";>

[Содержание]