在开始尝试了解实现 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
需要注意的是 dojo.dnd.Moveable("bad",{}) 中的大括号用来设置可拖动实体“bad”的一些与拖动相关的属性,目前可以暂时设为空,则不设置任何与拖动相关的属性。在后面的讲述中,一些相关的重要属性将被逐步介绍。
如果运行清单 1 和清单 2 中的代码,然后尝试在其页面中使用鼠标去选择可拖动实体中的内容。就会发现,无论使用何种方法都无法选择可拖动实体中的内容,当然就更谈不上复制可拖动实体中的内容了。
仔细分析无法选择可拖动实体中内容的原因,就会发现如果要选择页面中的某一部分内容,其动作步骤为,按住鼠标左键不放,然后拖动鼠标选择一块区域作为确定选择的内容;而如果要拖动一个可拖动实体,其动作步骤也为,按住鼠标左键,然后拖动鼠标引起可拖动实体的移动。
因此如果让某个实体具有了可拖动的功能,则当对这个实体点下鼠标左键,并拖动鼠标时,就浏览器看来,其将不能理解这个动作的目的是要拖动该实体还是选择该实体里面的内容。因为这两个具有不同含义的动作就其动作本身来说是一模一样的,浏览器没有办法对这两个动作进行区分。
但现实的情况往往需要一个实体既可以被拖动,又可以被选择其内部所包含的内容。 Dojo 通过给可拖动实体增加一个拖动柄,实现了选择内容动作和拖动实体动作的区分。
声明拖动柄的方法为在声明可拖动实体的时候,在可拖动实体的标签中再加上一个除 dojoType 之外的另外一个 Dojo 标签属性 handle= “”,handle 后面的双引号中需要填入的是作为拖动柄部分的那个实体的 id 值。
清单 3
在清单 4 中,将动态定义和静态定义的方法都对比着写了出来,前两个可拖动实体是使用的动态定义的方法,后两个实体所实现的效果和前面一样但使用的是静态定义的方法。
实现可拖动实体活动范围的第一种方法是通过定义一个实际不存在的矩形框来作为可拖动实体的“监狱”。例如在清单 4 可拖动实体“aaa”中,{Box: {l: 100,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
图 2 中可拖动实体的“within”属性为“false”,因此当前的位置是合法的。但是可能在某些时候,需要使用到更细致的活动范围控制,或者需要根据可拖动实体的 DOM 父节点确定其活动范围,而不是距页面的边距。对于这些情况,可以采用 Dojo 提供的第二种限制用户可拖动实体活动范围的方法。
Dojo 限制用户可拖动实体活动范围的第二种方法是在所有可拖动实体的外面创建一个 DOM 父节点,然后利用该父节点的“Layout”属性将可拖动实体的运动范围限制在父节点内。以此为基础,依据父节点四种不同的“Layout”属性分为“margin”类型的限制,“border”类型的限制,“padding”类型的限制,和“content”类型的限制。 ( “margin”,“border”,“padding”和“content”是 Web 页面中一个实体的 CSS 属性 ) 。
清单 5
在清单 5 中声明了四个可拖动实体,其中两个是以静态 Dojo 标签属性的方式声明,另外两个是以动态的方式声明。
Dojo 通过事件订阅发布机制实现了方便易用的捕获拖动事件 API 。 Dojo 在拖动开始、结束和过程中会发出一些消息,如果希望监测到页面内可拖动实体的拖动开始事件和拖动结束事件可以通过订阅“/dnd/move/start”和“/dnd/move/stop”来实现
清单 6
清单 6 的运行效果在 Firebug 下最易于观察。在 Firebug 的 Console 窗口中,每一次拖动可拖动实体,就会在 Console 窗口中有相对应的输出。
如果在某些时候,只希望监测某一个可拖动实体的事件,而不是所有可拖动实体的事件。则可以采用将可拖动实体的事件和一个函数连接起来以实现只监测某个特定可拖动实体的事件。
清单 7
在清单 7 中将可拖动实体“boy”和一个特定的输出函数连接了起来。这样在捕获可拖动实体“boy”的时候,不会“惊动”其它可拖放实体。
拖放是一个复杂而又充满魅力的功能。 Dojo 支持拖放功能的原因,就是因为 Dojo 的使用者在实际项目中开发高级拖拽操作功能的时候提出了这样的需求。
请读者将下面的代码在自己的机器上运行,并尝试感受拖放的实际效果。
清单 8
由清单 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
在清单 9 中动态声明了源容器和目标容器。由清单 9 中观察可知,容器的声明是建立在已经存在的页面实体基础之上的。
清单 10
在清单 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
注意清单 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
清单 13 是通过静态方法实现可拖放实体的拖放柄,紧接着介绍动态创建可拖放实体拖放柄的方法。
清单 14
清单 14 是动态创建拖放柄的一个完整的实例。
可拖放实体的替身是一个体积细微,但又作用重大的部分。替身分为两个部分,上面的一个部分是“头”,下面的一个部分是“身体”。
图 4
图 4 清楚的表明了替身的头部分和身体部分,其中红色框内的为“头”部分,绿色框内的为“身体”部分。对于替身身体形式的定义,可以在可拖放实体的构造函数中完成。对于替身头形式的定义,则需要通过 CSS 来完成。
如果要对替身的身体部分进行修改,则需要 hint 帮助 (hint 为拖放实体构造函数两个参数中的一个 ) 。替身身体的本质也是调用可拖放实体的构造函数来构建的。因此如果需要构建特定的替身身体,就需要在可拖放实体的构造函数里面来做文章。
所有的可拖放实体包括替身的身体部分都是通过可拖放实体的构造函数来构建的。那么带来的一个问题是构造函数如何判断,某次调用可拖放实体的构造函数是构建可拖放实体还是替身呢?这时候 hint 可以被用来帮助进行区分。如果 hint 的值不等于“avatar”的话,则说明是构建可拖放实体,反之就是说明构建的是替身。
清单 15
清单 15 表示当 hint 判断不是“avatar”的时候,根据传入的 data 的值构建可拖放实体,当判断是“avatar”的时候,替身的身体部分就写入“Haha,my avatar”。在能够按照自己的意愿构造替身的身体部分后,下一步想到的是改变替身的头部分。在 Dojo 设计 dnd 替身的时候,其暴露给使用者的只是对身体的修改,但是在个别情况下,可能希望美化替身,例如想对替身的头进行修改。这时候需要操作的是 CSS 。
在 dojo_path/dojo/resources 有一个 dnd.css 文件,在这个文件中,定义了 dnd 操作的一些页面效果。如果期望对替身的头进行修改的话,就必须在当前页面中重新定义与其相关的 CSS 定义。例如如果想将替身头部分的背景颜色重新定义为蓝色。则可以在当前页面的 head 部份写入 “.dojoDndAvatarHeader {background: blue;}”。
清单 16
清单 16 是将替身头部分修改为蓝色的实例。要实现修改后效果,需将其放入当前页面的 head 部分内。
Dojo 将其认为可能常用的一些事件进行了注册,并将这些注册的事件以相对应的名称发布出来。
- /dnd/start:当拖放开始的时候监测到该事件,能获取的相关值包括当前源容器、拖放的节点和判断这次操作是否是复制操作的布尔值。
- /dnd/source/over:当鼠标滑入或滑出一个容器的时候,监测到该事件。需要注意的是当滑入或滑出一个容器的时候都会获得容器。但如果从一个容器滑入到容器外,得到的第二个容器值为空。如果从一个容器直接滑入到另外一个容器,得到的第二个容器值不为空。
- /dnd/drop/before:该方法只被 Dojo1.1.0 或更高的版本所支持。在 drop 发生之前监测到该事件。换句话,可以在该方法中定义一些功能,这些功能将在 drop 发生之前的一刹那发生。
- /dnd/drop:当放下可拖放实体的时候,可监测到该事件。
- /dnd/cancel:当拖放动作取消,或者可拖放实体被拖放到一个无效区域时,可监测到该事件。
清单 17
在清单 17 中,监测了拖放操作中的开始、结束、取消、结束前和鼠标滑过容器五个动作,并将这些动作函数接受到的值进行了输出。在实际项目中对拖放事件的操作就是建立在监测这些事件和这些事件输出值的基础之上的。所不同的只是不同的处理方法和不同的处理顺序。
对于监听事件函数的输出值,“source”表示源容器;“nodes”表示进行拖放操作的“可拖放实体们”(“nodes”是一个数组);“iscopy”为“true”或“false”,表示这次操作是否是复制操作。
现在你应该了解了如何使用 Dojo 所支持的页面拖拽操作来开发自己的项目,同时也应该了解了“拖动”和“拖放”的区别。那么,接下来请在你的电脑上创建一个 HTML 格式的空白文本,去尝试上面的代码,在实践中去感受 Dojo 拖拽功能的强大。