分享

用dojo.dnd实现拖放功能

 jacklopy 2011-04-11
用dojo.dnd实现拖放功能
 
相信很多人都自己动手写过拖放。DHTML里做拖放的原理很简单,一般有这么三个阶段:mousedown 的时候做一些初始化, mousemove 的时候更新拖放对象的位置, mouseup 的时候再做一些清理工作。讲起来简单,但做起来总要花一些功夫的。 Dojo 的 dnd 模块提供了通用且功能强大的拖放支持,让我们可以不用自己造轮子,而且用起来也很方便。
废话少说,先来看看它到底有多方便。假设页面上有两个ul ,我们需要对 ul 里的 li 元素实现拖放,让它们可以自由地在两个列表间移动。如果自己手写,虽然不难但也要花点时间吧。用 Dojo 的话,除了加载模块之外,甚至连一行 javascript 语句都不需要:
 
view plaincopy to clipboardprint?
01.<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www./TR/html4/strict.dtd">  
02.<html>  
03.    <head>  
04.        <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">  
05.        <title>Untitled Document</title>  
06.        <style type="text/css">  
07.            @import "http://ajax./ajax/libs/dojo/1.5/dojo/resources/dojo.css";  
08.            @import "http://ajax./ajax/libs/dojo/1.5/dijit/themes/claro/claro.css";  
09.              
10.            ul{  
11.                border: 3px solid #ccc;  
12.                padding: 2em;  
13.                margin: 5em;  
14.                float: left;  
15.                cursor: default;  
16.            }  
17.            .dojoDndItemOver{  
18.                background: #ededed;  
19.                cursor: pointer;  
20.            }  
21.            .dojoDndItemSelected {  
22.                background: #ccf;   
23.            }  
24.            .dojoDndItemAnchor{  
25.                background: #ccf;  
26.            }  
27.        </style>  
28.        <script type="text/javascript" src="http://ajax./ajax/libs/dojo/1.5/dojo/dojo.xd.js" djConfig="parseOnLoad: true"></script>  
29.        <script type="text/javascript">  
30.            dojo.require("dojo.dnd.Source");  
31.        </script>  
32.    </head>  
33.    <body class="claro">  
34.        <ul dojoType="dojo.dnd.Source">  
35.            <li class="dojoDndItem">Item 1</li>  
36.            <li class="dojoDndItem">Item 2</li>  
37.            <li class="dojoDndItem">Item 3</li>  
38.            <li class="dojoDndItem">Item 4</li>  
39.            <li class="dojoDndItem">Item 5</li>  
40.        </ul>  
41.        <ul dojoType="dojo.dnd.Source">  
42.            <li class="dojoDndItem">Item A</li>  
43.            <li class="dojoDndItem">Item B</li>  
44.            <li class="dojoDndItem">Item C</li>  
45.            <li class="dojoDndItem">Item D</li>  
46.            <li class="dojoDndItem">Item E</li>  
47.        </ul>  
48.    </body>  
49.</html> 
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www./TR/html4/strict.dtd">
<html>
 <head>
  <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
  <title>Untitled Document</title>
  <style type="text/css">
   @import "http://ajax./ajax/libs/dojo/1.5/dojo/resources/dojo.css";
   @import "http://ajax./ajax/libs/dojo/1.5/dijit/themes/claro/claro.css";
   
   ul{
    border: 3px solid #ccc;
    padding: 2em;
    margin: 5em;
    float: left;
    cursor: default;
   }
   .dojoDndItemOver{
    background: #ededed;
    cursor: pointer;
   }
   .dojoDndItemSelected {
    background: #ccf;
   }
   .dojoDndItemAnchor{
    background: #ccf;
   }
  </style>
  <script type="text/javascript" src="http://ajax./ajax/libs/dojo/1.5/dojo/dojo.xd.js" djConfig="parseOnLoad: true"></script>
  <script type="text/javascript">
   dojo.require("dojo.dnd.Source");
  </script>
 </head>
 <body class="claro">
  <ul dojoType="dojo.dnd.Source">
   <li class="dojoDndItem">Item 1</li>
   <li class="dojoDndItem">Item 2</li>
   <li class="dojoDndItem">Item 3</li>
   <li class="dojoDndItem">Item 4</li>
   <li class="dojoDndItem">Item 5</li>
  </ul>
  <ul dojoType="dojo.dnd.Source">
   <li class="dojoDndItem">Item A</li>
   <li class="dojoDndItem">Item B</li>
   <li class="dojoDndItem">Item C</li>
   <li class="dojoDndItem">Item D</li>
   <li class="dojoDndItem">Item E</li>
  </ul>
 </body>
