Wednesday, December 3, 2008

DOM example 1

<html>
<head>
<title>Page</title>
<script>
window.onload = function() {
var node = document.getElementById("testimage");
node.onclick=testFunction;
}

function testFunction(evt) {

if(evt)
alert("evt.target.id="+evt.target.id);
else //IE
alert("window.event.srcElement.id="+window.event.srcElement.id);
}
</script>

</head>

<body>
<img id="testimage" src='someimage.jpg'/>
</body>
</html>

精通JavaScript

http://www.17xie.com/read-60506.html

在上一次Web开发领域的技术浪潮中,文档对象模型(Document Object Model,DOM)无疑是开发者能用来提升用户体验的最重要技术之一。

用DOM脚本编程为页面增加分离式(unobtrusive)JavaScript,既能为你的用户提供各种各样时髦的增效,同时又不会影响那些使用不支持或禁用了JavaScript的浏览器的人们。此外,DOM脚本编程还能精细地分离和轻松地管理你的代码。

幸运的是,所有的现代浏览器都支持DOM和当前HTML文档的内置HTML DOM表达。有了这些后就可以通过JavaScript轻松访问它们,从而为现代Web开发者提供了巨大的便利。理解如何使用并尽情发挥这些技术,你的下一个Web应用程序将会领先一筹。

这一章探讨关于DOM的一系列话题。假定你是DOM新手,我们将从基础开始,并涉及所有重要的概念。而对已经熟悉了DOM的读者,我保证能提供一批你会喜欢的高级技术。

2.1 DOM简介

DOM是一个表达XML文档的标准(由W3C制定的)。它未必是最快的方式,也未必是最轻量级的或者最容易使用的,但确是应用最广泛的,大部分Web开发的编程语言(比如Java、Perl、PHP、 Ruby、Python和JavaScript)都提供了相应的DOM实现。DOM给开发者提供了一种定位XML层级结构的直观方法。就算你尚未完全熟悉 XML也没关系,HTML文档(实际上从浏览器角度来说就是XML文档)已经有一个可用的DOM表达,你会为此而备受鼓舞。

 

 

2.2 遍历DOM

可以把XML的DOM表达方式看作是一棵导航树。一切术语都跟家谱术语(父、子、兄弟)类似。而与家谱的不同之处在于,XML文档从一个独立的根节点(称作文档元素,document element)开始,它包含指向子节点的指针。每一个子节点都包含指针指向它的父节点、相邻节点和子节点。

DOM还使用了一些特殊的术语来描述XML树里的对象种类。DOM树中的每个对象都是一个节点(node),每个节点可以有一个类型(type),比如元素、文本或者文档。要进一步学习下去,我们必须了解 DOM文档是如何表现和如何定位的。请参考以下简单的HTML片段,让我们来研究研究它的DOM结构是如何工作的:

<p><strong>Hello</strong> how are you doing?</p>

这个片段的每一部分被分解为独立的DOM节点指针,指向其亲戚(父、子、兄弟)。使用图谱来表示的话,它应该如图5-1所示:片断的每部分(圆角框描述元素,直角框描述文本节点)和它的引用。

图5-1 节点间的关系

每个独立的DOM节点都包含指向它的相关节点的一系列指针。你需要使用这些指针来学习如何遍历DOM。所有可用的指针如图5-2所示。DOM节点指针的各个属性,分别是指向另一个DOM元素的指针(如果不存在则为null)。

图5-2 使用指针遍历DOM树

只需使用不同的指针,就可以定位页面上的任何元素或者文本块。要理解它在实际环境中是如何工作的,最好的方式就是实践一下。代码清单5-1是一个简单的HTML页。

代码清单5-1 简单HTML页面,或者说是简单的XML文档

    <title>Introduction to the DOM</title>

    <h1>Introduction to the DOM</h1>

    <p class="test">There are a number of reasons why the

DOM is awesome, here are some:</p>

    <ul>

      <li id="everywhere">It can be found everywhere.</li>

      <li class="test">It's easy to use.</li>

      <li class="test">It can help you to find what you want, really quickly.</li>

    </ul>

在这个文档中,根元素是

document.documentElement

根节点与其他DOM节点一样,同样拥有所有用于定位的指针。使用这些指针你就可以浏览整个文档,定位到任何一个你要找的元素。比如,要获取<h1>元素,你可能会使用如下方法:

//不起作用

document.documentElement.firstChild.nextSibling.firstChild

这是我们遇到的第一个难题:DOM指针不仅可以指向元素,也可以指向文本节点。在这里,代码并不会真正指向<h1>元素,而是指向了<title>元素。为什么会发生这样的事?原因在于XML存在一个有争议的地方:空格。或许你已经注意到,在

l    当试图只使用指针来遍历DOM时,精细编写的HTML标记居然会产生混乱。

l    仅使用DOM指针来定位文档可能会过于烦琐和不切实际。

l    通常,你不需要直接存取文本节点,只存取包含文本节点的元素就行了。

这让我们遇到了难题:还有其他更好的方式来寻找文档中的节点吗?当然有!在你的工具箱中创建一些辅助函数,你就可以有效提升现有的方法,并且让DOM的定位更为方便。

2.2.1 处理DOM中的空格

回到我们的HTML文档例子。之前,你试图定位到一个<h1>元素去,但由于捣乱的文本节点使得困难重重。如果只是一个比较独立的元素还好说,但是要继续找<h1>元素的下一个元素呢?你仍然会遭遇这个麻烦的空格bug,使你必须用.nextSibling.nextSibling 来跳过<h1>和<p>之间的行结束符,空格一个都不能少。代码清单5-2所示是一个用以处理空格bug的补救办法。这个特别的技术删除所有空格——当然只是DOM文档中的文本节点,让DOM的遍历更容易。这样做不仅不会对你的HTML渲染产生副作用,还会让定位DOM变得更容易。但是需要注意的是,这个函数的结果并不是持久的,它需要在HTML文档的每一次加载时都重新执行一遍。

代码清单5-2 XML文档空格bug的补救方案

function cleanWhitespace( element ) {

  // 如果不提供参数,则处理整个HTML文档

  element = element || document;

  // 使用第一个子节点作为开始指针

  var cur = element.firstChild;

  // 一直到没有子节点为止

  while ( cur != null ) {

    // 如果节点是文本节点,并且只包含空格

    if ( cur.nodeType == 3 && ! /\S/.test(cur.nodeValue) ) {

      // 删除这个文本节点

      element.removeChild( cur );

      // 否则,它就是一个元素

    } else if ( cur.nodeType == 1 ) {

      // 递归整个文档

      cleanWhitespace( cur );

    }

    cur = cur.nextSibling; // 遍历子节点

  }

}

假设你要在前面例子中使用这个函数来查找位于<h1>元素后的元素。那么代码应该类似这样:

cleanWhitespace();

// 查找H1元素

document.documentElement

.firstChild // 查找Head元素

.nextSibling // 查找

.firstChild // 得到H1元素

.nextSibling // 得到相邻的段落(p)

该技术有优点也有缺点。最大的优点在于,你可以保证DOM文档的遍历在一定程度上的稳定性。但明显性能太差,想想必须遍历每个DOM元素和文本节点,目的只是为了找出包含空格的文本节点。假设你有一个包含大量内容的文档,它可能会严重降低网站的加载速度。此外,每次为文档注入新的HTML,你都需要重新扫描 DOM中的新内容,确保没有增加新的有空格填充的文本节点。

此外此函数重要的方面是节点类型的使用。节点的类型可以由检查它的nodeType属性来确定。可能会出现好几种值,但你经常会碰到的是以下3个:

l    元素(nodeType=1):匹配XML文件中的大部分元素。比如,<li>、<a>、<p>和

l    文本(nodeType=3):匹配文档内的所有文本块。当使用previousSibling和nextSibling来遍历DOM结构时,你会经常碰到元素内和元素间的文本块。

l    文档(nodeType=9):匹配文档的根元素。比如,在HTML文档内,它是

此外,你可以用常量来表明不同的DOM节点类型(但只是对非IE浏览器有用)。与其去记住1、3或9,还不如直接直观地使用document.ELEMENT_NODE、 document.TEXT_NODE或者document.DOCUMENT_NODE。因为经常清空DOM的空格会让人感到厌烦,所以你应该探索其他遍历DOM结构更有效的方法。

2.2.2 简单的DOM遍历

可以使用纯粹的DOM遍历规则(每个遍历方向都有指针)来开发一些更适合你的HTMLDOM文档遍历函数。大部分Web开发者在大多数情况下仅仅需要遍历DOM元素而非相邻的文本节点,该规则就是基于这样的事实而制定的。以下一系列的辅助函数可以帮助你,它们能够取代标准的previousSibling、nextSibling、firstChild、 lastChild和parentNode。代码清单5-3展示的函数,返回的是当前元素的前一个元素,如果前一个元素不存在则是null,类似于元素的 previousSibling属性。

代码清单5-3 查找相关元素的前一个兄弟元素的函数

function prev( elem ) {

  do {

    elem = elem.previousSibling;

  } while ( elem && elem.nodeType != 1 );

  return elem;

}

代码清单5-4展示的函数,返回的是当前元素的下一个元素,如果下一个元素不存在则是null,类似于元素的nextSibling属性。

代码清单5-4 查找相关元素的下一个兄弟元素的函数

function next( elem ) {

  do {

    elem = elem.nextSibling;

  } while ( elem && elem.nodeType != 1 );

  return elem;

}

代码清单5-5展示的函数,返回的是当前元素的第一个子元素,类似于firstChild元素属性。

代码清单5-5 查找元素第一个子元素的函数

function first( elem ) {

  elem = elem.firstChild;

  return elem && elem.nodeType != 1 ?

next ( elem ) : elem;

}

代码清单5-6展示的函数,返回的是当前元素的最后一个子元素,类似lastChild元素属性。

代码清单5-6 查找元素最后一个子元素的函数

function last( elem ) {

  elem = elem.lastChild;

  return elem && elem.nodeType != 1 ?

  prev ( elem ) : elem;

}

代码清单5-7展示的函数,返回当前元素的父元素,类似parentNode元素属性。你可以一次用一个数字来操纵多个父元素,例如parent(elem,2)就等同于parent(parent(elem))。

代码清单5-7 查找元素父元素的函数

function parent( elem, num ) {

num = num || 1;

for ( var i = 0; i < num; i++ )

if ( elem != null ) elem = elem.parentNode;

return elem;

}

