JavaScript 和文档对象模型(DOM)

为什么使用 DOM 而不使用 DHTML?

在本文中,我从文档对象模型的角度分析 Web 页面的结构:检查子节点和父节点,以及向现有文档添加并编辑节点。为了演示这个过程,我将构建一个页面。用户可以在这个页面上任意创建注释并编辑这些注释的内容(这个页面不会很复杂;我将把一些任务留给您,比如保存和移动内容以及重新调整内容的大小等)。既然使用 DHTML 技术就可以轻松实现这些操作,为什么还要使用 DOM 呢?

开发 XML 的其中一股推动力是将 HTML 片段化,而 DHTML 顺应了这一潮流。虽然某些方面已经形成标准,但浏览器如何处理不同的方面仍然存在很大的分歧。DOM 是可以在整个领域范围内实现的标准接口集,因此编程人员只需编写一个版本的页面。

另外,Web 页面不断朝 XML 的方向发展。XHTML 1.0 只是由 HTML 4.0.1 改编而来的 XML,并且也越来越多地使用 XML 生成 Web 页面。最终,DOM 将是在这些页面内访问元素和文本的主要技术(也可将它用于 XSLT 样式表)。

基础页面

让我们从基础的页面开始,该页面包含一个工作区域和表单,它显示了已创建注释的数量:


清单 1. 基础页面
                

<html>
  <head>
    <title>Getting Sticky</title>
    <style type="text/css">
      * {font-family: sans-serif}
      a {font-size: 6pt}
      .editButton {font-size:6pt}
    </style>

  </head>
  <body>
    <div id="mainDiv" style="height:95%; width:95%; border:3px solid red; 
                                                           padding: 10px;">
    
       <h1>Getting Sticky</h1>

       <form id="noteForm">
           Current number of notes:
                       <input type="text" name="total" value="0" size="3"/>
           <input type="button" value="Add a new note"/>
       </form>

    </div>
  </body>
</html>

清单 1 展示了带有几种样式(只是为了使页面好看些)的基础页面。页面的正文包括一个单个的 div 元素,该元素包含一个 heading 元素(h1)和一个 form 元素。即使是在没有使用 DOM 的浏览器里,也可以在表单内访问信息。


图 1. 基础页面
 

使用 DHTML

使用 DHTML 可以访问甚至修改表单字段的信息。例如,您可以创建一个脚本将当前表单的值增加 1,然后告诉页面在用户点击按钮时执行这个脚本:


清单 2. 增加当前的数值
                

<html><head>
    <title>Getting Sticky</title>
    <style type="text/css">
      * {font-family: sans-serif}
      a {font-size: 6pt}
      .editButton {font-size:6pt}
    </style>

    <script type="text/javascript">
      function incrementCurrent() {
        current = parseInt(document.forms["noteForm"].total.value);
        document.forms["noteForm"].total.value = current + 1;
      }
    </script>

  </head><body>
    <div id="mainDiv" style="height:95%; width:95%; border:3px solid red; 
                                                           padding: 10px;">
    
       <h1>Getting Sticky</h1>

       <form id="noteForm">
           Current number of notes:
                      <input type="text" name="total" value="0" size="3"/>
           <input type="button" value="Add a new note" 
                                             onclick="incrementCurrent()"/>
       </form>

    </div>
</body></html>

在 清单 2 中,incrementCurrent() 函数接受文档对象,然后将 noteForm 对象从页面内的表单数组中抽出。该函数从 noteForm 对象获得表单字段 total 并检索值,然后在页面内更新这个值。

这种更改是即时的。更改页面、重新加载并反复按按钮,您将发现文本字段每一次都会相应地进行更新。

同样,您也可以使用 DHTML 属性检索 div 元素的文本。由于它有一个 mainDiv 的 id,因此可以使用 innerHTML 属性,如:

theText = mainDiv.innerHTML;

在这个例子中,使用了两个 DHTML 技术:forms 数组,以及通过基于属性值的名称调用元素(而不是通过元素名或总体结构)。但存在的问题是这种方法不够通用。虽然可以对表单的名称使用一个变量,如果碰到不使用表单的呈现,应该怎么办呢?

DOM 和 JavaScript

如果存在一个定义文档对象模型的原则,它将把信息排列成父-子层次结构。例如,参看以下的 XML:

<parentElement><childElement>My Text Node</childElement></parentElement>

有 3 个节点:root 节点是 parentElement。它有一个子元素 childElementchildElement 元素有自己的父元素 parentElement,作为子元素的文本节点的值是 “My Text Node”。这个文本节点的父元素是 childElement。共享一个父元素的两个节点称为兄弟(sibling)节点。注意,文本就是它本身的节点。元素实际上并没有值,它们仅有文本子节点。

您应该比较熟悉如何使用 Java 技术或其他语言读取 XML 文档的结构。实施这个操作要用到一个带有定义良好的函数和对象属性的 API。例如,这个文档有一个作为其根元素的 html 元素,该元素有两个子元素 head 和 body(为简单起见,我删除了正常情况下会出现在这些元素之间的空格;至于如何处理这些空格,不同的浏览器之间还没有达成一致)。要通过 Java 技术访问 body 元素,您可以使用几个不同的表达式,假如您已经命名了 Document 对象 document

