posts - 35,  comments - 4,  trackbacks - 1
  2009年1月16日

转贴自:http://hi.baidu.com/kaka888/blog/item/820ffddc7b64bba5cd1166fe.html

//布种 直接从数据库读去数据生成列表
                var fabircTypeDs = new Ext.data.Store({
                     proxy: new Ext.data.HttpProxy({
                          url: CONTEXT_PATH+'/fabrictype/fabrictypeAll.action'
                     }),
                     reader: selReader,
                     remoteSort: false
                    });
                fabircTypeDs.load();
                var fabircTypeCmb = new Ext.form.ComboBox({
                    fieldLabel: '布种',
                    store: fabircTypeDs,
                    hiddenName:'softId',
                    loadingText: 'searching...',
                    displayField : 'name',
                    mode:'local',
                    editable : false,
                    valueField: 'id',
                    width: 170,
                    triggerAction: 'all'
                    });
                    //供应商   当用户输入数据时根据输入信息进行模糊查询,动态生成列表,类似google搜索框
                var dsSupplier = new Ext.data.Store({
                            proxy: new Ext.data.ScriptTagProxy({
                               url:CONTEXT_PATH+'/supplier/supplierAll.action'
                          }),
                              reader: new Ext.data.JsonReader({
                                root: 'gridRows',
                               totalProperty: 'totalCount'
                            }, [
                                  {name: 'id', mapping: 'id'},
                                {name: 'name', mapping: 'name'}
                              ])
                        });
                var supplierCmb = new Ext.form.ComboBox({
                    fieldLabel:'供应商',
                    store: dsSupplier,
                    displayField:'name',
                    valueField: 'id',
                    typeAhead: true,
                    loadingText: 'loading...',
                    width: 170,
                    hiddenName:'name',
                    hideTrigger:true,
                    minChars:1,
                    forceSelection:true,
                    triggerAction: 'all'
                });

posted @ 2009-01-16 14:12 啊凡 阅读(333) 评论(0) 编辑
  2008年9月29日

转载自:http://book.csdn.net/bookfiles/362/10036213882.shtml

随需下载的JavaScript

On-Demand JavaScript

●●○

行为,启动(Bootstrap),跨域(CrossDomain),动态,JavaScript,延迟加载(LazyLoading),随需应变(OnDemand),ScriptTag

图6-13:随需下载的Ja vaScript

目标故事

Bill正登录到他的银行网站,登录表单立刻出现,并且随着他键入用户名称和密码,浏览器安静地下载应用其余部分所需要的JavaScript。

问题

怎样部署大量的JavaScript程序代码?

先决条件

l   Ajax应用大量使用JavaScript。越丰富的浏览器行为就意味着越多的JavaScript需要下载。

l   下载JavaScript对性能有冲击。直到全部初始化JavaScript被下载,交互才能完全开始。

l   大量的JavaScript内容会造成带宽的问题。往往,并非所有下载的JavaScript实际上都能被用到,从而导致带宽的浪费。

方案

下载并执行JavaScript片段。初始页面下载的JavaScript代码包含能进一步下载其他 JavaScript 的启动代码(bootstrapping code)。有两种有效技术:Script Tag Creation(Script 标签建立)和 Service Eval(服务评估)。我们将分别描述这两种技术,然后介绍这个模式的 3 个主要应用。

采用Script Tag Creation技术,使用DOM操作把新的脚本(script)元素注入到页面。这种方式也许让人吃惊,但是这种效果的确完全等同于一开始就已经存在<script>标签:引用到的JavaScript将被下载并自动执行。标签可以被附加在头部消息(head)或主体部分(body)上,然而,前者较常见,因为那通常是你发现script标签的位置:

var head = document.getElementsByTagName("head")[0];

script = document.createElement('script');

script.id = 'importedScriptId';

script.type = 'text/javascript';

script.src = "http://path.to.javascript/file.js";

head.appendChild(script);