使用这些新函数你就可以迅速遍历DOM文档了,而且不必再为元素间的文本操心。比如,需要查找<h1>元素的下一个元素,现在可以这么做了:

// 查找<h1>元素的下一个元素

next( first( document.body ) )

在这段代码内有两件事值得注意。首先,此处有一个新的引用:document.body。所有的现代浏览器都在HTML DOM文档内通过body属性提供一个对

2.2.3 绑定到每一个HTML元素

Firefox和Opera中存在一个强大的对象原型(object prototype)叫HTMLElement,它允许你为每一个HTML DOM元素绑定函数和数据。上一节所描述的函数显得特别愚钝,但它们应该可以更清晰的。一个完美的方案是,为HTMLElement的原型直接绑定函数,由此每个独立的HTML DOM元素也直接绑定了你的函数。为了能让上一节描述的函数继续有效,你必须做出3个改变:

(1)  你需要在函数的顶部增加一行代码,使用this来引用元素,而不是从参数的变量中获取它。

(2) 你需要删除不再需要的元素参数。

(3)  你需要把函数绑定到HTMLElement原型,由此DOM中的各个HTML元素可以使用该函数。

新的next函数如代码清单5-8所示。

代码清单5-8 为所有HTML DOM元素动态绑定新的DOM遍历函数

HTMLElement.prototype.next = function() {

  var elem = this;

  do {

    elem = elem.nextSibling;

  } while ( elem && elem.nodeType != 1 );

  return elem;

};

现在你可以这样来使用新的next函数(和经过同样调整后的其他函数)了:

// 一个简单的例子 —— 获取<p>元素

document.body.first().next()

