[转帖]Mootools源码分析-44 -- Sortables
原帖地址:http://space.flash8.net/space/?uid-18713-action-viewspace-itemid-409214
原作者:我佛山人
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
//拖放排序类
//演示:http://demos.mootools.net/Sortables
var Sortables = new Class({
//继承自Events和Options,UI插件的特点
Implements: [Events, Options],
options: {/*
//排序事件
onSort: $empty,
//开始事件
onStart: $empty,
//完成事件
onComplete: $empty,*/
snap: 4,
//透明度
opacity: 1,
//是否复制
clone: false,
//是否使用形变动画
revert: false,
//拖动句柄对象
handle: false,
//约束
constrain: false
},
//构造函数
initialize: function(lists, options) {
//设置配置参数
this.setOptions(options);
this.elements = [];
this.lists = [];
this.idle = true;
//列表项可以单个,也可以是符合
this.addLists($$($(lists) || lists));
//如果不克隆副本,则不需要变形动画
if (!this.options.clone) this.options.revert = false;
//如果指定变形动画效果
if (this.options.revert) this.effect = new Fx.Morph(null, $merge({duration: 250, link: 'cancel'}, this.options.revert));
},
//附加事件
attach: function() {
this.addLists(this.lists);
return this;
},
//移除事件
detach: function() {
this.lists = this.removeLists(this.lists);
return this;
},
//添加列表项
addItems: function() {
//给参数降维再遍历
Array.flatten(arguments).each(function(element) {
//加到数组
this.elements.push(element);
//将关联本列表项的start方法缓存
var start = element.retrieve('sortables:start', this.start.bindWithEvent(this, element));
//如果指定拖动句柄,从当前列表项中获取,否则将当前列表项作为句柄
//然后给句柄添加鼠标按下事件,触发start方法
(this.options.handle ? element.getElement(this.options.handle) || element : element).addEvent('mousedown', start);
}, this);
return this;
},
//添加列表
addLists: function() {
//给参数降维再遍历
Array.flatten(arguments).each(function(list) {
this.lists.push(list);
//添加当前列表下的项
this.addItems(list.getChildren());
}, this);
return this;
},
//移除列表项
removeItems: function() {
//用于返回的数组
var elements = [];
//给参数降维再遍历
Array.flatten(arguments).each(function(element) {
//加到返回数组
elements.push(element);
//从数组中删除
this.elements.erase(element);
//取缓存中的事件绑定方法
var start = element.retrieve('sortables:start');
//类似添加列表项时的判断,找到句柄后移除事件
(this.options.handle ? element.getElement(this.options.handle) || element : element).removeEvent('mousedown', start);
}, this);
//返回移除的列表项集合
return $$(elements);
},
//移除列表
removeLists: function() {
//用于返回的数组
var lists = [];
//给参数降维再遍历
Array.flatten(arguments).each(function(list) {
//加到返回数组
lists.push(list);
//从数组中删除
this.lists.erase(list);
//移除当前列表的所有列表项,因为removeItems方法中自动给参数降维,所以可以直接传数组
this.removeItems(list.getChildren());
}, this);
//返回移除的列表集合
return $$(lists);
},
//获取复制的对象
getClone: function(event, element) {
//如果指定不复制,返回新创建的div并插入到body
if (!this.options.clone) return new Element('div').inject(document.body);
//如果参数为函数,修改其调用的上下文指向并传送指定参数
if ($type(this.options.clone) == 'function') return this.options.clone.call(this, event, element, this.list);
//剩下的是指定为复制的情况
//复制当前列表项并修改其样式
return element.clone(true).setStyles({
'margin': '0px',
'position': 'absolute',
'visibility': 'hidden',
'width': element.getStyle('width')
}).inject(this.list).position(element.getPosition(element.offsetParent));
},
//获取可放落的对象
getDroppables: function() {
//取当前列表下的所有列表项
var droppables = this.list.getChildren();
//如果没有约束,则其它列表也作为可放落对象
if (!this.options.constrain) droppables = this.lists.concat(droppables).erase(this.list);
//从可放落对象集合中排除当前对象及其克隆
return droppables.erase(this.clone).erase(this.element);
},
//插入
insert: function(dragging, element) {
//指定插入位置
var where = 'inside';
if (this.lists.contains(element)) {
this.list = element;
this.drag.droppables = this.getDroppables();
} else {
where = this.element.getAllPrevious().contains(element) ? 'before' : 'after';
}
this.element.inject(element, where);
this.fireEvent('onSort', [this.element, this.clone]);
},
//开始操作
start: function(event, element) {
//如果当前非闲置状态,退出
if (!this.idle) return;
//设置闲置状态标识
this.idle = false;
//当前操作对象
this.element = element;
//获取当前透明度
this.opacity = element.get('opacity');
//获取当前操作对象所在的列表对象
this.list = element.getParent();
//获取当前操作对象的克隆对象
this.clone = this.getClone(event, element);
//创建拖放对象
this.drag = new Drag.Move(this.clone, {
snap: this.options.snap,
//拖放范围,如果指定constrain参数为true才会取当前对象所在的列表对象
container: this.options.constrain && this.element.getParent(),
//获取可放落的对象
droppables: this.getDroppables(),
//onSnap事件监听
onSnap: function() {
//停止事件冒泡及返回值
event.stop();
//显示克隆对象
this.clone.setStyle('visibility', 'visible');
//设置透明度
this.element.set('opacity', this.options.opacity || 0);
//触发onStart事件
this.fireEvent('onStart', [this.element, this.clone]);
}.bind(this),
//拖动进入事件
onEnter: this.insert.bind(this),
//取消拖动事件
onCancel: this.reset.bind(this),
//拖动完成事件
onComplete: this.end.bind(this)
});
//插入克隆对象到当前对象前面
this.clone.inject(this.element, 'before');
//开始拖动
this.drag.start(event);
},
//结束拖动排序
end: function() {
//移除事件
this.drag.detach();
//恢复透明度
this.element.set('opacity', this.opacity);
//如果使用形变动画(主要是外形尺寸和坐标位置上的)
if (this.effect) {
//获取尺寸
var dim = this.element.getStyles('width', 'height');
//计算位置
var pos = this.clone.computePosition(this.element.getPosition(this.clone.offsetParent));
//将当前克隆对象作为形变对象
this.effect.element = this.clone;
//开始动画效果
this.effect.start({
top: pos.top,
left: pos.left,
width: dim.width,
height: dim.height,
opacity: 0.25
//动画完成后的后续操作
}).chain(this.reset.bind(this));
} else {
//后续操作
this.reset();
}
},
//重置
reset: function() {
//设置闲置状态标记
this.idle = true;
//清除克隆对象
this.clone.destroy();
//触发onComplete事件
this.fireEvent('onComplete', this.element);
},
//序列化
serialize: function() {
//使用Array.link处理参数
var params = Array.link(arguments, {modifier: Function.type, index: $defined});
//遍历列表集
var serial = this.lists.map(function(list) {
//遍历各列表的列表项,如果参数中传有函数的引用,使用该函数处理列表项,否则默认返回id
return list.getChildren().map(params.modifier || function(element) {
//取列表项的id
return element.get('id');
}, this);
}, this);
//参数中传送的索引值
var index = params.index;
//如果列表集中只有一项,则索引值为0
if (this.lists.length == 1) index = 0;
//如果索引存在并且值在合法范围内,返回指定索引值列表的序列化结果,否则返回整个列表集的序列化结果
return $chk(index) && index >= 0 && index < this.lists.length ? serial[index] : serial;
}
});
//演示:http://demos.mootools.net/Sortables
var Sortables = new Class({
//继承自Events和Options,UI插件的特点
Implements: [Events, Options],
options: {/*
//排序事件
onSort: $empty,
//开始事件
onStart: $empty,
//完成事件
onComplete: $empty,*/
snap: 4,
//透明度
opacity: 1,
//是否复制
clone: false,
//是否使用形变动画
revert: false,
//拖动句柄对象
handle: false,
//约束
constrain: false
},
//构造函数
initialize: function(lists, options) {
//设置配置参数
this.setOptions(options);
this.elements = [];
this.lists = [];
this.idle = true;
//列表项可以单个,也可以是符合
this.addLists($$($(lists) || lists));
//如果不克隆副本,则不需要变形动画
if (!this.options.clone) this.options.revert = false;
//如果指定变形动画效果
if (this.options.revert) this.effect = new Fx.Morph(null, $merge({duration: 250, link: 'cancel'}, this.options.revert));
},
//附加事件
attach: function() {
this.addLists(this.lists);
return this;
},
//移除事件
detach: function() {
this.lists = this.removeLists(this.lists);
return this;
},
//添加列表项
addItems: function() {
//给参数降维再遍历
Array.flatten(arguments).each(function(element) {
//加到数组
this.elements.push(element);
//将关联本列表项的start方法缓存
var start = element.retrieve('sortables:start', this.start.bindWithEvent(this, element));
//如果指定拖动句柄,从当前列表项中获取,否则将当前列表项作为句柄
//然后给句柄添加鼠标按下事件,触发start方法
(this.options.handle ? element.getElement(this.options.handle) || element : element).addEvent('mousedown', start);
}, this);
return this;
},
//添加列表
addLists: function() {
//给参数降维再遍历
Array.flatten(arguments).each(function(list) {
this.lists.push(list);
//添加当前列表下的项
this.addItems(list.getChildren());
}, this);
return this;
},
//移除列表项
removeItems: function() {
//用于返回的数组
var elements = [];
//给参数降维再遍历
Array.flatten(arguments).each(function(element) {
//加到返回数组
elements.push(element);
//从数组中删除
this.elements.erase(element);
//取缓存中的事件绑定方法
var start = element.retrieve('sortables:start');
//类似添加列表项时的判断,找到句柄后移除事件
(this.options.handle ? element.getElement(this.options.handle) || element : element).removeEvent('mousedown', start);
}, this);
//返回移除的列表项集合
return $$(elements);
},
//移除列表
removeLists: function() {
//用于返回的数组
var lists = [];
//给参数降维再遍历
Array.flatten(arguments).each(function(list) {
//加到返回数组
lists.push(list);
//从数组中删除
this.lists.erase(list);
//移除当前列表的所有列表项,因为removeItems方法中自动给参数降维,所以可以直接传数组
this.removeItems(list.getChildren());
}, this);
//返回移除的列表集合
return $$(lists);
},
//获取复制的对象
getClone: function(event, element) {
//如果指定不复制,返回新创建的div并插入到body
if (!this.options.clone) return new Element('div').inject(document.body);
//如果参数为函数,修改其调用的上下文指向并传送指定参数
if ($type(this.options.clone) == 'function') return this.options.clone.call(this, event, element, this.list);
//剩下的是指定为复制的情况
//复制当前列表项并修改其样式
return element.clone(true).setStyles({
'margin': '0px',
'position': 'absolute',
'visibility': 'hidden',
'width': element.getStyle('width')
}).inject(this.list).position(element.getPosition(element.offsetParent));
},
//获取可放落的对象
getDroppables: function() {
//取当前列表下的所有列表项
var droppables = this.list.getChildren();
//如果没有约束,则其它列表也作为可放落对象
if (!this.options.constrain) droppables = this.lists.concat(droppables).erase(this.list);
//从可放落对象集合中排除当前对象及其克隆
return droppables.erase(this.clone).erase(this.element);
},
//插入
insert: function(dragging, element) {
//指定插入位置
var where = 'inside';
if (this.lists.contains(element)) {
this.list = element;
this.drag.droppables = this.getDroppables();
} else {
where = this.element.getAllPrevious().contains(element) ? 'before' : 'after';
}
this.element.inject(element, where);
this.fireEvent('onSort', [this.element, this.clone]);
},
//开始操作
start: function(event, element) {
//如果当前非闲置状态,退出
if (!this.idle) return;
//设置闲置状态标识
this.idle = false;
//当前操作对象
this.element = element;
//获取当前透明度
this.opacity = element.get('opacity');
//获取当前操作对象所在的列表对象
this.list = element.getParent();
//获取当前操作对象的克隆对象
this.clone = this.getClone(event, element);
//创建拖放对象
this.drag = new Drag.Move(this.clone, {
snap: this.options.snap,
//拖放范围,如果指定constrain参数为true才会取当前对象所在的列表对象
container: this.options.constrain && this.element.getParent(),
//获取可放落的对象
droppables: this.getDroppables(),
//onSnap事件监听
onSnap: function() {
//停止事件冒泡及返回值
event.stop();
//显示克隆对象
this.clone.setStyle('visibility', 'visible');
//设置透明度
this.element.set('opacity', this.options.opacity || 0);
//触发onStart事件
this.fireEvent('onStart', [this.element, this.clone]);
}.bind(this),
//拖动进入事件
onEnter: this.insert.bind(this),
//取消拖动事件
onCancel: this.reset.bind(this),
//拖动完成事件
onComplete: this.end.bind(this)
});
//插入克隆对象到当前对象前面
this.clone.inject(this.element, 'before');
//开始拖动
this.drag.start(event);
},
//结束拖动排序
end: function() {
//移除事件
this.drag.detach();
//恢复透明度
this.element.set('opacity', this.opacity);
//如果使用形变动画(主要是外形尺寸和坐标位置上的)
if (this.effect) {
//获取尺寸
var dim = this.element.getStyles('width', 'height');
//计算位置
var pos = this.clone.computePosition(this.element.getPosition(this.clone.offsetParent));
//将当前克隆对象作为形变对象
this.effect.element = this.clone;
//开始动画效果
this.effect.start({
top: pos.top,
left: pos.left,
width: dim.width,
height: dim.height,
opacity: 0.25
//动画完成后的后续操作
}).chain(this.reset.bind(this));
} else {
//后续操作
this.reset();
}
},
//重置
reset: function() {
//设置闲置状态标记
this.idle = true;
//清除克隆对象
this.clone.destroy();
//触发onComplete事件
this.fireEvent('onComplete', this.element);
},
//序列化
serialize: function() {
//使用Array.link处理参数
var params = Array.link(arguments, {modifier: Function.type, index: $defined});
//遍历列表集
var serial = this.lists.map(function(list) {
//遍历各列表的列表项,如果参数中传有函数的引用,使用该函数处理列表项,否则默认返回id
return list.getChildren().map(params.modifier || function(element) {
//取列表项的id
return element.get('id');
}, this);
}, this);
//参数中传送的索引值
var index = params.index;
//如果列表集中只有一项,则索引值为0
if (this.lists.length == 1) index = 0;
//如果索引存在并且值在合法范围内,返回指定索引值列表的序列化结果,否则返回整个列表集的序列化结果
return $chk(index) && index >= 0 && index < this.lists.length ? serial[index] : serial;
}
});