</html>
 
这个例子用了host在google的dojo1.5版本,可以直接运行。这里唯一需要写的javascript 语句就是加载 dojo.dnd.Source 类。剩下的就是在要拖放的对象上做一些标记,用html和 CSS class 就行了。而且 Dojo 为拖放对象添加的 CSS class 非常丰富,让我们能自由定制它们的外观。
 
 
Fig.1: Source内部DnD
 
Fig.2: Source之间DnD
 
Fig.3: 在无法接受拖放内容的地方改变Avatar的外观

 
好,现在来仔细看一下dojo.dnd 模块到底是怎么一回事。
 
dojo.dnd包结构
打开dojo/dnd 源码文件夹,可以看到里面有很多东西:
 
Fig.4: dojo.dnd的目录结构
 
刚才用的 dojo.dnd.Source 就在Source.js里面。顾名思义, Source 就是拖放源,一个存放可拖放对象的容器。相对的还有 dojo.dnd.Target(也在Source.js里) ,它继承了 dojo.dnd.Source ,不过只能接受从别处拖过来的东西,却不能拖出去。另一个Source的子类是AutoSource,如果你需要在运行时添加可拖放的结点(实时更新可拖放结点列表),那么它就是为你准备的。
Dojo.dnd 包中的几个主要类之间的关系大致是这样:
 
Fig.5: DnD包中主要类的结构
 
其中Container 是顶层基类,它的实例包含有一些子元素,能感知 onmouseover/onmouseout 事件,并且知道具体 over 的是哪个元素。 Selector 是 Container 的子类,让容器支持鼠标选择,可以支持单选或多选。 Avatar 就是在拖放时跟着鼠标跑的那个东西,一般会直接包含拖放对象的 dom 结点。而 Manager (是一个 Singleton )则统筹了整个 dnd 过程,管理拖放的起点和终点,以及负责创建、更新和销毁 Avatar 。
包里剩下的东西其实组成了一个子模块: dojo.dnd.move ,如果你只是需要把某个 dom 结点拖来拖去,就应该用这个模块。这里只介绍 dojo.dnd ,以后再写 dojo.dnd.move 。
 
dojo.dnd工作流程
当你在要拖动的对象上按住鼠标左键并开始移动时,Source 会调用 Manager.startDrag 函数,标志拖放过程的开始。这个函数记录当前发起拖放的 Source 和拖放的结点,然后创建出 Avatar ,建立起一切必要的事件关联,并发布( dojo.publish )一个“开始拖放”( /dnd/start )的主题( topic )。 Dojo.dnd 里广泛采用主题广播的方式管理拖放过程,这样页面上所有的 Source 都能监听这些主题并作出反应。例如这个 /dnd/start 主题发布后,页面上所有的 Source (包括刚才拖出来的那个)都将检查自己是否能够接受那些正在被拖动的结点(通过一个叫 checkAcceptance 的方法)。
这里有必要提一下默认的检查方法。Source 有一个属性叫 accept ,这是一个字符串数组,默认是 ["text"] ,表示这个 Source 能够接受的东西只限于包含文本的结点。你可以自由定义 accept 里的内容,这将在下一节具体解释。
当这些结点被拖到一个Source 上时( onmouseover ),将使 Manager 发布 /dojo/source/over 主题,更新 Avatar 上的图标,以反映是否能在这个 Source 上 Drop 。
当你释放鼠标的时候,首先触发Manager 对 onmouseup 事件的响应函数。这个函数将判断当前是否有 Source 能够接受拖放的内容,如果有,就发布 /dnd/drop/before 以及 /dnd/drop 主题;如果没有,就发布 /dnd/cancel 主题。然后销毁 Avatar 、事件句柄、以及所有与本次拖放相关的信息。所有的 Source 都会监听这些主题,并作出相应的应对。
如果某个Source 在响应/dnd/drop主题时发现自己就是 Drop 的目标,就把这次拖放的结点传给一个叫 _normalizedCreator 的私有方法,该方法负责把这些结点转换成自己可以接受的形式。这里其实有一个定制点,让用户自定义转换的方式,这也将在下一节讲到。最后 insertNodes 方法把这些新结点插进来。如果做的是“移动”而不是“复制”(拖动时按住CTRL就是复制),还需要通知作为拖放起点的 Source 删除那些拖出来的子结点。
 

