Category: Web开发

Python框架

python 的框架基本上都是做了url跟action的映射配置文件

django的权限管理比较强大,如果有权限管理的可以用这个

比较轻量级的 tornado    webpy

twisted 是比较全面的框架,比较庞大

翻译:High Performance Web Sites(8)-Chapter 6

《High Performance Web Sites》 :Chapter 6

法则6:把script放到页面的下端

第五章我们将样式表放置于HTML的HEAD中以加快页面渲染。其实script也有类似的问题,但解决的方法正好相反:把script放到页面的下端,会利于页面的快速渲染。

Script所带来的问题
为了更好的说明这第6条法则,我们先看一个反例吧。把script放在HTML页面的中间位置,看看这种方式页面的下载情况,如:http://stevesouders.com/hpws/js-middle.php
这个script是睡眠10秒钟然后启动,我们访问一下这个页面,发现整个HTML已经全部download完毕,但页面在渲染到中间时,停住了,下一半仍是空白的未渲染区域。直到这个js睡眠的10秒过后,另一半页面的内容才开始渲染和下载后面所需的图片等组件。很显然,这个放在HTML中间的script阻碍了后面页面元素的下载,还阻碍了页面的渲染。
这也就是为什么我们要把所有的样式表放到HTML页面的上端HEAD处,它们才会被先下载,不会阻塞页面的渲染。而script后面的页面内容则会等待script执行完毕,才开始渲染。所以把script放置的越下,更多的页面内容才会更快的渲染。
并行下载
对下载速度影响最大的就是页面组件的数量。前几章介绍过,如果当前浏览器的缓存是空的,则每一个组件都产生一个HTTP请求。你可能会问:那为什么浏览器不一次性的请求所有的组件呢,非得一个一个的去请求?
要解释这个,就要说到HTTP/1.1规范了,它建议浏览器一次对同一个域名最多只发起2个并行下载请求.(http://www.w3.org/Protocols/rfc2616/rfc2616-sec8.html#sec8.1.4)
很多的页面连同其所包含的组件都是一个域名下的,如下图所示的组件下载的阶梯图 6-1:

 

如果一个页面的组件是分布在两个域名下的,那它的响应时间总体来说应该会快两倍,如图6-2所示的情况,会有四个组件并行下载。
通常情况下IE与Firefox都遵循了HTTP/1.1规范关于同一个域名每次只能有两个并行下载请求的建议。如果想扩展IE的下载束缚,可以参考:微软网站的文章:How to configure Internet Explorer to have more than two download sessions,” http://support.microsoft.com/?kbid=282402.
而要改Firefox的默认设置,则可以在配置页面中修改network.http.maxpersistent-connections-per-server属性.有意思的是,在对HTTP/1.0的支持中,Firefox默认的是每个域名下同时可有八个并行下载请求,也就是说可一次同时下载8个页面组件,这可比IE的默认值要大方多了。如图6-3就是显示的在Firefox下下载一个页面的示意图,这可要比6-2所示的看起来爽多了。

 

现在大多数的网站都遵循HTTP/1.1规范,增加每个域名下的并行下载数量是个办法,但我们总不能都指望着用户去调整自己浏览器的设置参数。前端工程师应该考虑把页面组件放在不同的域名下,增加并行下载最多也只是消耗一点客户端的CPU资源和带宽,除非是大量的并行下载才会影响到性能。在Yahoo!的实验下,我们发现把页面组件放在两个域名下要比划分成1,4或10个域名性能要好,具体可参见(Tenni Theurer, “Performance Research, Part 4: Maximizing Parallel Downloads in the Carpool Lane,”http://yuiblog.com/blog/2007/04/11/performance-research-part-4/.)
Script是怎么阻碍下载的
组件并行下载的好处我们都知道了。但script这个东西却可能会破坏这个和谐社会:因为script可能会用document.write语句来改变页面内容,所以浏览器会等待script执行完毕。
另一个原因是浏览器要保证script的执行顺序,先出现的script要先执行,哪怕后面的script的k数有多么小,也要等着,因为script在功能上可能会有前后关系,后面的script可能会用到前面script的某些操作。
来个例子说明
http://stevesouders.com/hpws/js-blocking.php
这个页面所包含的组件出现顺序如下:
1,host1下的图片A1
2,host2下的图片B1
3,host1下的script,等待10秒完成
4,host1下的图片A2
5,host2下的图片B2
看看是host1下的两张图片同时下载?还是其他?

 

IE和Firefox都会先下载host1和host2的图片,然后开始下载host1上的script,直到这个script下载完成,才会开始下载后面的两个图片。
最糟糕的情况:script在页面的顶部

在这种情况下将会导致页面是个空白:
• Script下的文字页面内容会被阻止渲染。
• Script下的组件也会被阻止下载
直到这个script下载并执行完毕才会解除阻止。给个实例:http://stevesouders.com/hpws/js-top.php
因为script在顶部,整个页面的渲染被阻止了,于是这个页面将会呈现一个我们在第五章中解释的空白屏。而逐步渲染是一个很关键的良好用户体验。图6-5就是这个例子的页面下载阶梯图,后面的图片组件都被阻止了。

 

最好的情况:script在页面的底部
放置script最佳的地方当然是页面的底部了。页面的内容不会被阻止渲染,可视组件能依次下载渲染。如实例http://stevesouders.com/hpws/js-bottom.php,图6-6就显示了这种方式script对页面的影响是最小的,我们可以对比一下图6-5看看,再给一个具体的比较实例吧:http://stevesouders.com/hpws/move-scripts.php是不是感觉更明显了。

 

这也不是说所有的scrript就应该放到页面底部了,如果是包含有document.write这样影响页面渲染内容的script,是不应该被放到最下面的,所以还是要以实际的页面功能、逻辑等方面来考虑,通常情况下,只要是不涉及强制更改页面渲染的js,都应该放到页面底部。
另外有一种方式是http://stevesouders.com/hpws/js-defer.php所示的defer方式,在script定义时声明,但这种只对IE有效,对firefox无效,大家知道一下就行了。

 

翻译:High Performance Web Sites(7)-Chapter 5

《High Performance Web Sites》 :Chapter 5

法则5:将样式表放到HEAD中 Put Stylesheets at the Top

在Yahoo!,有一个小组在他们的项目中使用了一些DHTML特性。其中一个复杂的功能是是在发送邮件时会弹出一个DIV层。其实这个层并不影响其所在页面的渲染,因为它是功能触发式的被渲染的页面部分,所以这个小组的前端工程师就把这个弹出层的CSS样式写到一个独立的样式表文件中,并把对该CSS的引用放在页面的最下方的位置,以确保页面能较快的下载和渲染。
我们知一个页面是由图片,样式表,scripts等组件渲染成的,而组件是按它们在页面中出现的顺序依次下载,把DHTML的动态功能的样式表文件放到后面,可以让更关健的组件先下载,这样能使页面更快的渲染显示给浏览者,好像事实应该是这样,不是吗。

事实是这样吗?

其实采用上面的方式在IE下反而会更慢。经过不断尝试,我们还是把这个DHTML所需的样式表放到了页面的上方HEAD中,这时的页面载入是最快的。这好像和我们所期望的有点相反,为什么我们把暂不渲染页面的CSS放到页面的上方位置,延缓了其它关健组件的下载,反而是加快了页面的载入速度呢?这就是我们马上要开始介绍的法则5。

Progressive Rendering
逐步渲染

前端工程师总是希望他的页面能尽可能快的展示给用户,所以他们希望页面能够随着下载逐步渲染给用户,特别是在页面比较大或是用户使用较慢的网络连接时。有人曾经讨论过给用户一个视觉反馈的重要性,提出要用视觉反馈来作为衡量进度的指标:让用户实实在在的感受到页面一块一块的渲染出来和让用户等着一个空白页让各组件下载到本地再渲染,可能前一种方法的实际组件下载完的速度要慢,但用户会觉得这要快的多。

把样式表放到页面的下方,在一些浏览器中(包括IE)会阻止我们上面所期望的逐步渲染。因为浏览器如果发现页面中的下方有样式表的引用,就会不进行逐步的渲染以避免样式表的的变更所带来的重新渲染,所以这个时候用户可能会看到一个空白页(其实可能网速并不慢,服务响应也够快,但您还是等一个空白页的过程)。Firefox则不会这样。

HTML规范中就清楚的说明了样式表要声明或引用在页面的HEAD中:”Unlike A, [LINK] may only appear in the HEAD section of a document, although it may appear any number of times.”

下面我们用数据来分析:
CSS在页面的下方:http://stevesouders.com/hpws/css-bottom.php
CSS在HEAD中:http://stevesouders.com/hpws/css-top.php
CSS在HEAD中,并用 @import的方式引入:http://stevesouders.com/hpws/css-top-import.php
(注意,这种用@import引入的方式,即使您将css写在HEAD中,也会出现类似将css放在页面下方的情况,出现一个空白的页等待中)
以上的三个页面中,组件和个数都一样。看看下面的三个页面的组件下载时序图:

 

第一个和第三个,都是在最后才下载样式表,这样就会导致在IE中,页面在6.3秒前都是不会被渲染的,而第二个页面,会最先下载样式表,这时页面就会开始渲染了。

现在我们知道该怎么做了。把样式表以link文件的形式放进HEAD吧。

相关章节:

翻译:High Performance Web Sites(6)-Chapter 4

《High Performance Web Sites》 :Chapter 4

法则4:使用Gzip压缩组件  Gzip Components

前端开发工程师能力的好坏直接关系着页面的访问快慢(John:要知道,用极其丑陋的table套table做出的页面比简洁的div与table做出的页面无论是从K字节上,还是载入速度上都是极大的消耗),但另一些如用户的带宽等我们无法控制的因素,也会影响到用户访问您web应用的速度。法则1法则3介绍了如何通过减少不必要的HTTP请求来提升页面加载时间,法则2介绍了用CDN,可以让我们离用户更近,但我们总不能把所有的HTTP请求都优化掉吧。于是,我们这法则4应潮流而生。

法则4,是通过减少HTTP响应的数据量来加快响应速度。要知道如果一个HTTP的响应足够少,那传输的时间多快呀,因为从服务器端传送到浏览器端的数据包小了,也少了,岂有不快之理。这对像那些网络不太好的用户可是特有效。这一章节就会展示如何通过gzip去压缩HTTP响应,这是减少数据量最easy的方法,但有得到就会有付出,gzip也会有一些不好的影响,这个后面介绍。


压缩是如何工作的

很久以前我们就把文件压缩应用在电子邮件和FTP站点当中。直到HTTP/1.1的规范制定,web客户端才开始通过HTTP请求中的 Accept-Encoding 头信息来来表示其支持压缩。

如果web服务器在请求信息中看到这个头信息,它就可以通过响应的 Content-Encoding头信息来返回服务器可用的压缩方式。
gzip是目前最流行和高效的压缩方式。它是一个可以自由使用,不受专利权限制的压缩格式,是由GNU项目开发出来并定义在RFC1952规范中。另外还有一种压缩方式是deflate,但它不如Gzip那么常用和高效,其实上,我只发现一家网站在使用deflate:msn.com 。而支持deflate数据压缩的浏览器,也支持Gzip,所以Gzip是非常好的选择。
 

该压缩什么

服务器要压缩什么是取决于文件格式。很多网站只压缩HTML文件,其实script和样式表文件的也都值得压缩(事实上,任何文本型的数据都可以压缩,包括XML和JSON数据)。图片和PDF文件的则不必再用gzip压缩,因为它们本身就是压缩格式,再多余的压缩只会浪费CPU资源,也得不到多少的文件压缩量。

gzip压缩需要消耗服务器端的CPU,而客户端也需要执行解压gzip格式的数据。至于是否值得压缩的问题,我觉得任何超过1K或2K的文件都值得压缩。mod_gzip_minimum_file_size 参数就可以配置您想要压缩的文件的大小范围,它的默认值是500 bytes.

老套路,我又调查了美国10大网站使用gzip的情况如下图。有9家都用gzip压缩他们的HTML文件,7家还用gzip压缩了script和样式表文件,其中有5家是完全压缩了所有的script和样式表。一个网站如果压缩所有的HTML文件,样式表,script文件,甚至可以减少70%的数据量,这是我们下面的段落里会提到的。

节省The Savingsgzip压缩后,返回的数据量一般会减少70%,如下图所示,显示了scrtip,样式表文件在压缩前和压缩后的数据量的变化,同时也显示了如果用deflate方式来压缩的数据量对比。

很显然,从上图中就知道我们为什么要选择gzip了。gzip会减少约66%的数据量,而deflate只会减少约60%。

配置 Configuration

配置gzip的模块要取决于您Apache的版本:Apache 1.3使用的是mod_gzip,而 Apache 2.x 使用 的是mod_deflate。这一段落就会讲解如何配置每个模块。

Apache 1.3: mod_gzip

Apache1.3的gzip压缩功能是通过 mod_gzip 模块来现实的。mod_gzip 有很多的配置参数,这些在mod_gzip的站点上有说明,在这里我介绍一些最常用的参数。

mod_gzip_on
开启mod_gzip.
mod_gzip_item_include
mod_gzip_item_exclude
定义要被gzip压缩或不压缩的文件的类型,MIME类型, user agent, 等等。

大部分的web服务器都会打开mod_gzip,并把text/html设为默认文件类型。最重要的是您应该为script与样式表文件也打开gzip功能。您可以参考下面对Apache 1.3 的配置:

mod_gzip_item_include  file    \.js$
mod_gzip_item_include  mime  ^application/x-javascript$
mod_gzip_item_include  file    \.css$
mod_gzip_item_include  mime  ^text/css$
gzip的命令行程序可以控制压缩的级别以及CPU的使用率,但在mod_gzip中却无法配置这些。有时对数据流的压缩会造成CPU的过分负荷,我们可以选择缓存压缩后的响应数据到磁盘或是内存中,如果要手动实现这一功能就太麻烦了,还好,我们可以通过mod_gzip来自动保存压缩后数据到磁盘上,还可以及时的响应更新。实现这一功能则要配置 mod_gzip_can_negotiate 和 mod_gzip_update_static 参数。

Apache 2.x: mod_deflate
 

Apache 2.x 是通过mod_deflate 模块来实现压缩。尽管它的名字是deflate,但实际它是实现的gzip压缩方式(John:怪异吧!)。如果要实现像对script和样式表的压缩这种基本的配置只需要下面这一行就够了:

AddOutputFilterByType   DEFLATE      text/html    text/css    application/x-javascript

与mod_gzip不同,mod_deflate可以通过参数配置来控制压缩级别。要获取更多的信息,可查阅Apache 2.0 mod_deflate的文档。

代理缓存  Proxy Caching

以上介绍的简单配置对直接访问web服务器的方式来说没有一点问题,web服务器通过请求中的Accept-Encoding头信息来判断客户端是否可以支持压缩的数据,然后把压缩或没压缩的数据再返回给客户端,这些都是通过HTTP的header头信息来实现的。

但如果用户是通过代理上网的,那用户的请求也都是通过代理服务器中转发送的,这时就比较麻烦了:假如用户发请求给代理服务器时,并表明其不支持gzip,而代理服务器再中转数据时却将已经缓存过的压缩的数据返回给用户,这时就会造成用户看到的是一堆乱码,反之亦然。

要解决这个问题则要在服务器端加上vary头信息。mod_gzip默认是为所有的响应都增加Vary:Accept Encoding头信息,这样代理服务器就会缓存压缩和未压缩版的数据了。

特殊情况 Edge Cases

目前90%的浏览器都支持gzip。但还是会有一些特殊情况我们不得不考虑,如在早期的IE版本中,特别是IE5.5和IE6.0 sp1存在一些bug,一种比较安全的做法是用”浏览器白名单(browser whitelist)“,让服务器端只针对一部分浏览器发送gzip格式的数据。如下的设置就是只针对IE6~9的版本和Mozila 5~9的版本发送gzip数据。

Apache 1.3使用User-Agent:

mod_gzip_item_include  reqheader  ”User-Agent: MSIE [6-9]”
mod_gzip_item_include  reqheader  “User-Agent: Mozilla/[5-9]“
Apache 2.x 使用 BrowserMatch :
BrowserMatch  ^MSIE [6-9] gzip
BrowserMatch  ^Mozilla/[5-9] gzip

而针对代理缓存,一种方法是在Vary的头信息中加上User-Agent标识来告诉代理服务器,我们使用了白名单;在mod_gzip模块中,如果它发现配置了白名单,则会自动为Vary加上上User-Agent标识。但是不要指望代理服务器会缓存所有的浏览器白名单的每一个url副本。所以我们不得不采用另一个方法:为返回的响应中加上Vary : * 或 Cache-Control:private 头信息,以表明完全禁止代理服务器的缓存,不让代理去缓存任何一个组件,让用户通过代理时,都要去请求真正的服务器以获得页面数据,事实上Google和Yahoo!都是使用的这一策略,尽管它会带来更多的流量消耗。

还有一个特殊情况要指出,就是ETags(我们将会在Chapter 13章节单独提到),默认情况下它都不会被压缩,所以最好的办法就是干脆禁用ETags,这个还是放在Chapter  13章节再详谈吧。


下面三个链接分别是没有gzip压缩,只压缩了HTML和压缩了全部组件的三个链接页面:
Nothing Gzipped
http://stevesouders.com/hpws/nogzip.html
HTML Gzipped
http://stevesouders.com/hpws/gzip-html.html
Everything Gzipped
http://stevesouders.com/hpws/gzip-all.html
下图就是三者的具体数据量对比和时间对比:

 

High Performance Web Sites

大概两个月前就在网上down下了这部书,并一口气读完。正值blog迁移,故现在才浓重推荐。此书是对于web前端的性能优化不可多得的一部好书,其可实践性极强,作者是原Yahoo!的首席性能官(Chief Performance Officer):Steve Souders,现已经跳至google。
在Yahoo,有一支性能小组,叫:Yahoo!’s Exceptional Performance team,翻译成中文就是是特别性能小组,他们主要是致力于以最佳的实践来提高Web性能。好像前段时间他们来过中国参加了CSDN的开发者大会,可惜俺未能参加,要是早知这一出演讲讨论,肯定要前往倾听了。Steve Souders就是这支小组的team leader。
书中,Steve Souders提出了一个performance golden rule,与其说是黄金法则,到不如说这是一个目前各网站所常见的一个表象:80-90 %的用户等待时间是来自于前端的网页加载
而《High Performance Web Sites》就是介绍如何去减少这80~90%的等待时间的一部优化手册。此书即适合前台开发者(html,js,css),也适合后台开发者。此书在yahoo的开发者网站有Html版本,具体可查阅http://developer.yahoo.com/performance/rules.html
如果您对后台的构架或性能更感兴趣,那在我前面介绍Flickr构架时,所提到的《Building Scalable Web Sites》就更值得一看了。
该书的中文版已经出来了,我就不翻译了,推荐大家买书学习。

Spry框架初步入门

Adobe的Ajax框架spry的正式版还没放出,所以文档是少之又少,在这里接合自己使用的情况总结20个spry的知识点给大家,相信会对大家有一定帮助,至少大家也会对spry有个初步的认识了,这个轻量型的框架就一个字:“易用”。
1,使用spry框架,必须引用的两个核心js文件

<script type="text/javascript" src="../../includes/xpath.js"></script>
<script type="text/javascript" src="../../includes/SpryData.js"></script>

2,创建一个数据器dataset

var dsPhotos = new Spry.Data.XMLDataSet("/photos.php", "/gallery/photos/photo", { method: "POST", postData: "galleryid=2000&offset=20&limit=10", headers: { "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" } });
var dsPhotos2 = new Spry.Data.XMLDataSet("/photos.php?galleryid=2000", "/gallery/photos/photo");

method:为请求发送方式,POST / GET ;默认为GET
postData:为请求参数,可省略,可直接把”/photos.php”换成/photos.php?galleryid=2000&offset=20&limit=10
Content-Type:为头信息

3,不使用缓存

Var dsPhotos = new  Spry.Data.XMLDataSet("/photos.php?galleryid=2000", "/gallery/photos/photo", { useCache:  false })

这里要重点说明一下,我在应用时,曾经发现设置不用缓存时,页面仍然不是最新数据,我是用一个servlet生成xml,然后对生成的数据进行删除和添加,但页面显示的数据不会自动更新。
后来在adobe的官方论坛上找到了答案,在IE中,使用userCache:false是不够的,还要在生成xml的jsp,php等中设置头不使用cache,如在生成xml的jsp/servlet中是要加上:
response.addHeader(”Cache-Control”,”no-cache”);
以上问题只存在IE中,在firefox和opera中不存在。注,spry对opera9版本支持很好,使用版本8的朋友要注意了。
另外,也可以在构造完数据器后,再设置缓存:

dsData.useCache = false;
dsData.loadData();

4,获取数据形式
假如我们上面的请求,返回的xml如下:

<gallery id="12345">
    <photographer id="4532">John Doe</photographer>
    <email>john@doe.com</email>  

    <photos id="2000">
        <photo path="sun.jpg" width="16" height="16" />
        <photo path="tree.jpg" width="16" height="16" />
        <photo path="surf.jpg" width="16" height="16" />  

    </photos>
</gallery>

那么“gallery/photos/photo”返回的数据是下面的数组:
[
{ “@path”: “sun.jpg”, “@width”: 16, “@height”: 16 },
{ “@path”: “tree.jpg”, “@width”: 16, “@height”: 16 },
{ “@path”: “surf.jpg”, “@width”: 16, “@height”: 16 }
]

var rows = dsPhotos.getData(); // 获取所有行.
var path = rows[0]["@path"];   // 获取第一行中"@path"的值   

dsPhotos.setCurrentRowNumber(2); // 将第3行做为当前处理行,下标以0开始   

var id = dsPhotos.getData()[2]["ds_RowID"]; // 获取第3行的ID.   

dsPhotos.setCurrentRow(id); // 通过第3行的id,将第3行设为当前处理行.

5,排序

dsPhotos.sort("@path"); //以"@path"列的值为关健字对行排序   

dsPhotos.sort("@path", "toggle");  //"ascending", "descending"和"toggle",默认是 "ascending"。
var dsPhotos = new Spry.Data.XMLDataSet("/photos.php?galleryid=2000", "/gallery/photos/photo", { sortOnLoad: "@path", sortOrderOnLoad: "descending" });//也可在数据构造器中设置初始排序   

dsPhotos.setColumnType("@width", "number");//设置类型
dsPhotos.setColumnType("@height", "number");   

...   

dsPhotos.sort("@width"); // 对 "@width" 列数据进行排序.

6,去除重复

dsPhotos.distinct(); // Remove all duplicate rows.
//distinct()方法是具有破坏性的,多余的行是被删掉的,如果你想再得到所有的包括重复的原始项就得重新载入XML数据。
var dsPhotos = new Spry.Data.XMLDataSet("/photos.php?galleryid=2000", "/gallery/photos/photo", { distinctOnLoad: true });//可在构造时预设

7,设置过滤器

var myFilterFunc = function(dataSet, row, rowNumber)
{
    if (row["@path"].search(/^s/) != -1)    //只返回以s开头的行
        return row;                     // Return the row to keep it in the data set.
    return null;                        // Return null to remove the row from the data set.
}   

dsPhotos.filterData(myFilterFunc); // Filter the rows in the data set.
dsPhotos.filter(myFilterFunc); // 不破坏数据,是建一个新的数组   

dsPhotos.filterData(null); // 取消过滤.

8,自动刷新,以毫秒为单位

var dsPhotos = new  Spry.Data.XMLDataSet("/photos.php?galleryid=2000", "/gallery/photos/photo", { useCache:  false, loadInterval: 10000 });//在构造器设置   

dsPhotos.startLoadInterval(10000); // 设置自动刷新
...   

dsPhotos.stopLoadInterval(); // 停止自动刷新

9,把类注册成观察器

var myObserver = new Object;
myObserver.onDataChanged = function(dataSet, data)      //可支持:onPreLoad / onPostLoad / onLoadError / onDataChanged / onPreSort / onPostSort /  onCurrentRowChanged
//第一个是发送通知的对象,做为数据器观察器,这个值永远都是dataSet对象。第二个参数可以不定义,也可以是一个对象(内置对象)
{
    alert("onDataChanged called!";
};   

dsPhotos.addObserver(myObserver);   

dsPhotos.removeObserver(myObserver);

10,把函数方法注册为观察器

function myObserverFunc(notificationType, dataSet, data)  //notfication,是通知器类型,dataSet是数据器对象,data是要观察的数据
{
    if (notificationType == "onDataChanged")
        alert("onDataChanged called!";
    else if (notificationType == "onPostSort")
        alert("onPostSort called!";
};   

dsPhotos.addObserver(myObserverFunc);

11,动态区域块
所有使用Spry动态区域块的HTML页面都要在它们的<html>标签中加入xmlns:spry=http://ns.adobe.com/spry

<html xmlns="http://www.w3.org/1999/xhtml" xmlns:spry="http://ns.adobe.com/spry">
 .... ....
   .... ....
    <ul spry:region="dsPhotos">
        <li>{dsPhotos::path}</li>
    </ul>

COL, COLGROUP, FRAMESET, HTML, IFRAME, STYLE, TABLE, TBODY, TFOOT, THEAD, TITLE, TR 不能设为动态区域

12,数据引用

{<数据器名>::<数据器列名>}

如果一个动态区域块只和一个数据器相关联,则你甚至可以省略掉数据器的名字
<li>{@path}</li>
ds_RowID – 这是数据器的行id。这个id可以帮我们指定数据器中的一个数据。它和数据是对应的,即使数据执行了排序操作,这个id和数据的对应关系也不会变化。
ds_RowNumber – 这是数据器当前数据的行号。
ds_RowNumberPlus1 – 这个与上面的ds_RowNumber相同,只不过它规定了数据的行号从1开始,而不是从0开始。
ds_RowCount – 它是数据器中数据的行的数量。如果使用了一个非破坏性的过滤器,则它的值是这个过滤器执行后得到的行的数量。
ds_UnfilteredRowCount – 在执行非破坏性过滤器前,数据器中行的数量。
ds_CurrentRowID – 当前行的id。这个值不会改变,除非使用了一个循环的构造。
ds_CurrentRowNumber – 当前行的行号。这个值不会改变,除非使用了一个循环的构造。
ds_SortColumn – 上一次排序所依赖的列名。如果这个数据器的数据还未进行过排序,则返回一个空字符串。
ds_SortOrder – 数据器中数据排序的参数,将返回三种字符串”ascending”, “descending”, 或空字符串.。
ds_EvenOddRow – 它返回的是”even”或”odd”,告诉我们ds_RowNumber的值是奇数还是偶数。

13,循环

//方式一:
<li spry:repeat="dsPhotos">{@path}</li>      

//方式二:
<ul spry:repeatchildren="dsPhotos">
  <li>{@path}</li>
</ul>     

//只输出以s开头的 spry:test属性的值可以是任何等于0或非0值的JavaScript表达示。如果这个表达示返回非0值,这个内容将会被输出,相当于if <>0就输出后面元素。
<li spry:repeat="dsPhotos" spry:test="'{@path}'.search(/^s/) != -1;">{@path}</li>

14,if 条件

<li spry:if="'{@path}'.search(/^s/) != -1;">{@path}</li>   

//if/else的形式,要使用"spry:choose"属性
            <div spry:choose="spry:choose">
                 <div spry:when="'{@path}' == 'surf.gif'">{@path}</div>
                 <div spry:when="'{@path}' == 'undefined'">Path was not defined.</div>
                 <div spry:default="spry:default">Unexpected value for path!</div>
          </div>

15,状态

<div spry:region="dsEmployees">
    <div spry:state="loading">正在载入数据 ...</div>
    <div spry:state="error">数据载入失败!</div>
    <ul spry:state="ready">
      <li spry:repeat="dsEmployees">{firstname} {lastname}</li>
    </ul>
</div>

16,通过对象将区域注册成观察者

myObserver = new Object;
myObserver.onPostUpdate = function(notifier, data)
{
     alert("onPostUpdate called for " + data.regionID);
};
...
// 调用addObserver() 将类注册为观察者.
Spry.Data.Region.addObserver("employeeListRegion", myObserver);
...
//注销
Spry.Data.Region.removeObserver("employeeListRegion", myObserver);
...   

<ul id="employeeListRegion" spry:region="dsEmployees">
...
</ul>

17,以函数将区域注册成观察者

function myRegionCallback(notificationState, notifier, data)
{
      if (notificationType == "onPreUpdate") //onLoadingData / onPreUpdate / onPostUpdate / onError
         alert(regionID + " is starting an update!");
      else if (notificationType == "onPostUpdate")
         alert(regionID + " is done updating!");
}   

...   

// 注册
Spry.Data.Region.addObserver("employeeListRegion", MyRegionCallback);
...   

// 注销
Spry.Data.Region.removeObserver("employeeListRegion", MyRegionCallback);
...   

<ul id="employeeListRegion" spry:region="dsEmployees">
...
</ul>

18,主细节模式,同一数据器

<span spry:region="dsEmployees">
<select spry:repeatchildren="dsEmployees" onchange="dsEmployees.setCurrentRow(this.value)">
    <option spry:if="{ds_RowNumber} == 0" value="{ds_RowID}" selected="selected">{username}</option>   

    <option spry:if="{ds_RowNumber} != 0" value="{ds_RowID}">{username}</option>
</select>
</span>
<span spry:detailregion="dsEmployees">{@id} - {firstname} {lastname} - {phone} </span>
//spry:detailregion"会在接收到"CurrentRowChanged"通知后改变自己的展示形式。

19,主细节模式,多个数据器

var dsStates = new Spry.Data.XMLDataSet("../../data/states/states.xml", "states/state");
var dsCities = new Spry.Data.XMLDataSet("../../data/states/{dsStates::url}", "state/cities/city");
//两个数据器有依赖关系,也可以用:"/webapp/cities.php?stateid={dsStates::@id}".   

<form name="selectForm">
//State:
    <span spry:region="dsStates" id="stateSelector">
        <select spry:repeatchildren="dsStates" name="stateSelect" onchange="document.forms[0].citySelect.disabled = true; dsStates.setCurrentRow(this.value);">   

            <option spry:if="{ds_RowNumber} == 0" value="{ds_RowID}" selected="selected">{name}</option>
            <option spry:if="{ds_RowNumber} != 0" value="{ds_RowID}">{name}</option>   

        </select>
    </span>   

//City:
    <span spry:region="dsCities" id="citySelector">
        <select spry:repeatchildren="dsCities" name="citySelect">   

            <option spry:if="{ds_RowNumber} == 0" value="{name}" selected="selected">{name}</option>
            <option spry:if="{ds_RowNumber} != 0" value="{name}">{name}</option>   

        </select>
    </span>
</form>

20,改变数据源

<select onchange="dsEmployees.setURL(this.value); dsEmployees.loadData();">   

    <option value="../../data/employees-01.xml" selected>Set 1</option>
    <option value="../../data/employees-02.xml">Set 2</option>
</select>   

<th scope="col" onclick="dsEmployees.sort('@id');">Employee ID </th>

Adobe的AJAX框架–Spry

最近看完了Adobe的AJAX框架Spry的所有文档和Demo,觉得这东西挺有意思的,在这里介绍给大家。
Spry框架的开发人员是来自于DreamWeaver开发组,他们把Spry框架做为DreamWeaver的一个完美补充为设计者和开发者提供对AJAX技术的支持。Spry框架是一个轻量级的AJAX框架,它的代码和标签十分的简洁和优雅,以保证让用户能便捷的使用,并不会为过繁杂的标签所惑。

Spry框架的官方网址:
http://labs.adobe.com/technologies/spry
在这里你能找到最新的文档和下载最新的Spry版本,目前版本是预览版1.3_08-11。
大家可以先在下面的看到Spry的示例和Demo:
http://labs.adobe.com/technologies/spry/samples/
http://labs.adobe.com/technologies/spry/demos/

 

Spry框架其实就是一个客户端的JavaScript类库,包含了一组JavaScript文件,CSS,图片文件,通过官方的框架结构图,我们能看出Spry框架的核心是四部分:XML数据器(XML Data Sets),动态区域(Dynamic Regions),装饰器库(Widgets)和变化效果库(Transition Effects)。

我们可以看出,Spry框架接收的数据格式只是XML数据格式。

一,XML数据器(XML Data Sets)
XML数据器是一个提供了从XML文档中载入和管理数据的JavaScript对象。它是Spry框架中处理XML格式数据的一个JavaScript功能实现。通过它,我们可以从XML中直接得到转换成表格数据格式的行和列的值,其实就是数组。它封装了获取XMLhttpRequest的方法,和发送并接收数据等一系列获取数据的方法。
要创建一个XML数据器,你必须在你的HTML文件中加入两行引入JavaScript文件的代码:

<script type=”text/javascript” src=”../../includes/xpath.js”></script>
<script type=”text/javascript” src=”../../includes/SpryData.js”></script>

上面引入的是Spry框架的核心js文件之一。”xpath.js”是Google基于XPath 1.0标准的JavaScript功能实现。你如果想获得更多的关于它的信息,可以访问Google的开源项目 google-ajaxslt project page .
”SpryData.js”则包含了定义XML数据器和动态区域的代码。
构造XML数据器就好像新建一个类一样,用”new”关键字即可:
<script type=”text/javascript”>
var dsPhotos = new Spry.Data.XMLDataSet(”/photos.php?galleryid=2000″, “/gallery/photos/photo”);
</script>
Spry框架为XML数据器提供了一些有特色的功能:如数据排序,数据过滤,按指定时间间隔自动更新,并引入了观察者通知模式以支持事件的触发。
关于XML数据器更多资料,大家可以参考http://labs.adobe.com/technologies/spry/articles/data_set_overview/,这一部分笔者已经翻译完成了,但英文原文其实写的通俗易懂,如果对一些名词翻译的不准确反倒会误导了大家,所以还是不贴出来了,大家可以参考官方英文文档。

二,动态区域(Dynamic Regions)
一旦你建立了XML数据器,你就可以在动态区域中去显示这个数据器的数据了。
创建动态区域块很简单,只要在html标签的相应位置加上”spry:region”属性就可以了,Spry框架就会知道这一块是被标识成动态区域了。
在动态区域,你可以有条件的选择要输出的数据,也可用循环输出。
动态区域另一个特点就是,它分为master和detail两个区域类型。
如下面的Demo截图:

master区域的数据改变会使detail区域的数据相应发生改变

两者都注册成XML数据器的观察者

当master区域的数据变化时,触发detail区域的响应事件,从面达到更新相应数据。
大家可参考http://labs.adobe.com/technologies/spry/samples/DataSetMasterDetailSample.html所示的功能。
这只是动态区域简单的示例,复杂的情况可能会有多个XML数据器与多个动态区域相互关联触发。

三,装饰器库(Widgets)
一个装饰器是由一组HTML,CSS,JavaScript封装成的高级UI。最常见的装饰器有可折叠的菜单,树型菜单和选项table面板等。这些对象都比较难于创建,需要一些更高级的编程经验。Spry的开发组在创建装饰器这一概念就是希望开发者们能相互协作,共享各自的设计,把这些高级界面元素用在自己的页面上。
Spry框架下的装饰器是易于编辑的。这种模型非常适合于设计者和编辑人员:要改变外观,只要改变CSS就可以了,要增加一个可折叠菜单只要copy和paste一个代码块就够了。
如,你能看懂的这段代码是创建了一个可折叠菜单吗?
<div id=”Acc1″ >
<div>
<div>Panel Header  1</div>
<div>Panel  1 Content </div>
</div>
<div>
<div>Panel Header  2</div>
<div>Panel  2 Content</div>
</div>
<div>
<div>Panel Header  3</div>
<div>Panel  3 Content</div>
</div>
</div>
<script>
var acc1 = new  Hanzo.Widget.Accordion(”Acc1″);
</script>
这段代码是非常简洁清晰的,没有什么繁杂的标签,这样设计是为了易于阅读。
关于这个例子,可以参考http://labs.adobe.com/technologies/spry/samples/accordion/AccordionSample.html

四,变化效果库(Transition Effects)
Spry框架的变化效果库都存于SpryEffects.js文件中,是基于JavaScript的一些动态变化效果,如,淡出,改变形状等。
Spry框架在设计时,曾考虑直接用第三方的效果库,如Script.aculo.us,但后来开发小组觉得要保证框架代码和标签的一致性,还是选择了自已开发,但是也基本上是以Script.aculo.us为原型进行设计,因为Script.aculo.us 本身就是一个非常优秀的变化效果库框架。
由于Spry框架现在只是预览版,所以目前只支持七种变换:
Appear/Fade Makes an element appear or fade away
Highlight Flashes a color as the background of an element
BlindUp/BlindDown Simulates a window blind, up or down, where the contents of the affected elements stay in place
SlideUp/SlideDown  Simulates a window blind, where the contents of the affected element scroll accordingly
Grow/Shrink Increases/reduces the size of the element
Shake Moves the element slightly to the left, then to the right, repeatedly
Squish Reduces the element to its top-left corner and disappears

可能到这大家对Spry框架已经有了大致的了解,其实这个东西已经足够我们的大多数应用的开发了,笔者也十分期待着正式版能早日放出,并打算在自己现在的项目中试一试了。