[笔记8]JavaScript DOM编程艺术_动态创建标记

2017-02-03  本文已影响37人  fumier

JS也可以用来改变网页的结构和内容

一些传统方法

document.write

document对象的write()方法可以方便快捷地把字符串插入到文档里。

如下所示:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Test</title>
</head>
<body>
<script type="text/javascript">
    document.write("<p>This is inserted.</p>");
</script>
</body>
</html>
屏幕快照 2017-02-03 下午11.13.04.png

document.write的最大缺点就是违背了行为应该与表现分离的原则。即使把document.write语句挪到外部函数里,也还是需要在标记的<body>部分使用<script>标签才能调用那个函数。

例如:

function insertParagraph(text){
    var str="<p>";
    str+=text;
    str+="</p>";
    document.write(str);
}
-----------------------------
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Test</title>
</head>
<body>
<script type="text/javascript" src="example.js"></script>
<script type="text/javascript">
    insertParagraph("This is inserted.");
</script>
</body>
</html>

PS:上面的这种写法缺点有,很容易导致验证错误。MIME类型application/xhtml+xml与document.write不兼容,浏览器在呈现这种XHTML文档时根本不会执行document.write方法。

innerHTML属性

现如今的浏览器几乎都支持属性innerHTML,这个属性不是W3C DOM标准的组成部分,但现在已经包含到HTML5规范中。innerHTML属性可以用来读写某个给定元素的HTML内容。

举个例子就很容易理解了。

<div id="testdiv">
<p>This is <em>my</em>content.</p>
</div>
屏幕快照 2017-02-04 下午10.57.46.png

从innerHTML属性的角度来看,id为testdiv的标记里面只有一个值为<p>This is <em>my</em>content.</p>的HTML字符串。

window.onload=function(){
    var testdiv=document.getElementById("testdiv");
    alert(testdiv.innerHTML);
}

很明显,innerHTML属性无细节可言,要想获得细节,就必须使用DOM方法和属性。标准化的DOM像手术刀一样精细,innerHTML属性就像一把大锤那样粗放。

当你需要把一大段HTML内容插入网页时,innerHTML属性更适用,它既支持读取,又支持写入,你不仅可以用它来读出元素的HTML内容,还可以用它把HTML内容写入元素。

window.onload=function(){
   var testdiv=document.getElementById("testdiv");
   testdiv.innerHTML="<p>I inserted<em>this</em>content.</p>";
}

利用这个技术无法区分插入和替换一段HTML内容,一旦使用了innerHTML属性,它的全部内容将会被替换。innerHTML属性时HTML专有属性,不能用于任何其他标记语言文档。浏览器在呈现正宗的XHTML文档时会直接忽略掉innerHTML属性。

PS:在任何时候,标准的DOM都可以用来替换innerHTML。

DOM方法

DOM是文档的表示。DOM所包含的信息与文档里的信息一一对应。DOM是一条双向的车道。不仅可以获取文档的内容,还可以更新文档的内容。

createElement方法

通过例子说明方法。
以下文档里,想把一段文本插入到testdiv元素。

<div id="testdiv">
</div>

用DOM的语言来说,就是想添加一个p元素节点。目前div已经又一个属性节点(子节点)。任务分为两步:

实际操作过程如下:

var para=document.createElement("p");
//虽然创建出来p元素的引用,但它还不是任何一颗DOM节点树的组成部分。这种情况称为文档碎片(document fragment)
通过para.nodeName查看节点名称
通过para.nodeType查看节点类型

appendChild方法

把新创建的节点插入某个文档的节点树的最简单的办法是,让它成为这个文档某个现在节点的一个子节点。

var testdiv=document.getElementById("testdiv");
var para=document.createElement("p");
testdiv.appendChild(para);

createTextNode方法

你现在已经创建出了一个元素节点并把它插入了文档的节点树,这个节点是一个空白的p元素。现在把一些文本放入这个p元素,创建文本节点,使用createTextNode方法。

createTextNode语法与createElement很相似。document.createTextNode(text)

在p元素中插入文本节点。

var txt=document.createTextNode("Hello World");
para.appendChild(txt);

综合上面的过程,完整代码如下:

window.onload=function(){
    var para=document.createElement("p");
    var testdiv=document.getElementById("testdiv");
    testdiv.appendChild(para);
    var txt=document.createTextNode("Hello world");
    para.appendChild(txt);
}

一个更复杂的组合

假如插入的内容如下所示。

<p>This is <em>my</em>content.</p>

PS:自己写完成,再与课本对对看有什么不一样。

var para=document.createElement("p");
var testdiv=document.getElementById("testdiv");
var txt=document.createTextNode("This is ");
var em=document.createElement("em");
var myTxt=document.createTextNode("my");
var contentTxt=document.createTextNode("content.");
para.appendChild(txt);
para.appendChild(em);
em.appendChild(myTxt);
para.appendChild(contentTxt);
testdiv.appendChild(para);

重回图片库

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
    <title>Image Gallery</title>
    <link rel="stylesheet" type="text/css" href="picture.css" media="screen">