定制dojo.dnd
定制dojo.dnd 的基本方式和 dijit 类似,就是在构造函数中传入参数对象。如果是声明式创建,就可以直接用 html 属性的方式写在 html 元素中。 Dojo.dnd 具有非常多的定制点,一一列举会过于冗长,这里只挑最常用的几个。(当然,一旦你阅读了源码,完全可以抛开一切约束,通过继承的方式任意扩展 dojo.dnd 里的内容)
1. 首当其冲是 accept 数组,刚才已经讲到,只有和这个数组有交集的拖放源才能被接受。例如,一个 Source 的 accept 数组是 ["text", "image"] ,另一个是 ["image", "video"] ,那么这两个 Source 就能接受从对方那里拖过来的东西。你肯定会问:为什么这是一个数组而不是单个字符串?答:对不同的拖放结点可以再定制其拖放类型。例如一个 Source 里可以既有 text 类型的结点,也有 image 类型的结点,你可以通过 dndType 属性在这些结点上做标记:
view plaincopy to clipboardprint?
01.<ul dojoType="dojo.dnd.Source" accept="['text', 'image']"> 
02.    <li class="dojoDndItem" dndType="text"></li> 
03.    <li class="dojoDndItem" dndType="image"></li> 
04.</ul> 
<ul dojoType="dojo.dnd.Source" accept="['text', 'image']">
    <li class="dojoDndItem" dndType="text"></li>
    <li class="dojoDndItem" dndType="image"></li>
</ul>
这样,如果你拖的是标记为text 的 li 元素,那么那个 accept=["image", "video"] 的 Source 就无法接受它了:
 

Fig.6: 运用accept和dndType精确控制拖放

2. 第二重要的个人感觉就是 creator ,前面提到,通过这个函数可以任意定制拖进来的东西。这个函数接受两个参数,一个是拖进来的 dom 结点的 innerHTML (注: Container 里说这是一个形如 {data: data, type: type} 的对象,但在 Source 的实际使用中,传的仅仅是 data ),另一个叫 hint 字符串,目前据我所知其唯一的可能值是 "avatar" ,表示创建出的结点是在 Avatar 中使用的。它需要返回一个形如: {node: node, data: data, type: type} 的对象。这里的 node 可以跟传进来的那个没有半点关系。 Data 表示拖动的真正内容,一般就是 node.innerHTML 。 Type 就是这个结点的 dndType 。例如我要在传进来的内容前面加一点东西,可以这样写:
view plaincopy to clipboardprint?
01.<ul dojoType="dojo.dnd.Source"> 
02.    <script type="dojo/method" event="creator" args="data, hint"> 
03.        var node = dojo.create("div", {  
04.            "id": dojo.dnd.getUniqueId(),   
05.            "class": "dojoDndItem",   
06.            "innerHTML": "<strong style='color: darkred'>Special</strong> " + String(data)  
07.        });  
08.        return {node: node, data: node.innerHTML, type: ["text"]};  
09.    </script> 
10.    <li class="dojoDndItem">Item A</li> 
11.    <li class="dojoDndItem">Item B</li> 
12.    <li class="dojoDndItem">Item C</li> 
13.    <li class="dojoDndItem">Item D</li> 
14.    <li class="dojoDndItem">Item E</li> 
15.</ul> 
<ul dojoType="dojo.dnd.Source">
 <script type="dojo/method" event="creator" args="data, hint">
  var node = dojo.create("div", {
   "id": dojo.dnd.getUniqueId(),
   "class": "dojoDndItem",
   "innerHTML": "<strong style='color: darkred'>Special</strong> " + String(data)
  });
  return {node: node, data: node.innerHTML, type: ["text"]};
 </script>
 <li class="dojoDndItem">Item A</li>
 <li class="dojoDndItem">Item B</li>
 <li class="dojoDndItem">Item C</li>
 <li class="dojoDndItem">Item D</li>
 <li class="dojoDndItem">Item E</li>
</ul>
效果如图:
 

 
3.  一个简单但有用的开关属性:horizontal 。如果你的拖放源是一个横向容器,请把它设为 true 。
 
4.  三个很有用的且互相有关联的开关属性:copyOnly 默认 false, selfAccept 默认 true ,  selfCopy 默认 false 。顾名思义,如果 copyOnly 是 true ,那么这个 Source 里的东西只能被复制而不能被移走。当 copyOnly 是 true ,且 selfAccept 是 false 的时候,在容器内 dnd 也被禁止了。当 copyOnly 是 true , selfAccept 是 true ,且 selfCopy 是 true 的时候,容器内 dnd 的意思是复制而不是移动。
 

 
结语
 
本文很粗浅地介绍了dojo.dnd 包的基本用法,如果要深入了解,强烈建议阅读源码并不断实践。 Dojo.dnd 是 dojo 的核心组件之一,功能强大且代码优雅,相信你一定能从中学到不少东西。
 

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/dojotoolkit/archive/2010/10/31/5977006.aspx

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多