- 浏览: 415155 次
- 性别:
- 来自: 广州
文章分类
最新评论
-
liyuanhoa_:
...
struts2.0中struts.xml配置文件详解 -
chenmingde:
...
Velocity应用(一) -
weizhikai_ai:
第二十六,当一个线程进入一个对象的一个synchronized ...
Java常见面试题(含答案) -
Aurora_lr:
...
Spring宠物商店学习笔记(一) - -
zs911zs:
all copy from http://www.iteye ...
Mule入门文档
拖拽,是目前较为常见的 Ajax 技术。当前以 Ajax 技术构建的电子商务网站往往都采用拖放商品到购物车这种直观的方式代替以往繁复的操作来实现客户对商品的选购。掌握 Dojo 系列 的这一篇文章将以作者亲身的开发经历为基础,详细讲述如何使用 Dojo 所提供的功能模块开发出各种各样的拖动和拖放效果。此外,还详细叙述如何对 Dojo 的拖拽功能进行修改和配置,以及对拖拽的实现原理进行了解释。
Dojo 支持的两种拖拽方式
在开始尝试了解实现 Dojo 拖拽效果的使用方法以前,首先必须明确拖拽具有两种截然不同的表现效果。
第一种表现效果是图标被拖拽到哪里,其就会被直接放到哪里,这个拖拽效果是图标完全紧跟拖拽的动作,与每一个拖拽动作的运动轨迹完全契合,这种效果被称为“拖动”。第二种表现效果是当图标被拖拽到一个地方,松开鼠标的时候,图标会以当前位置为基础而以其它图标为参照系进行位置的自动调整。这种效果被称为“拖放”。
Dojo 的拖动
“拖动”与“拖放”相比较,原理更加容易理解,使用更加简单。而且更加贴近于人们直观印象中的“拖拽”效果。
最简单的拖动实例
要在 Dojo 的支持下,实现拖动的效果所需要的只是使用 Dojo 所提供的 Dojo 标签属性标注出希望实现拖动效果的实体。简单的说,就是如果希望一个实体可以拖动,则只需要在这个实体的标签里面加上 dojoType=“dojo.dnd.Moveable”这个属性。例如要实现一个表格的拖动,则只需要在这个表格的声明标签“<table>”里加上 dojoType=“dojo.dnd.Moveable”这个属性。甚至就是在“<tr>”或“<td>”标签中加上 dojoType=“dojo.dnd.Moveable”,也可以实现对应实体的拖动效果。
清单 1
<script type="text/javascript">
dojo.require("dojo.dnd.move"); //引入Dojo的拖动功能模块
dojo.require("dojo.parser"); //引入解析Dojo标记的功能模块
</script>
<table dojoType="dojo.dnd.Moveable">
<tbody><tr><td>Haha, I am a good guy.</td></tr></tbody>
</table>
<!--引入dojoType="dojo.dnd.Moveable",让上面的表格可以拖动-->
需要注意的是静态创建可拖动实体需要引入 dojo.require("dojo.parser") 。
动态实现可拖动实体
在清单 1 中,通过在一些实体的标签里面加上相应的 Dojo 标签属性来实现可拖动实体的创建。这种静态实现可拖动实体的方法简单明了。但是在更多的情况下,往往需要根据一些实际情况运行得到的数据来动态的创建可拖动实体。在这种情况下,静态实现可拖动实体的方法就不能满足当下的需求。值得庆幸的是 Dojo 对于所有静态实现的方法都基本对应有一套相应的动态实现方法。
清单 2
<script type="text/javascript" src="dojo_path/dojo/dnd/move.js"></script>
<script type="text/javascript">
dojo.require("dojo.dnd.move");
var m1;
var init = function(){
m1 = new dojo.dnd.Moveable("bad",{});//申明 id 为 "bad" 的实体为可拖动实体
};
dojo.addOnLoad(init);// 表示在页面加载完成以后,执行 init 函数
</script>
<div id="bad">
You can cop me "Haha, I am a bad guy."
</div>
需要注意的是 dojo.dnd.Moveable("bad",{}) 中的大括号用来设置可拖动实体“bad”的一些与拖动相关的属性,目前可以暂时设为空,则不设置任何与拖动相关的属性。在后面的讲述中,一些相关的重要属性将被逐步介绍。
拖动柄
如果运行清单 1 和清单 2 中的代码,然后尝试在其页面中使用鼠标去选择可拖动实体中的内容。就会发现,无论使用何种方法都无法选择可拖动实体中的内容,当然就更谈不上复制可拖动实体中的内容了。
仔细分析无法选择可拖动实体中内容的原因,就会发现如果要选择页面中的某一部分内容,其动作步骤为,按住鼠标左键不放,然后拖动鼠标选择一块区域作为确定选择的内容;而如果要拖动一个可拖动实体,其动作步骤也为,按住鼠标左键,然后拖动鼠标引起可拖动实体的移动。
因此如果让某个实体具有了可拖动的功能,则当对这个实体点下鼠标左键,并拖动鼠标时,就浏览器看来,其将不能理解这个动作的目的是要拖动该实体还是选择该实体里面的内容。因为这两个具有不同含义的动作就其动作本身来说是一模一样的,浏览器没有办法对这两个动作进行区分。
但现实的情况往往需要一个实体既可以被拖动,又可以被选择其内部所包含的内容。 Dojo 通过给可拖动实体增加一个拖动柄,实现了选择内容动作和拖动实体动作的区分。
声明拖动柄的方法为在声明可拖动实体的时候,在可拖动实体的标签中再加上一个除 dojoType 之外的另外一个 Dojo 标签属性 handle= “”,handle 后面的双引号中需要填入的是作为拖动柄部分的那个实体的 id 值。
清单 3
<div id="bad" dojoType="dojo.dnd.Moveable" handle="dragme">
<!--- 声明 id 为 "dragme" 的实体为拖动柄 ->
<div id="dragme">You can drag it by me</div>
<! —上面是拖动柄的实体 -->
You can cop me "Haha, I am a bad guy."
</div>
如果要动态声明上面的“拖动柄”,则需要在页面加载完成以后通过执行 dojo.dnd.Moveable("bad", {handle: "dragme"}) 来实现。 handle 后面所跟的值为将成为可拖动实体“bad”拖动柄的实体 id 。
在清单 2 中,没有设置可拖动实体的任何属性。在清单 3 中设置了与拖动相关的其中一个属性——拖动柄。
限制可拖动实体拖动的范围
Dojo 限制用户可拖动实体活动范围的方法有两种。第一种方法为在生成可拖动实体的时候,给其设置一个逻辑上的活动范围空间;第二种方法是依据页面的一个实体,建立可拖动实体的活动范围空间。需要注意的是,在第一种方法中的活动范围空间是一个不存在相应页面实体的定义,即这个所谓的活动范围空间在页面是实际不存在的。首先要讨论的是第一种方法的使用。
清单 4
<script type="text/javascript">
dojo.require("dojo.dnd.move");
dojo.require("dojo.parser");
var init = function(){
new dojo.dnd.move.boxConstrainedMoveable("aa", {box: {l: 100, t: 100, w: 500,
h: 500}});// 定义可拖动实体 "aa" 和拖动的范围 box
new dojo.dnd.move.boxConstrainedMoveable("bb", {box: {l: 100, t: 100, w: 500,
h: 500}, within: true});
};
dojo.addOnLoad(init);
</script>
<div id="aa">aaa</div>
<div id="bb">bbb</div>
<div id="cc" dojoType="dojo.dnd.move.boxConstrainedMoveable"
box="{l: 100, t: 100, w: 500,h: 500}">ccc</div>
<div id="dd" dojoType="dojo.dnd.move.boxConstrainedMoveable"
box="{l: 100, t: 100, w: 500, h: 500}" within="true">ddd</div>
在清单 4 中,将动态定义和静态定义的方法都对比着写了出来,前两个可拖动实体是使用的动态定义的方法,后两个实体所实现的效果和前面一样但使用的是静态定义的方法。
实现可拖动实体活动范围的第一种方法是通过定义一个实际不存在的矩形框来作为可拖动实体的“监狱”。例如在清单 4 可拖动实体“aaa”中,{box: {l: 100, t: 100, w: 500, h: 500}} 是指在“aaa”外建立一个盒子区域,该盒子区域为“aaa”的活动范围。“l: 100, t: 100”表示该盒子区域距页面左边界和上边界各为 100px,“w: 500”表示盒子区域的宽度为 500px,“h: 500”表示盒子区域的高度为 500px 。需要注意的是盒子区域的 l 和 t 始终是以页面边界为标准。
图 1
图 1 显示出了清单 4 中可拖动实体“aaa”活动范围的“盒子区域”。
可拖动实体“within”的属性可设为“true”或者“false”,如果设为“true”的话表示“可拖动实体”的任何一个部分都不能超出其盒子区域的范围,如果设为“false”表示只要拖动实体的左上角不超出盒子区域的范围便为合法操作。
图 2 中可拖动实体的“within”属性为“false”,因此当前的位置是合法的。但是可能在某些时候,需要使用到更细致的活动范围控制,或者需要根据可拖动实体的 DOM 父节点确定其活动范围,而不是距页面的边距。对于这些情况,可以采用 Dojo 提供的第二种限制用户可拖动实体活动范围的方法。
Dojo 限制用户可拖动实体活动范围的第二种方法是在所有可拖动实体的外面创建一个 DOM 父节点,然后利用该父节点的“Layout”属性将可拖动实体的运动范围限制在父节点内。以此为基础,依据父节点四种不同的“Layout”属性分为“margin”类型的限制,“border”类型的限制,“padding”类型的限制,和“content”类型的限制。 ( “margin”,“border”,“padding”和“content”是 Web 页面中一个实体的 CSS 属性 ) 。
清单 5
<style type="text/css">/* 定义四个可拖动实体父节点的 Layout 结构 ,
对可拖动实体拖动范围有影响的属性包括 "border","padding" 和 "margin"*/
.parent
{
background: #BFECFF;
border: 10px solid lightblue;
width: 500px;
height: 500px;
padding: 10px;
margin: 10px;
}
</style>
<script type="text/javascript">
dojo.require("dojo.dnd.move");
dojo.require("dojo.parser");
function init()
{
new dojo.dnd.move.parentConstrainedMoveable("bad", {area: "padding", within: true});
<!-- 该可拖动实体的范围不能超过父节点的 padding 区域边界 -->
new dojo.dnd.move.parentConstrainedMoveable("good", {area: "content", within: true});
<!-- 该可拖动实体的范围不能超过父节点的 content 区域边界 -->
};
dojo.addOnLoad(init);
</script>
<div class="parent">
<div style="background:red" dojoType="dojo.dnd.move.parentConstrainedMoveable"
area="margin" within="true">I am restricted within my parent's margins.</div>
<!-- 该可拖动实体的范围不能超过父节点的 margin 区域边界 -->
<div style="background:green" dojoType="dojo.dnd.move.parentConstrainedMoveable"
area="border" within="true">I am restricted within my parent's border.</div>
<!-- 该可拖动实体的范围不能超过父节点的 border 区域边界 -->
<div style="background:blue" id="bad">I am restricted within my parent's paddings.</div>
<div style="background:pink" id="good">I am restricted within my parent's content.</div>
</div>
在清单 5 中声明了四个可拖动实体,其中两个是以静态 Dojo 标签属性的方式声明,另外两个是以动态的方式声明。
捕获拖动中的事件
Dojo 通过事件订阅发布机制实现了方便易用的捕获拖动事件 API 。 Dojo 在拖动开始、结束和过程中会发出一些消息,如果希望监测到页面内可拖动实体的拖动开始事件和拖动结束事件可以通过订阅“/dnd/move/start”和“/dnd/move/stop”来实现
清单 6
<script type="text/javascript">
dojo.require("dojo.dnd.move");
dojo.require("dojo.parser");
dojo.addOnLoad
(
function()
{
dojo.subscribe("/dnd/move/start",function(node){// 订阅开始拖动事件
console.debug("Start move", node)
}
);
dojo.subscribe("/dnd/move/stop",function(node){// 订阅拖动结束事件
console.debug("Stop move", node);}
);
}
);
</script>
<div dojoType="dojo.dnd.Moveable">Haha, I am a bad guy.</div><br/><br/>
<p dojoType="dojo.dnd.Moveable">Haha, I am boy</p>
清单 6 的运行效果在 Firebug 下最易于观察。在 Firebug 的 Console 窗口中,每一次拖动可拖动实体,就会在 Console 窗口中有相对应的输出。
如果在某些时候,只希望监测某一个可拖动实体的事件,而不是所有可拖动实体的事件。则可以采用将可拖动实体的事件和一个函数连接起来以实现只监测某个特定可拖动实体的事件。
清单 7
<script type="text/javascript">
dojo.require("dojo.dnd.move");
dojo.require("dojo.parser");
dojo.addOnLoad(
function(){
var boy = new dojo.dnd.Moveable("boy");
dojo.connect(boy, "onMoveStart", function(mover){
console.debug("Start moving boy", mover);
});
dojo.connect(boy, "onMoveStop", function(mover){
console.debug("Stop moving boy", mover);
});// 通过事件连接捕获了可拖动实体的事件。 "mover" 是与可拖动实体相关的对象,里面包含了相关的属性和方法
});
</script>
<body><p id="boy">Haha, I am boy</p></body>
在清单 7 中将可拖动实体“boy”和一个特定的输出函数连接了起来。这样在捕获可拖动实体“boy”的时候,不会“惊动”其它可拖放实体。
Dojo 的拖放
拖放是一个复杂而又充满魅力的功能。 Dojo 支持拖放功能的原因,就是因为 Dojo 的使用者在实际项目中开发高级拖拽操作功能的时候提出了这样的需求。
最简单的拖放实例
请读者将下面的代码在自己的机器上运行,并尝试感受拖放的实际效果。
清单 8
<html>
<head>
<title>test</title>
<script type="text/javascript" src="dojo_path/dojo/dojo.js"
djConfig="isDebug:true, parseOnLoad: true"></script>
<style type="text/css">
@import "dojo_path/dojo/resources/dnd.css";
</style>
<script type="text/javascript">
dojo.require("dojo.dnd.Source");
dojo.require("dojo.parser");
</script>
</head>
<body>
<table><tbody><tr>
<td>
<div dojoType="dojo.dnd.Source"
style="border: 1px solid black; height: 200px width: 300px;">SOURCE
<div class="dojoDndItem" style="height:50px;width:100%;background-color:blue">
<div class="bluesquare">BLUE</div>
</div><!-- 定义一个颜色为蓝色的矩形可拖放实体 -->
<div class="dojoDndItem" style="height:50px;width:100%;background-color:red">
<div class="redsquare">RED</div>
</div><!-- 定义一个颜色为红色的矩形可拖放实体 -->
</div>
<!-- 定义了一个容器,该容器为可拖放实体在拖放开始前,可拖放实体存放的地方。将其名定义为源容器 -->
</td>
<td>
<div dojoType="dojo.dnd.Target" style="border: 1px solid black; height: 200px;
width: 300px; ">TARGET</div>
<!-- 定义了一个容器,该容器为可拖放实体拖放后,可拖放实体存放的地方。将其名定义为目标容器 -->
</td>
</tr></tbody></table>
</body>
</html>
由清单 8 可以得知,对于拖放来说,可拖放实体必须是从一个容器中拖放到另外一个容器中。可拖放实体是不能存在于容器之外的任何地方。如果可拖放实体从“容器 A”出来,放入“容器 B”中,则一般习惯上称 A 为源容器,B 为目标容器。
被拖放的实体称之为“可拖放实体”。要实现可拖放实体的拖放,页面中必须要有“拖放源容器”和“拖放目标容器”。拖放源容器为起初可拖放实体存放的地方,而拖放目标容器为可拖放实体拖起后可以放的地方。
如果运行清单 8 中的代码,就会发现在拖起可拖放实体的时候,有一个与“原可拖放实体”很相似的小图标在随着鼠标移动,其被称为可拖放实体的“替身”。“替身”的主要作用有两个,第一是指明目前操作的实体是哪一个或是有哪几个实体被操作,第二是作为原实体的替身,帮助用户判断目前选中可拖放实体的鼠标所处的位置为哪里,判断该区域是否为合法的拖放区域。
替身是由“原可拖放实体”转换而来,包含了“原可拖放实体”的主要外貌特征,同时替身的细微化,大大减小了如果采用“原拖动实体”作为标识而带来的系统负担和提高了标识的精确度。
图 3
拖动的本质是可拖动实体象素位置的变化,而拖放的本质是一个页面 DOM 结构的变化。实体象素位置的变化,可以只通过修改这个实体的属性来实现,不用和页面的任何其它部分打交道。但是对于拖放,当将一个可拖放实体从源容器拖放到目标容器时,就是将该可拖放实体先从源容器的 DOM 节点上删除,再在目标容器的 DOM 节点上加上可拖放实体。
因此可拖放实体的存在,在拖放动作前必须依赖于一个父 DOM 节点,这就是源容器,拖放动作后也必须依赖于一个新的父 DOM 节点,这就是目标容器。
在某些情况下,可能需要的是能在几个容器之间将可拖放实体不断的拖来拖去。那么所做的修改是只需要将源容器和目标容器都用 dojoType= “dojo.dnd.Source”来进行声明就能实现可拖放实体从目标容器拖回源容器的操作。
动态生成源容器、目标容器和可拖放实体
动态声明源容器和目标容器的方法比较简单,动态创建源容器的方法为 var foo=new dojo.dnd.Source(Node, Params),而动态创建目标容器的方法为 var foo=new dojo.dnd.Target(Node, Params) 。在声明源容器的方法中,Node 为要声明为源容器的 DOM 节点 id,Params 所代表的是一些用来确定容器相关属性的参数。
清单 9
function init()
{
mysource = new dojo.dnd.Source("mysource",{});// 存放可拖放实体的 " 源容器 "
mytarget = new dojo.dnd.Target("mytarget",{});// 存放可拖放实体的 " 目标容器 "
}
<table><tbody><tr>
<td>
<div id="mysource" style="border: 1px solid black; height: 200px; width: 300px;">SOURCE
<div class="dojoDndItem" style="height:50px;width:100%;background-color:blue">
<div class="bluesquare">BLUE</div>
</div><!-- 定义一个颜色为蓝色的矩形可拖放实体 -->
<div class="dojoDndItem" style="height:50px;width:100%;background-color:red">
<div class="redsquare">RED</div>
</div><!-- 定义一个颜色为红色的矩形可拖放实体 -->
</div><!-- 定义了一个容器,该容器为可拖放实体在拖放开始前,可拖放实体存放的地方。
将其名定义为源容器 -->
</td>
<td>
<div id="mytarget" style="border: 1px solid black; height: 200px; width: 300px;">
TARGET</div>
</td>
</tr></tbody></table>
在清单 9 中动态声明了源容器和目标容器。由清单 9 中观察可知,容器的声明是建立在已经存在的页面实体基础之上的。
清单 10
function init()
{
mysource = new dojo.dnd.Source("mysource");// 存放可拖放实体的 " 源容器 "
mysource.insertNodes(false,["Do you love me?","Good good study","<div
style='height:50px;width:100%;background-color:blue'></div>"]);
mytarget = new dojo.dnd.Target("mytarget");// 存放可拖放实体的 " 目标容器 "
}
<table><tbody><tr>
<td>
<div id="mysource" style="border: 1px solid black; height: 200px; width:300px;">
SOURCE</div>
<!-- 定义了一个容器,该容器为可拖放实体在拖放开始前,可拖放实体存放的地方。
将其名定义为源容器 -->
</td>
<td>
<div id="mytarget" style="border: 1px solid black;
height: 200px; width: 300px; ">TARGET</div>
</td>
</tr></tbody></table>
在清单 10 中通过 dojo.dnd.Source.insertNodes 来将动态声明的字符串化的实体插入到“源容器”中。如果回顾一下前面关于拖放基本原理的内容,就可以知道拖放的过程就是删除一个 DOM 节点和重新创建一个 DOM 节点的过程。删除一个 DOM 节点对于不同的应用没有太大的差异性,但是创建一个 DOM 节点对于不同的应用情况可能就需要不同的创建方式。
在大多数情况下,拖放后在目标容器里重建一个可拖放实体的 DOM 节点是调用 Dojo 的一个默认构造函数。这个构造函数能够在目标容器内完全重建一个与源容器一模一样的可拖放实体。
但如果希望可拖放实体在拖放入目标容器以后发生变化,与在源容器中的可拖放实体不一样,则可以通过创建自己的构造函数来实现。
如果要创建自己的构造函数,首先要在构建源容器和目标容器的时候,指明自己要创建的构造函数的函数名。
dojo.dnd.Source("mysource",{creator: sourcenodecreater, copyOnly: false}) 表示创建存放可拖放实体的“源容器”。 dojo.dnd.Target("mytarget",{creator: targetnodecreater, copyOnly: false}) 表示创建存放可拖放实体的“目标容器”。而“creator: sourcenodecreater” 表示源容器中构建可拖放实体的函数名为“sourcenodecreater”,“creator: targetnodecreater”表示目标容器中构建可拖放实体的函数名为“targetnodecreater”。
首先来创建一个最简单的名为“sourcenodecreater”的构造函数。
清单 11
function sourcenodecreater(data, hint)
{
var myitem = dojo.doc.createElement("div");
myitem.id = dojo.dnd.getUniqueId();
myitem.innerHTML = data;
return {node: myitem, data: data};
}
注意清单 11 中源容器的构造函数“sourcenodecreater”只是一个例子,但其包含了一个构造函数最基本的要素。在这里只是为了阐述清楚最基本的原理。具体的功能和如何构造出所需要的可拖放实体,需要根据需求来进行分析设计。
对于“sourcenodecreater”函数所接受的两个参数,“data”所代表的是接受到的关于可拖放实体的一些特征数据。因为可拖放实体是动态构造的,所以在很多情况下,可拖放实体要根据系统前面所传来的数据来构造相应的可拖放实体,而“data”所包含的就是这些用来构造可拖放实体的数据。“hint”是与替身有关的参数。
var myitem = dojo.doc.createElement("div") 表示创建一个 div 节点。可拖放实体将在这个节点上创建。
myitem.id = dojo.dnd.getUniqueId() 表示将会给可拖放实体一个唯一的 id 。
dojo.dnd.getUniqueId() 可以在一次系统运行中每次产生一个绝对独一无二的 id 。因为可拖放实体的拖放是 DOM 节点的销毁和重建,因此,当一个可拖放实体的拖放完成以后,其 id 将会发生变化。
myitem.innerHTML = data 是用来构建可拖放实体的实际效果,目前这里只是简单的将数据作为字符串在可拖放实体中展现。
return {node: myitem, data: data} 表示在上面的工作完成以后,将创建的节点和创建节点所使用的数据返回给 inserNode 函数,由其完成将该可拖放实体插入到 DOM 树中的相应位置。
在实际的情况中,往往需要在一个可拖放实体拖入目标容器后发生变化。例如在线购买东西时,选中希望购买的东西,将其拖入购物车后,一般代表商品的图片将会变小许多。那么就其前面所了解的,在可拖放实体进入目标容器的时候,将调用“targetnodecreater”函数来构造在目标容器中的实体。那么如果希望其发生变化的话,就必须得在“targetnodecreater”上做些文章。比如希望任何可拖放实体进入目标容器后都变为字符串“111”。
清单 12
function targetnodecreater(data, hint)
{
var myitem = dojo.doc.createElement("div");
myitem.id = dojo.dnd.getUniqueId();
myitem.innerHTML = "111";
return {node: myitem, data: data};
}
清单 12 的代码中创建了一个任何可拖放实体进入目标容器后都将变为字符串“111”的构造函数。
拖放柄
同拖动操作一样,拖放操作也有自己的拖放柄。其拖放柄存在的原因和意义,这里也不再详细叙述。
要给一个可拖放实体增加一个拖放柄需要完成两步操作。
第一步,是在可拖放实体中增加一个拖放柄实体并将其 Class 声明为 class= “dojoDndHandle”。
第二步,是在容器中增加 withHandles= “true”这样一个属性。
在完成了上述两步以后,可拖放实体的拖放操作只能通过抓住拖放柄来实现,对可拖放实体的其余部分则无法进行拖放操作。
清单 13
<div class="dojoDndItem" style="height:50px;width:100%;background-color:blue">
<span class="dojoDndHandle" style="background-color:yellow">Handle</span>
<!-- 蓝色矩形的拖放柄 --><div class="bluesquare">BLUE</div>
</div><!-- 定义一个颜色为蓝色的矩形可拖放实体 -->
清单 13 是通过静态方法实现可拖放实体的拖放柄,紧接着介绍动态创建可拖放实体拖放柄的方法。
清单 14
function init()\
{
mysource = new dojo.dnd.Source("mysource",{creator: sourcenodecreater, copyOnly: false,
withHandles: true});// 存放可拖放实体的 " 源容器 ",其中声明 withHandles 的值为“true”
mysource.insertNodes(false,["Do you love me?",
"<span class='dojoDndHandle' style='background-color:yellow'>Handle
</span>Good good study","<span class='dojoDndHandle' style='background-color:yellow'>
Handle</span><div style='height:50px;width:100%;background-color:blue'></div>"]);
// 通过直接写入静态标签属性 class='dojoDndHandle' 构建“拖放柄”
mytarget = new dojo.dnd.Target("mytarget",
{creator: targetnodecreater, copyOnly: false}
);
// 存放可拖放实体的 " 目标容器 "
}
清单 14 是动态创建拖放柄的一个完整的实例。
可拖放实体的替身
可拖放实体的替身是一个体积细微,但又作用重大的部分。替身分为两个部分,上面的一个部分是“头”,下面的一个部分是“身体”。
图 4
图 4 清楚的表明了替身的头部分和身体部分,其中红色框内的为“头”部分,绿色框内的为“身体”部分。对于替身身体形式的定义,可以在可拖放实体的构造函数中完成。对于替身头形式的定义,则需要通过 CSS 来完成。
如果要对替身的身体部分进行修改,则需要 hint 帮助 (hint 为拖放实体构造函数两个参数中的一个 ) 。替身身体的本质也是调用可拖放实体的构造函数来构建的。因此如果需要构建特定的替身身体,就需要在可拖放实体的构造函数里面来做文章。
所有的可拖放实体包括替身的身体部分都是通过可拖放实体的构造函数来构建的。那么带来的一个问题是构造函数如何判断,某次调用可拖放实体的构造函数是构建可拖放实体还是替身呢?这时候 hint 可以被用来帮助进行区分。如果 hint 的值不等于“avatar”的话,则说明是构建可拖放实体,反之就是说明构建的是替身。
清单 15
if(hint!="avatar")
myitem.innerHTML = data;
else
myitem.innerHTML = "Haha,my avatar";
清单 15 表示当 hint 判断不是“avatar”的时候,根据传入的 data 的值构建可拖放实体,当判断是“avatar”的时候,替身的身体部分就写入“Haha,my avatar”。在能够按照自己的意愿构造替身的身体部分后,下一步想到的是改变替身的头部分。在 Dojo 设计 dnd 替身的时候,其暴露给使用者的只是对身体的修改,但是在个别情况下,可能希望美化替身,例如想对替身的头进行修改。这时候需要操作的是 CSS 。
在 dojo_path/dojo/resources 有一个 dnd.css 文件,在这个文件中,定义了 dnd 操作的一些页面效果。如果期望对替身的头进行修改的话,就必须在当前页面中重新定义与其相关的 CSS 定义。例如如果想将替身头部分的背景颜色重新定义为蓝色。则可以在当前页面的 head 部份写入 “.dojoDndAvatarHeader {background: blue;}”。
清单 16
<style type="text/css">
@import "../../resources/dnd.css";
dojoDndAvatarHeader {background: blue;}
</style>
清单 16 是将替身头部分修改为蓝色的实例。要实现修改后效果,需将其放入当前页面的 head 部分内。
捕获拖放中的事件
Dojo 将其认为可能常用的一些事件进行了注册,并将这些注册的事件以相对应的名称发布出来。
/dnd/start:当拖放开始的时候监测到该事件,能获取的相关值包括当前源容器、拖放的节点和判断这次操作是否是复制操作的布尔值。
/dnd/source/over:当鼠标滑入或滑出一个容器的时候,监测到该事件。需要注意的是当滑入或滑出一个容器的时候都会获得容器。但如果从一个容器滑入到容器外,得到的第二个容器值为空。如果从一个容器直接滑入到另外一个容器,得到的第二个容器值不为空。
/dnd/drop/before:该方法只被 Dojo1.1.0 或更高的版本所支持。在 drop 发生之前监测到该事件。换句话,可以在该方法中定义一些功能,这些功能将在 drop 发生之前的一刹那发生。
/dnd/drop:当放下可拖放实体的时候,可监测到该事件。
/dnd/cancel:当拖放动作取消,或者可拖放实体被拖放到一个无效区域时,可监测到该事件。
清单 17
function init()
{
mysource = new dojo.dnd.Source("mysource",{creator: sourcenodecreater, copyOnly:
false});// 存放可拖放实体的 " 源容器 "
mysource.insertNodes(false,["Do you love me?","Good good study","<div
style='height:50px;width:100%;background-color:blue'></div>"]);
mytarget = new dojo.dnd.Source("mytarget",{creator: targetnodecreater, copyOnly:
false});// 存放可拖放实体的 " 目标容器 "
}
function sourcenodecreater(data, hint)
{
var myitem = dojo.doc.createElement("div");
myitem.id = dojo.dnd.getUniqueId();
if(hint!="avatar")
myitem.innerHTML = data;
else
myitem.innerHTML = "Haha,my avatar";
return {node: myitem, data: data};
}
function targetnodecreater(data, hint)
{
var myitem = dojo.doc.createElement("div");
myitem.id = dojo.dnd.getUniqueId();
myitem.innerHTML = "111";
return {node: myitem, data: data};
}
dojo.subscribe("/dnd/start",
function(source,nodes,iscopy){
console.debug(source);console.debug(nodes);console.debug(iscopy);
});// 注册开始事件,当拖放动作开始时,便会有输出
dojo.subscribe("/dnd/source/over",
function(source){
console.debug(source);});// 注册鼠标滑过容器事件,当鼠标滑过容器的时候,便会有输出
dojo.subscribe("/dnd/drop/before",
function(source,nodes,iscopy){
console.debug(source);console.debug(nodes);console.debug(iscopy);
});// 注册结束前事件,当拖放动作接受前时,便会有输出
dojo.subscribe("/dnd/drop",
function(source,nodes,iscopy){
console.debug(source);
console.debug(nodes);
console.debug(iscopy);
console.debug("bad");
});// 注册结束事件,当拖放动作结束时,便会有输出
dojo.subscribe("/dnd/cancel", function(){
console.debug("cancel");});// 注册取消事件,当拖放动作取消时,便会有输出
dojo.addOnLoad(init);
</script>
在清单 17 中,监测了拖放操作中的开始、结束、取消、结束前和鼠标滑过容器五个动作,并将这些动作函数接受到的值进行了输出。在实际项目中对拖放事件的操作就是建立在监测这些事件和这些事件输出值的基础之上的。所不同的只是不同的处理方法和不同的处理顺序。
对于监听事件函数的输出值,“source”表示源容器;“nodes”表示进行拖放操作的“可拖放实体们”(“nodes”是一个数组);“iscopy”为“true”或“false”,表示这次操作是否是复制操作。
回页首
结束语
现在你应该了解了如何使用 Dojo 所支持的页面拖拽操作来开发自己的项目,同时也应该了解了“拖动”和“拖放”的区别。那么,接下来请在你的电脑上创建一个 HTML 格式的空白文本,去尝试上面的代码,在实践中去感受 Dojo 拖拽功能的强大。
参考资料
学习
访问 Dojo 的官方站点,关于 Dojo 的最权威的站点。
掌握 Dojo 工具包:阅读本系列以前的文章。
“教程:使用 Dojo 开发 HTML 小部件”(developerWorks,2006 年 12 月):您将学到使用 Dojo 开发 HTML 小部件的基础知识;包括如何引用一个图像、如何向 HTML 页面中添加事件处理程序以及如何处理复合小部件。
“评论专栏: Scott Johnson:沉迷于 Dojo”(developerWorks,2008 年 4 月):Dojo 内部人员讨论 Dojo Toolkit 如此广受欢迎而成为必备下载工具的原因、其现状和未来。
“基于 Dojo 的本地化开发”(developerWorks,2008 年 1 月):本文介绍了基于 Dojo 的本地化的实现,通过实例讲解了如何利用 Dojo 提供的本地化支持模块来实现软件的本地化。
“提高基于 Dojo 的 Web 2.0 应用程序的性能”(developerWorks,2008 年 2 月):本文通过演示一些实用的技巧来提高 Dojo 的性能,帮助开发人员找出 Web 2.0 应用程序的性能瓶颈。
“使用 Dojo 开发支持 Accessibility 的 Web 应用”(developerWorks,2008 年 5 月):帮助开发人员了解 Accessibility 的基本内容,掌握 Dojo 开发可访问性 Web 应用的基本技能。
“使用 Dojo 国际化 Web 应用程序”(developerWorks,2008 年 8 月):通过本文获得有关如何使用 Dojo 这个重要特性的简短的指导。
“用 Firebug 动态调试和优化应用程序”(developerWorks,2008 年 5 月):了解如何使用 Firefox 浏览器的免费开源扩展 Firebug,它提供了很多有用的开发特性和工具。
wikipedia 上关于 XMLHttpRequest 对象的详细介绍。
JSON 的官方网站,在这里可以找到最实用的关于 JSON 的第一手资料。
wikipedia 上关于 REST 架构风格 的介绍。
Ajax 资源中心:developerWorks 上所有有关 Ajax 的问题都可以在这里找到解答。
获得产品和技术
下载 最新版 Dojo 工具包。
讨论
访问 Dojo 发起人 Alex Russell 的博客:有很多有深度的文章。
http://alex.dojotoolkit.org/
Dojo 支持的两种拖拽方式
在开始尝试了解实现 Dojo 拖拽效果的使用方法以前,首先必须明确拖拽具有两种截然不同的表现效果。
第一种表现效果是图标被拖拽到哪里,其就会被直接放到哪里,这个拖拽效果是图标完全紧跟拖拽的动作,与每一个拖拽动作的运动轨迹完全契合,这种效果被称为“拖动”。第二种表现效果是当图标被拖拽到一个地方,松开鼠标的时候,图标会以当前位置为基础而以其它图标为参照系进行位置的自动调整。这种效果被称为“拖放”。
Dojo 的拖动
“拖动”与“拖放”相比较,原理更加容易理解,使用更加简单。而且更加贴近于人们直观印象中的“拖拽”效果。
最简单的拖动实例
要在 Dojo 的支持下,实现拖动的效果所需要的只是使用 Dojo 所提供的 Dojo 标签属性标注出希望实现拖动效果的实体。简单的说,就是如果希望一个实体可以拖动,则只需要在这个实体的标签里面加上 dojoType=“dojo.dnd.Moveable”这个属性。例如要实现一个表格的拖动,则只需要在这个表格的声明标签“<table>”里加上 dojoType=“dojo.dnd.Moveable”这个属性。甚至就是在“<tr>”或“<td>”标签中加上 dojoType=“dojo.dnd.Moveable”,也可以实现对应实体的拖动效果。
清单 1
<script type="text/javascript">
dojo.require("dojo.dnd.move"); //引入Dojo的拖动功能模块
dojo.require("dojo.parser"); //引入解析Dojo标记的功能模块
</script>
<table dojoType="dojo.dnd.Moveable">
<tbody><tr><td>Haha, I am a good guy.</td></tr></tbody>
</table>
<!--引入dojoType="dojo.dnd.Moveable",让上面的表格可以拖动-->
需要注意的是静态创建可拖动实体需要引入 dojo.require("dojo.parser") 。
动态实现可拖动实体
在清单 1 中,通过在一些实体的标签里面加上相应的 Dojo 标签属性来实现可拖动实体的创建。这种静态实现可拖动实体的方法简单明了。但是在更多的情况下,往往需要根据一些实际情况运行得到的数据来动态的创建可拖动实体。在这种情况下,静态实现可拖动实体的方法就不能满足当下的需求。值得庆幸的是 Dojo 对于所有静态实现的方法都基本对应有一套相应的动态实现方法。
清单 2
<script type="text/javascript" src="dojo_path/dojo/dnd/move.js"></script>
<script type="text/javascript">
dojo.require("dojo.dnd.move");
var m1;
var init = function(){
m1 = new dojo.dnd.Moveable("bad",{});//申明 id 为 "bad" 的实体为可拖动实体
};
dojo.addOnLoad(init);// 表示在页面加载完成以后,执行 init 函数
</script>
<div id="bad">
You can cop me "Haha, I am a bad guy."
</div>
需要注意的是 dojo.dnd.Moveable("bad",{}) 中的大括号用来设置可拖动实体“bad”的一些与拖动相关的属性,目前可以暂时设为空,则不设置任何与拖动相关的属性。在后面的讲述中,一些相关的重要属性将被逐步介绍。
拖动柄
如果运行清单 1 和清单 2 中的代码,然后尝试在其页面中使用鼠标去选择可拖动实体中的内容。就会发现,无论使用何种方法都无法选择可拖动实体中的内容,当然就更谈不上复制可拖动实体中的内容了。
仔细分析无法选择可拖动实体中内容的原因,就会发现如果要选择页面中的某一部分内容,其动作步骤为,按住鼠标左键不放,然后拖动鼠标选择一块区域作为确定选择的内容;而如果要拖动一个可拖动实体,其动作步骤也为,按住鼠标左键,然后拖动鼠标引起可拖动实体的移动。
因此如果让某个实体具有了可拖动的功能,则当对这个实体点下鼠标左键,并拖动鼠标时,就浏览器看来,其将不能理解这个动作的目的是要拖动该实体还是选择该实体里面的内容。因为这两个具有不同含义的动作就其动作本身来说是一模一样的,浏览器没有办法对这两个动作进行区分。
但现实的情况往往需要一个实体既可以被拖动,又可以被选择其内部所包含的内容。 Dojo 通过给可拖动实体增加一个拖动柄,实现了选择内容动作和拖动实体动作的区分。
声明拖动柄的方法为在声明可拖动实体的时候,在可拖动实体的标签中再加上一个除 dojoType 之外的另外一个 Dojo 标签属性 handle= “”,handle 后面的双引号中需要填入的是作为拖动柄部分的那个实体的 id 值。
清单 3
<div id="bad" dojoType="dojo.dnd.Moveable" handle="dragme">
<!--- 声明 id 为 "dragme" 的实体为拖动柄 ->
<div id="dragme">You can drag it by me</div>
<! —上面是拖动柄的实体 -->
You can cop me "Haha, I am a bad guy."
</div>
如果要动态声明上面的“拖动柄”,则需要在页面加载完成以后通过执行 dojo.dnd.Moveable("bad", {handle: "dragme"}) 来实现。 handle 后面所跟的值为将成为可拖动实体“bad”拖动柄的实体 id 。
在清单 2 中,没有设置可拖动实体的任何属性。在清单 3 中设置了与拖动相关的其中一个属性——拖动柄。
限制可拖动实体拖动的范围
Dojo 限制用户可拖动实体活动范围的方法有两种。第一种方法为在生成可拖动实体的时候,给其设置一个逻辑上的活动范围空间;第二种方法是依据页面的一个实体,建立可拖动实体的活动范围空间。需要注意的是,在第一种方法中的活动范围空间是一个不存在相应页面实体的定义,即这个所谓的活动范围空间在页面是实际不存在的。首先要讨论的是第一种方法的使用。
清单 4
<script type="text/javascript">
dojo.require("dojo.dnd.move");
dojo.require("dojo.parser");
var init = function(){
new dojo.dnd.move.boxConstrainedMoveable("aa", {box: {l: 100, t: 100, w: 500,
h: 500}});// 定义可拖动实体 "aa" 和拖动的范围 box
new dojo.dnd.move.boxConstrainedMoveable("bb", {box: {l: 100, t: 100, w: 500,
h: 500}, within: true});
};
dojo.addOnLoad(init);
</script>
<div id="aa">aaa</div>
<div id="bb">bbb</div>
<div id="cc" dojoType="dojo.dnd.move.boxConstrainedMoveable"
box="{l: 100, t: 100, w: 500,h: 500}">ccc</div>
<div id="dd" dojoType="dojo.dnd.move.boxConstrainedMoveable"
box="{l: 100, t: 100, w: 500, h: 500}" within="true">ddd</div>
在清单 4 中,将动态定义和静态定义的方法都对比着写了出来,前两个可拖动实体是使用的动态定义的方法,后两个实体所实现的效果和前面一样但使用的是静态定义的方法。
实现可拖动实体活动范围的第一种方法是通过定义一个实际不存在的矩形框来作为可拖动实体的“监狱”。例如在清单 4 可拖动实体“aaa”中,{box: {l: 100, t: 100, w: 500, h: 500}} 是指在“aaa”外建立一个盒子区域,该盒子区域为“aaa”的活动范围。“l: 100, t: 100”表示该盒子区域距页面左边界和上边界各为 100px,“w: 500”表示盒子区域的宽度为 500px,“h: 500”表示盒子区域的高度为 500px 。需要注意的是盒子区域的 l 和 t 始终是以页面边界为标准。
图 1
图 1 显示出了清单 4 中可拖动实体“aaa”活动范围的“盒子区域”。
可拖动实体“within”的属性可设为“true”或者“false”,如果设为“true”的话表示“可拖动实体”的任何一个部分都不能超出其盒子区域的范围,如果设为“false”表示只要拖动实体的左上角不超出盒子区域的范围便为合法操作。
图 2 中可拖动实体的“within”属性为“false”,因此当前的位置是合法的。但是可能在某些时候,需要使用到更细致的活动范围控制,或者需要根据可拖动实体的 DOM 父节点确定其活动范围,而不是距页面的边距。对于这些情况,可以采用 Dojo 提供的第二种限制用户可拖动实体活动范围的方法。
Dojo 限制用户可拖动实体活动范围的第二种方法是在所有可拖动实体的外面创建一个 DOM 父节点,然后利用该父节点的“Layout”属性将可拖动实体的运动范围限制在父节点内。以此为基础,依据父节点四种不同的“Layout”属性分为“margin”类型的限制,“border”类型的限制,“padding”类型的限制,和“content”类型的限制。 ( “margin”,“border”,“padding”和“content”是 Web 页面中一个实体的 CSS 属性 ) 。
清单 5
<style type="text/css">/* 定义四个可拖动实体父节点的 Layout 结构 ,
对可拖动实体拖动范围有影响的属性包括 "border","padding" 和 "margin"*/
.parent
{
background: #BFECFF;
border: 10px solid lightblue;
width: 500px;
height: 500px;
padding: 10px;
margin: 10px;
}
</style>
<script type="text/javascript">
dojo.require("dojo.dnd.move");
dojo.require("dojo.parser");
function init()
{
new dojo.dnd.move.parentConstrainedMoveable("bad", {area: "padding", within: true});
<!-- 该可拖动实体的范围不能超过父节点的 padding 区域边界 -->
new dojo.dnd.move.parentConstrainedMoveable("good", {area: "content", within: true});
<!-- 该可拖动实体的范围不能超过父节点的 content 区域边界 -->
};
dojo.addOnLoad(init);
</script>
<div class="parent">
<div style="background:red" dojoType="dojo.dnd.move.parentConstrainedMoveable"
area="margin" within="true">I am restricted within my parent's margins.</div>
<!-- 该可拖动实体的范围不能超过父节点的 margin 区域边界 -->
<div style="background:green" dojoType="dojo.dnd.move.parentConstrainedMoveable"
area="border" within="true">I am restricted within my parent's border.</div>
<!-- 该可拖动实体的范围不能超过父节点的 border 区域边界 -->
<div style="background:blue" id="bad">I am restricted within my parent's paddings.</div>
<div style="background:pink" id="good">I am restricted within my parent's content.</div>
</div>
在清单 5 中声明了四个可拖动实体,其中两个是以静态 Dojo 标签属性的方式声明,另外两个是以动态的方式声明。
捕获拖动中的事件
Dojo 通过事件订阅发布机制实现了方便易用的捕获拖动事件 API 。 Dojo 在拖动开始、结束和过程中会发出一些消息,如果希望监测到页面内可拖动实体的拖动开始事件和拖动结束事件可以通过订阅“/dnd/move/start”和“/dnd/move/stop”来实现
清单 6
<script type="text/javascript">
dojo.require("dojo.dnd.move");
dojo.require("dojo.parser");
dojo.addOnLoad
(
function()
{
dojo.subscribe("/dnd/move/start",function(node){// 订阅开始拖动事件
console.debug("Start move", node)
}
);
dojo.subscribe("/dnd/move/stop",function(node){// 订阅拖动结束事件
console.debug("Stop move", node);}
);
}
);
</script>
<div dojoType="dojo.dnd.Moveable">Haha, I am a bad guy.</div><br/><br/>
<p dojoType="dojo.dnd.Moveable">Haha, I am boy</p>
清单 6 的运行效果在 Firebug 下最易于观察。在 Firebug 的 Console 窗口中,每一次拖动可拖动实体,就会在 Console 窗口中有相对应的输出。
如果在某些时候,只希望监测某一个可拖动实体的事件,而不是所有可拖动实体的事件。则可以采用将可拖动实体的事件和一个函数连接起来以实现只监测某个特定可拖动实体的事件。
清单 7
<script type="text/javascript">
dojo.require("dojo.dnd.move");
dojo.require("dojo.parser");
dojo.addOnLoad(
function(){
var boy = new dojo.dnd.Moveable("boy");
dojo.connect(boy, "onMoveStart", function(mover){
console.debug("Start moving boy", mover);
});
dojo.connect(boy, "onMoveStop", function(mover){
console.debug("Stop moving boy", mover);
});// 通过事件连接捕获了可拖动实体的事件。 "mover" 是与可拖动实体相关的对象,里面包含了相关的属性和方法
});
</script>
<body><p id="boy">Haha, I am boy</p></body>
在清单 7 中将可拖动实体“boy”和一个特定的输出函数连接了起来。这样在捕获可拖动实体“boy”的时候,不会“惊动”其它可拖放实体。
Dojo 的拖放
拖放是一个复杂而又充满魅力的功能。 Dojo 支持拖放功能的原因,就是因为 Dojo 的使用者在实际项目中开发高级拖拽操作功能的时候提出了这样的需求。
最简单的拖放实例
请读者将下面的代码在自己的机器上运行,并尝试感受拖放的实际效果。
清单 8
<html>
<head>
<title>test</title>
<script type="text/javascript" src="dojo_path/dojo/dojo.js"
djConfig="isDebug:true, parseOnLoad: true"></script>
<style type="text/css">
@import "dojo_path/dojo/resources/dnd.css";
</style>
<script type="text/javascript">
dojo.require("dojo.dnd.Source");
dojo.require("dojo.parser");
</script>
</head>
<body>
<table><tbody><tr>
<td>
<div dojoType="dojo.dnd.Source"
style="border: 1px solid black; height: 200px width: 300px;">SOURCE
<div class="dojoDndItem" style="height:50px;width:100%;background-color:blue">
<div class="bluesquare">BLUE</div>
</div><!-- 定义一个颜色为蓝色的矩形可拖放实体 -->
<div class="dojoDndItem" style="height:50px;width:100%;background-color:red">
<div class="redsquare">RED</div>
</div><!-- 定义一个颜色为红色的矩形可拖放实体 -->
</div>
<!-- 定义了一个容器,该容器为可拖放实体在拖放开始前,可拖放实体存放的地方。将其名定义为源容器 -->
</td>
<td>
<div dojoType="dojo.dnd.Target" style="border: 1px solid black; height: 200px;
width: 300px; ">TARGET</div>
<!-- 定义了一个容器,该容器为可拖放实体拖放后,可拖放实体存放的地方。将其名定义为目标容器 -->
</td>
</tr></tbody></table>
</body>
</html>
由清单 8 可以得知,对于拖放来说,可拖放实体必须是从一个容器中拖放到另外一个容器中。可拖放实体是不能存在于容器之外的任何地方。如果可拖放实体从“容器 A”出来,放入“容器 B”中,则一般习惯上称 A 为源容器,B 为目标容器。
被拖放的实体称之为“可拖放实体”。要实现可拖放实体的拖放,页面中必须要有“拖放源容器”和“拖放目标容器”。拖放源容器为起初可拖放实体存放的地方,而拖放目标容器为可拖放实体拖起后可以放的地方。
如果运行清单 8 中的代码,就会发现在拖起可拖放实体的时候,有一个与“原可拖放实体”很相似的小图标在随着鼠标移动,其被称为可拖放实体的“替身”。“替身”的主要作用有两个,第一是指明目前操作的实体是哪一个或是有哪几个实体被操作,第二是作为原实体的替身,帮助用户判断目前选中可拖放实体的鼠标所处的位置为哪里,判断该区域是否为合法的拖放区域。
替身是由“原可拖放实体”转换而来,包含了“原可拖放实体”的主要外貌特征,同时替身的细微化,大大减小了如果采用“原拖动实体”作为标识而带来的系统负担和提高了标识的精确度。
图 3
拖动的本质是可拖动实体象素位置的变化,而拖放的本质是一个页面 DOM 结构的变化。实体象素位置的变化,可以只通过修改这个实体的属性来实现,不用和页面的任何其它部分打交道。但是对于拖放,当将一个可拖放实体从源容器拖放到目标容器时,就是将该可拖放实体先从源容器的 DOM 节点上删除,再在目标容器的 DOM 节点上加上可拖放实体。
因此可拖放实体的存在,在拖放动作前必须依赖于一个父 DOM 节点,这就是源容器,拖放动作后也必须依赖于一个新的父 DOM 节点,这就是目标容器。
在某些情况下,可能需要的是能在几个容器之间将可拖放实体不断的拖来拖去。那么所做的修改是只需要将源容器和目标容器都用 dojoType= “dojo.dnd.Source”来进行声明就能实现可拖放实体从目标容器拖回源容器的操作。
动态生成源容器、目标容器和可拖放实体
动态声明源容器和目标容器的方法比较简单,动态创建源容器的方法为 var foo=new dojo.dnd.Source(Node, Params),而动态创建目标容器的方法为 var foo=new dojo.dnd.Target(Node, Params) 。在声明源容器的方法中,Node 为要声明为源容器的 DOM 节点 id,Params 所代表的是一些用来确定容器相关属性的参数。
清单 9
function init()
{
mysource = new dojo.dnd.Source("mysource",{});// 存放可拖放实体的 " 源容器 "
mytarget = new dojo.dnd.Target("mytarget",{});// 存放可拖放实体的 " 目标容器 "
}
<table><tbody><tr>
<td>
<div id="mysource" style="border: 1px solid black; height: 200px; width: 300px;">SOURCE
<div class="dojoDndItem" style="height:50px;width:100%;background-color:blue">
<div class="bluesquare">BLUE</div>
</div><!-- 定义一个颜色为蓝色的矩形可拖放实体 -->
<div class="dojoDndItem" style="height:50px;width:100%;background-color:red">
<div class="redsquare">RED</div>
</div><!-- 定义一个颜色为红色的矩形可拖放实体 -->
</div><!-- 定义了一个容器,该容器为可拖放实体在拖放开始前,可拖放实体存放的地方。
将其名定义为源容器 -->
</td>
<td>
<div id="mytarget" style="border: 1px solid black; height: 200px; width: 300px;">
TARGET</div>
</td>
</tr></tbody></table>
在清单 9 中动态声明了源容器和目标容器。由清单 9 中观察可知,容器的声明是建立在已经存在的页面实体基础之上的。
清单 10
function init()
{
mysource = new dojo.dnd.Source("mysource");// 存放可拖放实体的 " 源容器 "
mysource.insertNodes(false,["Do you love me?","Good good study","<div
style='height:50px;width:100%;background-color:blue'></div>"]);
mytarget = new dojo.dnd.Target("mytarget");// 存放可拖放实体的 " 目标容器 "
}
<table><tbody><tr>
<td>
<div id="mysource" style="border: 1px solid black; height: 200px; width:300px;">
SOURCE</div>
<!-- 定义了一个容器,该容器为可拖放实体在拖放开始前,可拖放实体存放的地方。
将其名定义为源容器 -->
</td>
<td>
<div id="mytarget" style="border: 1px solid black;
height: 200px; width: 300px; ">TARGET</div>
</td>
</tr></tbody></table>
在清单 10 中通过 dojo.dnd.Source.insertNodes 来将动态声明的字符串化的实体插入到“源容器”中。如果回顾一下前面关于拖放基本原理的内容,就可以知道拖放的过程就是删除一个 DOM 节点和重新创建一个 DOM 节点的过程。删除一个 DOM 节点对于不同的应用没有太大的差异性,但是创建一个 DOM 节点对于不同的应用情况可能就需要不同的创建方式。
在大多数情况下,拖放后在目标容器里重建一个可拖放实体的 DOM 节点是调用 Dojo 的一个默认构造函数。这个构造函数能够在目标容器内完全重建一个与源容器一模一样的可拖放实体。
但如果希望可拖放实体在拖放入目标容器以后发生变化,与在源容器中的可拖放实体不一样,则可以通过创建自己的构造函数来实现。
如果要创建自己的构造函数,首先要在构建源容器和目标容器的时候,指明自己要创建的构造函数的函数名。
dojo.dnd.Source("mysource",{creator: sourcenodecreater, copyOnly: false}) 表示创建存放可拖放实体的“源容器”。 dojo.dnd.Target("mytarget",{creator: targetnodecreater, copyOnly: false}) 表示创建存放可拖放实体的“目标容器”。而“creator: sourcenodecreater” 表示源容器中构建可拖放实体的函数名为“sourcenodecreater”,“creator: targetnodecreater”表示目标容器中构建可拖放实体的函数名为“targetnodecreater”。
首先来创建一个最简单的名为“sourcenodecreater”的构造函数。
清单 11
function sourcenodecreater(data, hint)
{
var myitem = dojo.doc.createElement("div");
myitem.id = dojo.dnd.getUniqueId();
myitem.innerHTML = data;
return {node: myitem, data: data};
}
注意清单 11 中源容器的构造函数“sourcenodecreater”只是一个例子,但其包含了一个构造函数最基本的要素。在这里只是为了阐述清楚最基本的原理。具体的功能和如何构造出所需要的可拖放实体,需要根据需求来进行分析设计。
对于“sourcenodecreater”函数所接受的两个参数,“data”所代表的是接受到的关于可拖放实体的一些特征数据。因为可拖放实体是动态构造的,所以在很多情况下,可拖放实体要根据系统前面所传来的数据来构造相应的可拖放实体,而“data”所包含的就是这些用来构造可拖放实体的数据。“hint”是与替身有关的参数。
var myitem = dojo.doc.createElement("div") 表示创建一个 div 节点。可拖放实体将在这个节点上创建。
myitem.id = dojo.dnd.getUniqueId() 表示将会给可拖放实体一个唯一的 id 。
dojo.dnd.getUniqueId() 可以在一次系统运行中每次产生一个绝对独一无二的 id 。因为可拖放实体的拖放是 DOM 节点的销毁和重建,因此,当一个可拖放实体的拖放完成以后,其 id 将会发生变化。
myitem.innerHTML = data 是用来构建可拖放实体的实际效果,目前这里只是简单的将数据作为字符串在可拖放实体中展现。
return {node: myitem, data: data} 表示在上面的工作完成以后,将创建的节点和创建节点所使用的数据返回给 inserNode 函数,由其完成将该可拖放实体插入到 DOM 树中的相应位置。
在实际的情况中,往往需要在一个可拖放实体拖入目标容器后发生变化。例如在线购买东西时,选中希望购买的东西,将其拖入购物车后,一般代表商品的图片将会变小许多。那么就其前面所了解的,在可拖放实体进入目标容器的时候,将调用“targetnodecreater”函数来构造在目标容器中的实体。那么如果希望其发生变化的话,就必须得在“targetnodecreater”上做些文章。比如希望任何可拖放实体进入目标容器后都变为字符串“111”。
清单 12
function targetnodecreater(data, hint)
{
var myitem = dojo.doc.createElement("div");
myitem.id = dojo.dnd.getUniqueId();
myitem.innerHTML = "111";
return {node: myitem, data: data};
}
清单 12 的代码中创建了一个任何可拖放实体进入目标容器后都将变为字符串“111”的构造函数。
拖放柄
同拖动操作一样,拖放操作也有自己的拖放柄。其拖放柄存在的原因和意义,这里也不再详细叙述。
要给一个可拖放实体增加一个拖放柄需要完成两步操作。
第一步,是在可拖放实体中增加一个拖放柄实体并将其 Class 声明为 class= “dojoDndHandle”。
第二步,是在容器中增加 withHandles= “true”这样一个属性。
在完成了上述两步以后,可拖放实体的拖放操作只能通过抓住拖放柄来实现,对可拖放实体的其余部分则无法进行拖放操作。
清单 13
<div class="dojoDndItem" style="height:50px;width:100%;background-color:blue">
<span class="dojoDndHandle" style="background-color:yellow">Handle</span>
<!-- 蓝色矩形的拖放柄 --><div class="bluesquare">BLUE</div>
</div><!-- 定义一个颜色为蓝色的矩形可拖放实体 -->
清单 13 是通过静态方法实现可拖放实体的拖放柄,紧接着介绍动态创建可拖放实体拖放柄的方法。
清单 14
function init()\
{
mysource = new dojo.dnd.Source("mysource",{creator: sourcenodecreater, copyOnly: false,
withHandles: true});// 存放可拖放实体的 " 源容器 ",其中声明 withHandles 的值为“true”
mysource.insertNodes(false,["Do you love me?",
"<span class='dojoDndHandle' style='background-color:yellow'>Handle
</span>Good good study","<span class='dojoDndHandle' style='background-color:yellow'>
Handle</span><div style='height:50px;width:100%;background-color:blue'></div>"]);
// 通过直接写入静态标签属性 class='dojoDndHandle' 构建“拖放柄”
mytarget = new dojo.dnd.Target("mytarget",
{creator: targetnodecreater, copyOnly: false}
);
// 存放可拖放实体的 " 目标容器 "
}
清单 14 是动态创建拖放柄的一个完整的实例。
可拖放实体的替身
可拖放实体的替身是一个体积细微,但又作用重大的部分。替身分为两个部分,上面的一个部分是“头”,下面的一个部分是“身体”。
图 4
图 4 清楚的表明了替身的头部分和身体部分,其中红色框内的为“头”部分,绿色框内的为“身体”部分。对于替身身体形式的定义,可以在可拖放实体的构造函数中完成。对于替身头形式的定义,则需要通过 CSS 来完成。
如果要对替身的身体部分进行修改,则需要 hint 帮助 (hint 为拖放实体构造函数两个参数中的一个 ) 。替身身体的本质也是调用可拖放实体的构造函数来构建的。因此如果需要构建特定的替身身体,就需要在可拖放实体的构造函数里面来做文章。
所有的可拖放实体包括替身的身体部分都是通过可拖放实体的构造函数来构建的。那么带来的一个问题是构造函数如何判断,某次调用可拖放实体的构造函数是构建可拖放实体还是替身呢?这时候 hint 可以被用来帮助进行区分。如果 hint 的值不等于“avatar”的话,则说明是构建可拖放实体,反之就是说明构建的是替身。
清单 15
if(hint!="avatar")
myitem.innerHTML = data;
else
myitem.innerHTML = "Haha,my avatar";
清单 15 表示当 hint 判断不是“avatar”的时候,根据传入的 data 的值构建可拖放实体,当判断是“avatar”的时候,替身的身体部分就写入“Haha,my avatar”。在能够按照自己的意愿构造替身的身体部分后,下一步想到的是改变替身的头部分。在 Dojo 设计 dnd 替身的时候,其暴露给使用者的只是对身体的修改,但是在个别情况下,可能希望美化替身,例如想对替身的头进行修改。这时候需要操作的是 CSS 。
在 dojo_path/dojo/resources 有一个 dnd.css 文件,在这个文件中,定义了 dnd 操作的一些页面效果。如果期望对替身的头进行修改的话,就必须在当前页面中重新定义与其相关的 CSS 定义。例如如果想将替身头部分的背景颜色重新定义为蓝色。则可以在当前页面的 head 部份写入 “.dojoDndAvatarHeader {background: blue;}”。
清单 16
<style type="text/css">
@import "../../resources/dnd.css";
dojoDndAvatarHeader {background: blue;}
</style>
清单 16 是将替身头部分修改为蓝色的实例。要实现修改后效果,需将其放入当前页面的 head 部分内。
捕获拖放中的事件
Dojo 将其认为可能常用的一些事件进行了注册,并将这些注册的事件以相对应的名称发布出来。
/dnd/start:当拖放开始的时候监测到该事件,能获取的相关值包括当前源容器、拖放的节点和判断这次操作是否是复制操作的布尔值。
/dnd/source/over:当鼠标滑入或滑出一个容器的时候,监测到该事件。需要注意的是当滑入或滑出一个容器的时候都会获得容器。但如果从一个容器滑入到容器外,得到的第二个容器值为空。如果从一个容器直接滑入到另外一个容器,得到的第二个容器值不为空。
/dnd/drop/before:该方法只被 Dojo1.1.0 或更高的版本所支持。在 drop 发生之前监测到该事件。换句话,可以在该方法中定义一些功能,这些功能将在 drop 发生之前的一刹那发生。
/dnd/drop:当放下可拖放实体的时候,可监测到该事件。
/dnd/cancel:当拖放动作取消,或者可拖放实体被拖放到一个无效区域时,可监测到该事件。
清单 17
function init()
{
mysource = new dojo.dnd.Source("mysource",{creator: sourcenodecreater, copyOnly:
false});// 存放可拖放实体的 " 源容器 "
mysource.insertNodes(false,["Do you love me?","Good good study","<div
style='height:50px;width:100%;background-color:blue'></div>"]);
mytarget = new dojo.dnd.Source("mytarget",{creator: targetnodecreater, copyOnly:
false});// 存放可拖放实体的 " 目标容器 "
}
function sourcenodecreater(data, hint)
{
var myitem = dojo.doc.createElement("div");
myitem.id = dojo.dnd.getUniqueId();
if(hint!="avatar")
myitem.innerHTML = data;
else
myitem.innerHTML = "Haha,my avatar";
return {node: myitem, data: data};
}
function targetnodecreater(data, hint)
{
var myitem = dojo.doc.createElement("div");
myitem.id = dojo.dnd.getUniqueId();
myitem.innerHTML = "111";
return {node: myitem, data: data};
}
dojo.subscribe("/dnd/start",
function(source,nodes,iscopy){
console.debug(source);console.debug(nodes);console.debug(iscopy);
});// 注册开始事件,当拖放动作开始时,便会有输出
dojo.subscribe("/dnd/source/over",
function(source){
console.debug(source);});// 注册鼠标滑过容器事件,当鼠标滑过容器的时候,便会有输出
dojo.subscribe("/dnd/drop/before",
function(source,nodes,iscopy){
console.debug(source);console.debug(nodes);console.debug(iscopy);
});// 注册结束前事件,当拖放动作接受前时,便会有输出
dojo.subscribe("/dnd/drop",
function(source,nodes,iscopy){
console.debug(source);
console.debug(nodes);
console.debug(iscopy);
console.debug("bad");
});// 注册结束事件,当拖放动作结束时,便会有输出
dojo.subscribe("/dnd/cancel", function(){
console.debug("cancel");});// 注册取消事件,当拖放动作取消时,便会有输出
dojo.addOnLoad(init);
</script>
在清单 17 中,监测了拖放操作中的开始、结束、取消、结束前和鼠标滑过容器五个动作,并将这些动作函数接受到的值进行了输出。在实际项目中对拖放事件的操作就是建立在监测这些事件和这些事件输出值的基础之上的。所不同的只是不同的处理方法和不同的处理顺序。
对于监听事件函数的输出值,“source”表示源容器;“nodes”表示进行拖放操作的“可拖放实体们”(“nodes”是一个数组);“iscopy”为“true”或“false”,表示这次操作是否是复制操作。
回页首
结束语
现在你应该了解了如何使用 Dojo 所支持的页面拖拽操作来开发自己的项目,同时也应该了解了“拖动”和“拖放”的区别。那么,接下来请在你的电脑上创建一个 HTML 格式的空白文本,去尝试上面的代码,在实践中去感受 Dojo 拖拽功能的强大。
参考资料
学习
访问 Dojo 的官方站点,关于 Dojo 的最权威的站点。
掌握 Dojo 工具包:阅读本系列以前的文章。
“教程:使用 Dojo 开发 HTML 小部件”(developerWorks,2006 年 12 月):您将学到使用 Dojo 开发 HTML 小部件的基础知识;包括如何引用一个图像、如何向 HTML 页面中添加事件处理程序以及如何处理复合小部件。
“评论专栏: Scott Johnson:沉迷于 Dojo”(developerWorks,2008 年 4 月):Dojo 内部人员讨论 Dojo Toolkit 如此广受欢迎而成为必备下载工具的原因、其现状和未来。
“基于 Dojo 的本地化开发”(developerWorks,2008 年 1 月):本文介绍了基于 Dojo 的本地化的实现,通过实例讲解了如何利用 Dojo 提供的本地化支持模块来实现软件的本地化。
“提高基于 Dojo 的 Web 2.0 应用程序的性能”(developerWorks,2008 年 2 月):本文通过演示一些实用的技巧来提高 Dojo 的性能,帮助开发人员找出 Web 2.0 应用程序的性能瓶颈。
“使用 Dojo 开发支持 Accessibility 的 Web 应用”(developerWorks,2008 年 5 月):帮助开发人员了解 Accessibility 的基本内容,掌握 Dojo 开发可访问性 Web 应用的基本技能。
“使用 Dojo 国际化 Web 应用程序”(developerWorks,2008 年 8 月):通过本文获得有关如何使用 Dojo 这个重要特性的简短的指导。
“用 Firebug 动态调试和优化应用程序”(developerWorks,2008 年 5 月):了解如何使用 Firefox 浏览器的免费开源扩展 Firebug,它提供了很多有用的开发特性和工具。
wikipedia 上关于 XMLHttpRequest 对象的详细介绍。
JSON 的官方网站,在这里可以找到最实用的关于 JSON 的第一手资料。
wikipedia 上关于 REST 架构风格 的介绍。
Ajax 资源中心:developerWorks 上所有有关 Ajax 的问题都可以在这里找到解答。
获得产品和技术
下载 最新版 Dojo 工具包。
讨论
访问 Dojo 发起人 Alex Russell 的博客:有很多有深度的文章。
http://alex.dojotoolkit.org/
发表评论
-
使用 Dojo 开发支持 Accessibility 的 Web 应用
2008-12-18 18:11 1320简介 Accessibility,又经 ... -
掌握 Dojo 工具包,第 5 部分: Dojo 的 UI 组件库 - Dijit
2008-12-18 18:10 2211http://www.ibm.com/developerwor ... -
掌握 Dojo 工具包,第 3 部分: Dojo 事件机制
2008-12-18 18:09 1260DOM 事件模型 事件是用户与浏览器交互的基础,用户在界面的 ... -
掌握 Dojo 工具包,第 2 部分: XHR 框架与 Dojo
2008-12-18 18:08 2220XmlHttpRequest 对象的思考 ... -
掌握 Dojo 工具包,第 1 部分: Dojo 入门简介
2008-12-18 18:07 20252008 年 9 月 18 日 随着 ... -
针对 Java 开发人员的 Dojo 概念
2008-12-18 17:56 918声明类并设置上下文 ... -
prototype.js 1.4版开发者手册
2008-11-21 17:04 1141Lyn-事繁勿慌,事闲勿荒,取象于取,外圆内方 曲则全,缓应急 ...
相关推荐
### 实战Dojo工具包:全面解析与应用实践 #### Dojo工具包概览 **Dojo** 是一款强大的开源JavaScript库,旨在简化Web应用程序的开发过程,特别是那些需要复杂用户交互的应用。作为一款“远远超出‘原型建造’”的...
Dojo 工具包教程 Dojo 快速安装 Dojo和JSON建立无限级AJAX动态加载的功能模块树 Dojo学习笔记( 模块与包) Dojo学习笔记-- djConfig解说 Dojo学习笔记-- dojo.dom Dojo学习笔记-- dojo.event & dojo.event....
Dojo 工具包教程 Dojo 快速安装 Dojo和JSON建立无限级AJAX动态加载的功能模块树 Dojo学习笔记( 模块与包) Dojo学习笔记-- djConfig解说 Dojo学习笔记-- dojo.dom Dojo学习笔记-- dojo.event & dojo.event....
Dojo 工具包教程 Dojo 快速安装 Dojo和JSON建立无限级AJAX动态加载的功能模块树 Dojo学习笔记( 模块与包) Dojo学习笔记-- djConfig解说 Dojo学习笔记-- dojo.dom Dojo学习笔记-- dojo.event & dojo.event....
Dojo 工具包教程 Dojo 快速安装 Dojo和JSON建立无限级AJAX动态加载的功能模块树 Dojo学习笔记( 模块与包) Dojo学习笔记-- djConfig解说 Dojo学习笔记-- dojo.dom Dojo学习笔记-- dojo.event & dojo.event....
DOjo提供了许多有用的功能,如异步交互、拖拽、事件系统等。用户可以使用DOjo来实现复杂的交互效果。 DOjo的调试 DOjo提供了许多调试工具,帮助用户快速定位和解决问题。DOjo的调试方法包括使用浏览器的调试工具、...
Dojo是一个开源的JavaScript工具包,主要用于构建富互联网应用(Rich Internet Applications, RIA)。它具有轻量级且易于安装的特点,在Web 2.0时代,随着Ajax技术的发展而兴起。Dojo提供了一系列丰富的功能,包括但...
Dojo Toolkit 是一个开源的 DHTML 工具包,主要用 JavaScript 编写。该工具包旨在解决 Web 开发领域内存在的一些长期历史问题,如浏览器兼容性问题,并提供了丰富的功能来帮助开发者构建动态网页。 #### 为什么选择...
- **dojo.colors**:颜色处理工具包。 - **dojo.data**:统一的数据访问接口,支持读取XML、JSON等多种格式的数据。 - **dojo.fx**:动画效果库,提供基本的动画功能。 - **dojo.regexp**:正则表达式处理库。...
DOJO作为一个强大的JavaScript工具包,不仅包含了dojox.gfx这样的图形库,还提供了模块化、AJAX、事件处理、动画等许多功能,使得前端开发更为便捷。 虽然dojox.charting.Chart2D可以方便地创建条形图,但直接使用...
Dojo 是一个强大的JavaScript工具库,它为Web开发提供了丰富的功能和组件,其中包括提示框(Dialog)的使用。Dojo提示框是用户界面交互的重要部分,用于显示临时信息或者进行与用户的交互操作。本篇文章将深入探讨...
【Dojo.GUI_v6.zip for pencil】是一款专为Pencil设计的GUI模板资源包,它扩展了Pencil这款优秀的Web原型设计工具的功能和视觉元素。Pencil是一个免费且开源的应用程序,允许用户创建各种交互式原型,适用于网页、...
Dojo Toolkit 是一款开源的 JavaScript 工具包,专为构建现代化 Web 应用而设计。其核心价值在于通过提供一系列高质量的 API 和工具集,极大地简化了 Web 开发的过程。Dojo Toolkit 的特点包括但不限于: - **轻量...
在IT行业中,Dojo是一个广泛使用的JavaScript库,它提供了丰富的功能来处理前端开发中的各种任务,包括构建复杂的用户界面和处理数据结构。本篇将详细探讨"dojo任意级树的节点转移"这一主题,它是Dojo框架在处理树形...
Dojo是一个强大的JavaScript库和工具集,它提供了丰富的功能,包括DOM操作、动画效果、数据管理、AJAX通信以及拖放(Drag-and-Drop)功能。在这个例子中,我们主要关注Dojo的DnD模块(Drag-and-Drop)。 代码示例中,...
- **工具栏(Toolbar)**:介绍了工具栏中的动作按钮,如选择、缩放等。 - **菜单(Menu)**:列举了菜单项的功能,便于用户执行特定任务。 - **地图控制器(Controllersonthemap)**:说明了如何控制地图的显示区域。 ...
Dojo是ArcGIS JavaScript API的一个重要组成部分,它是一个强大的JavaScript工具包,提供了一整套用于构建复杂Web应用的模块和功能。Init通常指的是初始化脚本,这可能是`index.html`或`app.js`等文件,负责加载Dojo...