</head>

<script type="text/javascript" src="scripts/showpicture.js"></script>
<script type="text/javascript">
</script>
<body>
<h1>Snapshots</h1>
<ul id="imagegallery">
    <li><a href="images/fireworks.jpg" title="A fireworks display">Fireworks</a></li>
    <li><a href="images/coffee.jpg" title="A cup of black coffee">Coffee</a></li>
    <li><a href="images/rose.jpg" title="A red,red rose" >Rose</a></li>
    <li><a href="images/bigben.jpg" title="The famous clock">Big Ben</a></li>
</ul>
<!--下面这段代码仅仅为showPic脚本服务,如果能把结构和行为彻底分开那最好不过了-->
![](images/placeholder.gif)
<p id="description">Choose an image</p>
<!------------->
</body>
</html>

PS: 既然上面标注的图片和p只是为了让DOM方法处理它们,那么让DOM方法来创建它们才是最合适的选择。我们需要做的就是1)将这些元素从文档里删掉。2)动态的将这些元素创建出来。3)把新创建的文档元素插入到文档中。

涉及到的方法有,创建元素节点createElement,设置属性setAttribute,创建文本节点createTextNode。

var placeholder=document.createElement("img");
placeholder.setAttribute("id","placeholder");
placeholder.setAttribute("src","images/placeholder.gif");
placeholder.setAttribute("alt","my image gallery");
var description=document.createElement("p");
description.setAttribute("id","description");
var desText=document.createTextNode("Choose an image");
document.getElementsByTagName("body")[0].appendChild(placeholder);
document.getElementsByTagName("body")[0].appendChild(description);
-------------------------------------------HTML-DOM属性
document.body.appendChild(placeholder);
document.body.appendChild(description);

PS:这段代码有个问题,假如在body里面还会动态元素的话,它就会处于文档的中间而不会一直保持在最后的位置。

在已有元素前插入一个新元素

DOM提供了名为insertBefore()方法,这个方法将把一个新元素插入到一个现有元素的前面。调用此方法你必须告诉它三件事情。

  • 新元素:你想插入的元素(newElement)

我们不必搞清楚父元素到底是哪个,因为targetElement元素的parentNode就是它。既targetElement.parentNode=parentElement
比如说将图片插入到清单之前,可以使用以下语句。

var gallery=document.getElementById("imagegallery");
gallery.parentNode.insertBefore(placeholder,gallery);

但是,现在我们需要将图片插入到清单的后面,用下面的方法。

在现有方法后插入一个新元素

PS:我想当然的认为,既然有在insertBefore方法,那肯定也有insertAfter方法,可是没有。但是我们可以自己实现。

function insertAfter(newElement,targetElement){
var parent=targetElement.parentNode;
if(parent.lastChild==targetElement){//目标元素是parent的最后一个元素,直接追加到parent上。
   parent.appendChild(newElement);
}else{//把新元素插入到目标元素和目标元素的下一个兄弟元素之间
   parent.insertBefore(newElement,targetElement.nextSibling);
}
}

目标元素的下一个兄弟元素既目标元素targetElement.nextSibling

现在我们使用insertAfter函数,使用该方法改写最后两句话。

var gallery=document.getElementById("imagegallery");
insertAfter(placeholder,gallery);

整个代码改写如下,需要注意的是,我需要增加一些判断,判断浏览器是否支持我使用的属性,能保证平稳的退化:

function preparePlaceholder(){
if(!document.createElement)return false;
if(!document.createTextNode)return false;
if(!document.getElementById)return false;
if(!document.getElementById("imagegallery"))return false;
var placeholder=document.createElement("img");
placeholder.setAttribute("id","placeholder");
placeholder.setAttribute("src","images/placeholder.gif");
placeholder.setAttribute("alt","my image gallery");
var description=document.createElement("p");
description.setAttribute("id","description");
var desText=document.createTextNode("Choose an image");
 var gallery=document.getElementById("imagegallery");
insertAfter(placeholder,gallery);
insertAfter(placeholder,placeholder);
}

图片库二次改进版

function showPic(whichpic) {
    if(!document.getElementById("placeholder"))return false;
    var source=whichpic.getAttribute("href");
    var placeholder=document.getElementById("placeholder");
    placeholder.setAttribute("src",source);
    if(document.getElementById("description")){
    var text=whichpic.getAttribute("title");
    var description=document.getElementById("description");
    description.firstChild.nodeValue=text;
    }
    return true;
}

function prepareGallery(){
   if(!document.getElementsByTagName) return false;
   if(!document.getElementById)return false;
   if(!document.getElementById("imagegallery"))return false;
   var gallery=document.getElementById("imagegallery");
   var links=gallery.getElementsByTagName("a");
   for (var i = 0; i < links.length; i++) {
    links[i].onclick=function(){
        return !showPic(this);
    }
   }
}

function addLoadEvent(func){
   var oldonload=window.onload;
   if(typeof window.onload!='function'){
      window.onload=func;
   }else{
      window.onload=function(){
         oldonload();
         func();
      }
   }
 }