第一个步骤是获得所有元素的子元素的列表,然后从该列表选择一个特定的条目,就像以下的例子一样:

bodyElement = document.getChildNodes().item(0).getChildNodes().item(1);

在这里,首先获得 html 元素(该元素是文档的第一个子元素),然后获得它的第二个子元素 bodygetChildNodes() 是基于零的)。另一种办法是将 html 元素作为第一个子元素直接访问,然后移动到它的第一个子元素(head),最后移动到该元素的下一个兄弟元素(第二个子元素 body):

bodyElement = document.getFirstChild().getFirstChild().getNextSibling();

从这里您可以获得节点的类型:

typeInt = bodyElement.getNodeType()

节点类型以整数返回,并且允许您适当地处理每一个节点;一个元素(类型 1)有名称没有值,但是一个文本节点(类型 3)有值没有名称。

了解了这些后,就可以获取元素的名称:

elementName = bodyElement.getNodeName();

或它的文本内容:

elementContent = bodyElement.getFirstChild().getNodeValue();

(记住,元素的文本本身就是一个节点,而该元素则是它的父节点)。

当将这些函数移动到 JavaScript 时,相同的基本 API 已经准备就绪,但处理方式有些不同。例如,可以直接访问属性,而不是通过get 和 set 方法访问,因此 JavaScript 中的相同语句会表示为:

bodyElement = document.childNodes.item(0).childNodes.item(1);
bodyElement = document.firstChild.firstChild.nextSibling;

您可以从这里获得元素的类型和名称及其内容:

ElementType = bodyElement.nodeType;
elementName = bodyElement.nodeName;
elementContent = bodyElement.firstChild.nodeValue;

但是需要注意,只能更改属性的名称。返回对象的函数保持不变。例如,可以根据 ID 检索某个特定的对象,就像下面的示例一样:

formElement = document.getElementById("noteForm");

这只是基本原理,现在我们看看它的实际运行情况。

添加新的注释

实际节点本身只是一个带有标准文本和链接的框,这个框允许用户稍后再编辑文本,如 图 2 所示。


图 2. 新的节点
 

这个新节点与下面的 HTML 等效:


清单 3. 目标 HTML
                

<div id="note1" style="width: 100; height:100; border: 1px solid blue; 
                       background-color: yellow; position: absolute; 
                       top: 150; left: 135">
    <a href="javascript:editNote('note1')">edit</a>
    <br />
    New note
</div>

使用标准的 DOM 添加实际元素,如 清单 4 所示:


清单 4. 添加新的注释元素
                

<html>
  <head>
    <title>Getting Sticky</title>
    <style type="text/css">
      * {font-family: sans-serif}
      a {font-size: 6pt}
      .editButton {font-size:6pt}
    </style>

    <script type="text/javascript">
...
      function getCurrentNumber() {
        formElement = document.getElementById("noteForm");
        return formElement.childNodes.item(1).value;
      }

      function makeNewNote(){
        mainDivElement = document.getElementById("mainDiv");

        newNote = document.createElement("div");
        newNote.setAttribute("id", "note"+getCurrentNumber());

        mainDivElement.appendChild(newNote);

        incrementCurrent();

      }
    </script>
  </head>
  <body>
    <div id="mainDiv" style="height:85%; width:85%; border:3px solid red; 
                                             padding: 10px; z-index: -100" >
    
       <h1>Getting Sticky</h1>

       <form id="noteForm">
           Current number of notes  <input type="text" name="total" value="0" 
                                             size="3"/>
           <input type="button" value="Add a new note" 
                                             onclick="makeNewNote()"/>
       </form>

    </div>
  </body>
</html>

在这个示例中,更改了按钮从而执行 makeNewNote() 而不是 incrementCurrent(),尽管 makeNewNote() 还使用该函数。首先,通过getElementById() 获得针对主元素 div 的引用,这个元素最终会包含注释。然后可以使用 document 对象创建一个名为 div 的新元素,就像在其他语言中一样。要设置 id 属性,您只需要在新的元素上使用 setAttribute() 方法。

每一个注释都有一个独特的 id 属性,因此您需要知道当前的总和是多少。要获得这个信息,必须从 form 元素本身开始。可以从这里获取子元素列表。第一个(带有一个索引 0)是文本,第二个是(带有一个索引 1)是实际的 input 元素。

但 .value 又是什么呢?是不是输入错误?它应该是 nodeValue,是吗?

实际上不是。记住,元素没有值。乍一看,好像我将 DOM 和 DHTML 属性混在一起,但实际上现在获取的元素不仅是org.w3c.dom.Element 的实现,还是 org.w3c.dom.html.HTMLInputElement(它也包含了一个代表表单字段值的值属性)的实现。在这一方面,DOM 模仿了一些(虽然不是全部)通过 DHTML 可以实现的属性。

设置了属性之后,只需要将新的 div 元素附加到 mainDiv 元素,div 元素将出现在这个位置上。或者,如果它有任何显示属性或文本的话,它至少会出现在这个位置上。