如何知道脚本已经下载完毕?在这里,不同浏览器似乎有不同的行为,有些是同步下载脚本,有些则不是。在IE 里,你可能被一种类似于XMLHttpRequest(http://www.xml.com/ lpt/a/2005/11/09/fixing-ajax-xmlhttprequest-considered-harmful.htmlonreadystatechange)的机制所通知。其他浏览器里,你可能需要持续询问某种关于下载的指示(这里的指示取决于脚本的意图)。如果你碰巧能控制服务器脚本,可以实现一个通知机制;即通过回

调侦听函数(如果存在的话),完成该脚本的下载。实际上,有一个大有可为的建议,JSONP(http://bob.pythonmac.org/archives/2005/12/05/remote-json-jsonp/),其目标是完成一个简单(像这样具有灵活性的机制)的标准,适用于整个业界。

Script Tag Creation不让你获取任何旧文本;它必须是完整有效的JavaScript,而且你的脚本不能直接读取它,因为浏览器只用它做结果评估(evaluation)。那么,你怎样获得来自外部服务器的数据?对远程JavaScript最常见的方式,是为变量分配需要的数据:

var characters = new Array("Mario", "Sonic", "Lara");

内容通常是数据结构(data structure),例如,是标准的JSON消息,但是它也需要指定(assignment),以便被代码引用到。另外,它也可以是一个返回需要值的函数。两种方式的任何一种都不尽理想,因为浏览器代码必须使用在脚本中里提到的名称。再一次,像J这种具有灵活性的脚本机制的想法值得考虑,因为它让调用者自行决定变量的名称。

Service Eval是另一种On-Demand JavaScript技术,虽然不如Script Tag Creation卓越(其原因我们稍后将讨论)。在Service Eval里,以标准的XMLHttpRequest Call调用Web服务,输出一些JavaScript作为响应内容,这些JavaScript接着以eval()调用来执行。检查XMLHttpRequest Call的responseText属性,在评估(evaluating)之前,我们能够操作它,因此,它的body内容不必是完整、有效的JavaScript(与Script Tag Creation不同)。

任何不在函数内部的代码将立刻被执行。为了增加新函数以供稍后使用,JavaScript 能够直接添加到当前的窗口,或者现存的已知对象。例如,下面内容可以被发送:

self.changePassword = function(oldPassword, newpassword) {

  ...

}

XMLHttpRequest回调函数只需要把响应当作纯文本,并且把它传给eval()。再一次提醒:这里,异步状况要特别小心处理。你不能假设新的代码在请求之后立即生效,因此,别这么做:

if (!self.changePassword) {

  requestPasswordModuleFromServer();

}

changePassword(old, new) // 第一次将无效,因为 changePassword 尚未加载

相反地,你不需要对服务器产生同步调用,增加循环持续检查新函数,或者在响应处理函数中明确的发起调用。

On-Demand JavaScript有3个不同的应用:

Lazy Loading(延迟载入)

将大量JavaScript代码的加载往后推迟。以其中一种On-Demand JavaScript技术(Service EvalScript Tag Creation)运行。

Behavior Message(行为消息)

让服务器以一种“行为消息”(Behavior Message)的形式响应,行为消息指定浏览器的下一个动作。以其中一种On-Demand JavaScript技术(Service EvalScript Tag Creation)运行。

Cross-Domain Scripting(跨域 Scripting)

使用Script Tag Creation,绕过标准“同源”(same-origin)策略(这通常使 Cross- Domain Proxy成为必要),只能使用Script Tag Creation

先来看看Lazy Loading。传统上的最佳实践是避免不唐突地通过一或多个script标签包含(include)JavaScript。

<html>

  <head>

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

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

    <script type="text/javascript" src="visuals"></script>

  </head>

  ...

</html>

Lazy Loading则建议在初始HTML里,只加载最小的初始化模块(initialization module):

<html>

  <head>

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

  </head>

  ...

</html>

初始化模块声明页面启动的任何必要动作,或许足以覆盖该页面的一般使用。另外,它必须执行一个初始启动的函数(bootstrapping function),再根据需要下载JavaScript。

第2个应用,Behavior Message,是HTML Message模式的变形。Lazy Loading建立代码库提供持续性的使用,而Behavior Message 接受暂时性的代码并且立刻以eval()执行——这段脚本没有封装在函数中(虽然可以定义一些调用的函数)。实际上,这意味着浏览器正在询问服务器下一步做什么。

Cross-Domain Scripting是此模式的第3种应用。script 标签一直都能包含来自外部域的JavaScript。这个规则不仅适用于静态的<script>标签,对动态建立的script标签(如Script Tag Creation技术)也适用。因此,不同于XMLHttpRequest和IFrame,你的 script 能以这种方式直接访问外部内容。并且,因为src属性可以是任何的URL,你能够传递参数作为CGI变量。这种想法正变得相当受欢迎,有些公司(像是Yahoo!)特别为这种方法(见稍后的“真实世界的实例”)提供JavaScript API。

当你信任这种方式,并且理想地控制这种方式时,执行从外部域来的script是很有用的。其他时候,它肯定是安全性的风险。Douglas Crockford,JSON的创造者,警告外部 script能够造成一定的破坏(http://www.mindsack.com/uxe/dynodes/):

这种脚本能够交付数据,但是它以基础页面(base page)上的脚本的相同授权执行,因此,它能偷取cookie或者滥用服务器用户的授权。恶意的脚本能对用户和基础服务器之间的关系进行破坏….这个不受限制的script标签妙招是浏览器内最后的大安全漏洞。它无法轻易被修复,因为整个广告基础设施依赖此漏洞。要非常小心。

决策

使用Service Eval,还是使用Script Tag Creation?

两者之间的选择取决于几项因素。Service Eval包含一些胜过Script Tag Creation的好处:

l   以XMLHttpRequest为基础,当脚本准备好时,有标准机制被通知,因此没有调用尚未存在的函数的风险。

l   能够访问未经加工的脚本代码。

l   在脚本格式上具有较多灵活性:例如,你能够发送一些在不同XML节点里的JavaScript片段,并且让浏览器脚本将它们提取出来。

Script Tag Creation有两个胜过Service Eval的好处:

l   你能够从外部域载入JavaScript。这是两种风格之间唯一重大的功能性差别。

l   当第一次遇到<script>标签时,JavaScript将自动被评估(evaluated)—— 以大致相同于静态HTML所链接的JavaScript被评估的方式。因此,你不必为了稍后的使用,特别为文档添加变量和函数;只要正常声明它们即可。

使用 Lazy Loading,你将怎样分解模块?

你需要决定怎样切割你的JavaScript。软件开发的标准原则:模块应该职责明确(well- focused),模块间依赖应尽可能避免。另外,还存在一些Web方面的特定考虑:

l   理想的,任何模块不是根本没被用到,就是充分被使用。你不想要5 000行的模块被下载,却只为了获取一个3行的函数,这是带宽的浪费,这一点优于On-Demand JavaScript 的主要目的。

l   你需要一个或多个模块在启动时就存在。至少需要一个,用于开启更进一步的下载。

l   记住,代码可能在本地端被缓存。Alexander Kirk已经对On-Demand JavaScript的缓存做了一些实验(http://alexander.kirk.at/2005/10/11/caching-of-downloaded-code-testing- results/),结果是所有主流浏览器将缓存以Script Tag Generation创建的代码。通常情况下,你能够确保来自XMLHttpRequest的响应也被缓存。

使用Lazy Loading,脚本将在什么阶段下载JavaScript?

在必须使用JavaScript之前下载它是最容易的,但是这种方式未必总是最好的。下载 JavaScript 的过程中,将有一些延迟,这表示,如果你在最后的可能时刻抓取它,用户将等待片刻。当用户的行为或者系统状态,显示某些JavaScript很快将需要被用到时,考虑立刻下载它。

预测 JavaScript 是否将被用到是Predictive Fetch(第13章)的范例——抓取某些直觉上可能需要内容。你必须在使用的可能性和延迟加载可能引起的麻烦之间,有所取舍。例如,想象你有一些需要验证完成表单的JavaScript,如果你等到最后再下载,确实不会浪费带宽,但是代价是用户的满意度。当有一个或两个字段要进行验证,或者在这个表单最初被加载时,你可能就会下载它。每增加一次浪费下载的机会,也就会增加验证程序更平顺的机会。

真实世界的实例

MapBuilder

MapBuilder(http://mapbuilder.sourceforge.net)是一个地图网站(图6-14)框架。它使用 On-Demand JavaScript,降低代码被下载的数量。这个应用基于MVC模型(Model-View- Controller),Model信息被声明在XML文档中,随同需要用于下载相应小部件(widget)的JavaScript。当页面启动时,只有必要的JavaScript被下载,而不是全部代码。因此,这个范例是这个模式的部分实例;它确保只有最小的JavaScript子集被用到,但是不是以延迟方式载入(lazy),或者随需应变(on-demand)的风格。

Delicious/Yahoo! APIs

社交性书签(social bookmarking)网站,Delicious(http://del.icio.us),以及网站的拥有者,Yahoo!,两者皆提供以JSON为基础的API,具有合适的hook,允许你通过Script Tag Creation直接从浏览器访问。Delicious API(http://del.icio.us/help/json)将以此结果建立新对象(如果已经存在,就为它填值)。Yahoo! API(http://developer.yahoo.net/ common/json.html)让你在脚本里指定回调函数,JSON 将被传递给该函数。

图6-14:Map Builder

Dojo打包(packaging)框架

Dojo(http://dojotoolkit.org/download)是针对简化JavaScript开发的一种全面性框架。就其本身而言,它提供许多脚本,个别项目可能只需要使用当中的一个子集。为了管理脚本,有类似Java的包(package)系统,让你根据需要获取新的JavaScript(http://dojo.jot.com/ WikiHome/Documents/DojoPackageSystem)。你只需要直接包含单一的 JavaScript 文档:

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

接着使用Dojo API根据需要获取包:

dojo.hostenv.moduleLoaded("dojo.aDojoPackage.*");

运行以上命令将引起Dojo自动下载在dojo.aDojoPackage下的模块。

JSAN导入系统

JSAN(JavaScript Archive Network,http://openjsan.org)是一个在线的脚本知识库。此外,它包含一个用来导入 JavaScript 模块的代码库,使用的约定类似 Java。下列的调用:

JSAN.use('Module.To.Include');

映射到Module/To/Include.js。JSAN.includePath定义此路径可能存在的全部顶层目录。如果includePath是 ["/","/js"],JSAN将查找/Module/To/Include和/js/Module/ To/Include。

代码范例:AjaxPatterns On-Demand JavaScript Wiki

将On-Demand JavaScript引入到Wiki Demo

在Basic Wiki Demo(http://ajaxify.com/run/wiki)里,全部JavaScript被一次性下载。但是很多时候,用户仅仅从wiki读取信息——为什么要下载写入的代码呢?因此,这个demo分三阶段重构成On-Demand JavaScript:

1. uploadMessage函数被提取到第2个JavaScript文件,upload.js。还没有用到On- Demand JavaScript,因为两个文件都被包含了进来。

2. 倘若发生上传操作,通过确保只有upload.js文件被下载,进一步的重构引进On-Demand JavaScript。这个版本使用Script Tag Creation

3. 在更进一步的重构里,Script Tag Creation技术被替换成Service Eval

分离JavaScript:提取出upload.js

在第一个重构(http://ajaxlocal/run/wiki/separateJS/)中,上传函数仅仅被移到一个分离的 JavaScript文件。初始HTML包含新文件:

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

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

新的upload.js现在包含uploadMessage函数。为反映出分离,引入了两个参数,帮助从主wiki.js中解耦出这个函数:

function uploadMessages(pendingMessages, messageResponseHandler) {

  ...

}

调用代码几乎和以前一样:

uploadMessages(pendingMessages, onMessagesLoaded);

到目前为止,我们获得了一点模块化,但是对提升性能仍然没有帮助,因为这两个文件必须在一开始就被下载。

Script Tag Creation

使用On-Demand JavaScript,upload.js不再于启动时就需要,因此,它的引用不再出现于初始的HTML,只留下主要模块,wiki.js

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

增加一个新函数用于下载脚本。为避免多次下载,一个保护条件检查uploadMessages是否已经存在,如果存在,立即返回。遵循Script Tag Creation技术,它为文件头部添加脚本元素(使用upload.js的URL初始化),以及标准JavaScript的type属性:

function ensureUploadScriptIsLoaded() {

  if (self.uploadMessages) { // 已经存在

    return;

  }

  var head = document.getElementsByTagName("head")[0];

  script = document.createElement('script');

  script.id = 'uploadScript';

  script.type = 'text/javascript';

  script.src = "upload.js";

  head.appendChild(script);

}

调用的脚本必须调用这个函数。不过,正如前文“方案”中所述的,可能异步下载,因此,这里也必须作检查:

ensureUploadScriptIsLoaded();

if (self.uploadMessages) { // 如果还没有下载,等待下一次同步

  uploadMessages(pendingMessages, onMessagesLoaded);

  ....

如果脚本被浏览器异步下载,这个测试实际上在第一次将失败,因为在脚本被下载之前,下载函数将返回。但是在这个特定的应用中,实际上无关紧要——不论如何,整个同步程序每隔5秒被执行一次。如果上传程序还不存在,5秒之内,它应该会存在,这一点符合我们的目的。

Service Eval

Script Tag Creation代码改用Service Eval重构代替,在此,XMLHttpRequest Call取回 upload.js并且以eval()处理。初始的脚本——wiki.js只在取回JavaScript的实现上有所不同。文字响应被传送给eval:

function ensureUploadScriptIsLoaded() {

  if (self.uploadMessages) { // 已存在

    return;

  }

  ajaxCaller.getPlainText("upload.js", function(jsText) { eval(jsText); });

}

JavaScript响应也必须改变。如果它只是定义一个类似之前的全局函数,即:

function uploadMessages(pendingMessages, messageResponseHandler) {

  ...

}

当评估(evaluation)完成时,函数接着结束。相反地,我们能够通过声明函数如下,达到相同效果(这将把函数关联到当前的窗口上,因此在脚本被评估(evaluation)之后,它将继续存活)。

uploadMessages = function(pendingMessages, messageResponseHandler) {

  ...

}

相关模式

HTML Message

这个模式的Behavior Message用法是HTML Message(第9章)的对等物,并遵循一种类似以服务器为中心的哲学,在那里,服务器动态控制浏览器活动。

Predictive Fetch

当你预期JavaScript很快将需要用到时,通过先行下载它,将Predictive Fetch(第13章)应用到On-Demand JavaScript。

Multi-Stage Download

这个模式的Lazy Loading应用类似于Multi-Stage Download(多阶段下载),它也是递延下载。Multi-Stage Download的重点是下载的语义及显示内容,而不是下载JavaScript。另外,这个模式更多的是关于根据预定计划的顺序下载,而不是根据需要下载。

更多信息

l   Thomas Brattli提供一份教学课程,“Dynamic Data Using the DOM and Remote Scripting”(http://www.dhtmlcentral.com/tutorials/ tutorials.asp?id=11)。

posted @ 2008-09-29 12:04 啊凡 阅读(142) 评论(1) 编辑

转载自:http://www.cckee.com/edu/3/29/3555_4.html

在一个讨论web技术的网站vitamin上发现这篇《Serving JavaScript Fast》,读过之后大有收获,茅塞顿开。于是就有了翻译过来的念头——我这人有个毛病,看到有意思的英文文章,就想自己翻过来(虽然英文水平很烂)。先在网上查了查,已经有blog谈到这篇文章(我算是后知后觉了),有总结要点的《Flickr 的开发者的 Web 应用优化技巧》,也有延伸开来的《接着讲Flickr的八卦》,但似乎没有全文翻译的(这下就好,不会忙了半天发现是无用功)。之后,就写信问作者可不可以,作者一口答应:“sure - i’d love you to translate it”,只是要求我翻好之后给他一个链接地址。得到准许,心里就有底了。
先介绍一下作者。Cal Henderson,伦敦人,现居加利福尼亚的旧金山。PHP,MySQL和Perl专家,现任flickr架构师(flickr被收购后就在yahoo了),同时也是vitamin的特聘顾问(写些技术性文章)。
既然他是架构师,flickr用的应该就是文中谈到的这些技术,于是参照文章,再对比网站,种种迹象表明确实如此。虽然在中国访问flickr速度不敢恭维,加速效果不得而知,但其用了n多css和javascript资源却似乎从没出过什么问题,也从侧面印证了这些技术的有效性。
仔细的看完文章,还有个强烈的感觉:这老兄也太能卖关子了,一句话非分成三句说,摆事实讲道理是够透彻,就是有点太@#$%了…… 算了,他怎么说我怎么翻吧,忠实于原著嘛,要不就成篡改了。经过几天努力,加上同事thincat兄倾力援手(小弟不胜感激啊),终于完工(@_@ 真是苦力活啊,我再也不想干了~)。
全文翻译如下:
让javascript跑得更快

作者:Cal Henderson
下一代web应用让javascript和css得堪大用。我们会告诉你怎样使这些应用又快又灵。
建立了号称“Web 2.0”的应用,也实现了富内容(rich content)和交互,我们期待着css和javascript扮演更加重要的角色。为使应用干净利落,我们需要完善那些渲染页面的文件,优化其大小和形态,以确保提供最好的用户体验——在实践中,这就意味着一种结合:使内容尽可能小、下载尽可能快,同时避免对未改动资源不必要的重新获取。
由于 css和js文件的形态,情况有点复杂。跟图片相比,其源代码很有可能频繁改动。而一旦改动,就需要客户端重新下载,使本地缓存无效(保存在其他缓存里的版本也是如此)。在这篇文章里,我们将着重探讨怎样使用户体验最快:包括初始页面的下载,随后页面的下载,以及随着应用渐进、内容变化而进行的资源下载。
我始终坚信这一点:对开发者来说,应该尽可能让事情变得简单。所以我们青睐于那些能让系统自动处理优化难题的方法。只需少许工作量,我们就能建立一举多得的环境:它使开发变得简单,有极佳的终端性能,也不会改变现有的工作方式。
好大一沱

老的思路是,为优化性能,可以把多个css和js文件合并成极少数大文件。跟十个5k的js文件相比,合并成一个50k的文件更好。虽然代码总字节数没变,却避免了多个HTTP请求造成的开销。每个请求都会在客户端和服务器两边有个建立和消除的过程,导致请求和响应header带来开销,还有服务器端更多的进程和线程资源消耗(可能还有为压缩内容耗费的cpu时间)。
(除了HTTP请求,)并发问题也很重要。默认情况下,在使用持久连接(persistent connections)时,ie和firefox在同一域名内只会同时下载两个资源(在HTTP 1.1规格书中第8.1.4节的建议)(htmlor注:可以通过修改注册表等方法改变这一默认配置)。这就意味着,在我们等待下载2个js文件的同时,将无法下载图片资源。也就是说,这段时间内用户在页面上看不到图片。
(虽然合并文件能解决以上两个问题,)可是,这个方法有两个缺点。第一,把所有资源一起打包,将强制用户一次下载完所有资源。如果(不这么做,而是)把大块内容变成多个文件,下载开销就分散到了多个页面,同时缓解了会话中的速度压力(或完全避免了某些开销,这取决于用户选择的路径)。如果为了随后页面下载得更快而让初始页面下载得很慢,我们将发现更多用户根本不会傻等着再去打开下一个页面。
第二(这个影响更大,一直以来却没怎么被考虑过),在一个文件改动很频繁的环境里,如果采用单文件系统,那么每次改动文件都需要客户端把所有css和js重新下载一遍。假如我们的应用有个100k的合成的js大文件,任何微小的改动都将强制客户端把这100k再消化一遍。
分解之道

(看来合并成大文件不太合适。)替代方案是个折中的办法:把css和js资源分散成多个子文件,按功能划分、保持文件个数尽可能少。这个方案也是有代价的,虽说开发时代码分散成逻辑块(logical chunks)能提高效率,可在下载时为提高性能还得合并文件。不过,只要给build系统(把开发代码变成产品代码的工具集,是为部署准备的)加点东西,就没什么问题了。
对于有着不同开发和产品环境的应用来说,用些简单的技术可以让代码更好管理。在开发环境下,为使条理清晰,代码可以分散为多个逻辑部分(logical components)。可以在Smarty(一种php模板语言)里建立一个简单的函数来管理javascript的下载:
SMARTY:
{insert_js files="foo.js,bar.js,baz.js"}

PHP:
function smarty_insert_js($args){
foreach (explode(',', $args['files']) as $file){
echo "<script type=\"text/javascript\" SOURCE=\"/javascript/$file\"></script>\n";
}
}

OUTPUT:
<script type="text/javascript" SOURCE="/javascript/foo.js"></script><script type="text/javascript" SOURCE="/javascript/bar.js"></script><script type="text/javascript" SOURCE="/javascript/baz.js"></script>

(htmlor注:wordpress中会把“src”替换成不知所谓的字符,因此这里只有写成“SOURCE”,使用代码时请注意替换,下同)
就这么简单。然后我们就命令build过程(build process)去把确定的文件合并起来。这个例子里,合并的是foo.js和bar.js,因为它们几乎总是一起下载。我们能让应用配置记住这一点,并修改模板函数去使用它。(代码如下:)
SMARTY:
{insert_js files="foo.js,bar.js,baz.js"}

PHP:# 源文件映射图。在build过程合并文件之后用这个图找到js的源文件。

$GLOBALS['config']['js_source_map'] = array( 'foo.js' => 'foobar.js

 

', 'bar.js' => 'foobar.js', 'baz.js' => 'baz.js',);
function smarty_insert_js($args){
if ($GLOBALS['config']['is_dev_site']){
$files = explode(',', $args['files']);
}else{
$files = array();
foreach (explode(',', $args['files']) as $file){
$files[$GLOBALS['config']['js_source_map'][$file]]++;
}
$files = array_keys($files);
}
foreach ($files as $file){
echo "<script type=\"text/javascript\" SOURCE=\"/javascript/$file\"></script>\n";
}
}

OUTPUT:
<script type="text/javascript" SOURCE="/javascript/foobar.js"></script><script type="text/javascript" SOURCE="/javascript/baz.js"></script>

模板里的源代码没必要为了分别适应开发和产品阶段而改动,它帮助我们在开发时保持文件分散,发布成产品时把文件合并。想更进一步的话,可以把合并过程(merge process)写在php里,然后使用同一个(合并文件的)配置去执行。这样就只有一个配置文件,避免了同步问题。为了做的更加完美,我们还可以分析 css和js文件在页面中同时出现的几率,以此决定合并哪些文件最合理(几乎总是同时出现的文件是合并的首选)。
对css来说,可以先建立一个主从关系的模型,它很有用。一个主样式表控制应用的所有样式表,多个子样式表控制不同的应用区域。采用这个方法,大多数页面只需下载两个css文件,而其中一个(指主样式表)在页面第一次请求时就会缓存。
对没有太多css和js资源的应用来说,这个方法在第一次请求时可能比单个大文件慢,但如果保持文件数量很少的话,你会发现其实它更快,因为每个页面的数据量更小。让人头疼的下载花销被分散到不同的应用区域,因此并发下载数保持在一个最小值,同时也使得页面的平均下载数据量很小。
压缩

谈到资源压缩,大多数人马上会想到mod_gzip(但要当心,mod_gzip实际上是个魔鬼,至少能让人做恶梦)。它的原理很简单:浏览器请求资源时,会发送一个header表明自己能接受的内容编码。就像这样:
Accept-Encoding: gzip,deflate服务器遇到这样的header请求时,就用gzip或deflate压缩内容发往客户端,然后客户端解压缩。这过程减少了数据传输量,同时消耗了客户端和服务器的cpu时间。也算差强人意。但是,mod_gzip的工作方式是这样的:先在磁盘上创建一个临时文件,然后发送(给客户端),最后删除这个文件。在高容量的系统中,由于磁盘io问题,很快就会达到极限。要避免这种情况,可以改用mod_deflate(apache 2才支持)。它采用更合理的方式:在内存里做压缩。对于apache 1的用户来说,可以建立一块ram磁盘,让mod_gzip在它上面写临时文件。虽然没有纯内存方式快,但也不会比往磁盘上写文件慢。
话虽如此,其实还是有办法完全避免压缩开销的,那就是预压缩相关静态资源,下载时由mod_gzip提供合适的压缩版本。如果把压缩添加在build过程,它就很透明了。需要压缩的文件通常很少(用不着压缩图片,因为并不能减小更多体积),只有css和js文件(和其他未压缩的静态内容)。
配置选项会告诉mod_gzip去哪里找到预压缩过的文件。
mod_gzip_can_negotiate Yesmod_gzip_static_suffix .gzAddEncoding gzip .gz新一点的mod_gzip版本(从1.3.26.1a开始)添加一个额外的配置选项后,就能自动预压缩文件。不过在此之前,必须确认apache有正确的权限去创建和覆盖压缩文件。
mod_gzip_update_static Yes可惜,事情没那么简单。某些Netscape 4的版本(尤其是4.06-4.08)认为自己能够解释压缩内容(它们发送一个header这么说来着),但其实它们不能正确的解压缩。大多数其他版本的 Netscape 4在下载压缩内容时也有各种各样的问题。所以要在服务器端探测代理类型,(如果是Netscape 4,就要)让它们得到未压缩的版本。这还算简单的。ie(版本4-6)有些更有意思的问题:当下载压缩的javascript时,有时候ie会不正确的解压缩文件,或者解压缩到一半中断,然后把这半个文件显示在客户端。如果你的应用对javascript的依赖比较大(htmlor注:比如ajax应用),那么就得避免发送压缩文件给ie。在某些情况下,一些更老的5.x版本的ie倒是能正确的收到压缩的javascript,可它们会忽略这个文件的etag header,不缓存它。(thincat 友情提示:尽管压缩存在一些浏览器不兼容的现象,由于这些不能很好的支持压缩的浏览器数量现在已经非常少了,我认为这种由于浏览器导致的压缩不正常的情况可以忽略不计。这些过时的浏览器还能不能在现在流行的windows或unix环境下面安装都存在不小的问题)
既然gzip压缩有这么多问题,我们不妨把注意力转到另一边:不改变文件格式的压缩。现在有很多这样的javascript压缩脚本可用,大多数都用一个正则表达式驱动的语句集来减小源代码的体积。它们做的不外乎几件事:去掉注释,压缩空格,缩短私有变量名和去掉可省略的语法。
不幸的是,大多数脚本效果并不理想,要么压缩率相当低,要么某种情形下会把代码搞得一团糟(或者两者兼而有之)。由于对解析树的理解不完整,压缩器很难区分一句注释和一句看似注释的引用字符串。因为闭合结构的混合使用,要用正则表达式发现哪些变量是私有的并不容易,因此一些缩短变量名的技术会打乱某些闭合代码。
还好有个压缩器能避免这些问题:dojo压缩器(现成的版本在这里)。它使用rhino(mozilla的javascript引擎,是用java实现的)建立一个解析树,然后将其提交给文件。它能很好的减小代码体积,仅用很小的成本:因为只在build时压缩一次。由于压缩是在build过程中实现的,所以一清二楚。(既然压缩没有问题了,)我们可以在源代码里随心所欲的添加空格和注释,而不必担心影响到产品代码。
与javascript相比,css文件的压缩相对简单一些。由于css语法里不会有太多引用字符串(通常是url路径跟字体名),我们可以用正则表达式大刀阔斧的干掉空格(htmlor注:这句翻的最爽,哈哈)。如果确实有引用字符串的话,我们总可以把一串空格合成一个(因为不需要在url路径和字体名里查找多个空格和tab)。这样的话,一个简单的perl脚本就够了:

#!/usr/bin/perl
my $data = '';
open F, $ARGV[0] or die "Can't op

 

en source file: $!";
$data .= $_ while <F>;
close F;
$data =~ s!/*(.*?)*/!!g; # 去掉注释
$data =~ s!s+! !g; # 压缩空格
$data =~ s!} !}\n!g; # 在结束大括号后添加换行
$data =~ s!\n$!!; # 删除最后一个换行
$data =~ s! { ! {!g; # 去除开始大括号后的空格
$data =~ s!; }!}!g; # 去除结束大括号前的空格
print $data;

然后,就可以把单个的css文件传给脚本去压缩了。命令如下:
perl compress.pl site.source.css > site.compress.css
做完这些简单的纯文本优化工作后,我们就能减少数据传输量多达50%了(这个量取决于你的代码格式,可能更多)。这带来了更快的用户体验。不过我们真正想做的是,尽可能避免用户请求的发生——除非确实有必要。这下HTTP缓存知识派上用场了。

 


缓存是好东西

当用户代理(如浏览器)向服务器请求一个资源时,第一次请求过后它就会缓存服务器的响应,以避免重复之后的相同请求。缓存时间的长短取决于两个因素:代理的配置和服务器的缓存控制header。所有浏览器都有不同的配置选项和处理方式,但大多数都会把一个资源至少缓存到会话结束(除非被明确告知)。
为了不让浏览器缓存改动频繁的页面,你很可能已经发送过header不缓存动态内容。在php中,以下两行命令可以做到:

<?php
header("Cache-Control: private");
header("Cache-Control: no-cache", false);
?>

听起来太简单了?确实如此——因为有些代理(浏览器)在某些环境下将忽略这些header。要确保浏览器不缓存文档,应该更强硬一些:

<?php
# 让它在过去就“失效”
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");# 永远是改动过的
header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT");# HTTP/1.1
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", false);# HTTP/1.0
header("Pragma: no-cache");?>

这样,对于我们不想缓存的内容来说已经行了。但对于那些不会每次请求时都有改动的内容,应该鼓励浏览器更霸道的缓存它。“If-Modified- Since”请求header能够做到这点。如果客户端在请求中发送一个“If-Modified-Since”header,apache(或其他服务器)会以状态代码304(没改过)响应,告诉浏览器缓存已经是最新的。使用这个机制,能够避免重复发送文件给浏览器,不过仍然导致了一个HTTP请求的消耗。嗯,再想想。
与If-Modified-Since机制类似的是实体标记(entity tags)。在apache环境下,每个对静态文件的响应都会发出一个“ETag”header,它包含了一个由文件修改时间、文件大小和inode号生成的校验和(checksum)。在下载文件之前,浏览器会发送一个HEAD请求去检查文件的etag。可ETag跟If-Modified-Since 有同样的问题:客户端仍旧需要执行HTTP请求来验证本地缓存是否有效。
此外,如果你使用多台服务器提供内容,得小心使用if-modified-since和etags。在两台负载平衡的服务器环境下,对一个代理(浏览器)来说,一个资源可以这次从A服务器得到,下次从B服务器得到(htmlor注:lvs负载平衡系统就是个典型的例子)。这很好,也是采用平衡负载的原因。可是,如果两台服务器给同一个文件生成了不同的etag或者文件修改日期,浏览器就无所适从了(每次都会重新下载)。默认情况下,etag是由文件的inode号生成的,而多台服务器之间文件的inode号是不同的。可以使用apache的配置选项关掉它:
FileETag MTime Size使用这个选项,apache将只用文件修改日期和文件大小来决定etag。很不幸,这导致了另一个问题(一样能影响if-modified- since)。既然etag依赖于修改时间,就得让时间同步。可往多台服务器上传文件时,上传时间差个一到两秒是常有的事。这样一来,两台服务器生成的 etag还是不一样。当然,我们还可以改变配置,让etag的生成只取决于文件大小,但这就意味着如果文件内容变了而大小没变,etag也不会变。这可不行。
缓存真是个好东西

看来我们正从错误的方向入手解决问题。(现在的问题是,)这些可能的缓存策略导致了一件事情反复发生,那就是:客户端向服务器查询本地缓存是否最新。假如服务器在改动文件的时候通知客户端,客户端不就知道它的缓存是最新的了(直到接到下一次通知)?可惜天公不做美——(事实)是客户端向服务器发出请求。
其实,也不尽然。在获取js或css文件之前,客户端会用 <script>或<link>标记向服务器发送一个请求,说明哪个页面要加载这些文件。这时候就可以用服务器的响应来通知客户端这些文件有了改动。有点含糊,说得再详细点就是:如果改变css和js文件内容的同时,也改变它们的文件名,就可以告诉客户端对url全都永久缓存—— 因为每个url都是唯一的。
假如能确定一个资源永不更改,我们就可以发出一些霸气十足的缓存header(htmlor注:这句也很有气势吧)。在php里,两行就好:

<?php
header("Expires: ".gmdate("D, d M Y H:i:s", time()+315360000)." GMT");
header("Cache-Control: max-age=315360000");
?>

我们告诉浏览器这个内容在10年后(10年大概会有315,360,000秒,或多或少)过期,浏览器将会保留它10年。当然,很有可能不用php输出css和js文件(因此就不能发出header),这种情况将在稍后说明。
人力有时而穷

当文件内容更改时,手动去改文件名是很危险的。假如你改了文件名,模板却没有指向它?假如你改了一些模板另一些却没改?假如你改了模板却没改文件名?还有最糟的,假如你改动了文件却忘了改名或者忘了改变对它的引用?最好的结果,是用户看到老的而看不到新的内容。最坏的结果,是找不到文件,网站没法运转了。听起来这(指改动文件内容时修改url)似乎是个馊主意。
幸运的是,计算机做这类事情——当某种变化发生,需要相当准确地完成的、重复重复再重复的(htmlor注:番茄鸡蛋伺候~)、枯燥乏味的工作——总是十分在行。
这个过程(改变文件的url)没那么痛苦,因为我们根本不需要改文件名。资源的url和磁盘上文件的位置也没必要保持一致。使用apache的mod_rewrite模块,可以建立简单的规则,让确定的url重定向到确定的文件。

RewriteEngine onRewriteRule ^/(.*.)v[0-9.]+.(css|js|gif|png|jpg)$ /$1

6;2 [L]

这条规则匹配任何带有指定扩展名同时含有“版本”信息(version nugget)的url,它会把这些url重定向到一个不含版本信息的路径。如下所示:

URL Path/images/foo.v2.gif -> /images/foo.gif/css/main.v1.27.css -> /css/main.css/javascript/md5.v6.js -> /javascript/md5.js

使用这条规则,就可以做到不改变文件路径而更改url(因为版本号变了)。由于url变了,浏览器就认为它是另一个资源(会重新下载)。想更进一步的话,可以把我们之前说的脚本编组函数结合起来,根据需要生成一个带有版本号的<script>标记列表。
说到这里,你可能会问我,为什么不在url结尾加一个查询字符串(query string)呢(如/css/main.css?v=4)?根据HTTP缓存规格书所说,用户代理对含有查询字符串的url永不缓存。虽然ie跟 firefox忽略了这点,opera和safari却没有——为了确保所有浏览器都缓存你的资源,还是不要在url里用查询字符串的好。
现在不移动文件就能更改url了,如果能让url自动更新就更好了。在小型的产品环境下(如果有大型的产品环境,就是开发环境了),使用模板功能可以很轻易的实现这点。这里用的是smarty,用其他模板引擎也行。

SMARTY:
<link xhref="{version xsrc='/css/group.css'}" rel="stylesheet" type="text/css" />

PHP:
function smarty_version($args){
$stat = stat($GLOBALS['config']['site_root'].$args['src']);
$version = $stat['mtime'];
echo preg_replace('!.([a-z]+?)$!', ".v$version.$1", $args['src']);
}

OUTPUT:
<link xhref="/css/group.v1234567890.css" mce_href="/css/group.v1234567890.css" rel="stylesheet" type="text/css" />

对每个链接到的资源文件,我们得到它在磁盘上的路径,检查它的mtime(文件最后修改的日期和时间),然后把这个时间当作版本号插入到url中。对于低流量的站点(它们的stat操作开销不大)或者开发环境来说,这个方案不错,但对于高容量的环境就不适用了——因为每次stat操作都要磁盘读取(导致服务器负载升高)。
解决方案相当简单。在大型系统中每个资源都已经有了一个版本号,就是版本控制的修订号(你们应该使用了版本控制,对吧?)。当我们建立站点准备部署的时候,可以轻易的查到每个文件的修订号,写在一个静态配置文件里。

<?php
$GLOBALS['config']['resource_versions'] = array( '/images/foo.gif' => '2.1', '/css/main.css' => '1.27', '/javascript/md5.js' => '6.1.4',);
?>

当我们发布产品时,可以修改模板函数来使用版本号。

<?php
function smarty_version($args){
if ($GLOBALS['config']['is_dev_site']){
$stat = stat($GLOBALS['config']['site_root'].$args['src']);
$version = $stat['mtime'];
}else{
$version = $GLOBALS['config']['resource_versions'][$args['src']];
}
echo preg_replace('!.([a-z]+?)$!', ".v$version.$1", $args['src']);}
?>

就这样,不需要改文件名,也不需要记住改了哪些文件——当文件有新版本发布时它的url就会自动更新——有意思吧?我们就快搞定了。
只欠东风

之前谈到为静态文件发送超长周期(very-long-period)的缓存header时曾说过,如果不用php输出,就不能轻易的发送缓存header。很显然,有两个办法可以解决:用php输出,或者让apache来做。
php出马,手到擒来。我们要做的仅仅是改变rewrite规则,把静态文件指向php脚本,用php在输出文件内容之前发送header。

Apache:
RewriteRule ^/(.*.)v[0-9.]+.(css|js|gif|png|jpg)$ /redir.php?path=$1$2 [L]

PHP:
header("Expires: ".gmdate("D, d M Y H:i:s", time()+315360000)." GMT");
header("Cache-Control: max-age=315360000");# 忽略带有“..”的路径
if (preg_match('!..!', $_GET[path])){ go_404(); }# 保证路径开头是确定的目录
if (!preg_match('!^(javascript|css|images)!', $_GET[path])){ go_404(); }# 文件不存在?
if (!file_exists($_GET[path])){ go_404(); }# 发出一个文件类型
header$ext = array_pop(explode('.', $_GET[path]));
switch ($ext){
case 'css':
header("Content-type: text/css");
break;
case 'js' :
header("Content-type: text/javascript");
break;
case 'gif':
header("Content-type: image/gif");
break;
case 'jpg':
header("Content-type: image/jpeg");
break;
case 'png':
header("Content-type: image/png");
break;
default: header("Content-type: text/plain");
}
# 输出文件内容
echo implode('', file($_GET[path]));
function go_404(){
header("HTTP/1.0 404 File not found");
exit;
}

这个方案有效,但并不出色。(因为)跟apache相比,php需要更多内存和执行时间。另外,我们还得小心防止可能由path参数传递伪造值引起的 exploits。为避免这些问题,应该用apache直接发送header。rewrite规则语句允许当规则匹配时设置环境变量(environment variable),当给定的环境变量设置后,Header命令就可以添加header。结合以下两条语句,我们就把rewrite规则和header设置绑定在了一起:

RewriteEngine onRewriteRule ^/(.*.)v[0-9.]+.(css|js|gif|png|jpg)$ /$1$2 [L,E=VERSIONED_FILE:1]
Header add "Expires" "Mon, 28 Jul 2014 23:30:00 GMT" env=VERSIONED_FILEHeader add "Cache-Control" "max-age=315360000" env=VERSIONED_FILE
考虑到apache的执行顺序,应该把rewrite规则加在主配置文件(httpd.conf)而不是目录配置文件(.htaccess)中。否则在环境变量设置之前, header行会先执行(就那没意义了)。至于header行,则可以放在两文件任何一个当中,没什么区别。
眼观六路

(htmlor注:多谢tchaikov告知“skinning rabbits”的含义,但我不想翻的太正式,眼下的这个应该不算太离谱吧。)
通过结合使用以上技术,我们可以建立一个灵活的开发环境和一个快速又高性能的产品环境。当然,这离终极目标“速度”还有一段距离。有许多更深层的技术(比如分离伺服静态内容,用多域名提升并发量等)值得我们关注,包括与我们谈到的方法(建立apache过滤器,修改资源url,加上版本信息)殊途同归的其他路子。你可以留下评论,告诉我们那些你正在使用的卓有成效的技术和方法。

posted @ 2008-09-29 11:40 啊凡 阅读(55) 评论(0) 编辑
  2008年9月26日

 转摘自:http://hi.baidu.com/xiaobai0119/blog/item/52ed29dc129ed6a4cc116611.html

ASP.NET网页上的每一个控件都必须是可唯一标识的。通常,为控件的 ID 属性分配一个值来唯一标识该控件。该值将成为控件的实例名称(即,在代码中引用控件所依据的名称)。例如,如果将 TextBox 控件的 ID 属性设置为 Text1,则可以在代码中使用 Text1 来引用该控件。

许多数据绑定控件(包括 DataList、Repeater、GridView、FormView 和 DetailsView 控件)可作为其他(子)控件的容器。当这些控件运行时,它们会生成子控件的多个实例。例如,如果创建一个包含 Label 控件的 DataList 模板,则当页运行时,DataList 控件的数据源中包含多少个记录,就会在页中生成多少个该 Label 控件的实例。

注意
使用模板的控件(例如 DataList 和 Repeater 控件)承载模板对象。例如,当 DataList 控件运行时,它会创建 DataListItem 类的多个实例。而这些模板对象又包含单个控件,例如,标签、文本框、按钮,等等。

由于控件可在同一页上多次实例化,而且您可以在不同的页上重复使用控件名称,所以,ASP.NET 页框架提供了确保页上和应用程序中控件具有唯一标识符的机制。它还为您提供了找到这些单个控件的方法,以便您可以在自己的代码中操作它们。

命名容器
可作为其他控件的容器的控件会为其子控件生成“命名容器”或 ID 命名空间。通过提供此命名容器,控件可以保证其子控件的 ID 属性在整个应用程序内是唯一的。(控件通过实现 INamingContainer 接口来生成命名容器。)如果在运行时创建了子控件,命名容器将与子控件的 ID 属性进行组合,以创建每个子控件的 UniqueID 属性值。因此,UniqueID 属性会成为控件的完全限定标识符,引用其命名容器以及控件的各个 ID 值。

在上面的示例中,在父级 DataList 控件的命名容器(即命名空间)内创建 Label 控件的多个实例。每个 Label 控件的 UniqueID 属性将反映此命名空间,其格式类似于 DataList1:_ctl:MyLabel、DataList1:_ct2:MyLabel,依此类推。

注意
请不要编写使用生成的 UniqueID 属性的值引用控件的代码。可以将 UniqueID 属性视为一个句柄(例如,通过将它传递到进程),但不应指望它拥有特定结构。

每个容器控件都会为其子控件提供命名容器,此外,页本身也会为容器控件的所有子控件提供命名容器。这样,就可以在应用程序内为该页上的所有控件创建唯一的命名空间。

使用 NamingContainer 属性
子控件可以通过 NamingContainer 属性引用其命名容器。此属性会返回一个 Control 类型的对象,您可以将该对象强制转换为相应的 DataList 控件、DataListItem 对象等。

在需要从子控件访问容器控件的属性时,引用命名容器是很有用的。例如,在子控件的 DataBinding 事件的处理程序中,可以通过从命名容器获取 DataItem 对象来访问该对象。

注意
NamingContainer 属性和 Parent 属性引用的控件不必相同。例如,在 Repeater 控件中,可能有一个包含表的项模板,而该表又包含 Label 控件。标签的父级控件是表单元格(例如,HtmlTableCell 对象),但其命名容器是 DataListItem 对象,因为它是定义 Label 控件的命名空间的 DataListItem,而不是该表。

引用控件
如果页中包含在运行时生成的控件,例如,位于 DataList、Repeater 或 GridView 控件的模板中的控件,就不能直接按 ID 来引用这些控件,因为 ID 不是唯一的。但是,有多种方式可以找到页中的各个控件。

Web 窗体控件 ID 解析

当您声明 Web 服务器控件的 ID 属性以通过编程方式访问该控件时,ASP.NET 页框架将自动确保声明的 ID 在整个 ASP.NET Web 应用程序中是唯一的。

命名容器
ASP.NET 页框架通过 INamingContainer 接口为应用程序提供自动控件 ID 解决方案,该接口为实现它的每个类生成一个“命名容器”。命名容器在 ASP.NET 网页控件层次结构中定义一个新的 ID 命名空间。这样,命名容器便允许页框架为在该命名空间内生成的每个 Control 对象的 UniqueID 属性生成一个值。UniqueID 属性不同于您声明的 ID 属性,因为它是控件的完全限定标识符。

实现 INamingContainer 的类包括:Page、DataList、GridView、DataListItem、DataGridItem 和 Repeater。通常,可以创建子控件的那些控件都会动态实现 INamingContainer。

Page 类充当该页的控件层次结构中的顶级命名容器。

数据绑定方案中的名称解析
页框架提供的自动名称解决方案在数据绑定方案中很重要。请考虑下面的示例,该示例演示页中声明的控件。

当 Label 控件绑定到数据源,而 Repeater 控件循环访问该数据源中的项时,该页必须能够以编程的方式区分 Label 控件的不同实例,即使您只为每个实例指定了 ID MyLabel 也是如此。页框架通过对每个控件使用完全限定的 UniqueID 属性来实现这一点。例如,下面的代码生成了 Label 控件的三个版本,并将它们的 UniqueID 属性值写入该页。

当请求此页时,它将下面的内容写入该页:

名为 MyDataList 的 Repeater 控件的命名容器。此命名容器取决于指定给 .aspx 文件的名称。

注意
如果此示例的 .aspx 文件是 MySample1.aspx,则命名容器的类是 ASP.mysample1_aspx,但命名容器是 Page。

充当命名容器的下一个控件的实例,即 Repeater 控件。此容器的名称是随同它的整个命名空间限定符一起显示的。

Repeater 控件内每个 Label 控件的 UniqueID 属性。

注意
请不要编写使用生成的 UniqueID 属性的值引用控件的代码。可以将 UniqueID 属性视为一个句柄(例如,通过将它传递到进程),但不应指望它拥有特定结构。

如何在 ASP.NET 网页中按 ID 定位子控件

可以用一个方法来获取对特定控件的引用,该方法按控件 ID 搜索其命名容器。

按 ID 定位控件
调用命名容器的 FindControl 方法,向该方法传递包含要使用的控件的 ID 的字符串。该方法会返回一个类型为 Control 的对象,可以将该类型强制转换为适当的类型。

下面的代码示例演示如何定位特定的控件。该示例是 GridView 控件中某按钮的 Click 事件的处理程序。单击该按钮时,代码在当前的 GridView 项(它是 Label 控件的命名容器)中搜索名为 Label1 的控件。如果找到该控件,其文本便会显示在页面其他位置上第二个名为 LabelText 的 Label 控件中。
C#
protected void GridView1_ItemCommand(object source,
        GridViewCommandEventArgs e)
{
    Label l;
    l = (Label) e.Item.FindControl("Label1");
   
    if(!(l == null) ){
        LabelText.Text = l.Text;
    }
}

在 ASP.NET 网页中使用控件集合

    Control 类及其派生类(包括 Page 类)会公开一个返回 ControlCollection 的 Controls 属性。通过这种层次结构,可以通过编程方式对控件树进行遍历以搜索某页上的特定控件,并检查集合中的控件类型,以便访问其属性。下面的代码示例演示如何通过遍历该页的控件层次结构来查找 <asp:TextBox> 控件的实例(只有一个)。

如何通过遍历控件集合定位页上的 Web 窗体控件

在 Controls 集合中定位控件
依次通过容器控件的 Controls 集合。该集合的类型为 ControlCollection,它返回 Control 类型的对象。

下面的示例说明如何浏览 Controls 集合。该示例假定 ASP.NET 网页上至少有一个 TextBox 控件,还包含一个 Label 控件和一个 Button 控件。该代码会获取 Page 对象的所有子控件。由于这样只会产生几个高级别子控件(包括 HtmlForm 对象),所以该代码还要浏览每个单独的子控件的 Controls 集合。该代码会通过比较每个控件的类型来查找文本框。当它找到文本框时,它会获取该文本框的值并将该值串联成字符串,该字符串会显示在 Label 控件的末尾。

此示例只查找 Page 对象中包含的控件以及作为该页的直接子级的控件。如果文本框是控件的子级,而该控件又是页的子级,则该示例不查找此文本框。例如,如果您向该页中添加了 Panel 控件,则 Panel 控件将是包含在 Page 中的 HtmlForm 控件的子级,在此示例中可以找到该控件。然而,如果您之后将某个 TextBox 控件添加到 Panel 控件中,则此示例将不显示该 TextBox 控件的文本,因为该控件既不是页的子级,也不是页的子控件的子级。以这种方式浏览控件有一种更实用的方法:即创建递归方法。一遇到某个控件,就可调用该方法来浏览该控件的 Controls 集合。然而,为清楚起见,没有将下面的示例创建为递归函数。

使用 NamingContainer 属性确定控件的命名容器

通过 NamingContainer 属性,可以遍历某页中的控件树。与只在内联代码(即位于<%# 和 %> 标记中)中可用的 Container 关键字相比,NamingContainer 属性在该类或派生类的任何实例代码中可用。

下面的代码示例阐释了如何遍历 ASP.NET 网页中的控件树。按钮的 ChangeBtn_Click 方法处理程序首先使用 FindControl 方法搜索控件,然后确定该控件的 NamingContainer 对象。此后,它会确定首次调用 NamingContainer 属性时返回的控件的命名容器,然后会遍历控件树,直到找到不包含命名容器的控件为止。(请注意,WalkContainers 方法还将在最低级别添加控件的类型,该类型本身不是命名容器。)

如何访问 Web 服务器控件的命名容器的成员

有时,您需要访问控件的命名容器的属性或方法。例如,在数据绑定期间,命名容器可以提供包含控件所绑定的数据的 DataItem 属性。您可以根据上下文,采用不同方式来访问该包含控件。

从数据绑定表达式访问命名容器
在数据绑定表达式中,使用 Container 关键字。该关键字可以返回指向容器的引用。然后,可以访问容器的属性或方法。

在 Eval 方法中,经常使用此关键字获取命名容器的 DataItem 对象值;不过,也可以在该方法之外使用此关键字。下面的示例演示 Label 控件。该控件可能位于 DataList、Repeater 或 GridView 控件的模板中。该控件显示当前项的编号。

下面是一个类似的示例,不同之处在于从命名容器的 DataItem 对象中获取值:

注意
NamingContainer 属性和 Parent 属性引用的控件不必相同。例如,在 Repeater 控件中,可能有一个包含表的项模板,而该表又包含 Label 控件。标签的父级控件是表单元格(例如,HtmlTableCell 对象),但其命名容器是 DataListItem 对象,因为它是定义 Label 控件的命名空间的 DataListItem,而不是该表。

从代码访问命名容器
获取控件的 NamingContainer 属性,并将其强制转换为容器的类类型,如 GridViewRow。

有关使用 NamingContainer 属性获取各个控件命名容器方面的信息的示例,请参见使用 NamingContainer 属性确定控件的命名容器。

    Conctrol,Controls,DataItem这三者之间的联系与区分要重点学习。

    基于对.net的控件访问的理解还不是很透彻,现将MSDN中部分内容找出,希望对自己的学习有帮助。学习了一遍,对有些地方还不是很明白,以后慢慢研读吧。

posted @ 2008-09-26 15:33 啊凡 阅读(110) 评论(0) 编辑
转摘于:http://www.saike.org/QuickStartv20/util/srcview.aspx?path=~/aspnet/samples/ctrlref/login/LoginViewTemplates.src&file=Login_cs\LoginViewTemplates.aspx&lang=C%23+Source
http://www.saike.org/QuickStartv20/howto/
<%@ Page Language="C#" MasterPageFile="~/UserInfo.master" %>
<script language="C#" runat="server">
protected void DropDownList1_SelectedIndexChanged(object sender, EventArgs e)
{
Label1.Text = "The Admin of the site is "
+ ((DropDownList)(LoginView1.FindControl("DropDownList1"))).SelectedItem.Text;
}
</script>
<asp:Content ID="Content1" ContentPlaceHolderID="ContentPlaceHolder1" runat="server">
<h2>
LoginView Template Find Control Sample</h2>
This sample demonstrates how to find and retrieve a value from a control contained
in a LoginView template. In this example <b>log in as User2 and select a choice from
the drop down list displayed in the Admin template.</b>
<br />
<br />
<table cellpadding="8" width="90%">
<tr>
<td bgcolor="#ffffcc">
In this example the templates are in the following order:
<ol>
<li>Admins</li><li>Users</li><li>Customers</li><li>*Annonymous</li><li>*LoggedIn</li>
</ol>
</td>
<td width="10">
</td>
<td bgcolor="#ffccff" bordercolor="#cc00ff" valign="top">
In this example the templates are in the following order:
<ol>
<li>Admins</li><li>Customers</li><li>Users</li><li>*Annonymous</li><li>*LoggedIn</li>
</ol>
</td>
</tr>
<tr>
<td bgcolor="#ff00cc" colspan="3">
<asp:Label ID="Label1" runat="server" Font-Bold="True"></asp:Label></td>
</tr>
<tr>
<td bgcolor="#ffffcc">
<asp:LoginView ID="LoginView1" runat="server">
<LoggedInTemplate>
<br />
You are logged in as:<asp:LoginName ID="LoginName1" runat="server" />
<br />
<br />
<asp:LoginStatus ID="LoginStatus1" runat="server" />
</LoggedInTemplate>
<AnonymousTemplate>
<br />
You are not logged in. Please Login.
<br />            <br />
            <asp:Login ID="Login1" runat="server" BorderColor="Peru"
BorderStyle="Solid" BorderWidth="1px">
</asp:Login>
</AnonymousTemplate>
<RoleGroups>
<asp:RoleGroup Roles="Admins">
<ContentTemplate>
<b>How hard are you working:</b>
                <asp:DropDownList ID="DropDownList1" runat="server"
AutoPostBack="True"
OnSelectedIndexChanged="DropDownList1_SelectedIndexChanged">
<asp:ListItem>WorkingHard</asp:ListItem>
<asp:ListItem>WorkingVeryHard</asp:ListItem>
<asp:ListItem>WorkingExtraHard</asp:ListItem>
</asp:DropDownList>
<asp:LoginName ID="LoginName4" runat="server" />
&nbsp;is viewing the <b>Admin Role</b> template and is
<br />
<br />
<asp:LoginStatus ID="LoginStatus2" runat="server" />
<br />
<br />
<br />
</ContentTemplate>
</asp:RoleGroup>
<asp:RoleGroup Roles="Users">
<ContentTemplate>
<asp:LoginName ID="LoginName2" runat="server" />
&nbsp;is viewing the <b>User Role</b> template.<br />
<asp:LoginStatus ID="LoginStatus3" runat="server" />
</ContentTemplate>
</asp:RoleGroup>
<asp:RoleGroup Roles="Customers">
<ContentTemplate>
<asp:LoginName ID="LoginName3" runat="server" />
is viewing the <b>Customer Role</b>template.<br />
<asp:LoginStatus ID="LoginStatus4" runat="server" />
</ContentTemplate>
</asp:RoleGroup>
</RoleGroups>
</asp:LoginView>
</td>
<td>
</td>
<td bgcolor="#ffccff" bordercolor="#cc00ff">
<asp:LoginView ID="LoginView2" runat="server">
<RoleGroups>
<asp:RoleGroup Roles="Admins">
<ContentTemplate>
<asp:LoginName ID="LoginName4" runat="server" />
&nbsp;is viewing the <b>Admin Role</b> template.<br />
<br />
<asp:LoginStatus ID="LoginStatus2" runat="server" />
</ContentTemplate>
</asp:RoleGroup>
<asp:RoleGroup Roles="Customers">
<ContentTemplate>
<asp:LoginName ID="LoginName3" runat="server" />
is viewing the <b>Customer Role</b> template.<br />
<asp:LoginStatus ID="LoginStatus4" runat="server" />
</ContentTemplate>
</asp:RoleGroup>
<asp:RoleGroup Roles="Users">
<ContentTemplate>
<asp:LoginName ID="LoginName2" runat="server" />
&nbsp;is viewing the <b>User Role</b> template.<br />
<asp:LoginStatus ID="LoginStatus3" runat="server" />
</ContentTemplate>
</asp:RoleGroup>
</RoleGroups>
<LoggedInTemplate>
<br />
You are logged in as:<asp:LoginName ID="LoginName1" runat="server" />
<br />
<br />
<asp:LoginStatus ID="LoginStatus1" runat="server" />
</LoggedInTemplate>
<AnonymousTemplate>
You are not logged in. Please Login.<br />
<br />
            <asp:Login ID="Login1" runat="server" BorderColor="MidnightBlue"
BorderStyle="Solid" BorderWidth="1px">
</asp:Login>
</AnonymousTemplate>
</asp:LoginView>
</td>
</tr>
</table>
</asp:Content>
posted @ 2008-09-26 12:11 啊凡 阅读(162) 评论(0) 编辑

转摘于:http://www.dojochina.com/index.php?q=node/1100

/**
 * allows for downloading of grid data (store) directly into excel
 * Method: extracts data of gridPanel store, uses columnModel to construct XML excel document,
 * converts to Base64, then loads everything into a data URL link.
 *
 * @author  Animal  <extjs support team>
 *
 */

/**
 * base64 encode / decode
 *
 * @location  http://www.webtoolkit.info/
 *
 */

var Base64 = (function() {
    var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
    function utf8Encode(string) {
        string = string.replace(/\r\n/g,"\n");
        var utftext = "";
        for (var n = 0; n < string.length; n++) {
            var c = string.charCodeAt(n);
            if (c < 128) { utftext += String.fromCharCode(c); }
            else if((c > 127) && (c < 2048)) {
                utftext += String.fromCharCode((c >> 6) | 192);
                utftext += String.fromCharCode((c & 63) | 128);
            } else {
                utftext += String.fromCharCode((c >> 12) | 224);
                utftext += String.fromCharCode(((c >> 6) & 63) | 128);
                utftext += String.fromCharCode((c & 63) | 128);
            }
        }
        return utftext;
    }
    return {
        encode: function (input) {
            var output = "";
            var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
            var i = 0;
            input = utf8Encode(input);
            while (i < input.length) {
                chr1 = input.charCodeAt(i++);
                chr2 = input.charCodeAt(i++);
                chr3 = input.charCodeAt(i++);
                enc1 = chr1 >> 2;
                enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
                enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
                enc4 = chr3 & 63;
                if (isNaN(chr2)) { enc3 = enc4 = 64; }
                else if (isNaN(chr3)) { enc4 = 64; }
                output = output + keyStr.charAt(enc1) + keyStr.charAt(enc2) + keyStr.charAt(enc3) + keyStr.charAt(enc4);
            }
            return output;
        }
    };
})();

 

Ext.override(Ext.grid.GridPanel, {
    getExcelXml: function(includeHidden) {
        var worksheet = this.createWorksheet(includeHidden);
        var totalWidth = this.getColumnModel().getTotalWidth(includeHidden);
        return *<xml version="1.0" encoding="utf-8">* +
            *<ss:Workbook xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns:o="urn:schemas-microsoft-com:office:office">* +
            *<o:DocumentProperties><o:Title>* + this.title + *</o:Title></o:DocumentProperties>* +
            *<ss:ExcelWorkbook>* +
                *<ss:WindowHeight>* + worksheet.height + *</ss:WindowHeight>* +
                *<ss:WindowWidth>* + worksheet.width + *</ss:WindowWidth>* +
                *<ss:ProtectStructure>False</ss:ProtectStructure>* +
                *<ss:ProtectWindows>False</ss:ProtectWindows>* +
            *</ss:ExcelWorkbook>* +
            *<ss:Styles>* +
                *<ss:Style ss:ID="Default">* +
                    *<ss:Alignment ss:Vertical="Top" ss:WrapText="1" />* +
                    *<ss:Font ss:FontName="arial" ss:Size="10" />* +
                    *<ss:Borders>* +
                        *<ss:Border ss:Color="#e4e4e4" ss:Weight="1" ss:LineStyle="Continuous" ss:Position="Top" />* +
                        *<ss:Border ss:Color="#e4e4e4" ss:Weight="1" ss:LineStyle="Continuous" ss:Position="Bottom" />* +
                        *<ss:Border ss:Color="#e4e4e4" ss:Weight="1" ss:LineStyle="Continuous" ss:Position="Left" />* +
                        *<ss:Border ss:Color="#e4e4e4" ss:Weight="1" ss:LineStyle="Continuous" ss:Position="Right" />* +
                    *</ss:Borders>* + *<ss:Interior />* + *<ss:NumberFormat />* +*<ss:Protection />* +
                *</ss:Style>* +*<ss:Style ss:ID="title">* +*<ss:Borders />* +*<ss:Font />* +*<ss:Alignment ss:WrapText="1" ss:Vertical="Center" ss:Horizontal="Center" />* +
                    *<ss:NumberFormat ss:Format="@" />* +*</ss:Style>* +*<ss:Style ss:ID="headercell">* +*<ss:Font ss:Bold="1" ss:Size="10" />* +
                    *<ss:Alignment ss:WrapText="1" ss:Horizontal="Center" />* +*<ss:Interior ss:Pattern="Solid" ss:Color="#A3C9F1" />* +
                *</ss:Style>* +*<ss:Style ss:ID="even">* +*<ss:Interior ss:Pattern="Solid" ss:Color="#CCFFFF" />* +
                *</ss:Style>* +*<ss:Style ss:Parent="even" ss:ID="evendate">* +*<ss:NumberFormat ss:Format="[ENG][$-409]dd\-mmm\-yyyy;@" />* +
                *</ss:Style>* +*<ss:Style ss:Parent="even" ss:ID="evenint">* +*<ss:NumberFormat ss:Format="0" />* +
                *</ss:Style>* +*<ss:Style ss:Parent="even" ss:ID="evenfloat">* +*<ss:NumberFormat ss:Format="0.00" />* +
                *</ss:Style>* +*<ss:Style ss:ID="odd">* +*<ss:Interior ss:Pattern="Solid" ss:Color="#CCCCFF" />* +
                *</ss:Style>* +*<ss:Style ss:Parent="odd" ss:ID="odddate">* +*<ss:NumberFormat ss:Format="[ENG][$-409]dd\-mmm\-yyyy;@" />* +
                *</ss:Style>* +*<ss:Style ss:Parent="odd" ss:ID="oddint">* +*<ss:NumberFormat ss:Format="0" />* +
                *</ss:Style>* +*<ss:Style ss:Parent="odd" ss:ID="oddfloat">* +*<ss:NumberFormat ss:Format="0.00" />* +
                *</ss:Style>* +*</ss:Styles>* +worksheet.xml +*</ss:Workbook>*;
    },

 

createWorksheet: function(includeHidden) {
 // Calculate cell data types and extra class names which affect formatting
        var cellType = [];
        var cellTypeClass = [];
        var cm = this.getColumnModel();
        var totalWidthInPixels = 0;
        var colXml = **;
        var headerXml = **;
        var visibleColumnCountReduction = 0;
        for (var i = 0; i < cm.getColumnCount(); i++) {
            if (includeHidden || !cm.isHidden(i)) {
                var w = cm.getColumnWidth(i)
                totalWidthInPixels += w;
                if (cm.getColumnHeader(i) === ""){
                 cellType.push("None");
                 cellTypeClass.push("");
                 ++visibleColumnCountReduction;
                }
                else
                {
                 colXml += *<ss:Column ss:AutoFitWidth="1" ss:Width="* + w + *" />*;
                 headerXml += *<ss:Cell ss:StyleID="headercell">* +
                     *<ss:Data ss:Type="String">* + cm.getColumnHeader(i) + *</ss:Data>* +
                     *<ss:NamedCell ss:Name="Print_Titles" /></ss:Cell>*;
                 var fld = this.store.recordType.prototype.fields.get(cm.getDataIndex(i));
                 switch(fld.type) {
                     case "int":
                         cellType.push("Number");
                         cellTypeClass.push("int");
                         break;
                     case "float":
                         cellType.push("Number");
                         cellTypeClass.push("float");
                         break;
                     case "bool":
                     case "boolean":
                         cellType.push("String");
                         cellTypeClass.push("");
                         break;
                     case "date":
                         cellType.push("DateTime");
                         cellTypeClass.push("date");
                         break;
                     default:
                         cellType.push("String");
                         cellTypeClass.push("");
                         break;
                 }
                }
            }
        }
        var visibleColumnCount = cellType.length - visibleColumnCountReduction;

        var result = {
            height: 9000,
            width: Math.floor(totalWidthInPixels * 30) + 50
        };

 

// Generate worksheet header details.
        var t = *<ss:Worksheet ss:Name="* + this.title + *">* +
            *<ss:Names>* +
                *<ss:NamedRange ss:Name="Print_Titles" ss:RefersTo="=\** + this.title + *\*!R1:R2" />* +
            *</ss:Names>* +
            *<ss:Table x:FullRows="1" x:FullColumns="1"* +
                * ss:ExpandedColumnCount="* + visibleColumnCount +
                *" ss:ExpandedRowCount="* + (this.store.getCount() + 2) + *">* +
                colXml +
                *<ss:Row ss:Height="38">* +
                    *<ss:Cell ss:StyleID="title" ss:MergeAcross="* + (visibleColumnCount - 1) + *">* +
                      *<ss:Data xmlns:html="http://www.w3.org/TR/REC-html40" ss:Type="String">* +
                        *<html:B>Generated by ExtJS</html:B></ss:Data><ss:NamedCell ss:Name="Print_Titles" />* +
                    *</ss:Cell>* +
                *</ss:Row>* +
                *<ss:Row ss:AutoFitHeight="1">* +
                headerXml +
                *</ss:Row>*;

 // Generate the data rows from the data in the Store
        for (var i = 0, it = this.store.data.items, l = it.length; i < l; i++) {
            t += *<ss:Row>*;
            var cellClass = (i & 1) ? *odd* : *even*;
            r = it[i].data;
            var k = 0;
            for (var j = 0; j < cm.getColumnCount(); j++) {
                if (includeHidden || !cm.isHidden(j)) {
                    var v = r[cm.getDataIndex(j)];
                    if (cellType[k] !== "None") {

                     t += *<ss:Cell ss:StyleID="* + cellClass + cellTypeClass[k] + *"><ss:Data ss:Type="* + cellType[k] + *">*;
                         if (cellType[k] == *DateTime*) {
                             t += v.format(*Y-m-d*);
                         } else {
                             t += v;
                         }
                     t +=*</ss:Data></ss:Cell>*;
                    }
                    k++;
                }
            }
            t += *</ss:Row>*;
        }

        result.xml = t + *</ss:Table>* +
            *<x:WorksheetOptions>* +
                *<x:PageSetup>* +
                    *<x:Layout x:CenterHorizontal="1" x:Orientation="Landscape" />* +
                    *<x:Footer x:Data="Page &amp;P of &amp;N" x:Margin="0.5" />* +
                    *<x:PageMargins x:Top="0.5" x:Right="0.5" x:Left="0.5" x:Bottom="0.8" />* +
                *</x:PageSetup>* +
                *<x:FitToPage />* +
                *<x:Print>* +
                    *<x:PrintErrors>Blank</x:PrintErrors>* +
                    *<x:FitWidth>1</x:FitWidth>* +
                    *<x:FitHeight>32767</x:FitHeight>* +
                    *<x:ValidPrinterInfo />* +
                    *<x:VerticalResolution>600</x:VerticalResolution>* +
                *</x:Print>* +
                *<x:Selected />* +
                *<x:DoNotDisplayGridlines />* +
                *<x:ProtectObjects>False</x:ProtectObjects>* +
                *<x:ProtectScenarios>False</x:ProtectScenarios>* +
            *</x:WorksheetOptions>* +
        *</ss:Worksheet>*;
        return result;
    }
});

posted @ 2008-09-26 11:46 啊凡 阅读(1798) 评论(0) 编辑
  2008年9月25日

转载自:http://hi.baidu.com/cdbrain/blog/item/bf292aa44f2c4bf19052ee1d.html

操作HTML DOM文档的一个难题是,你的JavaScript代码可能在DOM完全载入之前运行,这会导致你的代码产生一些问题。页面加载时浏览器内部操作的顺序大致是这样的:
  1. HTML被解析。
  2. 外部脚本/样式表被加载。
  3. 文档解析过程中内联的脚本被执行。
  4. HTML DOM构造完成。
  5. 图像和外部内容被加载。
  6. 页面加载完成。
  头部包含的和从外部文件中载入的脚本实际上在HTML DOM构造好之前就执行了。正如前面提到的,这一个问题是很重要的,因为在那两种地方的执行的所有脚本将不能访问DOM。可喜的是,存在许多绕开这一问题的办法。
  
  等待页面加载

  到目前为止,最常用的技术是在任何DOM操作之前简单地等待整个页面加载。使用这一技术,可以通过简单地给window对象的load事件附加一个在页面载入后触发的函数。在第六章中我将讨论关于事件的更多细节。程序5-10展示了一个在页面加载完成后执行DOM相关代码的例子。

  程序5-10. 为window.onload属性附加回调函数的addEvent函数

复制内容到剪贴板
代码:
//等待页面加载完成
//(使用了下一章描述的addEvent函数)
addEvent(window, "load", function() {
     //执行HTML DOM操作
     next( id("everywhere") ).style.background = 'blue';
});
  尽管这一操作可能是最简单的,它也将总是最慢的。从加载操作的顺序中,你可能已发现页面加载完成绝对是最后一步。这意味着如果在你的页面上有大量的图像、视频等等,你的用户在JavaScript最终执行前得等待很大一阵子。

  等待大部分DOM加载

  第二种技术很迂回,不太推荐使用。如果你还记得,我在上一节里说了,内联的脚本是在DOM构造以后执行的。这是一个半真半假的说法。那些脚本实际上是在DOM构造时遇上了就执行的。这就是说如果你有一段内联的脚本嵌在页面的中间部分,则该脚本只能立即拥有前半部分DOM的访问权。然而,把脚本作为非常靠后的元素嵌入页面中,就意味着你能够有效地对先于它出现的所有的DOM元素进行访问,获得一种假冒的模拟DOM加载的方式。这种方法的典型实现通常如程序5-11所示。

  程序5-11. 通过向HTML DOM的结尾置入(包含函数调用的)<script>标签来判定DOM是否已经加载
复制内容到剪贴板
代码:
<html>
<head>
     <title>Testing DOM Loading</title>
     <script type="text/javascript">
         function init() {
             alert( "The DOM is loaded!" );
             tag("h1")[0].style.border = "4px solid black";
         }
     </script>
</head>
<body>
     <h1>Testing DOM Loading</h1>
     <!--这里是大量的HTML -->
     <script type="text/javascript">init();</script>
</body>
</html>
  在这个例子里,一个内联脚本作为DOM的最后一个元素;它将是最后一个被解析和执行的。它所做的唯一的事情是调用init函数(函数内部应包含你想要处理的任何DOM相关的代码)。这一解决方案的存在的最大的问题在于,它是混乱的:给你的HTML里加入了额外的标记,只为了判定DOM是否已经加载。

  断定DOM何时加载完成

  最后一种可用来监视DOM加载的技术,可能是最复杂(从实现的角度来看)但也是最有效的。它结合了绑定到window的load事件的简易性和内联脚本技术的速度。
  这一技术的原理是在不阻塞浏览器的前提下尽可能快地反复检查HTML DOM是否已经具有了你所需的特性。有几种东西可以被检查以判断HTML文档是否已经可以操作了:
  1. document: 你需要检查DOM document是否已经存在。如果你检查得够快的话,它一开始可能仅仅是undefined。
  2. document.getElementsByTagName和document.getElementByID: 检查document是否已经具备了经常使用的getElementsByTagName和getElementById函数;这些函数将在它们准备好被使用以后存在。
  3. document.body: 作为额外的保障,检查<body>元素是否已完成被载入。理论上讲,前面的检查应该已经足够了,但是我发现过它们还不够好的例子。
  使用这些检查,你将对DOM何时准备好被使用有一个足够好的把握(好到可能只错过了几毫秒)。这一方法近乎没有瑕疵。仅使用前面的检查,脚本可以在所有的现代浏览器里运行得相对很好了。然而,Firefox某些新的缓存机制的实现,导致了window的load事件实际上能够在你的脚本判断DOM是否就绪之前就触发。为了利用这一优势,我也加入了对window的load事件的检查,希望获得一些额外的速度。
  最终,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() {
     //如果我们断定页面已经加载完成了,则返回
     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章)一样,绑定你的特定函数到文档准备好导航和操作的时候被触发。在下面的例子里我把domReady函数放入了一个名为domready.js的外部JavaScript文件里。程序5-3展示了怎样使用新的domReady函数来监视DOM何时已载入。

  程序5-13. 使用domReady函数在判定DOM何时准备好导航和修改
复制内容到剪贴板
代码:
<html>
<head>
     <title>Testing DOM Loading</title>
     <script type="text/javascript" src="domready.js"></script>
     <script type="text/javascript">
         function tag(name, elem) {
             //如果上下文元素未提供,则搜索整个文档
             return (elem || document).getElementsByTagName(name);
         }
         domReady(function() {
             alert( "The DOM is loaded!" );
             tag("h1")[0].style.border = "4px solid black";
         });
     </script>
</head>
<body>
     <h1>Testing DOM Loading</h1>
     <!--这里是大量的HTML -->
     </body>
</html>
  既然你了解了用来导航一般的XML DOM文档的和克服HTML DOM文档加载难题的几种方法,这个问题应该被摆在眼前了:有没有更好的在HTML文档中查找元素的方法呢?可喜的是,答案是响亮的"有"。
posted @ 2008-09-25 16:58 啊凡 阅读(233) 评论(0) 编辑
简介:XMLHttpRequest对象是当今所有AJAX和Web 2.0应用程序的技术基础。尽管软件经销商和开源社团现在都在提供各种AJAX框架以进一步简化XMLHttpRequest对象的使用;但是,我们仍然很有必要理解这个对象的详细工作机制 ...
关键字:对象
XMLHttpRequest对象是当今所有AJAX和Web 2.0应用程序的技术基础。尽管软件经销商和开源社团现在都在提供各种AJAX框架以进一步简化XMLHttpRequest对象的使用;但是,我们仍然很有必要理解这个对象的详细工作机制。

一、 引言

  异步JavaScript与XML(AJAX)是一个专用术语,用于实现在客户端脚本与服务器之间的数据交互过程。这一技术的优点在于,它向开发者提供了一种从Web服务器检索数据而不必把用户当前正在观察的页面回馈给服务器。与现代浏览器的通过存取浏览器DOM结构的编程代码(JavaScript)动态地改变被显示内容的支持相配合,AJAX让开发者在浏览器端更新被显示的HTML内容而不必刷新页面。换句话说,AJAX可以使基于浏览器的应用程序更具交互性而且更类似传统型桌面应用程序。

  Google的Gmail和Outlook Express就是两个使用AJAX技术的我们所熟悉的例子。而且,AJAX可以用于任何客户端脚本语言中,这包括JavaScript,Jscript和VBScript。

  AJAX利用一个构建到所有现代浏览器内部的对象-XMLHttpRequest-来实现发送和接收HTTP请求与响应信息。一个经由XMLHttpRequest对象发送的HTTP请求并不要求页面中拥有或回寄一个<form>元素。AJAX中的"A"代表了"异步",这意味着XMLHttpRequest对象的send()方法可以立即返回,从而让Web页面上的其它HTML/JavaScript继续其浏览器端处理而由服务器处理HTTP请求并发送响应。尽管缺省情况下请求是异步进行的,但是,你可以选择发送同步请求,这将会暂停其它Web页面的处理,直到该页面接收到服务器的响应为止。

  微软在其Internet Explorer(IE) 5中作为一个ActiveX对象形式引入了XMLHttpRequest对象。其他的认识到这一对象重要性的浏览器制造商也都纷纷在他们的浏览器内实现了XMLHttpRequest对象,但是作为一个本地JavaScript对象而不是作为一个ActiveX对象实现。而如今,在认识到实现这一类型的价值及安全性特征之后,微软已经在其IE 7中把XMLHttpRequest实现为一个窗口对象属性。幸运的是,尽管其实现(因而也影响到调用方式)细节不同,但是,所有的浏览器实现都具有类似的功能,并且实质上是相同方法。目前,W3C组织正在努力进行XMLHttpRequest对象的标准化,并且已经发行了有关该W3C规范的一个草案。

  本文将对XMLHttpRequest对象API进行详细讨论,并将解释其所有的属性和方法。

二、 XMLHttpRequest对象的属性和事件


  XMLHttpRequest对象暴露各种属性、方法和事件以便于脚本处理和控制HTTP请求与响应。下面,我们将对此展开详细的讨论。
readyState属性

  当XMLHttpRequest对象把一个HTTP请求发送到服务器时将经历若干种状态:一直等待直到请求被处理;然后,它才接收一个响应。这样以来,脚本才正确响应各种状态-XMLHttpRequest对象暴露一个描述对象的当前状态的readyState属性,如表格1所示。

        ReadyState属性

  表格1.XMLHttpRequest对象的ReadyState属性值列表。
ReadyState取值 描述
0
描述一种"未初始化"状态;此时,已经创建一个XMLHttpRequest对象,但是还没有初始化。
1
描述一种"发送"状态;此时,代码已经调用了XMLHttpRequest open()方法并且XMLHttpRequest已经准备好把一个请求发送到服务器。
2
描述一种"发送"状态;此时,已经通过send()方法把一个请求发送到服务器端,但是还没有收到一个响应。
3
描述一种"正在接收"状态;此时,已经接收到HTTP响应头部信息,但是消息体部分还没有完全接收结束。
4
描述一种"已加载"状态;此时,响应已经被完全接收。


  onreadystatechange事件

  无论readyState值何时发生改变,XMLHttpRequest对象都会激发一个readystatechange事件。其中,onreadystatechange属性接收一个EventListener值-向该方法指示无论readyState值何时发生改变,该对象都将激活。

  responseText属性

  这个responseText属性包含客户端接收到的HTTP响应的文本内容。当readyState值为0、1或2时,responseText包含一个空字符串。当readyState值为3(正在接收)时,响应中包含客户端还未完成的响应信息。当readyState为4(已加载)时,该responseText包含完整的响应信息。

  responseXML属性

  此responseXML属性用于当接收到完整的HTTP响应时(readyState为4)描述XML响应;此时,Content-Type头部指定MIME(媒体)类型为text/xml,application/xml或以+xml结尾。如果Content-Type头部并不包含这些媒体类型之一,那么responseXML的值为null。无论何时,只要readyState值不为4,那么该responseXML的值也为null。

  其实,这个responseXML属性值是一个文档接口类型的对象,用来描述被分析的文档。如果文档不能被分析(例如,如果文档不是良构的或不支持文档相应的字符编码),那么responseXML的值将为null。

  status属性

  这个status属性描述了HTTP状态代码,而且其类型为short。而且,仅当readyState值为3(正在接收中)或4(已加载)时,这个status属性才可用。当readyState的值小于3时试图存取status的值将引发一个异常。

  statusText属性

  这个statusText属性描述了HTTP状态代码文本;并且仅当readyState值为3或4才可用。当readyState为其它值时试图存取statusText属性将引发一个异常。
 

 三、 XMLHttpRequest对象的方法

  XMLHttpRequest对象提供了各种方法用于初始化和处理HTTP请求,下列将逐个展开详细讨论。

  abort()方法

  你可以使用这个abort()方法来暂停与一个XMLHttpRequest对象相联系的HTTP请求,从而把该对象复位到未初始化状态。

  open()方法

  你需要调用open(DOMString method,DOMString uri,boolean async,DOMString username,DOMString password)方法初始化一个XMLHttpRequest对象。其中,method参数是必须提供的-用于指定你想用来发送请求的HTTP方法(GET,POST,PUT,DELETE或HEAD)。为了把数据发送到服务器,应该使用POST方法;为了从服务器端检索数据,应该使用GET方法。另外,uri参数用于指定XMLHttpRequest对象把请求发送到的服务器相应的URI。借助于window.document.baseURI属性,该uri被解析为一个绝对的URI-换句话说,你可以使用相对的URI-它将使用与浏览器解析相对的URI一样的方式被解析。async参数指定是否请求是异步的-缺省值为true。为了发送一个同步请求,需要把这个参数设置为false。对于要求认证的服务器,你可以提供可选的用户名和口令参数。在调用open()方法后,XMLHttpRequest对象把它的readyState属性设置为1(打开)并且把responseText、responseXML、status和statusText属性复位到它们的初始值。另外,它还复位请求头部。注意,如果你调用open()方法并且此时readyState为4,则XMLHttpRequest对象将复位这些值。

  send()方法

  在通过调用open()方法准备好一个请求之后,你需要把该请求发送到服务器。仅当readyState值为1时,你才可以调用send()方法;否则的话,XMLHttpRequest对象将引发一个异常。该请求被使用提供给open()方法的参数发送到服务器。当async参数为true时,send()方法立即返回,从而允许其它客户端脚本处理继续。在调用send()方法后,XMLHttpRequest对象把readyState的值设置为2(发送)。当服务器响应时,在接收消息体之前,如果存在任何消息体的话,XMLHttpRequest对象将把readyState设置为3(正在接收中)。当请求完成加载时,它把readyState设置为4(已加载)。对于一个HEAD类型的请求,它将在把readyState值设置为3后再立即把它设置为4。

  send()方法使用一个可选的参数-该参数可以包含可变类型的数据。典型地,你使用它并通过POST方法把数据发送到服务器。另外,你可以显式地使用null参数调用send()方法,这与不用参数调用它一样。对于大多数其它的数据类型,在调用send()方法之前,应该使用setRequestHeader()方法(见后面的解释)先设置Content-Type头部。如果在send(data)方法中的data参数的类型为DOMString,那么,数据将被编码为UTF-8。如果数据是Document类型,那么将使用由data.xmlEncoding指定的编码串行化该数据。

  setRequestHeader()方法

  该setRequestHeader(DOMString header,DOMString value)方法用来设置请求的头部信息。当readyState值为1时,你可以在调用open()方法后调用这个方法;否则,你将得到一个异常。

  getResponseHeader()方法

  getResponseHeader(DOMString header,value)方法用于检索响应的头部值。仅当readyState值是3或4(换句话说,在响应头部可用以后)时,才可以调用这个方法;否则,该方法返回一个空字符串。

  getAllResponseHeaders()方法

  该getAllResponseHeaders()方法以一个字符串形式返回所有的响应头部(每一个头部占单独的一行)。如果readyState的值不是3或4,则该方法返回null。

四、 发送请求

  在AJAX中,许多使用XMLHttpRequest的请求都是从一个HTML事件(例如一个调用JavaScript函数的按钮点击(onclick)或一个按键(onkeypress))中被初始化的。AJAX支持包括表单校验在内的各种应用程序。有时,在填充表单的其它内容之前要求校验一个唯一的表单域。例如要求使用一个唯一的UserID来注册表单。如果不是使用AJAX技术来校验这个UserID域,那么整个表单都必须被填充和提交。如果该UserID不是有效的,这个表单必须被重新提交。例如,一个相应于一个要求必须在服务器端进行校验的Catalog ID的表单域可能按下列形式指定:

<form name="validationForm" action="validateForm" method="post">
<table>
 <tr><td>Catalog Id:</td>
  <td>
   <input type="text" size="20" id="catalogId" name="catalogId" autocomplete="off" onkeyup="sendRequest()">
  </td>
  <td><div id="validationMessage"></div></td>
 </tr>
</table></form>

  前面的HTML使用validationMessage div来显示相应于这个输入域Catalog Id的一个校验消息。onkeyup事件调用一个JavaScript sendRequest()函数。这个sendRequest()函数创建一个XMLHttpRequest对象。创建一个XMLHttpRequest对象的过程因浏览器实现的不同而有所区别。如果浏览器支持XMLHttpRequest对象作为一个窗口属性(所有普通的浏览器都是这样的,除了IE 5和IE 6之外),那么,代码可以调用XMLHttpRequest的构造器。如果浏览器把XMLHttpRequest对象实现为一个ActiveXObject对象(就象在IE 5和IE 6中一样),那么,代码可以使用ActiveXObject的构造器。下面的函数将调用一个init()函数,它负责检查并决定要使用的适当的创建方法-在创建和返回对象之前。

<script type="text/javascript">
function sendRequest(){
 var xmlHttpReq=init();
 function init(){
  if (window.XMLHttpRequest) {
   return new XMLHttpRequest();
  }
 else if (window.ActiveXObject) {
  return new ActiveXObject("Microsoft.XMLHTTP");
 }
}
</script>

  接下来,你需要使用Open()方法初始化XMLHttpRequest对象-指定HTTP方法和要使用的服务器URL。

var catalogId=encodeURIComponent(document.getElementById("catalogId").value);
xmlHttpReq.open("GET", "validateForm?catalogId=" + catalogId, true);

  默认情况下,使用XMLHttpRequest发送的HTTP请求是异步进行的,但是你可以显式地把async参数设置为true,如上面所展示的。
在这种情况下,对URL validateForm的调用将激活服务器端的一个servlet,但是你应该能够注意到服务器端技术不是根本性的;实际上,该URL可能是一个ASP,ASP.NET或PHP页面或一个Web服务-这无关紧要,只要该页面能够返回一个响应-指示CatalogID值是否是有效的-即可。因为你在作一个异步调用,所以你需要注册一个XMLHttpRequest对象将调用的回调事件处理器-当它的readyState值改变时调用。记住,readyState值的改变将会激发一个readystatechange事件。你可以使用onreadystatechange属性来注册该回调事件处理器。

xmlHttpReq.onreadystatechange=processRequest;

  然后,我们需要使用send()方法发送该请求。因为这个请求使用的是HTTP GET方法,所以,你可以在不指定参数或使用null参数的情况下调用send()方法。

xmlHttpReq.send(null);

五、 处理请求

  在这个示例中,因为HTTP方法是GET,所以在服务器端的接收servlet将调用一个doGet()方法,该方法将检索在URL中指定的catalogId参数值,并且从一个数据库中检查它的有效性。

  本文示例中的这个servlet需要构造一个发送到客户端的响应;而且,这个示例返回的是XML类型,因此,它把响应的HTTP内容类型设置为text/xml并且把Cache-Control头部设置为no-cache。设置Cache-Control头部可以阻止浏览器简单地从缓存中重载页面。

public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
 ...
 ...
 response.setContentType("text/xml");
 response.setHeader("Cache-Control", "no-cache");
}

  来自于服务器端的响应是一个XML DOM对象,此对象将创建一个XML字符串-其中包含要在客户端进行处理的指令。另外,该XML字符串必须有一个根元素。

out.println("<catalogId>valid</catalogId>");

  【注意】XMLHttpRequest对象的设计目的是为了处理由普通文本或XML组成的响应;但是,一个响应也可能是另外一种类型,如果用户代理(UA)支持这种内容类型的话。

  当请求状态改变时,XMLHttpRequest对象调用使用onreadystatechange注册的事件处理器。因此,在处理该响应之前,你的事件处理器应该首先检查readyState的值和HTTP状态。当请求完成加载(readyState值为4)并且响应已经完成(HTTP状态为"OK")时,你就可以调用一个JavaScript函数来处理该响应内容。下列脚本负责在响应完成时检查相应的值并调用一个processResponse()方法。

function processRequest(){
 if(xmlHttpReq.readyState==4){
  if(xmlHttpReq.status==200){
   processResponse();
  }
 }
}

  该processResponse()方法使用XMLHttpRequest对象的responseXML和responseText属性来检索HTTP响应。如上面所解释的,仅当在响应的媒体类型是text/xml,application/xml或以+xml结尾时,这个responseXML才可用。这个responseText属性将以普通文本形式返回响应。对于一个XML响应,你将按如下方式检索内容:

var msg=xmlHttpReq.responseXML;

  借助于存储在msg变量中的XML,你可以使用DOM方法getElementsByTagName()来检索该元素的值:

var catalogId=msg.getElementsByTagName("catalogId")[0].firstChild.nodeValue;

  最后,通过更新Web页面的validationMessage div中的HTML内容并借助于innerHTML属性,你可以测试该元素值以创建一个要显示的消息:

if(catalogId=="valid"){
 var validationMessage = document.getElementById("validationMessage");
 validationMessage.innerHTML = "Catalog Id is Valid";
}
else
{
 var validationMessage = document.getElementById("validationMessage");
 validationMessage.innerHTML = "Catalog Id is not Valid";
}

六、 小结

  上面就是XMLHttpRequest对象使用的所有细节实现。通过不必把Web页面寄送到服务器而实现数据传送,XMLHttpRequest对象为客户端与服务器之间提供了一种动态的交互能力。你可以使用JavaScript启动一个请求并处理相应的返回值,然后使用浏览器的DOM方法更新页面中的数据。

posted @ 2008-09-25 16:56 啊凡 阅读(52) 评论(0) 编辑
  2008年9月24日

1。 取消缓存


(2)客户端取消

<html>
<head>
<meta http-equiv="Expires" CONTENT="0">
<meta http-equiv="Cache-Control" CONTENT="no-cache">
<meta http-equiv="Pragma" CONTENT="no-cache">
</head>

(3)服务器具端取消:

服务器端:
   Response.Buffer = true;
   Response.ExpiresAbsolute = DateTime.Now.AddDays(-1);
   Response.Cache.SetExpires(DateTime.Now.AddDays(-1));
   Response.Expires = 0;
   Response.CacheControl = "no-cache";
   Response.Cache.SetNoStore();

Global里面: 
protected  void  Application_BeginRequest(Object  sender,  EventArgs  e) 

       HttpContext.Current.Response.Cache.SetNoStore(); 
}
<%@ OutPutCache Location="None"%>

页面基类:
public  class  PageBase  :  Page 

     public  PageBase()  {} 
 
     protected  override  OnLoad(  EventArgs  e  )  { 
             Response.Cache.SetNoStore(); 
             base.OnLoad(); 
     } 

最简单的办法 :-)
学CSDN的这个论坛,在URL后面随机的加一些没用的参数,比如:
http://xxx/xxx/xxx.jpg?p=xxx

IE是用过URL来控制缓存的,这样就解决了

(4)web.config可以设置吧?

2。

(1)客户端缓存和我们常在代码中使用的Cache对象,有什么差别,当我们仅用了客户端缓存时,他对我们在代码中使用的Cache有没有什么影响!

两个完全不同的东西,Cache对象是给你自己缓存东西用的,好处是会自动清理掉过期的东西。客户端缓存是HTTP的一个规范,用于在客户端缓存网页的,有时候我们需要客户端缓存提高浏览速度,减轻服务器负担,但有时候我们又需要在客户端禁用缓存,以避免客户端看到过期的信息

(2)当客户端浏览器被关闭时,要如何移除Cache中的值。当用户是正常注销是可以做到,但是如果用户是直接关闭浏览器要如何实现呢?

<body onbeforeunload="window.open('clear.aspx','','top=4000')">

(3)1.Page.Cache,Context.Cache,HttpRuntime.Cache是否引用同一个Cache对象.

         2.它们之间的区别是什么?

         3.它们的使用场合是什么?

它们指的同一个对象,在某个请求其间,Page.Cache和HttpContext.Current.Cache是有效的,至于这2者的区别,一般是由你调用的地方决定的,如果在Page里,那么用Page.Cache,如果在global.asax或自己的函数里,那么用后者

但如果你需要在某个事件,譬如基于Timer的处理函数里访问,因为其时没有HttpContext,那么用HttpRuntime.Cache

posted @ 2008-09-24 15:33 啊凡 阅读(27) 评论(0) 编辑
  2008年9月22日
摘要: 法则1. 减少HTTP请求次数80%的最终用户响应时间花在前端程序上,而其大部分时间则花在各种页面元素,如图像、样式表、脚本和Flash等,的下载上。减少页面元素将会减少HTTP请求次数。这是快速显示页面的关键所在。一种减少页面元素个数的方法是简化页面设计。但是否存在其他方式,能做到既有丰富内容,又能获得快速响应时间呢?以下是这样一些技术:Image maps组合多个图片到一张图片中。总文件大小变...阅读全文
posted @ 2008-09-22 14:29 啊凡 阅读(54) 评论(0) 编辑