function insertAfter(newElement,targetElement){
   var parent=targetElement.parentNode;
   if(parent.lastChild==targetElement){
      parent.appendChild(newElement);
   }else{
      parent.insertBefore(newElement,targetElement.nextSibling);
   }
}

function preparePlaceholder(){
   if(!document.createElement)return false;
   if(!document.createTextNode)return false;
   if(!document.getElementById)return false;
   if(!document.getElementById("imagegallery"))return false;
   var placeholder=document.createElement("img");
   placeholder.setAttribute("id","placeholder");
   placeholder.setAttribute("src","images/placeholder.gif");
   placeholder.setAttribute("alt","my image gallery");
   var description=document.createElement("p");
   description.setAttribute("id","description");
   var descText=document.createTextNode("Choose an image");
   description.appendChild(descText);
   var gallery=document.getElementById("imagegallery");
   insertAfter(placeholder,gallery);
   insertAfter(description,placeholder);
}

addLoadEvent(preparePlaceholder);
addLoadEvent(prepareGallery);

Ajax

上面的例子,将某些节点放到JS中动态的添加。假如页面只更新了一小部分,那怎样才能真正得到原来并不存在于初始页面中的内容呢?

使用Ajax就可以做到只更新页面中的一小部分内容。它的主要优势就是对页面的请求以异步方式发送到服务器。Ajax技术的核心就是XMLHttpRequest对象,这个对象充当着浏览器中的脚本(客户端)与服务器间的中间人的角色。以往的请求都由浏览器发出,而JS通过这个对象可以自己发送请求,同时也自己处理响应。

虽然XMLHttpRequest得到了几乎所有现代浏览器的支持。但问题是,不同浏览器实现XMLHttpRequest对象的方式不太一样。为了保证跨浏览器,你不得不为同样的事情写不同的代码分支。

不同的IE版本中使用的XMLHTTP对象也不完全相同。为了兼容所有的浏览器,getHTTPObject函数要这样写:

function getHTTPObject(){
   if(typeof XMLHttpRequest == "undefined")
   XMLHttpRequest=function(){
   try{
    return new ActiveXObject("Msxml2.XMLHTTP.6.0");
    }catch(e){}
   try{
    return new ActiveXObject("Msxml2.XMLHTTP.3.0");
    }catch(e){}
   try{
    return new ActiveXObject("Msxml2.XMLHTTP");
    }catch(e){}
    return false;
   }
   return new XMLHttpRequest();
}

当你脚本要使用XMLHttpRequest对象时,可以将这个新对象直接赋值给一个变量。var request=getHttpObject();

在这之前,我需要搞懂两点。

XMLHttpRequest有很多方法,最有用的是open方法,用来指定服务器上将要访问的文件,指定请求类型:GET、POST或SEND。

function getNewContent(){
  var request=getHTTPObject():
  if(request){
  //发送GET请求,请求与ajax.html文件位于同一目录的example.txt文件
   request.open("GET","example.txt",true);
 //onreadystatechange是一个事件处理函数,服务器响应时被触发
   request.onreadystatechange=function(){
    if(request.readyState==4){
     var para=document.createElement("p");
     var txt=document.createTextNode(request.responseText);
     para.appendChild(txt);
     document.getElementById("new").appendChild(para);
     }
    };
    request.send(null);
  }else{
    alert("Sorry,your browser doesn\'t support XMLHttpRequest");
}
}
addLoadEvent(getNewContent);

服务器在向XMLHttpRequest对象发回响应时,该对象有许多属性可用,readyState有5个可能的值。

可以通过responseText和responseXML属性来访问发送回来的数据。

重做下例子,在谷歌运行发出现以下错误,safari就可以:

屏幕快照 2017-02-07 下午10.39.12.png

PS:原因是有些浏览器限制Ajax的使用协议,在Chrome中,如果你使用了file://协议从自己的硬盘中加载example.txt,就会看到Cross origin requests are only supported for protocol schemes的错误。

注意一点:异步请求有个容易忽略的点就是脚本在发送XMLHttpRequest请求后,仍然会继续执行,不会等待响应返回。

Ajax的特色就是减少重复加载页面的次数,但是缺少状态记录的技术会与浏览器的一些使用惯例产生冲突,导致用户无法使用后退按钮或者无法为特定状态下的页面添加书签。

渐进增强与Ajax

构建Ajax网站,从一开始就从构建一个常规网站开始,然后再使用Ajax。

Hijax

Hijax指渐进增强的使用Ajax。

Ajax应用主要依赖后台服务器,实际上是服务器端的脚本语言完成了绝大部分工作。XMLHttpRequest对象作为浏览器与服务器之间的“中间人”,它只是负责传递请求和响应。如果把这个中间人请开。浏览器与服务器之间的请求和响应应该继续完成。

构建表单的传统方式。

Hijax的方式

Ajax应用主要依赖于服务器端处理,而非客户端处理。

具体的Hijax实例参考笔记9:

上一篇 下一篇

猜你喜欢

热点阅读