这让你的代码更加清晰和易于理解。现在你可以按照自然想法的顺序来编写代码了,JavaScript总体上也更清晰了。如果你对这种编程风格感兴趣,我特别推荐尝试一下jQuery JavaScript库(http://jquery.com),它大量使用到这种技术。

注意 因为只有3个现代浏览器(Firefox、Safari和Opera)支持HTMLElement,要让它也能在IE中工作,必须给予特别的处理。有一个可以直接使用的库,由Jason Karl Davis(http://browser-land.org)编写,它为两个不支持的浏览器提供了访问HTMLElement的方法。可以从这找到该库的具体信息:http://www.browserland.org/scripts/htmlelement/.

2.2.4 标准的DOM方法

所有的现代DOM实现都包含一系列让编程更轻松的方法。结合一些自定义函数,遍历DOM可以是一种平滑的体验。那么让我们从JavaScript DOM的两个强大方法开始:

l    getElementById("everywhere"):该方法只能运行在document对象下,从所有中找出ID是everywhere的元素。这是一个非常强大的函数,并且是迅速的访问一个元素的最快方法。

l    getElementsByTagName("li"):该方法能运行在任何元素下,找出该元素下的所有标签名为li的后代元素,并返回一个NodeList(节点列表)。

警告 getElementById本来会按照你的预期工作:查找所有元素,揪出一个包含id的属性,且值为指定值的元素。但是,如果你加载一个远程XML 文档,并使用getElementById(或是使用除了JavaScript之外的实现DOM的语言),它默认并不会使用id属性。这是由于设计上的问题,一个XML文档如需明确指出id属性是什么,通常使用XML的定义或配置。

警告 getElementsByTagName返回一个节点NodeList.该结构跟普通的JavaScript数组非常相似,但一个重要的不同之处在于:它并不支持.push()、.pop()、.shift()等JavaScript数组的常用方法。使用getElementsByTagName时请务必记住这点,这能把你从很多混乱中解救出来。

这两个方法在所有现代的浏览器都可用,对定位到指定元素有非常大的帮助。回头看看我们前面尝试查找<h1>元素的例子,现在可以这么做了:

document.getElementsByTagName("h1")[0]

这行代码保证有效并返回文档中的第一个<h1>元素。再回头看看例子文档,假设你要获取所有<li>元素并为之加上边框。

var li = document.getElementsByTagName("li");

for ( var j = 0; j < li.length; j++ ) {

  li[j].style.border = "1px solid #000";

}

最后,需要使第一个<li>元素的文本加粗,假设它已经有了一个关联它的id:

document.getElementById("everywhere").style.fontWeight = 'bold';

你可能已经注意到获取指定ID的单个元素需要冗长的代码,依靠标签名获取元素也如此。其实你可以封装一个函数来简化获取的过程:

function id(name) {

  return document.getElementById(name);

}

代码清单5-9展示了一个函数,它依靠HTML DOM内的文档标签来定位元素。该函数带一个或两个参数。如果只传入一个标签名作为参数,函数将会遍历整个文档。另外你可以传入一个DOM上下文元素作为第一个参数的辅助,则函数只遍历该参数元素下的标签。

代码清单5-9 依靠HTML DOM文档标签定位元素的函数

function tag(name, elem) {

  // 如果不提供上下文元素,则遍历整个文档

  return (elem || document).getElementsByTagName(name);

}

让我们重新回到寻找<h1>元素后的第一个元素的问题。十分幸运的是,现在的代码可以比之前任何一次的都要精炼:

// 查找<h1>元素后的第一个元素

next( tag("h1")[0] );

以上这些函数为你在DOM文档中获取元素提供了强大而迅速的解决方法。在进一步学习修改DOM的方法之前,首先需要大致了解脚本首次执行时DOM的加载问题。

 

2.3 等待HTML DOM的加载

处理HTML DOM文档存在的一个难题是,JavaScript可以在DOM完全加载之前执行,这会给你的代码引发不少的潜在问题。浏览器的渲染和操作顺序大致如以下列表:

l    HTML解析完毕。

l    外部脚本和样式表加载完毕。

l    脚本在文档内解析并执行。

l    HTML DOM完全构造起来。

l    图片和外部内容加载。

l    网页完成加载。

在网页头部并且从外部文件加载的脚本会在HTML真正构造之前执行。如前所述,这是个至关重要的问题,因为这两处执行的脚本并不能访问还不存在的DOM。幸好,我们还有若干的补救办法。

2.3.1 等待整个页面的加载

目前,最常用的技术是完全等待整个页面加载完毕才执行DOM操作。这种技术只需利用window对象的load事件来绑定一个函数,页面加载完毕即可触发。我们将会在第6章更详细地讨论事件。代码清单5-10展示了一个页面完成加载后执行DOM相关代码的例子。

代码清单5-10 使用addEvent函数为window.onload属性绑定回调函数

// 直到页面加载完毕

// (使用的addEvent, 下一章会有阐述)

addEvent(window, "load", function() {

    // 执行HTML DOM操作

    next( id("everywhere") ).style.background = 'blue';

});

最简单的操作却是最慢的。在加载过程的顺序列表中,你会注意到页面的加载完毕与否完全被最后一步所掌控。这就是说,如果你页面有很多的图片、视频等,用户可能得等上一段时间JavaScript才执行。

2.3.2 等待大部分DOM的加载

第二种技术相当绕弯子,所以并不是十分推荐使用。如果你还记得,前面说过行内的脚本在DOM构造后就可立即执行。这是准真理。只有在DOM构造后,执行到该位置上脚本才真正执行。这意味着在你在页面中途嵌入的行内脚本只能访问该位置之前的DOM。所以,在页面最后元素之前嵌入脚本,你就可以在DOM中访问这个元素之前全部的元素,成为一条模拟DOM加载的伪路子。该方法典型的实现如代码清单5-11所示。

代码清单5-11 依靠在HTML DOM最后插入一个

    <h1>Testing DOM Loading</h1>

    <!-- 这里是大量的HTML -->

在这个例子中,你必须把行内脚本作为DOM 的最后一个元素,以便它最后一个被解析和执行。它唯一执行的是初始化函数,该函数应该包含你需要操作的DOM的相关代码。这个解决方案的最大问题在于混乱:你为HTML增添无关标记的理由仅仅是检查DOM是否已经执行。该技术通常被认为是不合理的,因为你为页面增加额外的代码的目的只是检查加载状态而已。

2.3.3 判断DOM何时加载完毕

最后一种技术可用以监听DOM加载状态,可能是最复杂的(从实现角度来看),但也是最有效的。在该技术中,你既能像绑定window加载事件那般简单,又能获得行内脚本技术那样的速度。

这项技术在不堵塞浏览器加载的情况下尽可能快地检查HTML DOM文档是否已经加载了执行所必须的属性。以下是检查HTML DOM是否可用的几个要点:

(1) document:你需要知道DOM文档是否已经加载。若能足够快地检查,运气好的话你会看到undefined。

(2) document.getElementsByTagName和documents.getElementById:频繁使用docum- ent.getElementsByTagName和document.getElementById函数检查文档,当存在这些函数则表明已完成加载。

(3) document.body:作为额外补充,检查

使用这些检查就足够判断DOM是否可用了(“足够”在此表示可能会有一定毫秒级的时间差)。这个方法几乎没有瑕疵。单独使用前述检查,脚本应该可以在现代浏览器中运行得相对良好。但是,最近 Firefox实现了缓存改进,使得window 加载事件实际上可以在脚本能检查到DOM是否可用之前触发。为了能发挥这个优势,我同时为window加载事件附加检查,以期能获得更快的执行速度。

最后,domReady函数集合了所有需要在DOM可用时就执行的函数的引用。一旦DOM被认为是可用的,就调用这些引用并按顺序一一执行。代码清单5-12展示了一个监听DOM何时加载完毕的函数。

代码清单5-12 监听DOM是否可用的函数

function domReady( f ) {

  // 假如 DOM 已经加载,马上执行函数

  if ( domReady.done ) return f();

  // 假如我们已经增加了一个函数

  if ( domReady.timer ) {

    // 把它加入待执行函数清单中

    domReady.ready.push( f );

  } else {

    // 为页面加载完毕绑定一个事件,

    // 以防它最先完成。使用addEvent(该函数见下一章)。

    addEvent( window, "load", isDOMReady );

    // 初始化待执行函数的数组

    domReady.ready = [ f ];

   // 尽可能快地检查DOM是否已可用

    domReady.timer = setInterval( isDOMReady, 13 );

  }

}

// 检查DOM是否已可操作

function isDOMReady() {

  // 如果我们能判断出DOM已可用,忽略

  if ( domReady.done ) return false;

  // 检查若干函数和元素是否可用

  if ( document && document.getElementsByTagName &&

document.getElementById && document.body )

{

    // 如果可用,我们可以停止检查

    clearInterval( domReady.timer );

    domReady.timer = null;

    // 执行所有正等待的函数

    for ( var i = 0; i < domReady.ready.length; i++ )

      domReady.ready[i]();

    // 记录我们在此已经完成

    domReady.ready = null;

    domReady.done = true;

  }

}

现在该来看看这在HTML文档中是如何执行的。 domReady函数应该按addEvent函数(第6章将讨论)的方式使用,文档可操作时即绑定你需要执行的函数。代码清单5-13展示了如何使用 domReady函数来监听DOM是否可用。在这个例子中,假设已经将domReady函数写到一个名为domready.js的外部文件中。

代码清单5-13 使用domReady函数来确定何时DOM可操作和修改

    <title>Testing DOM Loading</title>

  <h1>Testing DOM Loading</h1>

  <!--这里是大量的HTML -->

现在你已经掌握若干定位一般XML DOM文档与如何补救HTML DOM文档加载难题的方法,另一个问题也该摆上台面了:还有更好的方法来查找HTML文档中的元素吗?当然,答案是十分肯定的。

2.4 在HTML文档中查找元素

在HTML文档中与在XML文档中查找元素通常情况下还是有很大不同的。现代HTML事实上是XML的一个子集,这么说来似乎有点自相矛盾。但HTML文档包含了许多本质的不同,而这正是你可以充分利用的地方。

对JavaScript/HTML开发者来说,最重要的两个优势是利用类和CSS选择器(selector)。将这熟记于心,你就可创建一系列强大的函数,使DOM的操作更简单、更易理解。

2.4.1 通过类的值查找元素

通过类名字定位元素是一种很普遍的技术,2003年由Simon Willison(http://simon.incutio. com)普及,而原创的则是Andrew Hayward(http://www.mooncalf.me.uk)。该技术直截了当:遍历所有的元素(或者某元素的所有后代元素)直至找到指定类的元素。一种可能的实现方法如代码清单5-14所示。

代码清单5-14 找出全部有指定类值的元素的函数

function hasClass(name,type) {

  var r = [];

  // 定位到类值上(允许多类值)

  var re = new RegExp("(^|\\s)" + name + "(\\s|$)");

  // 限制类型的查找,或者遍历所有元素

  var e = document.getElementsByTagName(type || "*");

  for ( var j = 0; j < e.length; j++ )

    // 如果元素拥有指定类,把它添加到函数的返回值中

    if ( re.test(e[j]) ) r.push( e[j] );

  // 返回符合的元素列表

  return r;

}

现在你就可使用这个函数来在任意的或者指定类型(比如<li>或<p>)的元素中迅速查找有指定类值的元素。指定标签名会比遍历所有元素(*)的查找更快,因为需检索的目标更少。比如,在我们的HTML文档中,如果需要找出类值有test的所有元素你可以这么做:

hasClass("test")

如果只需找类值有test的<li>元素,则可以这么做:

hasClass("test","li")

当然,如果只需找出类值有test的<li>元素的第一个,则可以这么做:

hasClass("test","li")[0]

这个函数本身就已非常强大,一旦结合getElementById和getElementsByTagName,你就可以创建更强大的工具集合解决大部分棘手的DOM问题了。

2.4.2 使用CSS选择器查找元素

作为一个Web开发者,你应该已经知道选择HTML 元素的一种方案:CSS选择器。CSS选择器是用于赋予元素样式的表达式。随着CSS标准(1、2和3)的每次修订,选择器规范增加了越来越多的重要特点,由此开发者更容易精确定位到所需的元素。不幸的是,浏览器对CSS 2和CSS 3的实现慢得不可思议,所以你或许并不知道CSS中某些新奇酷的特点。如果你对这些感兴趣,建议你浏览W3C的这些主题页面:

l    CSS 1选择器:http://www.w3.org/TR/REC-CSS1#basic-concepts。

l    CSS 2选择器:http://www.w3.org/TR/REC-CSS2/selector.html。

l    CSS 3选择器:http://www.w3.org/TR/2005/WD-css3-selectors-20051215/。

每种选择器规范可用的特点基本上都差不多,每一个后续的版本也都包含前一版的所有特点,同时,每次新版都会增加一系列的新特点。举个例子,CSS 2包含了特性(attribute)选择器和子选择器,而CSS 3则提供了额外的语言支持、性质类型选择以及逻辑非等。比如,以下所有这些都是正确的CSS选择器。

l    #main<div>p:该表达式查找一个id为main的元素下所有的<div>元素,然后是这些元素下所有的<p>元素。它们都是CSS 1下的选择器。

l    div.items>p:该表达式查找所有的类值为items的<div>元素,然后定位到所有的子<p>元素。这是正确的CSS 2选择器。

l    div:not(.items):这则定位到所有没有值为items的类的所有<div>元素。这是正确的CSS 3选择器。

现在,你可能会觉得疑惑,实践中如果并不能使用它们来定位元素(而只能赋予样式)的话,为何会在此讨论CSS选择器。实际上很多前卫的开发者已经涉足开发能兼容CSS1甚至完全支持CSS 3的选择器的实现。使用这些库能让你顺利、方便地选择任意元素并对它们进行操作。

1.cssQuery

第一个完全支持CSS 1-3可用的公开库叫cssQuery,由Dean Edwards(http://dean.edwards.name)创立。其背后的动机很简单:你提供CSS选择器,cssQuery帮你找出所有匹配的元素。此外,cssQuery可以分解成多个子库,有一个是每个CSS选择器“管理员”,它甚至能执行CSS 3的语法,如果有必要的话。这个独特的库非常复杂但能运行在所有现代浏览器上(Dean是一位跨浏览器支持的忠实拥趸)。要应用整个库,你需要提供选择器,也可选择加入上下文元素以便加快搜索。以下是例子:

// 查找所有<div>元素的子<p>元素

cssQuery("div > p");

// 查找所有的<div>, <p>和<form>元素

cssQuery("div,p,form");

// 查找所有<p>元素和<div>元素,然后查找在这些元素内的a元素

var p = cssQuery("p,div");

cssQuery("a",p);

执行cssQuery函数会返回匹配的元素列表。由此你就可以像使用getElementsByTagName一样对元素进行操作。比如,为所有指向Google的链接增加边框,可按如下方法做:

// 为所有指向Google的链接增加边框

var g = cssQuery("a[href^='google.com']");

for ( var i = 0; i < g.length; i++ ) {

  g[i].style.border = "1px dashed red";

}

关于cssQuery的更多信息可以在Dean Edwards的网站上获取,那里同时也提供完整的源码下载:http://dean.edwards.name/my/cssQuery/.

提示 Dean Edwards是一位JavaScript魔术师,他的代码令人惊讶。强烈建议你稍加浏览他的cssQuery库,看看他的JavaScript扩展性写得多么好。

2.jQuery

尽管jQuery是JavaScript库世界的新成员,但它提供了一些新颖而引人注目的JavaScript编程方式。我最初只想把jQuery写成一个“简便”的CSS选择器库,类似于 cssQuery,直到Dean Edwards发布了卓越的cssQuery库,迫使我开辟了另一个不同的方向。这个库提供了完整的CSS 1-3的支持,同时也有基本的XPath支持。在此之上,它还提供了进一步定位和操作DOM的能力。跟cssQuery一样,jQuery完全支持现代浏览器。以下是使用混合CSS和XPath的jQuery自定义方式来选择元素的例子:

// 查找所有类值为'links', 同时内有p元素的<div>元素

$("div.links[p]")

// 查找所有<p>, <div>元素的所有子孙元素

$("p,div").find("*")

// 查找指向Google的所有偶数链接

$("a[@href^='google.com']:even")

现在,如需进一步使用jQuery元素选择返回的结果,你有两种方式。第一,你可以运行$("expression").get()来获取匹配元素的列表——这跟cssQuery完全一致。第二,你可以使用jQuery内置的特殊函数来操作CSS和DOM。所以,回到使用cssQuery为指向Google的链接加边框的例子上,你现在就可以这么做:

// 为所有指向Google的链接增加边框

$("a[@href^=google.com]").css("border","1px dashed red");

你可以从jQuery项目网站上找到大量的例子、演示和文档等,此外,还有可定制的下载:http://jquery.com/。

注意 应该指出的是,cssQuery或jQuery实际上并不只是能定位HTML文档而已,它们可以在任何XML文档上使用。至于纯粹的XML形式定位,请继续阅读下面的XPath部分。

2.4.3 XPath

XPath表达式是一种不可思议的定位XML文档的强大的方式。自问世几年来,几乎可以肯定DOM实现的背后必有XPath。尽管相对冗长,但XPath表达式比起CSS选择器来,能做的事情更多也更强大。表5-1并行比较了一些CSS选择器和XPath表达式的某些不同。

表5-1 CSS 3选择器与XPath表达式的比较

目 标

CSS 3

XPath

所有元素

*

//*

所有<p>元素

p

//p

所有子元素

p> *

//p/*

由ID获取元素

#foo

//*[@id='foo']

由类获取元素

.foo

//*[contains(@class, 'foo')]

由特性(attribute)获取元素

*[title]

//*[@title]

<p>的第一个子元素

p >*:first-child

//p/*[0]

所有拥有子元素的<p>

不支持

//p[a]

下一个元素

p + *

//p/下一兄弟元素::*[0]

如果前面这些表达式激发了你的兴趣,推荐你浏览XPath的两个规范(尽管现代浏览器通常只完全支持XPath 1.0)以初步了解它们是如何工作的:

l    XPath 1.0:http://www.w3.org/TR/xpath/。

l    XPath 2.0:http://www.w3.org/TR/xpath20/。

如果需要深入这个主题,我推荐你阅读O’Reilly出版的由Elliotte Harold和Scott Means所著的XML in a Nutshell2004),或者Apress出版的由Jeni Tennison所著的Beginning XSLT 2.0: From Novice to Professional2005)。此外,还有一些精彩的教程帮助你学习使用XPath:

l    W3Schools的XPath教程:http://w3schools.com/xpath/。

l    ZVON XPath 教程:http://zvon.org/xxl/XPathTutorial/General/examples.html。

目前,浏览器对XPath的支持各不相同:IE和 Mozilla都比较完整(虽然不一样)地支持XPath实现,而Safari和Opera的版本还在开发中。作为补救,有几种完全使用 JavaScript写的XPath实现方法。虽然它们通常会比较慢(跟基于浏览器的实现相比),但可以确保所有现代浏览器都能稳定运行:

l    XML for Script:http://xmljs.sf.net/。

l    Google AJAXSLT:http://goog-ajaxslt.sf.net/。

此外,一个名为Sarissa的项目(http://sarissa.sf.net/)打算为各种浏览建立一个通用的实现包装。它可以让你一次写成XML访问代码,同时仍能从支持的浏览器中获得更快的运行速度。这项技术的最大问题在于,Opera和Safari浏览器对XPath支持仍不足,走不出XPath先前实现的困境。

使用浏览器内置的XPath与使用得到广泛支持的纯粹的JavaScript解决方案比起来,通常被认为是试验性质的技术。尽管如此,XPath的使用和普及渐渐成长起来,可把它当作CSS选择器强有力的竞争对手。

现在你已经掌握了定位任意DOM元素或任意DOM元素集合的必要知识和工具,那么我们现在应该利用这些知识了:从特性的操作到增加和删除DOM元素。

2.5 获取元素的内容

所有的DOM元素无外乎包含以下三者之一:文本、元素、文本与元素的混合。一般来说,常见的是第一种和最后一种。在这一节中你将了解现有的获取元素内容的常用方法。

2.5.1 获取元素内的文本

文本框:  
图5-3 包含元素与文本的DOM结构例子获取元素内的文本可能会令DOM新手最容易迷惑,同时它也配合了HTML DOM文档和XML DOM文档。在图5-3所示的DOM结构例子中,有一个<p>主元素,它包含一个<strong>元素和一段文本块。<strong>元素本身同时包含文本块。

让我们来看看如何获取这些元素中的文本。<strong>元素很简单,因为它除了文本节点没别的干扰信息。

应该注意的是,所有非基于Mozilla的浏览器中都有一个叫作innerText的属性,可用它来获取元素内的文本。这十分方便。不幸的是,因为它在一个比较流行的浏览器上并不可用,同时在XML DOM文档中也不行,你还是需要开发一个可行的替代方案。

获取元素内文本内容的一个窍门是,需要记住元素并不是直接包含文本的,而包含在一个子文本节点中,这可能有点让人不习惯。假设变量strongElem包换一个<strong>元素的引用,让我们来看看代码清单5-15是如何使用DOM从元素内抽出文本的。

代码清单5-15 获取<strong>元素的文本内容

// 非Mozilla浏览器:

strongElem.innerTex

// 其他的浏览器:

strongElem.firstChild.nodeValue

了解如何从单个元素中获取文本内容后,你需要继续学习如何从<p>元素中获取经过组合的文本内容。为此,你或许需要开发一个通用的函数来获取任意元素的文本内容,而不用考虑元素究竟嵌套了几层,如代码清单5-16所示。调用text()将返回元素及其所有后代元素包含的文本内容。

代码清单5-16 一个获取元素文本内容的通用函数

function text(e) {

  var t = "";

  // 如果传入的是元素,则继续遍历其子元素,

  // 否则假定它是一个数组

  e = e.childNodes || e;

  // 遍历所有字节点

  for ( var j = 0; j < e.length; j++ ) {

    // 如果不是元素,追加其文本值

    // 否则,递归遍历所有元素的子节点

    t += e[j].nodeType != 1 ?

      e[j].nodeValue : text(e[j].childNodes);

  }

  // 返回匹配的文本

  return t;

}

使用这个能够获取任何元素文本内容的函数,在上面这个例子中,你就可以从<p>元素中获取它的文本内容了,代码大致如下:

// 获取<p>元素的文本内容

text( pElem );

这个函数还有一个显著的好处,那就是它可以确保在HTML和XML DOM文档中都能工作,那就是说你现在拥有了一个能获取任何元素文本内容的兼容方法。

2.5.2 获取元素内的HTML

与获取元素内的文本比起来,获取元素内的HTML是一项更容易上手的DOM任务。令人高兴的是,由于一个由IE小组开发的特色方法,所有的现代浏览器现在都实现了HTML DOM元素的一个额外属性:innerHTML。使用这个属性你就可以从一个元素中提取所有HTML和文本了。此外,使用innerHTML十分快——通常会比递归查找元素内的所有文本内容快得多。但是并非事事称心如意。它取决于浏览器如何去实现innerHTML属性,而且因为并没有标准,每种浏览器都可以返回它自以为是的内容。例如,以下是一些使用innerHTML属性的怪异bug:

l    基于Mozilla的浏览器在innerHTML声明中并不会返回<style>元素。

l    IE返回的元素字符都是大写的,如果你想保持一致性可能会感到失望。

l    innerHTML作为一个只能用在HTML DOM文档的元素中的属性,若在XML DOM文档中使用的话只会返回null值。

使用innerHTML属性是很直观的,存取该属性会返回一个包含该元素的HTML内容的字符串。如果元素仅包含文本而不包含任何子元素,则返回的字符串只包含文本。我们通过调试图5-2中两个元素来看它是如何工作的:

// 获取<strong>元素的innerHTML

// 应返回"Hello"

strongElem.innerHTML

// 获取<p>元素的innerHTML

// 应返回"<strong>Hello</strong> how are you doing?"

pElem.innerHTML

如果你确定元素只包含文本,这个方法也可用来取代前面那个获取元素文本的复杂方法,并且十分简便。另一方面,能获取元素内的HTML内容,你就可以利用就地编辑的特点来构建一些动态的时髦应用。在第10章我们会讨论这个主题。

2.6 操作元素特性

几乎跟获取元素内容一样,获取和设置元素特性(attribute)的值也是最频繁的操作之一。通常,元素自带的特性列表会随着元素自身的XML表示信息预先加载,并存储成关联数组备用,例如以下这个网页的HTML片段:

<form name="myForm" action="/test.cgi" method="POST">

...

</form>

一旦加载到DOM中,表示HTML表单元素的变量formElem就会有一个关联数组,它是名/值特性对。其结果大致如下:

formElem.attributes = {

  name: "myForm",

  action: "/test.cgi",

  method: "POST"

};

检查元素特性是否存在对于使用特性数组来说是最简单不过的,但还是存在一个问题:不知何故Safari不支持。最为严重的是,IE也不支持可能是最有用的hasAttribute函数。那么如何才能检查某个特性是否存在呢?一个可行的办法是使用getAttribute函数(我将会在下一节讲述)并且检查返回值是否为null,如代码清单5-17所示。

代码清单5-17 检查元素是否有用一个指定的特性

function hasAttribute( elem, name ) {

  return elem.getAttribute(name) != null;

}

有了这个函数,并了解特性如何使用,那么你就可以获取和设置特性的值了。

获取和设置特性的值

根据你所使用的DOM文档的类型,从元素中获取特性的信息有两种方法。如果你希望保险并且总是兼容通用XML DOM的方式,那么可用getAttribute和setAttribute。可以使用如下方法:

// 获取特性

id("everywhere").getAttribute("id")

// 设置特性的值

tag("input")[0].setAttribute("value","Your Name");

除了这对标准的 getAttribute/setAttribute外,HTML DOM文档还有作为快速特性获取器(getter)/设置器(setter)的额外属性集合。它们通常在现代浏览器的DOM实现中都可用(但只能给 HTML DOM文档打包票),使用它们十分有助于编写精炼的代码。以下的代码向你展示了如何使用DOM属性(property)同时取得和设置DOM特性:

// 快捷获取特性

tag("input")[0].value

// 快捷设置特性

tag("div")[0].id = "main";

你应该注意到,特性存在某些怪异的情形。最常碰到问题的情形之一是存取类名称特性。为了能在所有的浏览器中都生效,你必须使用elem.className来存取className特性,而不是使用看起来更合适的getAttribute("class")。这种情形也发生在for特性上,而它被另外起名为htmlFor。此外,这也会在一些CSS特性上发生:cssFloat和cssText。导致这些特别的命名约定是因为class、for、float和text等都是JavaScript的保留字。

为了补救这些怪异的情形并且简化获取、设置正确特性的处理流程,你应该使用一个辅助函数来处理这些特殊情形。代码清单5-18展示了一个获取和设置元素特性值的函数。使用两个参数来调用这个函数时,比如 attr(element,id),返回指定元素特性的值;而使用3个参数来调用的话,比如attr(element,class,test),则是设置特性的值并返回它的新值。

代码清单5-18 获取和设置元素特性的值

function attr(elem, name, value) {

  // 确保提供的 name 是正确的

  if ( !name || name.constructor != String ) return '';

  // 检查name是否处在怪异命名的情形中

  name = { 'for': 'htmlFor', 'class': 'className' }[name] || name;

  // 如果用户传入了value参数的话,那么

  if ( typeof value != 'undefined' ) {

    // 首先使用快捷方式

    elem[name] = value;

    // 可以的话,使用setAttribute

    if ( elem.setAttribute )

      elem.setAttribute(name,value);

  }

  // 返回特性的值

  return elem[name] || elem.getAttribute(name) || '';

}

不用关心具体实现,使用标准的方法来同时存取和改变特性是一个强大的工具。代码清单5-19展示了一些例子,从中你可以学习到如何在多种场合中使用attr函数来简化处理特性的流程。

代码清单5-19 在DOM元素中使用attr函数设置和获取特性的值

// 设置<h1>元素的class

attr( tag("h1")[0], "class", "header" );

// 设置各个<input>元素的值

var input = tag("input");

for ( var i = 0; i < input.length; i++ ) {

  attr( input[i], "value", "" );

}

// 为name的值是'invalid'的<input>元素增加边框

var input = tag("input");

for ( var i = 0; i < input.length; i++ ) {

  if ( attr( input[i], "name" ) == 'invalid' ) {

    input[i].style.border = "2px solid red";

  }

}

到目前为止,我们已经讨论了DOM中常用的特性(比如id、class、name等)的获取和设置。但是,还有一个派得上用场的技术,那就是设置和获取非传统的特性。比如,你可以增添一个新特性(它只对存取DOM版本的元素可见)然后再次获取,完全不用修改文档的实际属性。举个例子,假设你有一个术语的自定义列表,并需要在点击术语的时候展开它的描述,HTML的结构大致如代码清单5-20所示。

代码清单5-20 自定义列表的HTML,其中把描述事先隐藏起来

    <title>Expandable Definition List</title>

    <style>dd { display: none; }</style>

    <h1>Expandable Definition List</h1>

    <dl>

      <dt>Cats</dt>

      <dd>A furry, friendly, creature.</dd>

      <dt>Dog</dt>

      <dd>Like to play and run around.</dd>

      <dt>Mice</dt>

      <dd>Cats like to eat them.</dd>

    </dl>

我们将会在第6章详细讲解事件的细节,现在先尽量保持这些事件代码的简单。以下例子是一个能让你点击术语显示(或隐藏)描述的高效脚本。该脚本应在页面的头部或者从一个外部文件中引入。代码清单5-21展示了构建一个可扩展自定义列表的必要代码。

代码清单5-21 允许动态切换的自定义列表

// 直至DOM可用

domReady(function(){

    // 遍历所有的术语

    var dt = tag("dt");

    for ( var i = 0; i < dt.length; i++ ) {

    // 等待用户点击术语

    addEvent( dt[i], "click", function() {

      // 检查描述已经open与否

      var open = attr( this, "open" );

      // 切换描述的display

      next( this ).style.display = open ? 'none' : 'block';

      // 记录描述是否open

      attr( this, "open", open ? '' : 'yes' );

      });

    }

});

现在已经知道如何遍历DOM与如何检查及修改特性,接下来你需要学习如何创建新的DOM元素,插入到所需的地方,并删除不再需要的元素。

2.7 修改DOM

了解如何修改DOM后,你就可以干任何事了,从即时创建自定义的XML文档到建立接受用户键入的动态表单,几乎所有可能的操作。修改DOM有3个步骤:首先要知道怎么创建一个新元素,然后要知道如何把它插入DOM中,最后要学习如何删除它。

2.7.1 使用DOM创建节点

修改DOM的主要方法是使用createElement函数,它可以让你即刻创建新元素。但是,已经创建的新元素并不会马上插入到DOM中(刚开始使用DOM的人常会对此感到迷惑)。所以,我先把重点放在创建DOM元素上。

createElement方法带有元素的标记名称,并返回该元素的实际DOM引用,这里没有特性和样式。如果你要开发使用XSLT驱动的XHTML页面(或者使用正确的MIME侍服的XHTML页面)的应用程序,必须记住,你使用的是XML文档,所以创建的元素必须使用正确的XML命名空间来关联它们。为无缝地解决问题,你可以使用一个简单的函数,用它来测试你正使用的HTML DOM文档是否支持使用命名空间(XHTML DOM文档的一个特点)来创建新的元素。在这种情况下,你必须使用正确的XHTML命名空间来创建新的DOM元素,如代码清单5-22所示。

代码清单5-22 创建新DOM元素的通用函数

function create( elem ) {

  return document.createElementNS ?

    document.createElementNS( 'http://www.w3.org/1999/xhtml', elem ) :

document.createElement( elem );

}

例如,使用这个函数你就可以创建一个简单的<div>元素,并附上一些额外的信息:

var div = create("div");

div.className = "items";

div.id = "all";

此外需要注意的是,一个创建新文本节点的DOM方法叫做createTextNode。它需要传入一个参数,即你需要插入到节点中的文本,并返回已创建的文本节点。

使用新建立的DOM元素和文本节点,就可以把它们插入到DOM文档所需的位置中去了。

2.7.2 插入到DOM中

即使是经验丰富的DOM老手,有时也难免会感到插入元素到DOM中非常麻烦。在你的JavaScript工具库中准备以下两个函数可以让你把事情做好。

第一个函数是insertBefore,可以在另一个子元素前插入一个元素。使用该函数的方法类似如下例子:

parentOfBeforeNode.insertBefore( nodeToInsert, beforeNode );

我使用了一个助记诀窍辅助记忆它的参数顺序:“insert第一个元素,before第二个。”没错吧,你只需片刻就记住了它。

有了一个能在其他节点前插入节点(可以是元素,也可以是文本节点),你是否该考虑考虑:“如何插入一个父节点中最后一个子节点?”一个名为appendChild的函数可以做到这个。 appendChild调用一个元素参数,追加指定的节点到子节点列表中的最后一个。这个函数的用法大致如下:

parentElem.appendChild( nodeToInsert );

为避免死记硬背insertBefore和 appendChild的参数顺序,你可以使用我写的两个辅助函数解决问题,如代码清单5-23和代码清单5-24所示,参数以相关的元素/节点然后是需插入的元素/节点的顺序调用。此外,before函数还有一个传入父节点的可选项,它可以为你节约一些代码。最后,两个函数都允许你传入需要插入/追加的字符串并自动地帮你转化为文本节点。建议你提供父元素作为引用(防止elem变成null)。

代码清单5-23 在另一个元素之前插入元素的函数

function before( parent, before, elem ) {

  // 检查parent是否传入

  if ( elem == null ) {

    elem = before;

    before = parent;

    parent = before.parentNode;

  }

  parent.insertBefore( checkElem( elem ), before );

}

代码清单5-24 为另一个元素追加一个子元素的函数

function append( parent, elem ) {

  parent.appendChild( checkElem( elem ) );

}

代码清单5-25的辅助函数允许你插入元素和文本(自动转化为正确的文本节点)。

代码清单5-25 before和append的辅助函数

function checkElem( elem ) {

  // 如果只提供字符串,则把它转化成文本节点

  return elem && elem.constructor == String ? document.createTextNode( elem ) : elem;

}

现在,使用before和append函数,通过创建新的DOM元素,你就可以在DOM中增添更多的信息呈现给用户了,如代码清单5-26所示。

代码清单5-26 使用append和before函数

// 创建一个新的<li>元素

var li = create("li");

attr( li, "class", "new" );

// 创建文本内容并添加到<li>中

append( li, "Thanks for visiting!" );

// 把<li>插入到有序列表的顶端

before( first( tag("ol")[0] ), li );

// 运行这些语句会转化空<ol>

<ol></ol>

// 为以下:

<ol>

<li class='new'>Thanks for visiting!</li>

</ol>

一将这些信息“插入”到DOM(包括使用insertBefore和appendChild),它马上就会渲染并呈现在用户前。因此,你可用之作实时的反馈。这对于需要用户输入的交互应用尤其有用。

现在你看到的只是基于DOM的方法来创建和插入节点,进一步了解其他向DOM注入内容的方法将更有用。

2.7.3 注入HTML到DOM

比创建普通DOM元素并插入到DOM中更为流行的技术是,直接向文档注入HTML。这个最为简便的实现方法使用了前面讨论的innerHTML。除了从元素获取HTML之外,它同时也是设置元素内的HTML的一条途径。作为一个展示其简单性的例子,假设有一个空的<ol>元素并需要往里加入<li>,那么的代码大致如下:

// 给ol 元素加入部分li

tag("ol")[0].innerHTML = "<li>Cats.</li><li>Dogs.</li><li>Mice.</li>";

难道这不比繁复地创建大量的DOM元素和相应的文本节点要简单得多?更好的是,它还比使用DOM方法要快得多(根据http://quirksmode.org的说法)。但世事无完美,使用innerHTML注入方法也伴随着一些棘手的问题:

l    如前所述,XML DOM文档中并不存在innerHTML方法,就是说你得继续使用通用的DOM 创建方法。

l    使用客户端XSLT创建的XHTML文档也不存在innerHTML方法,因为它们也是纯粹的XML文档。

l    innerHTML完全删除了元素内容的任何节点,意味着没有像DOM方法一样追加或者插入的简便方法。

最后一条尤为棘手,因为在另一个元素之前插入元素或者追加到子元素列表末尾特别有用。但是,编织一些DOM魔法,去改造append和before方法,除了常规的DOM元素,还可以让它跟常规的HTML 字符串和平共处。改造过程分两步走。第一步建立一个checkElem函数,它可以处理HTML字符串、DOM元素以及DOM元素数组,如代码清单 5-27所示。

代码清单5-27 转化一个DOM节点/HTML字符串混合型参数数组为纯粹的DOM节点数组

function checkElem(a) {

  var r = [];

  // 如果参数不是数组, 则强行转换

  if ( a.constructor != Array ) a = [ a ];

  for ( var i = 0; i < a.length; i++ ) {

    // 如果是字符串

    if ( a[i].constructor == String ) {

      // 用一个临时元素来存放HTML

      var div = document.createElement("div");

     // 注入HTML, 转换成DOM结构

      div.innerHTML = a[i];

      // 提取DOM结构到临时 div 中

      for ( var j = 0; j < div.childNodes.length; j++ )

        r[r.length] = div.childNodes[j];

    } else if ( a[i].length ) { // If it's an array

      // 假定是DOM节点数组

      for ( var j = 0; j < a[i].length; j++ )

        r[r.length] = a[i][j];

    } else { // 否则,假定是DOM节点

      r[r.length] = a[i];

    }

  }

  return r;

}

第二步,你需要修改这两个插入函数以适应新的checkElem,接受数组元素,如代码清单5-28所示。

代码清单5-28 插入和追加内容到DOM的改进函数

function before( parent, before, elem ) {

  // 检查是否提供 parent 节点参数

  if ( elem == null ) {

    elem = before;

    before = parent;

    parent = before.parentNode;

  }

  // 获取元素的新数组

  var elems = checkElem( elem );

  // 向后遍历数组,

  // 因为我们向前插入元素

  for ( var i = elems.length - 1; i >= 0; i-- ) {

    parent.insertBefore( elems[i], before );

  }

}

function append( parent, elem ) {

  // 获取元素数组

  var elems = checkElem( elem );

  // 把它们所有都追加到元素中

  for ( var i = 0; i <= elems.length; i++ ) {

    parent.appendChild( elems[i] );

  }

}

现在,使用这些新函数为有序列表追加<li>就非常简单了:

append( tag("ol")[0], "<li>Mouse trap.</li>" );

// 运行该简单的一行就能追加HTML到这个<ol>中

<ol>

<li>Cats.</li>

<li>Dogs.</li>

<li>Mice.</li>

</ol>

// 把它变成了这样:

<ol>

<li>Cats.</li>

<li>Dogs.</li>

<li>Mice.</li>

<li>Mouse trap.</li>

</ol>

// 而用before()运行一个类似的语句

before( last( tag("ol")[0] ), "<li>Zebra.</li>" );

// 则会变成这样:

<ol>

<li>Cats.</li>

<li>Dogs.</li>

<li>Zebra.</li>

<li>Mice.</li>

</ol>

这将能帮助你开发简洁和清晰的代码。但是,如果从DOM中移动元素和删除节点要怎么办呢?当然,肯定也会有其他方法处理这些问题。

2.7.4 删除DOM节点

删除DOM节点的操作几乎与创建和插入一样频繁。根据用户的需求允许创建无限量的项目,那么允许用户能够删除它们之中再也不需要处理的部分也就变得很重要了。删除节点的能力可封装成一个函数:removeChild。它跟appendChild用法一致效果相反。该函数的用法大致如下:

NodeParent.removeChild( NodeToRemove );

记住这点,你就可以创建两个独立的函数来删除节点了,如代码清单5-29所示。

代码清单5-29 删除DOM节点的函数

// 删除一个独立的DOM节点

function remove( elem ) {

  if ( elem ) elem.parentNode.removeChild( elem );

}

代码清单5-30展示了从一个元素中删除所有子节点的函数,仅需要DOM元素的引用作为参数。

代码清单5-30 从一个元素中删除所有子节点的函数

// 在DOM中删除一个元素的所有子节点

function empty( elem ) {

  while ( elem.firstChild )

    remove( elem.firstChild );

}

举个例子,要删除上一节例子中添加的<li>,同时假设你已经给用户充足的时间来浏览<li>了,在没有提示的情况下被删除。可以使用以下的JavaScirpt代码来完成这个操作:

// 删除<ol>的最后一个<li>

remove( last( tag("ol")[0] ) )

// 将会把

<ol>

<li>Learn Javascript.</li>

<li>???</li>

<li>Profit!</li>

</ol>

// 转换成:

<ol>

<li>Learn Javascript.</li>

<li>???</li>

</ol>

// 如果要求执行empty()函数而不是remove()

empty( last( tag("ol")[0] ) )

// 它将会简单地清空<ol>, 只留下:

<ol></ol>

学习完删除DOM节点,你已经清楚了我们的文档对象模型是如何运作的,并学会如何充分运用它。

2.8 小结

在这一章里讨论了与文档对象模型相关的大量东西。不幸的是,其中有些主题比其他的要复杂得多(比如等待DOM加载),而且在一段时间里都一直会这么复杂。不过仅使用目前所学你也能搭建出任何动态的Web应用程序了。

如果想看看一些DOM脚本编程的实际运用,在附录A中包含了大量补充代码。此外,还可以从本书的网站http://jspro.org或Apress网站 http://www.apress.com的源代码/下载部分找到更多DOM脚本编程例子。接下来,你要把注意力转向分离式DOM脚本编程的下一个组成部分——事件。

Wednesday, November 19, 2008

Cake PHP 学习备忘录

 

2

Posted in Cakephp, VC & PHP by DYH1919(Freshow) at 04月 23, 2008
Tags: Cakephp   欢迎引用、转载、修改,在转载时附上本文URL即可。

你的数据库中的表也应该遵循下面的命名规则:

● cake使用的表名(Table name)应该有英文的复数形式组成,比如users, authors, articles. 注意,对应的model是单数形式

● 所有的表 都必须有一个主键叫做 id

● 如果你需要关联tables,使用外键比如 article_id 。表的名字是单数,必须是小写,比如id

● 另外,最好遵从下面的命名规则以便更方便的使用某些功能

Include a ‘created’ 列

Include a ‘modified’列

UltraEdit 32 中高亮显示 CakePHP 的 视图(View),.ctp ,同理可扩展其它自定义后缀:

找到 UltraEdit 32 的 安装目录,打开 wordfile.txt 找到 253 行,在后面添加上 CTP 就行了。

例如:/L3″HTML” HTML_LANG Nocase Noquote Block Comment On = <!– Block Comment Off = –> File Extensions = HTM HTML SHTML HTT HTA HTX CFM JSP PHP PHTML ASP CTP

在CakePHP 中使用自定义全局变量,常量,全局函数等全局性东西:(/app/config/bootstrap.php),另一个办法是在 app 目录重载 app_controller.php,这样可以让全局函数。

Bootstrapping CakePHP

If you have any additional configuration needs, use CakePHP’s bootstrap file, found in /app/config/bootstrap.php. This file is executed just after CakePHP’s core bootstrapping.

This file is ideal for a number of common bootstrapping tasks:

  1. •Defining convenience functions
  2. •Registering global constants
  3. •Defining additional model, view, and controller paths

数据库乱码

app\config\database.php中设置 ‘encoding’ => ‘utf8′;

更新单个表项或多个表项时要指明 id ,如果不指明则是插入新行

$this -> Controller -> id = $this -> Session -> read('id');

$this -> :Controller -> id = $this -> Session -> read(’id’);

$this -> :Controller -> saveField(’actived’,'Y’)

saveField() 和 save()

上面也是说在 CakePHP 中如何使用 Update 这样的效果

显示乱码:

可以修改 view 文件夹下的 模版文件,添加:

$html -> charset(”UTF-8″);

Session使用:

Controller :$this->Session->write($key,$value); $this->Session->read()…..

使用数组:$dataArray1 = array (’g'=>$generalfile,’b'=>$bigfile,’s’=>$smallfile,’m'=>$minifile);  $this -> Session -> write($dataArray1);

View:$session->read($key);…..

function afterSave()
{
// mail me when a new bookmark is added
mail('example@example.com', 'Bookmark saved to database');
return true;

}


不使用 layout: $this-> layout = false;


Model 名和数据库名不对应:var $useTable = 'Table Name';


==============


setFlash

string $flashMessage


string $layout = ‘default’


array $params


string $key = ‘flash’


将变量$flashMessage中的信息写入Session(提供给之后的flash()方法来获取)。


如果$layout设置为“default”,该消息被存储为’<div class=”message”> ‘.$flashMessage.’</div> ‘.如果$default设置为”,该消息就按原样保存。如果参数为其他任何值,该消息以 $layout 所指定的格式保存在Cake view里.



新增 我收集、整理的一些 CakePHP 学习资料:这里下载 [2008.5.6]


原文地址:http://www.oblank.com/?p=475 转载时请不要删除本地址





CakePHP V1.2 中 Session 丢失修正方法。



0



Posted in Cakephp, VC & PHP by DYH1919(Freshow) at 04月 15, 2008


Tags: Cakephp, session   欢迎引用、转载、修改,在转载时附上本文URL即可。



CakePHP V1.2 中,即使在 ./cake/app/config/core.php 设置 Session.start 值为 true




Configure::write(’Session.start’,true);




Session 也会在刷新,离开本页,甚至于在 Controller 中使用了 redirect() 时丢失,翻遍了互联网也没找到答案,询问 Cake Google Group 也没有很好的 fix 方案。今天再通过 Subversion 从 V1.2.0.6331 升级到了 V1.2.0.6661 ,可问题仍没有解决。 后来想到看一下 CakePHP 的 libs 库文件或许可以解决问题,于是找到了 ./cake/cake/libs/controller/components/ 目录下的 session.php 文件,发现里面有一个变量 var $__started = false; 将它改为 true 后再测试,Session 一切正常,不会再丢失。 比较理想的修正方法: 1,将 ./cake/app/config/core.php 的 session.start 设为 true:




Configure::write(’Session.start’,true);




2,拷贝 ./cake/cake/libs/controller/components/ 目录下的 session.php 到 ./cake/app/controller/components 下,然后将 session.php 中的 var $__started = false; 设值为 true即可:




var $__started = true;




为什么要复制文件到 ./cake/app… 目录下呢?这是因为 app 是用户目录,而 ./cake/cake…. 是 CakePHP 的核心文件,一般不能修改,以方便以后升级使用。复制到 app 目录,可以通过重载的方法实现 fix bug 。 顺便提一下,session components 是每个 Controller 的默认组件,使用时不需要再次包含进去。



下面这部分是写给外国佬看的,英语好的绕道:



How to fix session gone/lost in CakePHP V1.2.x



I have got the bug(maybe!) ,fix steps :



first:confige session autostart in ./cake/app/config/core.php



Configure::write(’Session.start’,true);



then,copy the ’session.php’ file in ./cake/cake/libs/controller/components/ To ./cake/app/controller/components And set the var __started value to true, as below:



var $__started = true;



it work fine for me, help hope it useful



GooglGroup 关于这个问题的修正的完整讨论看这里



原文地址:http://www.oblank.com/?p=450 转载时请不要删除本地址





Subversion 安装记录



0



Posted in Subversion, VC & PHP by DYH1919(Freshow) at 04月 15, 2008


Tags: Apache, Cakephp, Subversion   欢迎引用、转载、修改,在转载时附上本文URL即可。



CakePHP Session 一直不能持久化,几天来用蹩脚的英语在Cakephp Google Group 上求助得来的方法都不起作用,后来看到似乎是将 Cake 从 V1.2.0.6113 升级到了一个新版本才算解决了问题,在 CakePHP 的 Svn 看到已经有了 1.2.0.6661,希望也可以通过升级来解决,于是就得用到了 Subversion 这个版本控制软件。



说起 Subversion ,以前在 Eclipse 中使用过插件,但也就是这个插件让我的 Eclipse 崩溃,所以这次不准备使用插件了而改用客户端。



SVN 的最新版是 1.4.6 ,但不能与 Apache 2.2.x 搭配,我先是在官方提供的“built against Apache 2.2.x ”下载“svn-1.4.6-setup.exe”,安装发现根本不能在 Apache 2.2.4 上使用,又下载“svn-win32-1.4.6.zip”,按照网上一些教程进行手动设置,费掉了九牛二虎之力也没能解决。



最后又下载“Subversion modules svn-1.4.6 available”,但安装到最后才发现它的教程很模糊,其间又有提到集成方案“EasyCM_0.8.0_Server_Win32”,(包括Apache-2.0.59、subvertion-1.4.4、TortoiesSVN-1.4.4),安装时发现 Apache 和 subversion 版本都很低,没道理我在返回去使用低版本的软件(宁愿做只小白鼠!),所以都跳过只安装了 TortoiesSVN 和 Python 2.4。



后来又在官方的“built against Apache 2.2.x ”下载了一个 Python 2.4 ,最后发现已经可以使用了,不过网上提到教程那些 浏览器中访问什么什么的我这里同样不能,只是右键菜单多了很多 SVN 方面的操作,也是前面的 TortoiesSVN 图形化软件。



经实验,能够成功能过 Subversion 从 CakePHP 获取最新版。



总结一下:回顾这个安装过程,安装 EasyCM 的 TortoiesSVN 、Python2.4 和最后安装 “svn-python-1.4.6.win32-py2.4.exe” 是关键。

Sunday, November 16, 2008

如何在cakephp的布局文件中包含多个模板?

2008/10/23

cake中的布局(layout)确实不错,可惜不能包含多个模板文件(view)进来。
前几天在网上看到一个实现方法,http://www.xuzhousoft.cn/content/view/48/1/ ,看了一下代码,是用helper来实现的,感觉有点费劲。从代码看,其view文件中应该不支持$session,$html之类的了。
下面介绍一款简单实用的方法。先来看看cake的layout是如何实现下面这个的:

1 <?php echo $content_for_layout; ?>

在cake/libs/view/view.php中有个方法叫做: function renderLayout()其中有$data_for_layout如下:

01 <?php
02 $data_for_layout = array_merge($this->viewVars,
03 array(
04 'title_for_layout' => $pageTitle,
05 'content_for_layout' => $content_for_layout,
06 'scripts_for_layout' => join("\n\t", $this->__scripts),
07 'cakeDebug' => $debug
08 )
09 );
10 ?>

也就是说实现的数据都是准备好的,从这一点上来讲就很难扩展了,因为变量就那么几个啊。
下面介绍一个方法,看代码:

01 <?php
02 /**
03 * 引入其他view文件,在layout中支持多个view
04 *
05 * @param string $name 在view目录下的文件
06 * @return string
07 *
08 */
09 function template($name) {
10 $s = addslashes( ROOT.DS.APP_DIR.DS.'views'.DS.$name.'.ctp' );
11 return "require(\"$s\");";
12 }
13 ?>

将这个函数添加到你的项目中去,然后在你的layout中添加如下代码即可包含多个view:
view/layout/default.ctp

1 ...
2 <?php
3 //包含模板 include you template
4 eval(template('elements'.DS.'hello'));
5 ?>
6 ...

view/elements/hello.ctp

1 <?php
2 echo 'yes!';
3 //可以支持controller中声明的所有变量和helper
4 echo $session->read('Login.name');
5 ?>

呵呵,很简单吧!

Friday, November 14, 2008

Trouble shooting

Warning (2): Cannot modify header information - headers already sent by (output started at 


F:\Prj_PHP\1.2.x.x\app\config\routes.php:61) [CORE\cake\libs\controller\controller.php, line 587]


Code | Context



$status	=	"Location: http://localhost/posts/view/1"


header - [internal], line ??
Controller::header() - CORE\cake\libs\controller\controller.php, line 587
Controller::redirect() - CORE\cake\libs\controller\controller.php, line 568
BidsController::add() - APP\controllers\bids_controller.php, line 25
Object::dispatchMethod() - CORE\cake\libs\object.php, line 114
Dispatcher::_invoke() - CORE\cake\dispatcher.php, line 259
Dispatcher::dispatch() - CORE\cake\dispatcher.php, line 213
[main] - APP\webroot\index.php, line 90


(default) 4 queries took 53 ms



Nr

Query


Error


Affected


Num. rows


Took (ms)



 



I found some tips from internet:



> Well I'm not surely what exactly is going on but I did have a similar

> problem the other day.


> First of all I would recommend keeping DEBUG set to 2 until you've got


> rid of the issue - at least then if there's an error message you'll see


> it.


> My problem was simply some extra whitespace before the <?php tag in my


> controller. The redirect function uses the PHP header function which


> must be called before ANYTHING is sent to the browser. Extra whitespace


> outwith PHP tags in your controller, or in any other file being


> included will prevent the redirect. This a common reason for redirects


> not working.



 



And I finally found it hide in router.php, which have many blank spaces after  ?>

Thursday, November 13, 2008

什么是ActiveRecord

ActiveRecord是什么:

1. 每一个数据库表对应创建一个类.类的每一个对象实例对应于数据库中表的一行记录; 通常表的每个字段在类中都有相应的Field;

2. ActiveRecord同时负责把自己持久化. 在ActiveRecord中封装了对数据库的访问, 即CRUD;

3. ActiveRecord是一种领域模型

说实话,从去年开始用SubSonic, 我就一直有点困惑, 什么是ActiveRecord, 什么不是ActiveRecord, 以及应该怎么样使用它才对. 把我的一些想法写下来, 希望能和大家交流.

ActiveRecord是什么:
1. 每一个数据库表对应创建一个类.类的每一个对象实例对应于数据库中表的一行记录; 通常表的每个字段在类中都有相应的Field;
2. ActiveRecord同时负责把自己持久化. 在ActiveRecord中封装了对数据库的访问, 即CRUD;
3. ActiveRecord是一种领域模型(Domain Model), 封装了部分业务逻辑;
ActiveRecord不是什么:
1. Row Data Gateway
Row Data Gateway模式中每个对象也封装了数据库记录的状态和持久化到数据库的访问方法; 这两个有时候很难区分. 细微的区别在于Row Data Gateway不封装任何业务逻辑;
2. TableGateway
TableGateway是一种数据访问模式, 对每个表有一个类, 类的方法封装了对单个表的数据操作, 如CRUD; 方法的接受表字段的值作为参数;
比如说对表Person有DAOPerson, 有以下方法:
int Create(string name, bool isMale)
DataSet Find(int personId)
void Delete(int personId)
void Update(int personId, string name, bool isMale)
微软的很多代码示例中使用了此模式;
ActiveRecord的区别在于ActiveRecord的对象中保持了记录的值, 是有状态的, 而TableGateway是没有状态的, 只是一系列数据库访问方法的集合;
3. Table Module
Table Module是一种领域逻辑模式, 一个类对应于数据库中的一个表; Table Module通常和Table Gateway合作, 前者负责基本的业务逻辑, 后者负责数据库访问, 以达到逻辑层和持久化层的隔离; 微软的实例代码经常使用这两者, 如对表Person, 通常会定义两个类, PersonBL和PersonDB, 在PersonBL中处理验证等逻辑, 并调用PersonDB访问数据库, 层间调用使用DataSet或自定义数据传输对象传输数据
在业务逻辑比较简单并且有和表的一一对应时, ActiveRecord相对来说更简单, 因为它在一个类中包括了业务逻辑对象和数据访问, 而且不需要数据传输对象, 减少了维护的工作量;
和Table Module比较起来, ActiveRecord与数据库耦合更紧;
ActiveRecord适用于:
1. 业务逻辑比较简单;当你的类基本上和数据库中的表一一对应时, ActiveRecord是非常方便的, 即你的业务逻辑大多数是对单表操作;
2. 当发生跨表的操作时, 往往会配合使用事务脚本(Transaction Script), 把跨表事务提升到事务脚本中;
3. ActiveRecord最大优点是简单, 直观; 一个类就包括了数据访问和业务逻辑. 如果配合代码生成器使用就更方便了;
4. 这些优点使ActiveRecord特别适合web快速开发, 而正是快速开发框架ROR采用了ActiveRecord, 并且很多类ROR框架如Castle的纷纷效仿才使ActiveRecord重新进入大家视线;
我想这也是为什么Martin Fowler在PoEAA中早就提出了这个模式, 但是直到最近两三年ActiveRecord才热起来可能就是这个原因;
ActiveRecord不适合于
1. ActiveRecord虽然有业务逻辑, 但基本上都是基于单表的. 跨表逻辑一般会放到当发生跨表的操作时, 往往会配合使用事务脚本(Transaction Script)中. 如果对象间的关联越来越多, 你的事务脚本越来越庞大, 重复的代码越来越多, 你就要考虑Domain Model + O/R Mapper了;
2. ActiveRecord保存了数据, 使它有时候看上去像数据传输对象(DTO). 但是ActiveRecord有数据库访问能力, 不要把它当DTO用. 尤其在跨越进程边界调用的时候, 不能传递ActiveRecord对象;

Feedback

2008-05-09 11:04 by 怪怪

不错, 很准确, 赞一个。
另外, 这个模式只是在POEAA中介绍过, 并非Fowler提出的(其实Fowler这辈子, 基本没提出过什么自己的东西); 这种用法也早就普遍存在了, 只是最近一热炒, 就被人为放大了。
不过我个人认为,ActiveRecord虽然提供了便利性, 同时也会带来思维方式和实践方法上的禁锢和负面影响, 所以并不总是提高, 有时也会降低生产效率。这是因为从根本上讲, 有时候我们并不真的需要一个有着固定接口的对象或者类。
回头说到SubSonic, 说实话其作者可能比园子里一些做框架的同志水平还菜很多, 但是SubSonic在很多场景下确实轻量、好用, 这反而是我们的同志应该好好思考的了。

2008-05-09 11:56 by wanghualiang

@怪怪
为什么说作者水平菜?
是代码写的有问题还是架构不是很合理?
没看过源码,但用在项目上了,很简单很好用。

2008-05-09 12:26 by jackyw [未注册用户]

SubSonic提供了很好用的代码生成工具,框架也比较轻量级
是比较好的实现了ActiveRecord模式的框架2008-05-09 12:56 by lovecherry      

在SubSonic之前也看到过buildprovider做orm的demo,但是SubSonic对作者把它做成了产品

#7楼 [楼主]   回复  引用  查看    

2008-05-09 13:37 by kuber      

@怪怪
任何方法都有其要解决的问题域, 我想是因为最近两年Web 2.0热导致的快速的web应用开发大行其道使ActiveRecord被重新注意了.
另: 好久没有看见怪怪有新的文章了. 我很喜欢"闲言碎语"这篇文章. 不过里面好像还有很多未竟之言, 我还等着下文呢.

2008-05-09 13:44 by designbeauty

第一次听见有人说Rob菜的。。。,那正常的人应该是什么水平。
btw. subsonic真的很好用很强大。 楼主可以分享一下经验

2008-05-09 15:10 by 怪怪      

@金色海洋(jyk)
没时间啊..., 而且感觉江郎才尽, 白活不出什么有价值的内容了。 倒是今天早上无缘无故的感觉到一阵喜悦, 似乎有些事更明白了, 但暂时又说不清楚明白了什么。 反正接触的东西越多, 感觉需要学的东西和需要做的工作就更多了。
@kuber
嗯, 可以这么说。 不过其前提是在面向对象或者基于对象的设计和实现方式下。 我对AcitveRecord、 Row Data Gateway、 DTO等等一些方法的质疑, 实际上更多的是针对面向对象的一些痼疾了。
比如就这种CRUD较多的场景, 不采用这些方式, 很显然光写代码时的类型转换, 和数据字典存取的安全性和便捷性, 就够我们一呛。而采用这些方式呢, 通过对象我们就得到了一组固定的接口, 要么接口太多, 要么接口太大, 且不说什么性能和维护的问题,损失掉的灵活性和直接性也非常可惜; 如果再通过新增接口去适配不同的场景, 除了显配一下自己的面向对象功力, 很多时候只是徒增麻烦。
只是我也仅仅在探索阶段, 说些怪话的目的只是抛砖引玉, 呼吁大家一起来看看有啥新的路子没有,而不是局限于过去那些旧的观点和争论。那篇文章之所以写不下去了, 关键是在现有的语言框架下, 我还没找到能完全满足我的想法的设计和表达方式,感觉还需要琢磨一阵子。 如果仅仅是一些小的改进, 感觉说起来也没啥意思~
@designbeauty
呵呵, 菜不菜的也要看那些方面。 我是指对概念的理解和掌握的技巧这两方面。 我看过他的一些Video, 说实话此人对很多概念是相当的模糊; 也用过和改过一阵子SubSonic,这个东西涉及的技巧也绝对比不上园子里一些高手的东西。
不过就像@lovecherry说的, 人家在做产品这一块, 可一点也不菜, 我还是相当的佩服的。另外, 去你网站看了, 很漂亮啊, 至少我很喜欢这种风格, 唉感觉设计还是比技术需要更多的天赋。

2008-05-09 16:34 by good man      

其实在这里我们应该对别人多鼓励,不能说菜不菜,你应该
把别人的错误找出来,达到共同学习,一起进步啊

2008-05-09 16:37 by RicCC      

ActiveRecord的主要优点是一个过渡模式,当你没法从失血一步走到充血模型,或者不大确定是否该这样做时,是一种比较好的选择
另外就算使用充血模式的复杂项目,还是会存在很多适合ActiveRecord的对象
我想当你ActiveRecord使用熟练之后思路应该会不大一样,选择范围会比较开阔

2008-05-09 17:07 by 怪怪      

@good man
哈哈, 这不是个老外吗, 他又不会跑博客园来, 跑来也不认识中文~ 另外, 我一向对名人比对咱一般的兄弟要求严格的多, 可以说刻薄了~
@RicCC
写的比较乱, 重新编辑一下, 两点:
1. 在面向对象这个大前提下, AcitveRecord、 Row Data Gateway、 DTO等形式是否是必要的。
是不是一定要将数据包裹以任何一种形式的对象的方式来处理, 我觉得也是可以有多种判断方式的;比如对于最简单的CRUD系统,或许可以根据在于我们在展示与持久之间,需要(比如各种逻辑包括业务逻辑产生的)手工编码操作数据的次数(和工作)来进行选择?如果在其它地方下功夫(如操作和流程的自动化),可以使得没有对象化而导致的问题小于一个限度, 我们也许就没有必要对象化。
这个问题的根本在于首先要确认我们手头的这个活儿,是否在大模型(不是我们常说的模型)上真的需要将数据对象化, 还是可以用其它模型更好的解决。
2. 如果其实现的核心不是面向对象特性, 比如说利用FP的特性, 或者在支持TMP的语言里。
如果我能得到使用某一方式方法的一切好处, 避免它基于面向对象实现时的缺陷, 根据其特征, 也许各种方式方法还可以分别叫做 AcitveRecord、 Row Data Gateway、 DTO, 但由于不再带有面向对象令人不爽的阴暗方面,那么也许进行选择和判断就容易的多。
如果能够做到, 而且成本很低, 那么也许我们可以忽略这个问题, 长期的按一定规则使用某些方式方法, 这样就可以更加集中注意力解决其它问题。

Monday, November 3, 2008

加拿大老人的幸福生活从房屋开始

www.comefromchina.com
和中国老人们的消费观念不同,加拿大的老年人有钱有闲,穿的最漂亮的,最受尊敬和爱护的就是老人。老年人也自然一直是商家很看重的一片市场。房屋开发商当然 也不会放过这个市场,近年来更是投其所好,开发不少针对老年人的楼盘。公寓的居住生活正在悄然改变老年人退休后的生活方式。
八成人希望提前退休
加拿大老年人协会市场高级副主席David Cravit表示,50岁以上的人群正成为增长迅速、开销额度大的消费群体,他们正在改变着加拿大的经济前景,对各行各业都产生了不可或缺的影响。
很多公司都对这个市场制定了特殊战略,例如Home Depot就针对性的开发了“独立生活项目”,为房主提供灵活性的服务和产品,它可以提供和安装特殊的产品,例如斜坡、可调整的橱柜、活动吧台等,生活因此变的更加简单。和以前相比,现在50岁以上的老人可以更加弹性和舒适地生活。
不知道是代沟还是现实,最近有一项调查,“您希望在多少岁退休?”年龄段在30岁左右的被调查者中,只有20%的人希望在56岁至60岁退休;年龄段在50岁的被调查者中,只有12%的人希望在56岁至60岁退休。看来,现在的退休年龄已经不太接近现实了。
这已经成为一种生活趋势,在30至49岁的人群中,21%的人希望退休后可以过更有意义自由的生活,在50岁以上的人群中,有这种想法的占到36%。
根据对50岁以上人群的调查,34%的人表示,退休后最重要的就是多和家人相处;其次是投身于自己的兴趣爱好,这占到32%;16%的人希望去旅游。
地区和性别的差异也有不同的影响,居住在大西洋省的人,最愿意退休后把时间花在陪伴家人上,占到43%的比例;有趣的是,40%的女性表示,退休后的首要安排就是多和家人相处,而只有26%的男性有这样的想法,他们更多的关注自我,36%的男性希望可以从事自己的兴趣爱好,只有28%的女性这么打算。
大多数50岁以上的加拿大人都对现状表示满意。当被问及,“如果不用考虑钱的问题,退休后您想居住在什么地方?”59%的人希望仍居住在现有的环境里;17%的人希望可以两地迁徙居住;11%的人希望环游世界,不需要固定居所;只有3%的人希望能和全家迁移他国豪宅。
老人换房:省心获利
Royal LePage对50岁以上人群的调查显示,50岁以上的加拿大人超过一千万,这个人口统计的结果,对全国的房屋市场会有一定的影响。
针对这个人口变化的情况,Royal LePage地产投入一个新项目,组建了老年地产专门设计服务组(SRES),这个设计组是经过北美地产认证的。Royal LePage将成为加拿大第一个通过认证的机构,进而成为合格的供应者。这也使得它的经纪们能更好的面对市场的变化,满足50岁以上人群的需求。
Royal LePage地产主席及CEO Phil Soper称,以往的几代人,退休会带来的情况都可以预想到,而现在50岁以上的人群更有自己多样的想法,他们讨厌一成不变的模式,寻找更好的更适合自己的生活方式。而这个SRES项目就是第一时间接触市场,对50岁以上人群进行整体分析和服务,帮助他们计划、讨论销售房屋。
目前,有28%五十岁以上的加拿大人计划出售他们的房屋,以此作为他们老年生活重新安排的一个部分,在这部分人群中,卖房的主要原因如下:
房屋太难打理:67%;
享受房屋增值带来的利益:46%;
计划旅游,无力照顾房子:38%;
没有能力生活自理:30%;
无力支付目前的房屋:23%;
这些五十岁以上的潜在售房者对售房后的计划也不同:37%的人想搬进一个更小的房子,更容易管理,并可以坐享房屋的增值。86%的人想用增值的部分支持退休生活,48%的人想用于旅游,42%的人想留给孩子,7%的人打算用于购买休闲娱乐设施。
原来,50岁以上的人搬家,一般都是选择较小的房子,搬去与孩子同住,或者搬进有人照顾的老人院。而现在50岁以上的老人彻底改变这一状况,他们重新定义了这部分人群想要的生活。由于生命的不断延长,现在的老人有了更多的财富和更好的身体,50岁以上的人群有更多样的选择,而不会只选择一种最好的。
银行:倒按揭以房养老
面对这个大趋势,一些金融机构也有所动作,例如TD银行,针对这些成熟可靠的顾客,他们提供了更多新的服务和产品。现在比较流行的做法是倒按揭,即“住房反向抵押贷款”,说简单点就是“以房养老”。其放贷对象是有住房的老年人,以其自有住房作抵押,银行定期向借款人放贷,到期以出售住房的收入或其他资产还贷。其特点是分期放贷,一次偿还,贷款本金随着分期放贷而上升,负债增加,自有资产减少。由于这种方式与传统的按揭贷款相反,故被称为“倒按揭”。
据了解,这种模式是上世纪80年代中期美国新泽西州一家银行创立的。如今,美国模式的“倒按揭”在加拿大也很受欢迎。美国的倒按揭贷款放贷对象是62 岁以上的老年人,分三种形式发放,联邦住房局有保险的住房倒按揭贷款、联邦住房局无保险的倒按揭贷款、放贷者有保险的倒按揭贷款。
“倒按揭”可以使老人们每个月有充裕的养老金支配,外出旅游、聘请护理等,基本生活得到保障的同时,还能享受生活的乐趣。通过正确的理财,退休一点都不会影响收入和生活质量。
在婴儿潮中,55%的人坦言,房屋是他们退休后的重要收入来源。
低收入:老有所居
加拿大“老有所居”的问题是不是就不成问题了呢?答案应当是:基本上不成问题,但并非全部解决。这是因为:富裕国家也有贫困人口,有些老人(包括早年移民来加的华人)老来没有住房的情况也还是有的;即便是那些已经拥有自有住房的老人,因其年老,在身体健康状况和心理上出现新情况,新需求,因而在“住”的问题上也还需要政府和社会重新加以解决。
在多伦多,低收入人士可以申请房屋补助,获得廉租屋或住房津贴;老年人则主要由公款资助的大多市房屋管理局、大多市房屋公司和多伦多房屋机构等单位根据老人护理需求的程度,提供多种多样的居所:
Senior’s Apartment (高龄人士公寓单位)。入住这里的老人基本上能照顾自己。这些老人公寓多半混杂于其它公寓之间,以便获得照料。
Retirement Home (退休人士房屋)。入住老人大体上能照料自己,但每天需要约一个小时左右的医护照顾。这里有医护人员值班,或有医生定期到访。
Home for the Aged (老人屋)。入住者差不多都是没有独立生活能力的老人,饮食起居和健康状况都需要由他人来照顾。
Nursing Home (护理安老院)。这是提供给那些饮食起居和健康状况都需要长期照顾的老人居住的。主办者主要是私营公司或教会、慈善机构和社区团体,诸如为华人耆老和残障人士提供服务的非牟利机构——多伦多耆晖会等。
上述的老人居所无论是政府主办还是私营和社团主办的,全都不是免费的。当然,交不起房租的低收入老人可以申请住房补助。
对于移民赴加的“团聚父母”来说,住满十年之后,不但可以获得每人每月大约900加元左右的老人保障金,还可以根据身体健康状况、个人意愿和需求,申请入住这些老人公寓或老人屋等老人居所。
一直以来,加拿大都享受“老人天堂”的美誉。而这美好的生活,就从房屋开始。