要添加样式信息,将需要实际使用 DHTML 样式对象:


清单 5. 添加样式信息
                

...
      function makeNewNote(){
        mainDivElement = document.getElementById("mainDiv");

        newNote = document.createElement("div");
        newNote.setAttribute("id", "note"+getCurrentNumber());

        newNote.style.width="100";
        newNote.style.height="100";
        newNote.style.border="1px solid blue";
        newNote.style.backgroundColor="yellow";
        newNote.style.position="absolute";
        newNote.style.top=(150);
        newNote.style.left=(25 + 110*getCurrentNumber());

        mainDivElement.appendChild(newNote);

        incrementCurrent();

      }
...

添加的结果是产生一个蓝边的黄色小框,如 图 3 所示。


图 3. 空的框
 

注意,注释的 left 属性取决于当前注释的数量,它随着每个注释的添加而增长。您可以通过这种方式添加一系列框,如 图 3 所示。

添加内容

添加 div 的内容会呈现一个小问题。我可以使用 innerHTML 属性:


                

newNote.innerHTML = "<a href=\"javascript:editNote('note"  
                     +getCurrentNumber()+")\">edit</a><br />New note";

但我如何直接使用 DOM 方法来实现?首先想到的可能是简单地设置 div 元素的文本子节点的值:


                

noteText = document.createTextNode(
          "<a href=\"javascript:editNote('note"+getCurrentNumber()+")\">"+
          "edit</a><br />New note");
newNote.appendChild(noteText);

虽然文本真的得到了添加,但结果可能与预期不同,如 图 4 所示。


图 4. 框中的文本
 

问题是您并没有真的添加文本,而是混合了内容,包括文本和元素。浏览器认为您将这看作是 CDATA(完全按照字面意义得出),并且也没有创建元素。不是简单地在一个块中添加所有的内容,您必须实际添加每个元素:


清单 6. 添加内容
                

...
  function makeNewNote(){
    mainDivElement = document.getElementById("mainDiv");

    newNote = document.createElement("div");
    newNote.setAttribute("id", "note"+getCurrentNumber());
...

      editLink = getEditLink("note"+getCurrentNumber());
      newNote.appendChild(editLink);
      newNote.appendChild(document.createElement("br"));
 
      noteText = document.createTextNode("New Form");
      newNote.appendChild(noteText);

    mainDivElement.appendChild(newNote);

    incrementCurrent();

  }

    function getEditLink(thisId){
      editLink = document.createElement("a");
      linkText = document.createTextNode("edit");

      editLink.setAttribute("href", "javascript:editNote('"+thisId+"')");
        
      editLink.appendChild(linkText);
      return editLink;
    }
...

首先,您已经创建了一个返回一个对象的新函数 getEditLink。这个对象是用标准 DOM 方法创建的 a 元素。接下来,添加一个标准的 break 标记 br,最后添加包含实际节点文本的节点。

结果是得到完成的节点,而元素保持完整并可使用。

更改现有的节点

现在有了内容,但怎样才能更改它呢?因为您单个地添加元素,所以只能编辑代表节点文本的单个文本节点。这可以通过 3 种方式来实现。首先删除有问题的节点,然后添加一个新的节点:


清单 7. 删除节点并添加一个新的节点
                

...
      function editNote(editLink){
        theDiv = document.getElementById(editLink);
        newText = prompt("What should the note say?");

        oldNode = theDiv.firstChild.nextSibling.nextSibling;
        theDiv.removeChild(oldNode);

        newNode = document.createTextNode(newText);
        theDiv.appendChild(newNode);

      }
...

另一种方法很简单,只需代换现有的节点:


清单 8. 替换一个节点
                

...
      function editNote(editLink){
        theDiv = document.getElementById(editLink);
        newText = prompt("What should the note say?");

        oldNode = theDiv.firstChild.nextSibling.nextSibling;
        newNode = document.createTextNode(newText);
        theDiv.replaceChild(newNode, oldNode);
      }
...

在这个示例中,用 newNode 代替了 oldNode,同时也相应地更改了文档。

最后一个办法是更改现有节点的文本:


清单 9. 更改现有的节点
                

...
      function editNote(editLink){
        theDiv = document.getElementById(editLink);
        newText = prompt("What should the note say?");

        theDiv.firstChild.nextSibling.nextSibling.nodeValue=newText;
      }
...

因为 editNote 使用相应的 div 的 id 值作为参数,相同的函数可以用于任意节点,如 图 5 所示。


图 5. 最后的页面
 

结束语

在本文,您已经了解在 Web 页面的 JavaScript 中使用 DOM 的基础知识。您可以在使用 JavaScript 的地方(比如在 XSLT 样式表中)使用这些基本原理。

文档对象模型将 XML 文档内的元素、文本和其他类型的节点呈现为一系列的父子关系。操作这些单独的节点可以影响页面本身。除了 DOM Core 方法以外,XHTML 页面也可以公开作为 DOM HTML 模块的一部分的属性和方法,DOM HTML 模块试图整合许多编程人员用了好几年的 DHTML 属性。

你可能感兴趣的:(JavaScript 和文档对象模型(DOM))