4.2. Drag and Drop
在桌面应用中经常强调直接操纵屏幕上的对象的能力,但是对web界面来说更多的是强调操纵复杂DOM的能力。Script.aculo.us将这二者的特点结合起来提供了简单而又强大的界面拖拽支持。这意味着web开发者可以决定使用基本的拖拽功能,而不关心技术层面的东西。使用视觉效果时,要记得拖拽功能有时候并不是解决界面问题的最佳途径。但是如果需要,script.aculo.us可以做到!
4.2.1
. Draggables
Script.aculo.us
提供了一个
Draggable
的类,用来添加
DOM
元素实现拖拽的能力。在开始之前,新建一个模板文件,
draggables.rhtml,
添加下面的代码:
<div id= "dragDIV" class= "green box">drag</div>
<%= javascript_tag "new Draggable('dragDIV')" %>
当页面被加载后
([url]http://localhost:3000/chapter4/draggables[/url]),Javascript
声明了一个
Draggable
类的实例变量,跟元素的
ID
绑定在一起。现在你可以在页面内拖拽这个元素。注意它是怎么实现在移动中的轻度透明,这里使用了前面讲过的
Opacity
效果。
Rails
提供了
draggable_element helper
来实现拖拽,就像
Draggable.initialize,
第一个参数是元素的
ID
,
第二个参数是
hash
选项,
例如:
<div id= "helperDIV" class= "green box">helper</div>
<%= draggable_element :helperDIV %>
draggable_element
输出的是一个<script>元素,在<script>标签里面有new Draggable的声明,如果你只想要这个Javascript的声明而不需要<script>标签,那么你可以使用draggable_element_js来替换draggable_element.例如:
<div id= "clickDIV" class= "green box">
<%= button_to_function "Make draggable",
draggable_element_js(:clickDIV) %>
</div>
为了让给用户一个更好的体验,当鼠标经过可拖拽元素的位置是,改变鼠标指针形状。可以用CSS的鼠标指针属性来轻松实现:
<div class= "green box" style= "cursor:move">drag</div>
当用户鼠标放到这个元素上面时,指针就变成了“可移动”的图标,指示这个元素可拖拽。当然,
CSS
也可以不写在
div
里面,可以放在外部的
stylesheet
里。
4.2.1
.1. Draggable options
在
Effect.*
方法中,
Draggable.initialize
用一个
JavaScript
表达式作为
hash
选项来定制它们的行为,
draggable_element helper
用
ruby
表达式作为
hash
选项,最后将其翻译成
Javascript
表达式。
revert
参数如果设置为true,那么元素在拖拽之后会回到原始位置。revert的设置也可以是一个function,指明拖拽结束后调用的功能。决定拖拽是否回到某一位置。例如:
<div id= "revertDIV" class= "green box">revert</div>
<%= draggable_element :revertDIV, :revert => true %>
---------------------------------------------------------------------
<div id= "functionRevertDIV" class= "green box"> function revert</div>
<%= draggable_element :functionRevertDIV,
:revert => " function(el){
return Position.page(el)[0] > 100; }" %>
在第二段代码中,:revert后面的function用来指定元素拖拽结束后的位置。
这里,只有当拖动元素超过100像素(从窗口左侧开始算起)时,回被恢复到原来的位置,也就是说,它被拖拽后静止的范围只有长度为100像素高度不限,在窗口内的柱形区域。
Ghosting
参数,如果设置为true,会在拖拽时在原位置生成一个镜像,直到拖拽结束,镜像也随之消失:
<div id= "ghostingDIV" class= "green box">ghosting</div>
<%= draggable_element :ghostingDIV, :ghosting => true %>
handle
允许你为要拖拽的元素内部加一个子元素,这个子元素作为拖拽的把手。它的值是一个JavaScript表达式,这个Javascript表达式以元素的 ID或者元素的引用作为参数。例如:
<div id= "handleDIV" class= "green box">
<span id= "myHandle">handle</span>
</div>
<%= draggable_element :handleDIV, :handle => "'myHandle'" %>
看到了么,这里myHandle被放在双重引号里,这是因为JavaScript表达式要将其作为字符串来执行。
change
选项后面跟一个function,这个function会在整个拖拽的过程中时刻被调用。Fuction的调用就像是以拖拽作为参数的:
<div id= "changeDIV" class= "green box">change</div>
<%= draggable_element :changeDIV, :change => " function(draggable) {
draggable.element.innerHTML=draggable.currentDelta( );
}" %>
这段代码实现的功能:当你拖动这个green box时,里面会根据拖拽的位置时时显示其坐标。
constraint,
如果设置为horizontal(水平)或者vertical(垂直),会约束元素拖动的角度。这个元素ID是作为JavaScript表达式中的参数的,所以要在DOM元素ID加上两对引号:
<div id= "constraintDIV" class= "green box">constraint</div>
<%= draggable_element :constraintDIV, :constraint => "'vertical'" %>
snap
是让元素像是在格子化的画面上拖拽,如果snap设置为false(默认设置),那么不会有这种效果,如果将值设为一个整数n, 元素会在拖拽时跳到最近的与其间隔为n像素的一个格子里。这个值也可以被设定为一个数组形式[x,y],这样横轴和纵轴方向都会被不同的值限制住。Snap的值也可以设置为一个function,function会传递元素当前位置的坐标(是以其开始位置的偏移量,而不是其绝对坐标),返回snap坐标,例如:
<div id= "snapDIV_50" class= "green box">snap to 50</div>
<%= draggable_element :snapDIV_50, :snap => 50 %>
-----------
<div id= "snapDIV_50_100" class= "green box">snap to 50,100</div>
<%= draggable_element :snapDIV_50_100, :snap => '[50,100]' %>
---------------
<div id= "snapDIV_function" class= "green box">snap to function</div>
<%= draggable_element :snapDIV_function, :snap => " function(x, y) {
new_x = (x > 100) ? 100 : ((x < 0) ? 0 : x);
new_y = (y > 100) ? 100 : ((y < 0) ? 0 : y);
return [ new_x, new_y ];
}" %>
最后一个示例证实了为snap选项定义一个function的功效,同时使用x与y两个维度的值从0-100,结果实现了拖拽只能在一个小方框内进行。
4.2.2
. Droppables
Droppable
可以让
DOM
元素接受一个拖拽过来的目标元素并执行某些动作,例如
Ajax
调用,用
javascript
建立一个
droppable
,使用
Dropables.add:
<div id= "dropDIV" class= "pink box">drop</div>
<%= javascript_tag "Droppables.add('dropDIV', {hoverclass:'hover'})" %>
第二个参数是一个hash选项,更多细节参见Droppable options这节。Rails helper使用drop_receiving_element和drop_receiving_element_js来生成droppable.例如:
<div id= "dropHelperDIV" class= "pink box">drop here.</div>
<%= drop_receiving_element :dropHelperDIV, :hoverclass => 'hover' %>
drop_receiving_element_js helper
与 drop_receiving_element不是一回事,它不被夹在<script>标签中,而是直接输出javascript代码。
droppable
没必要接受每种拖拽元素,下面的提供的一些droppable选项可以用来决定什么时候接受draggable.
4.2.2
.1. Droppable options
hoverclass
是一个被加在
droppable
属性中的类的类名,当可接受的拖拽元素在
droppable
上停留时,
告诉用户
droppable
是活跃的。我们在上一节中已经看了几个例子了。
accept
后面是一个
CSS
的类别,可以是字符串或者是一个字符串数组。如果提供了这个选项,
droppable
只会接受
CSS
类别属于
accept
后面集合中的拖拽元素。例如:
<div id= "dragGreen" class= "green box">drag</div>
<%= draggable_element :dragGreen, :revert => true %>
<div id= "dragPink" class= "pink box">drag</div>
<%= draggable_element :dragPink, :revert => true %>
<div id= "dropAccept" class= "pink box">drop here (green only).</div>
<%= drop_receiving_element :dropAccept, :hoverclass => "hover",
:accept => 'green' %>
containment
限制droppable只接受ID属于containment指定的元素ID数组的拖拽元素。这个ID会作为JavaScript表达式中的参数来执行,所以DOM元素的ID要用两对引号。例如:
<div id= "one">
<div id= "dragGreen2" class= "green box">drag</div>
<%= draggable_element :dragGreen2, :revert => true %>
</div>
<div id= "two">
<div id= "dragPink2" class= "pink box">drag</div>
<%= draggable_element :dragPink2, :revert => true %>
</div>
<div id= "dropContainment" class= "pink box">drop here.</div>
<%= drop_receiving_element :dropContainment, :hoverclass => "hover",
:containment => "'one'" %>
onHover
选项是当拖拽元素被拖到droppable上时引发的一段function。这个调用有3个参数: draggable, droppable和overlap(二者的重叠的百分比度)。看一个没有参数情况下的实例:
<div id= "dropOnHover" class= "pink box">drop</div>
<%= drop_receiving_element :dropOnHover, :hoverclass => "hover",
:onHover => "function( ){ $('dropOnHover').update('hover!'); }" %>
下面这个是3个参数都使用的实例:
<div id= "dropOnHover" class= "pink box">drop</div>
<%= drop_receiving_element :dropOnHover, :hoverclass => "hover",
:onHover => " function(draggable, droppable, overlap){
$('dropOnHover').update('you dragged ' + draggable.id +
' over ' + droppable.id + ' by ' + overlap +
' percent'); }" %>
onDrop
是当你将拖拽元素放到droppable上面放手时,droppable接受拖拽元素并发生动作。这个调用有两个参数:拖动元素和droppable元素,例如:
<div id= "dropOnDrop" class= "pink box">drop</div>
<%= drop_receiving_element :dropOnDrop, :hoverclass => "hover",
:onDrop => " function(drag, drop){
alert('you dropped ' + drag.id + ' on ' + drop.id) }" %>
4.2.2
.2. Droppables with Ajax
不管你是通过
JavaScript(Droppables.add)
还是通过
Rails
的
helper(drop_receiving_element
和
drop_receiving_element_js)
创建的
droppable
的大部分选项我们在前面一节都了解了。然而当我们用
helper
创建
droppable
时,还有很多可用的附加选项。也就是说,所有的
link_to_remote
选项,例如
update
和
url
(第三章讲述过的)也是可用的,这些选项被用来创建一个
onDrop
调用的
function
来执行
droppable
的
Ajax
调用:
<div id= "drag" class= "green box">drag</div>
<%= draggable_element :drag, :revert => true %>
<div id= "drop" class= "pink box">drop</div>
<%= drop_receiving_element :drop, :hoverclass => "hover",
:update => "status", :url => { :action => "receive_drop" } %>
<div id= "status"></div>
这里:url选项,里面有个指向receive_drop的action,我们需要在chapter4_controller.rb中定义:
def receive_drop
render :text => "you dropped element id #{params[:id]}"
end
除非用:with选项来指明传递的draggable,不然drop_receiving_element的Ajax调用会自动接收draggable的id作为请求的id参数。
4.2.3
. Sortables
sortable
是建立在draggable和droppable基础之上的,当你将一个元素往下拖的时候,你可以同时给出其他元素的拖拽行为,让他们在画面上重新排列。
使用Javascript的Sortable.create创建一个sortable:
<ul id= "list">
<li>Buy milk</li>
<li>Take out trash</li>
<li>Make first million</li>
</ul>
<%= javascript_tag "Sortable.create('list')" %>
当然,Rails也提供了helper来完成这个任务:sortable_element和sortable_element_js.就像产生其他拖拽效果元素相关的helper一样,第一个参数是目标DOM元素,第二个是options,hash选项,用来控制行为。其他可用的选项有:
hoverclass
传递给
droppable,
指定的这些
CSS
类型加到
droppable
中,以控制只有当
draggable
的
CSS
类型在其中时才被
droppable
所接受。
handle
传递给
droppable,
当元素之间会发生交互作用时(例如链接或者是表单元素)这个选项是非常有用的,例如:
<ul id= "listHandle">
<li><span class= "handle">x</span> Buy milk</li>
<li><span class= "handle">x</span> Take out trash</li>
<li><span class= "handle">x</span> Make first million</li>
</ul>
<%= sortable_element :listHandle, :handle => 'handle' %>
拖放那个x时,发生重新排列。
ghosting
也是传递给draggables.例如:
<ul id= "listGhosting">
<li>Buy milk</li>
<li>Take out trash</li>
<li>Make first million</li>
</ul>
<%= sortable_element :listGhosting, :ghosting => true %>
在原位置产生镜像,拖拽结束后消失
constraint
and
overlap
他们一起工作来决定sortable运作的方向:垂直(默认)vertical或水平horizontal。constraint是传递给draggable的,它决定元素的拖拽方向。overlap传递给droppable,让droppable只有当draggable元素覆盖在其上50%时产生作用。例如:
<ul id="listHorizontal">
<li style="display: inline; margin-right: 10px;">Buy milk</li>
<li style="display: inline; margin-right: 10px;">Take out trash</li>
<li style="display: inline; margin-right: 10px;">Make first million</li>
</ul>
<%= sortable_element :listHorizontal,
:constraint => 'horizontal',
:overlap => 'horizontal' %>
tag
设定
sortable
元素的标签类型,默认情况下是
<li>
标签,
li
是
ul(
无序的项目
)
或者
ol
(有序的项目)中的项目。如果你要设定
sortable
元素是其他的例如图像或者是
div
标签时,要用
tag
选项标识出来:
<div id= "listTag">
<div>Buy milk</div>
<div>Take out trash</div>
<span>Make first million</span>
</div>
<%= sortable_element :listTag, :tag => 'div' %>
这里有个<span>标签,它不属于sortable的元素。不参与拖拽
only
限制sortable元素的CSS 类型为给定的CSS类型或类型数组。例如:
<ul id= "listOnly">
<li class= "sortable">Buy milk</li>
<li class= "sortable">Take out trash</li>
<li>Make first million</li>
</ul>
<%= sortable_element :listOnly, :only => 'sortable' %>
containment
用来使元素的拖拽在多个容器中进行。一个容器只会接受那些在
containment
中指定的父类元素,这个指定元素的方法可以使用元素的
ID
或者是
ID
数组。
<ul id= "list1">
<li>Buy milk</li>
<li>Take out trash</li>
</ul>
<ul id= "list2">
<li>Make first million</li>
</ul>
<%= sortable_element :list1, :containment => ['list1', 'list2'] %>
<%= sortable_element :list2, :containment => ['list1', 'list2'] %>
dropOnEmpty
当你有2个sortable容器,你希望元素可以在这两个容器之间拖动时可以使用dropOnEmpty,默认情况下,是不允许将另一个容器中的元素拖拽到一个空的容器中的。但是当dropOnEmpty设定为true时,情况就改变了。例如:
<ul id= "listFull">
<li id= "thing_1">Buy milk</li>
<li id= "thing_2">Take out trash</li>
<li id= "thing_3">Make first million</li>
</ul>
<ul id= "listEmpty">
</ul>
<%= sortable_element :listFull,
:containment => ['listFull', 'listEmpty'],
:dropOnEmpty => true %>
<%= sortable_element :listEmpty,
:containment => ['listFull', 'listEmpty'],
:dropOnEmpty => true %>
scroll
可以让
sortables
被放在一个有滚动游标的区域里,拖拽元素会自动调整游标。完了完成这个有滚动游标容器的功能,必须设定容器的
style=”overflow: scroll” ,
而且
scroll
选项应该设置为容器的
id
。这个
id
会作为
Javascript
表达式运行的参数,所以必须加两对引号。还必须通过
Position.includeScrollOffsets
参数设定为
true
将
script.aculo.us
中的
scrolling
功能激活,例如:
<div id= "container" style= "overflow: scroll; height: 200px;">
<ul id= "listScroll">
<% 20.times do |i| %>
<li>Buy milk</li>
<li>Take out trash</li>
<li>Make first million</li>
<% end %>
</ul>
</div>
<%= javascript_tag "Position.includeScrollOffsets = true" %>
<%= sortable_element :listScroll, :scroll => "'container'" %>
onChange
当拖拽元素使得其排列顺序发生改变时就调用该参数设定的内容。这个调用以拖拽的元素作为其参数:
<ul id= "listChange">
<li>Buy milk</li>
<li>Take out trash</li>
<li>Make first million</li>
</ul>
<%= sortable_element :listChange,
:onChange => "function(el) { alert(el.innerHTML); }" %>
onUpdate
当拖拽结束
sortable
顺序发生改变时被调用,这个调用以容器作为其参数:
<ul id= "listUpdate">
<li>Buy milk</li>
<li>Take out trash</li>
<li>Make first million</li>
</ul>
<%= sortable_element :listUpdate,
:onUpdate => "function(el) { alert(el.innerHTML); }" %>
<%#似乎不工作%>
4.2.3
.1. Ajax-enabled sortables
对于
droppable
来说,
sortable_element helper
也可以使用常见的
Ajax
选项,例如
link_to_remote
中提供的
Ajax
选项一样。默认情况下,当建立
Ajax
调用时,对于
action
的调用会以连续的
sortable
元素作为参数。在使用时,
sortable
元素的
ID
应该按这样的风格来命名:
Sortable.serialize:
唯一的
ID
标识应该放到后面,前面可以加上下划线。例如
item_1,person_2
和
_3
都可以作为好的
ID
的来用,但是
item1
,
2_person
和
3
就不好了:
<ul id= "listAjax">
<li id= "item_1">Buy milk</li>
<li id= "item_2">Take out trash</li>
<li id= "item_3">Make first million</li>
</ul>
<%= sortable_element :listAjax,
:url => { :action => 'repeat' },
:complete => "alert(request.responseText);" %>
这段代码意思是,当重新排序列表时引发Ajax调用repeat这个action,使用listAjax数组作为参数包含了在当前顺序下(发送重新排列后)sortable元素的所有ID,为了让这段代码能够正确运行,在controller里定义repeat action,显示调用时发送的参数情况。
def repeat
render :text => params.inspect
end