Dojo 体系架构
Dojo 是一个分层的体系架构。最下面的一层是包系统,Dojo API 的结构与 Java 很类似,它把所有的 API 分成不同的包(package),当您要使用某个 API 时,只需导入这个 API 所在的包。包系统上面一层是语言库,这个语言库里包含一些语言工具 API,类似于 Java 的 util 包。再上一层是环境相关包,这个包的功能是处理跨浏览器的问题。
常用包介绍
Dojo 1.1.1 提供了上百个包,这些包分别放入三个一级命名空间:Dojo,Dijit 和 DojoX 。其中 Dojo 是核心功能包 , Dijit 中存放的是 Dojo 所有的 Widget 组件,而 DojoX 则是一些扩展或试验功能,DojoX 中的试验功能在成熟之后有可能在后续版本中移入到 Dojo 或 Dijit 命名空间中。
dojo.io 不同的 IO 传输方式。 script、IFrame 等等;
dojo.dnd 拖放功能的辅助 API 。
dojo.string这个包可以对字符串进行如下的处理:修整、转换为大写、编码、esacpe、填充(pad)等等;
dojo.date 解析日期格式的有效助手;
dojo.event 事件驱动的 API,支持 AOP 开发,以及主题 / 队列的功能;
dojo.back 用来撤销用户操作的栈管理器
dojo.colors 颜色工具包;
dojo.data Dojo 的统一数据访问接口,可以方便地读取 XML、JSON 等不同格式的数据文件;
dojo.fx 基本动画效果库;
dojo.regexp 正则表达式处理函数库;
dijit.forms 表单控件相关的 Widget 库;
dijit.layout 页面布局 Widget 库;
dijit.popup 这个包用于以弹出窗口方式使用 Widget ;
Dojo 版的 Hello World
<html>
<head>
<title>test</title>
<script type="text/javascript"
src="dojo_path/dojo/dojo.js"
djConfig="parseOnLoad: true"></script>
<style type="text/css">
@import "dojo_path/dijit/themes/tundra/tundra.css";
</style>
<script type="text/javascript">
dojo.require("dojo.parser");
dojo.require("dijit.form.TextBox");
dojo.require("dijit.form.Button");
function init()
{
dojo.connect(dijit.byId("mybutton").domNode,"onclick","login");
}
function login()
{
if( dijit.byId("myname").value=="goodguy" &&
dijit.byId("mypassword").value=="goodgoodstudy")
alert("Dojo World welcome you!");
else
{
dijit.byId("myname").setValue("");
dijit.byId("mypassword").setValue("");
alert("Dojo does not like you!");
}
}
dojo.addOnLoad(init);
</script>
</head>
<body class="tundra">
UserName:
<input type="text" length="20" id="myname" dojoType="dijit.form.TextBox"><br>
PassWord:
<input type="password" length="20" id="mypassword" dojoType="dijit.form.TextBox"><br>
<div id="mybutton" dojotype="dijit.form.Button">Submit</div>
</body>
</html>
djConfig="parseOnLoad: true" 表示在页面加载完成以后,启用 Dojo 的解析模块对页面中的 Dojo 标签属性(Dojo 标签属性是指由 Dojo 定义的一些标记,这些标记只有在被处理以后,才能为浏览器识别执行)进行解析。djConfig 是使用 Dojo 页面的一个全局配置参数。通过对这个参数不同的赋值,可以控制页面中 Dojo 的解析模块是否运行, Dojo 的调试模块是否工作等。
@import "dojo_path/dijit/themes/tundra/tundra.css" 表示引入 Dojo tundra 风格的层叠样式表。
dojo.require("dojo.parser") 表示引入 Dijit 的解析功能模块。该模块将会把 Dojo 标签属性替换成浏览器可以识别执行的标记。需要与 djConfig="parseOnLoad:true" 相区别的是,djConfig="parseOnLoad:true" 表示确定在页面加载完成以后执行解析功能,但解析功能模块的引入要靠 dojo.require("dojo.parser") 来实现。
dojo.require("dijit.form.TextBox") 和 dojo.require("dijit.form.Button") 表示引入 Dojo 风格的文本输入框和按钮的功能模块。
dojo.connect(dijit.byId("mybutton").domNode, "onclick", "login") 表示将按钮的点击事件和 login 函数联系起来,当点击 id 为 mybutton 的按钮时,执行 login 函数。
dijit.byId("myname").setValue("") 表示调用 id 为 myname 的 Dojo 文本框的 setValue 函数,将文本框里面的内容清为空。
<input type="text" length="20" id="myname" dojoType="dijit.form.TextBox"> 中的 dojoType="dijit.form.TextBox" 表示在页面中文本输入框是 Dojo 风格的。需要注意的是,通过声明 dojoType="dijit.form.TextBox" 这种方式来实现某些 Dojo 功能的使用,其表现形式上如同声明一个 HTML 标签的属性(如同 width="10px"),因此在本文中称其为 Dojo 标签属性。在页面加载完成以后,Dojo 的解析模块会将 Dojo 标签属性转化为浏览器能够识别执行的标记。
FireDebug
console.log("My test num is %d",num);
console.debug("I am debug");
console.info("I am info");
console.warn("I am warn");
console.error("I am error");
在处于 HTML 窗口的模式下,点击 Edit 按钮,将切换查看模式到编辑模式。需要注意的是,在使用 Edit 模式前,最好如图 11 先提前选中页面的 body 代码区块
XHR 框架与 Dojo
XmlHttpRequest 对象是 Dojo 中的 XHR 框架的基础,目前主流浏览器都已经支持此对象,但是不同浏览器上实现方式却不一样,IE5、IE6 采用 ActiveX 对象的方式,Firefox 和 Safari 都实现为一个内部对象,所以创建 XHR 对象之前需要先测试浏览器的类型
function createXHR(){
if (window.XMLHttpRequest) { // Non IE
return new XMLHttpRequest();
}
else if (window.ActiveXObject) { // IE
return new ActiveXObject("Microsoft.XMLHTTP");
}
}
XHR 对象创建方式不一致是 Dojo 的 XHR 框架诞生的一个原因,更重要的原因是原始 XHR 对象还不够强大,有些方面不能满足开发的需要:首先 XHR 对象支持的返回类型有限,原始 XHR 对象只有 responseText 和 responseXML 两个属性代表返回的数据,重要的数据交换格式 JSON 就不被支持;其次不能设置 HTTP Request 的超时时间,设置超时时间可以让客户端脚本控制请求存在的时间,而不是被动的等待服务器端的返回。
基于这些问题,Dojo 组织提供了一组函数来支持各种 HTTP 请求,包括 xhrGet,rawXhrPost,xhrPut,rawXhrPut,xhrPut,xhrDelete,这几个函数与 HTTP 协议中的四种请求是一一对应的,HTTP 四种请求是:Get(读取),Post(更新),Put(创建),Delete(删除)。 Dojo 组织的发起者 Alex Russell 把这些跟 XHR 对象相关的函数放在一起称为 XHR 框架
使用 xhrGet 请求资源
xhrGet 是 XHR 框架中最重要的函数,使用它即可以请求服务器上的资源,只要从服务器返回的是字符数据流即可。
function helloWorld(){
dojo.xhrGet({
url: "helloworld.txt" ,
handleAs: "txt",
content: {username:useranme,password:password},
load: function(response, ioArgs){alert(response);},
error: function(error, ioArgs){alert(error.message);}
});
}
dojo.xhrGet 的参数是一个 JSON 对象,JSON 对象由很多的属性 / 值对组成,其中的值可以是任意类型的数据: 整形、字符串、函数……甚至是 JSON 对象
* url:请求的服务器资源 url,url 标识的只能是文本文件,而不能是二进制文件。
*handleAs:返回的数据类型,可以是 text(默认)、json、json-comment-optional, json-comment-filtered、javascript、xml 。 Dojo 将根据 handleAs 设置的数据类型对从服务器返回的数据进行预处理,再传给 load 属性指向的回调函数。
* load:在请求的资源成功返回之后调用回调函数。
* error:在 http 请求出错之后调用回调函数。
* content:传递参数,params为对象
函数包含两个参数:response 和 ioArgs
response:表示从服务器端返回的数据,Dojo根据 handleAs设置的数据类型进行了预处理。
ioArgs: 这是一个对象,包含调用 xhrGet 时使用的一些参数。之所以把这些信息放在一个对象中并传递给回调函数是为了给回调函数一个执行“上下文”,让回调函数知道自己属于哪个 HTTP 请求,请求有哪些参数,返回的数据是什么类型等。这些信息在调试程序时特别有用。
*ioArgs.url:请求的 URL,与调用 xhrGet 时设置的值一样。
ioArgs.query:请求中包含的参数, URL 中“ ? ”后面的内容。
ioArgs.handAs:如何对返回的数据进行预处理,与调用 xhrGet 时设置的值一样。
ioArgs.xhr: xhrGet 函数使用的 XHR 对象。
handleAs 预处理方式
text 默认值,不对返回的数据做任何处理
xml 返回 XHR 对象的 responseXML
javascript 使用 dojo.eval 处理返回的数据,返回处理结果
json 使用 dojo.fromJSon 来处理返回的数据,返回生成的 Json 对象
假设 handleAs 被设置为“ json ”,按照上表,则 load 回调函数的参数 response 为 JSON 对象。如果 handleAs 不是“ json ”,还能不能生成 JSON 对象呢?答案是肯定的,可以把 handleAs 设为“ text ”,那么返回的是普通的字符串,只要字符串是 JSON 对象的文本形式,则可以简单地使用 eval() 函数把它转换为真正的 JSON 对象,而不再需要任何其他的 API 完成转换工作
function submitForm(){
dojo.xhrGet({
form: "loginForm" ,
handleAs: "text" ,
handle: handler,
content: { pwd: "modified" },
sync: false
});
return false;
}
<form id="loginForm" οnsubmit="return submitForm();" action="data.php">
<input type="textfield" name="id" />
<input type="password" name="pwd" />
<input type="submit" name="sub" value="submit" />
</form>
form:需要异步提交的表单的 id 。只有把它设置成想要异步提交的表单的 id,并在这个表单的 onsubmit 事件中调用自定义的 submitForm() 函数,才能真正做到异步提交。注意在 submitForm 函数中最后返回了 false,这是为了阻止系统默认的表单提交事件,让表单提交异步进行,如果不返回 false,会引起页面跳转。
handle:handle 也是一个回调函数,在 xhrGet 返回时被调用,正常和错误返回的情况都能处理,可以说是 load 和 error 的混合体,但优先级比 load 低,只有在没有设置 load 时才起作用。
content:在这里可以修改来自表单的信息
sync:设置同步还是异步提交。默认是异步提交
xhrPost 发送的是 Post 请求
xhrPost 一般用来发送表单数据,当然 xhrGet 也可以做到,区别是 xhrPost 把表单数据封装在 HTTP 请求的 Body 部分。在服务器端只能使用取 POST 信息的方法获取这些表单数据
iframe
使用 dojo.io.iframe 同样可以跟服务器交互,但是它采用了与 XHR 对象不同的实现思路
function iframeTest(){
dojo.io.iframe.send({
form: dojo.byId('testForm') ,
url: "data.php" ,
method: "post" ,
handleAs: "html" ,
load: function(response, ioArgs) {
alert(response);
}
});
}
dojo.io.iframe 的使用方式、参数与 xhrGet 非常相似。其中,from,url,handleAs,load 等在 xhrGet 中也存在,唯一不同的是 method,method 表示 dojo.io.iframe 将以何种 HTTP Method 来发送请求
handleAs 参数,dojo.io.iframe 一般使用 html,因为在 iframe 中存的其实是另一个 HTML 页面。如果 handleAs 设置为其他值,像 json,text 等,则在服务器端须使用 <textarea></textarea> 把要返回的数据包装起来,比如 hellow, world 要被包装成 <textarea>hello,world</textarea>,所以最后存在 iframe 中的是一个文本域(textarea),这个文本域包含了从服务器端返回的数据。这么做的原因很简单,就是为了保持从服务器返回的数据“一成不变”,因为任何字符数据都可以“安全的”放在 HTML 页面的文本域中。想像一下,我们是不是可以在文本域中输入各种字符! dojo.io.iframe 会对 textarea 包装的数据进行处理:首先把 textarea 标签去掉,然后把数据转换为 handleAs 指定的类型传递给 handle 中设置的回调函数。
dojo.io.iframe 首先会创建一个隐藏的 iframe 并插入到父页面的最后,然后设置此 iframe 的 src 属性为dojo-module-path/resources/blank.html(dojo-module-path 指 dojo 包所在的目录),iframe 页面的 onload 事件的处理函数被设置为父窗体的回调函数。接下来就是在 iframe 页面中发送请求,并接收服务器的响应。当 iframe 接收到服务器的反馈并加载完之后,父窗体的回调函数即被调用。
动态 Script,跨域访问
XHR 框架中的函数功能强大,使用方便。但是 XHR 框架的函数有一问题就是不能跨域访问,浏览器不允许 XHR 对象访问其他域的站点。比如有一个页面属于 a.com,在这个页面中使用 XHR 对象去访问 b.com 的某一页面,这是被禁止的。如果使用 XHR 对象来做跨域访问一般需要服务器端的程序做“中转”,先由服务器端的程序获取其他域的数据,然后浏览器再使用 XHR 对象从服务器上获取这些数据,这种方式即增加了服务器端的开销,浏览器端的效率也不高。
有没有方法直接在浏览器中实现跨域访问呢?当然有,它就是 script 标签,使用 script 标签可以引用本域或其他域的文件,只要这些文件最后返回的是 javascript 。返回的 javascript 会立即在浏览器中执行,执行结果存储在本地浏览器。这一点很重要,它使得各站点可以通过 javascript 来发布自己的服务,像 google 的很多的服务都是通过这种方式提供的。 script 标签不仅可以静态添加到页面中,也可以被动态插入到页面中,而且通过 DOM 操作方式动态插入的 script 标签具有与静态 script 标签一样的效果,动态 script 标签引用的 javascript 文件也会被执行(注意:通过 innerHTML 方式插入的 javascript 是不会被执行的,这一点在前文已经介绍过)。
function createScript() {
var element = document.createElement("script" );
element.type = "text/javascript" ;
element.src = url;
document.getElementsByTagName("head" )[0].appendChild(element);
}
动态创建 script 标签的例子,script 标签即可以放在页面的 head 部分,也可以放在 body 部分
动态插入 script 标签的一个问题是如何判断返回的 Javascript 执行完了,只有在执行完之后才能引用 Javascript 中的对象、变量、调用它中间的函数等。最简单的方法是“标志变量法”,即在脚本中插入标志变量,在脚本最后给这个变量赋值,而在浏览器的脚本中判断这一变量是否已经被赋值,如果已经被赋值则表示返回的脚本已经执行完。但是这种方法缺点也很明显,首先如果一个页面有很多的动态 script 标签,而每个 script 标签引用的 javascript 都使用一个标志变量,那就有很多变量需要判断,而且这些变量的命名可能冲突,因为这些 Javascript 是由不同的组织、公司提供的,难保不产生冲突。另外在浏览器本地脚本中需要轮询这些变量的值,虽然可以实现,但实在不是高明的做法。目前被广泛使用的是另一种方法:JSONP(JSON with Padding)。 JSON 表示返回的 Javascript 其实就是一 JSON 对象,这是使用 JSONP 这种方式的前提条件。 Padding 表示在 JSON 对象前要附加上一些东西,究竟是什么呢?请往下看!
JSONP 的思路很简单,与其让浏览器脚本来判断返回的 Javascript 是否执行完毕,不如让 Javascript 在执行完毕之后自动调用我们想要执行的函数。是不是想起了学习面向对象设计中的“依赖倒置”原则时的那句名言:“Don't call us, we will call you ”。使用 JSONP 这种方法,只需要在原来的 Javascript 引用链接上加上一个参数,把需要执行的回调函数传递进去。请看下面的两个 script 标签。
<script src= ” http://url/js.php?parameter= … . ” >
<script src= ” http://url/js.php? parameter= … .&callbackname=mycallback” >
如果 http://url/js.php?parameter= … . 返回的是 JSON 对象 {result:“hello,world”} , 那么第二个 script 标签返回的则是 mycallback({result: ” hello, world ” }),这一函数将在写入到浏览器后立即被执行,这不就实现了在 Javascript 执行完之后自动调用我们需要执行的回调函数了吗?
介绍了这么多动态脚本的背景知识,终于来到了 Dojo 对动态脚本的支持上。 Dojo 即支持标志变量法,也支持 JSONP 方式。
<html>
<head>
<script src="../dojo/dojo/dojo.js" djConfig="parseOnLoad: true"></script>
dojo.require("dojo.io.script" );
</head>
<body>
<script>
dojo.io.script.get({
url:"data.php" ,
checkString:"test_01" ,
handle:function(response, ioArgs) {
greetFromServer("hello, world");
}
});
</script>
</body>
</html>
checkString 正是"标志变量法"的关键,checkString 表示从服务器返回的 javascript 需要定义的变量
服务器输出了一段javascript,在这段javasript的最后给变量 test_01 赋值
dojo.io.script.get 函数的 handle 指向的回调函数又调用了这段 javascript 中定义的函数 greetFromServer() 。
只有在 test_01 被赋值后,调用 greetFromServer 才是安全的。
dojo.io.script.get 函数也支持 JSONP 方式,当 dojo.io.script.get 函数的参数对象使用了 callbackParamName 属性时,表示它工作在 JSONP 方式下。 callbackParamName 表示在 url 中添加回调函数名的参数名称,有点拗口,但是看了下面 dojo.io.script.get 函数在页面中动态创建的 script 标签一切就都清楚了,最终出现在 URL 中的是 callbackName,而不是 callbackParamName 。
<script src="data2.php?callbackName=dojo.io.script.jsonp_dojoIoScript1._jsonpCallback" >
<html><head>
<script src="../dojo/dojo/dojo.js"
djConfig="parseOnLoad: true"></script>
dojo.require("dojo.io.script" );
</head>
<body>
<script>
dojo.io.script.get({
url:"data2.php",
callbackParamName: "callbackName" ,
handle: function(response, ioArgs) {
document.write(response.greet);
}
});
}
</script>
</body></html>
Dojo 会自动创建一个名为 dojo.io.script.jsonp_dojoIoScript1._jsonpCallback 的 javascript 函数,这个函数其实什么都不做,只是作为一个回调函数传给服务器端程序。 php 的服务器端程序如清单 12 所示,callbackName 像浏览器和服务器之间的一个“信令”,服务器端必须返回对 callbackName 所代表的函数的调用,因为 Dojo 会检查它是否被调用过。
所以服务器端返回的是 dojo.io.script.jsonp_dojoIoScript1._jsonpCallback({greet:’hello, world’}) 。参数 {greet:’hello, world’} 正是要返回到浏览器的 JSON 对象。
response 参数就是从服务器端返回的 JSON 对象,服务器端的 JSON 对象终于成功的传递到浏览器了。前面介绍了这么多的机制都是为了使这个 JSON 对象安全返回到浏览器中。当然你可以在服务器端返回任何数据,比如直接返回一个字符串,但此时 response 就变成字符串了,当然也就不能再叫 JSONP 了,因为 JSONP 特指返回的是 JSON 对象。
dojo.io.script 对象中除了 get 函数之外,还有 attach,和 remove 两个函数
* attach: function(/*String*/id, /*String*/url)
创建动态 script 标签,标签的 id 由参数 id 指定,src 由 url 指定。
* remove: function(/*String*/id)
删除 id 代表 script 标签。
三种方式的比较
支持的 HTTP 请求类型 期望的输出 跨域访问
XHR Get, post, delete, put text, json, xml, javascript … N
iframe Get, post html N
script Get javascript Y
使用上述三种方法时需要遵循一条简单的原则:传送文件则 iframe,跨域访问则使用动态脚本,其余则选 XHR 框架。
Dojo 是一个分层的体系架构。最下面的一层是包系统,Dojo API 的结构与 Java 很类似,它把所有的 API 分成不同的包(package),当您要使用某个 API 时,只需导入这个 API 所在的包。包系统上面一层是语言库,这个语言库里包含一些语言工具 API,类似于 Java 的 util 包。再上一层是环境相关包,这个包的功能是处理跨浏览器的问题。
常用包介绍
Dojo 1.1.1 提供了上百个包,这些包分别放入三个一级命名空间:Dojo,Dijit 和 DojoX 。其中 Dojo 是核心功能包 , Dijit 中存放的是 Dojo 所有的 Widget 组件,而 DojoX 则是一些扩展或试验功能,DojoX 中的试验功能在成熟之后有可能在后续版本中移入到 Dojo 或 Dijit 命名空间中。
dojo.io 不同的 IO 传输方式。 script、IFrame 等等;
dojo.dnd 拖放功能的辅助 API 。
dojo.string这个包可以对字符串进行如下的处理:修整、转换为大写、编码、esacpe、填充(pad)等等;
dojo.date 解析日期格式的有效助手;
dojo.event 事件驱动的 API,支持 AOP 开发,以及主题 / 队列的功能;
dojo.back 用来撤销用户操作的栈管理器
dojo.colors 颜色工具包;
dojo.data Dojo 的统一数据访问接口,可以方便地读取 XML、JSON 等不同格式的数据文件;
dojo.fx 基本动画效果库;
dojo.regexp 正则表达式处理函数库;
dijit.forms 表单控件相关的 Widget 库;
dijit.layout 页面布局 Widget 库;
dijit.popup 这个包用于以弹出窗口方式使用 Widget ;
Dojo 版的 Hello World
<html>
<head>
<title>test</title>
<script type="text/javascript"
src="dojo_path/dojo/dojo.js"
djConfig="parseOnLoad: true"></script>
<style type="text/css">
@import "dojo_path/dijit/themes/tundra/tundra.css";
</style>
<script type="text/javascript">
dojo.require("dojo.parser");
dojo.require("dijit.form.TextBox");
dojo.require("dijit.form.Button");
function init()
{
dojo.connect(dijit.byId("mybutton").domNode,"onclick","login");
}
function login()
{
if( dijit.byId("myname").value=="goodguy" &&
dijit.byId("mypassword").value=="goodgoodstudy")
alert("Dojo World welcome you!");
else
{
dijit.byId("myname").setValue("");
dijit.byId("mypassword").setValue("");
alert("Dojo does not like you!");
}
}
dojo.addOnLoad(init);
</script>
</head>
<body class="tundra">
UserName:
<input type="text" length="20" id="myname" dojoType="dijit.form.TextBox"><br>
PassWord:
<input type="password" length="20" id="mypassword" dojoType="dijit.form.TextBox"><br>
<div id="mybutton" dojotype="dijit.form.Button">Submit</div>
</body>
</html>
djConfig="parseOnLoad: true" 表示在页面加载完成以后,启用 Dojo 的解析模块对页面中的 Dojo 标签属性(Dojo 标签属性是指由 Dojo 定义的一些标记,这些标记只有在被处理以后,才能为浏览器识别执行)进行解析。djConfig 是使用 Dojo 页面的一个全局配置参数。通过对这个参数不同的赋值,可以控制页面中 Dojo 的解析模块是否运行, Dojo 的调试模块是否工作等。
@import "dojo_path/dijit/themes/tundra/tundra.css" 表示引入 Dojo tundra 风格的层叠样式表。
dojo.require("dojo.parser") 表示引入 Dijit 的解析功能模块。该模块将会把 Dojo 标签属性替换成浏览器可以识别执行的标记。需要与 djConfig="parseOnLoad:true" 相区别的是,djConfig="parseOnLoad:true" 表示确定在页面加载完成以后执行解析功能,但解析功能模块的引入要靠 dojo.require("dojo.parser") 来实现。
dojo.require("dijit.form.TextBox") 和 dojo.require("dijit.form.Button") 表示引入 Dojo 风格的文本输入框和按钮的功能模块。
dojo.connect(dijit.byId("mybutton").domNode, "onclick", "login") 表示将按钮的点击事件和 login 函数联系起来,当点击 id 为 mybutton 的按钮时,执行 login 函数。
dijit.byId("myname").setValue("") 表示调用 id 为 myname 的 Dojo 文本框的 setValue 函数,将文本框里面的内容清为空。
<input type="text" length="20" id="myname" dojoType="dijit.form.TextBox"> 中的 dojoType="dijit.form.TextBox" 表示在页面中文本输入框是 Dojo 风格的。需要注意的是,通过声明 dojoType="dijit.form.TextBox" 这种方式来实现某些 Dojo 功能的使用,其表现形式上如同声明一个 HTML 标签的属性(如同 width="10px"),因此在本文中称其为 Dojo 标签属性。在页面加载完成以后,Dojo 的解析模块会将 Dojo 标签属性转化为浏览器能够识别执行的标记。
FireDebug
console.log("My test num is %d",num);
console.debug("I am debug");
console.info("I am info");
console.warn("I am warn");
console.error("I am error");
在处于 HTML 窗口的模式下,点击 Edit 按钮,将切换查看模式到编辑模式。需要注意的是,在使用 Edit 模式前,最好如图 11 先提前选中页面的 body 代码区块
XHR 框架与 Dojo
XmlHttpRequest 对象是 Dojo 中的 XHR 框架的基础,目前主流浏览器都已经支持此对象,但是不同浏览器上实现方式却不一样,IE5、IE6 采用 ActiveX 对象的方式,Firefox 和 Safari 都实现为一个内部对象,所以创建 XHR 对象之前需要先测试浏览器的类型
function createXHR(){
if (window.XMLHttpRequest) { // Non IE
return new XMLHttpRequest();
}
else if (window.ActiveXObject) { // IE
return new ActiveXObject("Microsoft.XMLHTTP");
}
}
XHR 对象创建方式不一致是 Dojo 的 XHR 框架诞生的一个原因,更重要的原因是原始 XHR 对象还不够强大,有些方面不能满足开发的需要:首先 XHR 对象支持的返回类型有限,原始 XHR 对象只有 responseText 和 responseXML 两个属性代表返回的数据,重要的数据交换格式 JSON 就不被支持;其次不能设置 HTTP Request 的超时时间,设置超时时间可以让客户端脚本控制请求存在的时间,而不是被动的等待服务器端的返回。
基于这些问题,Dojo 组织提供了一组函数来支持各种 HTTP 请求,包括 xhrGet,rawXhrPost,xhrPut,rawXhrPut,xhrPut,xhrDelete,这几个函数与 HTTP 协议中的四种请求是一一对应的,HTTP 四种请求是:Get(读取),Post(更新),Put(创建),Delete(删除)。 Dojo 组织的发起者 Alex Russell 把这些跟 XHR 对象相关的函数放在一起称为 XHR 框架
使用 xhrGet 请求资源
xhrGet 是 XHR 框架中最重要的函数,使用它即可以请求服务器上的资源,只要从服务器返回的是字符数据流即可。
function helloWorld(){
dojo.xhrGet({
url: "helloworld.txt" ,
handleAs: "txt",
content: {username:useranme,password:password},
load: function(response, ioArgs){alert(response);},
error: function(error, ioArgs){alert(error.message);}
});
}
dojo.xhrGet 的参数是一个 JSON 对象,JSON 对象由很多的属性 / 值对组成,其中的值可以是任意类型的数据: 整形、字符串、函数……甚至是 JSON 对象
* url:请求的服务器资源 url,url 标识的只能是文本文件,而不能是二进制文件。
*handleAs:返回的数据类型,可以是 text(默认)、json、json-comment-optional, json-comment-filtered、javascript、xml 。 Dojo 将根据 handleAs 设置的数据类型对从服务器返回的数据进行预处理,再传给 load 属性指向的回调函数。
* load:在请求的资源成功返回之后调用回调函数。
* error:在 http 请求出错之后调用回调函数。
* content:传递参数,params为对象
函数包含两个参数:response 和 ioArgs
response:表示从服务器端返回的数据,Dojo根据 handleAs设置的数据类型进行了预处理。
ioArgs: 这是一个对象,包含调用 xhrGet 时使用的一些参数。之所以把这些信息放在一个对象中并传递给回调函数是为了给回调函数一个执行“上下文”,让回调函数知道自己属于哪个 HTTP 请求,请求有哪些参数,返回的数据是什么类型等。这些信息在调试程序时特别有用。
*ioArgs.url:请求的 URL,与调用 xhrGet 时设置的值一样。
ioArgs.query:请求中包含的参数, URL 中“ ? ”后面的内容。
ioArgs.handAs:如何对返回的数据进行预处理,与调用 xhrGet 时设置的值一样。
ioArgs.xhr: xhrGet 函数使用的 XHR 对象。
handleAs 预处理方式
text 默认值,不对返回的数据做任何处理
xml 返回 XHR 对象的 responseXML
javascript 使用 dojo.eval 处理返回的数据,返回处理结果
json 使用 dojo.fromJSon 来处理返回的数据,返回生成的 Json 对象
假设 handleAs 被设置为“ json ”,按照上表,则 load 回调函数的参数 response 为 JSON 对象。如果 handleAs 不是“ json ”,还能不能生成 JSON 对象呢?答案是肯定的,可以把 handleAs 设为“ text ”,那么返回的是普通的字符串,只要字符串是 JSON 对象的文本形式,则可以简单地使用 eval() 函数把它转换为真正的 JSON 对象,而不再需要任何其他的 API 完成转换工作
function submitForm(){
dojo.xhrGet({
form: "loginForm" ,
handleAs: "text" ,
handle: handler,
content: { pwd: "modified" },
sync: false
});
return false;
}
<form id="loginForm" οnsubmit="return submitForm();" action="data.php">
<input type="textfield" name="id" />
<input type="password" name="pwd" />
<input type="submit" name="sub" value="submit" />
</form>
form:需要异步提交的表单的 id 。只有把它设置成想要异步提交的表单的 id,并在这个表单的 onsubmit 事件中调用自定义的 submitForm() 函数,才能真正做到异步提交。注意在 submitForm 函数中最后返回了 false,这是为了阻止系统默认的表单提交事件,让表单提交异步进行,如果不返回 false,会引起页面跳转。
handle:handle 也是一个回调函数,在 xhrGet 返回时被调用,正常和错误返回的情况都能处理,可以说是 load 和 error 的混合体,但优先级比 load 低,只有在没有设置 load 时才起作用。
content:在这里可以修改来自表单的信息
sync:设置同步还是异步提交。默认是异步提交
xhrPost 发送的是 Post 请求
xhrPost 一般用来发送表单数据,当然 xhrGet 也可以做到,区别是 xhrPost 把表单数据封装在 HTTP 请求的 Body 部分。在服务器端只能使用取 POST 信息的方法获取这些表单数据
iframe
使用 dojo.io.iframe 同样可以跟服务器交互,但是它采用了与 XHR 对象不同的实现思路
function iframeTest(){
dojo.io.iframe.send({
form: dojo.byId('testForm') ,
url: "data.php" ,
method: "post" ,
handleAs: "html" ,
load: function(response, ioArgs) {
alert(response);
}
});
}
dojo.io.iframe 的使用方式、参数与 xhrGet 非常相似。其中,from,url,handleAs,load 等在 xhrGet 中也存在,唯一不同的是 method,method 表示 dojo.io.iframe 将以何种 HTTP Method 来发送请求
handleAs 参数,dojo.io.iframe 一般使用 html,因为在 iframe 中存的其实是另一个 HTML 页面。如果 handleAs 设置为其他值,像 json,text 等,则在服务器端须使用 <textarea></textarea> 把要返回的数据包装起来,比如 hellow, world 要被包装成 <textarea>hello,world</textarea>,所以最后存在 iframe 中的是一个文本域(textarea),这个文本域包含了从服务器端返回的数据。这么做的原因很简单,就是为了保持从服务器返回的数据“一成不变”,因为任何字符数据都可以“安全的”放在 HTML 页面的文本域中。想像一下,我们是不是可以在文本域中输入各种字符! dojo.io.iframe 会对 textarea 包装的数据进行处理:首先把 textarea 标签去掉,然后把数据转换为 handleAs 指定的类型传递给 handle 中设置的回调函数。
dojo.io.iframe 首先会创建一个隐藏的 iframe 并插入到父页面的最后,然后设置此 iframe 的 src 属性为dojo-module-path/resources/blank.html(dojo-module-path 指 dojo 包所在的目录),iframe 页面的 onload 事件的处理函数被设置为父窗体的回调函数。接下来就是在 iframe 页面中发送请求,并接收服务器的响应。当 iframe 接收到服务器的反馈并加载完之后,父窗体的回调函数即被调用。
动态 Script,跨域访问
XHR 框架中的函数功能强大,使用方便。但是 XHR 框架的函数有一问题就是不能跨域访问,浏览器不允许 XHR 对象访问其他域的站点。比如有一个页面属于 a.com,在这个页面中使用 XHR 对象去访问 b.com 的某一页面,这是被禁止的。如果使用 XHR 对象来做跨域访问一般需要服务器端的程序做“中转”,先由服务器端的程序获取其他域的数据,然后浏览器再使用 XHR 对象从服务器上获取这些数据,这种方式即增加了服务器端的开销,浏览器端的效率也不高。
有没有方法直接在浏览器中实现跨域访问呢?当然有,它就是 script 标签,使用 script 标签可以引用本域或其他域的文件,只要这些文件最后返回的是 javascript 。返回的 javascript 会立即在浏览器中执行,执行结果存储在本地浏览器。这一点很重要,它使得各站点可以通过 javascript 来发布自己的服务,像 google 的很多的服务都是通过这种方式提供的。 script 标签不仅可以静态添加到页面中,也可以被动态插入到页面中,而且通过 DOM 操作方式动态插入的 script 标签具有与静态 script 标签一样的效果,动态 script 标签引用的 javascript 文件也会被执行(注意:通过 innerHTML 方式插入的 javascript 是不会被执行的,这一点在前文已经介绍过)。
function createScript() {
var element = document.createElement("script" );
element.type = "text/javascript" ;
element.src = url;
document.getElementsByTagName("head" )[0].appendChild(element);
}
动态创建 script 标签的例子,script 标签即可以放在页面的 head 部分,也可以放在 body 部分
动态插入 script 标签的一个问题是如何判断返回的 Javascript 执行完了,只有在执行完之后才能引用 Javascript 中的对象、变量、调用它中间的函数等。最简单的方法是“标志变量法”,即在脚本中插入标志变量,在脚本最后给这个变量赋值,而在浏览器的脚本中判断这一变量是否已经被赋值,如果已经被赋值则表示返回的脚本已经执行完。但是这种方法缺点也很明显,首先如果一个页面有很多的动态 script 标签,而每个 script 标签引用的 javascript 都使用一个标志变量,那就有很多变量需要判断,而且这些变量的命名可能冲突,因为这些 Javascript 是由不同的组织、公司提供的,难保不产生冲突。另外在浏览器本地脚本中需要轮询这些变量的值,虽然可以实现,但实在不是高明的做法。目前被广泛使用的是另一种方法:JSONP(JSON with Padding)。 JSON 表示返回的 Javascript 其实就是一 JSON 对象,这是使用 JSONP 这种方式的前提条件。 Padding 表示在 JSON 对象前要附加上一些东西,究竟是什么呢?请往下看!
JSONP 的思路很简单,与其让浏览器脚本来判断返回的 Javascript 是否执行完毕,不如让 Javascript 在执行完毕之后自动调用我们想要执行的函数。是不是想起了学习面向对象设计中的“依赖倒置”原则时的那句名言:“Don't call us, we will call you ”。使用 JSONP 这种方法,只需要在原来的 Javascript 引用链接上加上一个参数,把需要执行的回调函数传递进去。请看下面的两个 script 标签。
<script src= ” http://url/js.php?parameter= … . ” >
<script src= ” http://url/js.php? parameter= … .&callbackname=mycallback” >
如果 http://url/js.php?parameter= … . 返回的是 JSON 对象 {result:“hello,world”} , 那么第二个 script 标签返回的则是 mycallback({result: ” hello, world ” }),这一函数将在写入到浏览器后立即被执行,这不就实现了在 Javascript 执行完之后自动调用我们需要执行的回调函数了吗?
介绍了这么多动态脚本的背景知识,终于来到了 Dojo 对动态脚本的支持上。 Dojo 即支持标志变量法,也支持 JSONP 方式。
<html>
<head>
<script src="../dojo/dojo/dojo.js" djConfig="parseOnLoad: true"></script>
dojo.require("dojo.io.script" );
</head>
<body>
<script>
dojo.io.script.get({
url:"data.php" ,
checkString:"test_01" ,
handle:function(response, ioArgs) {
greetFromServer("hello, world");
}
});
</script>
</body>
</html>
checkString 正是"标志变量法"的关键,checkString 表示从服务器返回的 javascript 需要定义的变量
服务器输出了一段javascript,在这段javasript的最后给变量 test_01 赋值
dojo.io.script.get 函数的 handle 指向的回调函数又调用了这段 javascript 中定义的函数 greetFromServer() 。
只有在 test_01 被赋值后,调用 greetFromServer 才是安全的。
dojo.io.script.get 函数也支持 JSONP 方式,当 dojo.io.script.get 函数的参数对象使用了 callbackParamName 属性时,表示它工作在 JSONP 方式下。 callbackParamName 表示在 url 中添加回调函数名的参数名称,有点拗口,但是看了下面 dojo.io.script.get 函数在页面中动态创建的 script 标签一切就都清楚了,最终出现在 URL 中的是 callbackName,而不是 callbackParamName 。
<script src="data2.php?callbackName=dojo.io.script.jsonp_dojoIoScript1._jsonpCallback" >
<html><head>
<script src="../dojo/dojo/dojo.js"
djConfig="parseOnLoad: true"></script>
dojo.require("dojo.io.script" );
</head>
<body>
<script>
dojo.io.script.get({
url:"data2.php",
callbackParamName: "callbackName" ,
handle: function(response, ioArgs) {
document.write(response.greet);
}
});
}
</script>
</body></html>
Dojo 会自动创建一个名为 dojo.io.script.jsonp_dojoIoScript1._jsonpCallback 的 javascript 函数,这个函数其实什么都不做,只是作为一个回调函数传给服务器端程序。 php 的服务器端程序如清单 12 所示,callbackName 像浏览器和服务器之间的一个“信令”,服务器端必须返回对 callbackName 所代表的函数的调用,因为 Dojo 会检查它是否被调用过。
所以服务器端返回的是 dojo.io.script.jsonp_dojoIoScript1._jsonpCallback({greet:’hello, world’}) 。参数 {greet:’hello, world’} 正是要返回到浏览器的 JSON 对象。
response 参数就是从服务器端返回的 JSON 对象,服务器端的 JSON 对象终于成功的传递到浏览器了。前面介绍了这么多的机制都是为了使这个 JSON 对象安全返回到浏览器中。当然你可以在服务器端返回任何数据,比如直接返回一个字符串,但此时 response 就变成字符串了,当然也就不能再叫 JSONP 了,因为 JSONP 特指返回的是 JSON 对象。
dojo.io.script 对象中除了 get 函数之外,还有 attach,和 remove 两个函数
* attach: function(/*String*/id, /*String*/url)
创建动态 script 标签,标签的 id 由参数 id 指定,src 由 url 指定。
* remove: function(/*String*/id)
删除 id 代表 script 标签。
三种方式的比较
支持的 HTTP 请求类型 期望的输出 跨域访问
XHR Get, post, delete, put text, json, xml, javascript … N
iframe Get, post html N
script Get javascript Y
使用上述三种方法时需要遵循一条简单的原则:传送文件则 iframe,跨域访问则使用动态脚本,其余则选 XHR 框架。