calyptus (owner)

Revisions

gist: 216161 Download_button fork
public
Public Clone URL: git://gist.github.com/216161.git
Embed All Files: show embed
Element.Draggable.js #
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
/*
@import Event.Drag.js
 
Script: Element.Draggable.js
Enables HTML 5 drag operation events in legacy browsers.
 
License:
MIT-style license.
 
Authors:
Sebastian Markbåge
*/
 
(function(window, document){
 
var startEvent = null;
 
var useNativeIE = false;
 
var simulateDragEvent = Element.dispatchDragEvent;
 
var dragPreStart = function(event){
if (startEvent) return;
startEvent = (Browser.Engine.gecko) ? new Event($extend({}, event.event)) : event;
event.preventDefault();
document.addEvents({ mousemove: dragCheck, mouseup: dragCancel, selectstart: stopEvent });
};
 
var dragCancel = function(){
this.removeEvents({ mousemove: dragCheck, mouseup: dragCancel });
startEvent = null;
};
 
var dragCheck = function(event){
if (Math.sqrt(Math.pow(event.page.x - startEvent.page.x, 2) + Math.pow(event.page.y - startEvent.page.y, 2)) <= 20) return;
 
this.removeEvents({ mousemove: dragCheck, mouseup: dragCancel });
event.stop();
 
if (useNativeIE && startEvent.target.dragDrop){
startEvent.target.dragDrop();
} else if (simulateDragEvent(startEvent.event, 'dragstart')){
attachListeners();
return;
}
this.removeEvent('selectstart', stopEvent);
startEvent = null;
};
 
// TODO: Cancelable using ESC key
var attachListeners = window.addEventListener ? function(){
var win = window;
win.addEventListener('mousemove', dragOver, true);
win.addEventListener('mouseover', dragEnter, true);
win.addEventListener('mouseout', dragLeave, true);
win.addEventListener('mouseup', dragEnd, true);
} : function(){
var body = document.body;
body.attachEvent('onmousemove', dragOver);
body.attachEvent('onmousemove', dragEnter);
body.attachEvent('onmousemove', dragLeave);
body.attachEvent('onmouseup', dragEnd);
body.setCapture();
};
 
var detachListeners = window.addEventListener ? function(){
var win = window;
win.removeEventListener('mousemove', dragOver, true);
win.removeEventListener('mouseover', dragEnter, true);
win.removeEventListener('mouseout', dragLeave, true);
win.removeEventListener('mouseup', dragEnd, true);
} : function(){
var body = document.body;
body.detachEvent('onmousemove', dragOver);
body.detachEvent('onmousemove', dragEnter);
body.detachEvent('onmousemove', dragLeave);
body.detachEvent('onmouseup', dragEnd);
document.releaseCapture();
};
 
var dragOver = function(event){
event = event || window.event;
stopEvent(event);
resetDropEffect('none');
var end = !simulateDragEvent(event, 'drag', startEvent.target);
resetDropEffect();
if (end || simulateDragEvent(event, 'dragover')) resetDropEffect('none');
if (end) dragEnd(event);
};
 
var dragEnter = function(event){
event = event || window.event;
stopEvent(event);
resetDropEffect();
if (simulateDragEvent(event, 'dragenter')) resetDropEffect('none');
};
 
var dragLeave = function(event){
event = event || window.event;
stopEvent(event);
simulateDragEvent(event, 'dragleave');
};
 
var dragEnd = function(event){
event = event || window.event;
 
stopEvent(event);
 
var dataTransfer = new DataTransfer();
if (dataTransfer.get && dataTransfer.get('dropAllowed'))
simulateDragEvent(event, 'drop');
 
simulateDragEvent(event, 'dragend', startEvent.target);
 
startEvent = null;
 
this.removeEvent('selectstart', stopEvent);
 
detachListeners();
};
 
var stopEvent = window.addEventListener ? function(event){
event.stopPropagation();
event.preventDefault();
} : function(event){
event = event || window.event;
event.cancelBubble = true;
event.returnValue = false;
};
 
var resetDropEffect = function(value){
var dataTransfer = new DataTransfer();
if (dataTransfer.set) dataTransfer.set('dropEffect', value);
};
 
var blockNativeStart = function(event){
if ((event.event || event).dataTransfer){ event.preventDefault(); }
};
 
/*if (Browser.Engine.gecko19) window.addEventListener('dragover', function(event){
simulateDragEvent(event, 'drag', startEvent.target);
}, true);*/
 
Element.Properties.draggable = /*Browser.Engine.webkit ? {
// TODO: Gesture option
// TODO: Too many bugs in WebKit on PC? Use fallback instead?
 
get: function(){
var drag = this.getStyle('-khtml-user-drag');
if (!drag) return this.draggable == undefined ? 'auto' : this.draggable;
return drag == 'auto' ? drag : (drag == 'element');
},
 
set: function(value){
this.setStyle('-khtml-user-drag', value == 'auto' ? 'auto' : (value && value != 'false' ? 'element' : 'none'));
this.setStyle('-webkit-user-select', 'none');
//if (value) this.addEvent('mousedown', stopEvent);
//else this.removeEvent('mousedown', stopEvent);
}
 
} :*/ {
 
get: function(){
//if (this.draggable != undefined) return this.draggable;
var events = this.retrieve('events');
return !events || !events['mousedown'] || !events['mousedown'].contains(dragPreStart);
},
 
set: function(value){
if (!useNativeIE && Browser.Engine.trident) this.addEvent('dragstart', blockNativeStart);
/*if (this.draggable != undefined) this.draggable = value;
else*/
if (value) this.addEvent('mousedown', dragPreStart);
else this.removeEvent('mousedown', dragPreStart);
}
 
};
 
})(window, document);
Element.Snapshot.js #
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
/*
@import Element.Measure.js
 
Script: Element.Snapshot.js
Extends Element with methods that clones an element with it's current relative stylesheets intact.
 
License:
MIT-style license.
 
Authors:
Sebastian Markbåge
*/
 
(function(window){
 
var isPercentage = /\%$/, isRelative = /\%|em|ex/, isDigits = /^\d+$/, hasUnit = /[a-z]{2}$/i,
isExcludedStyle = /cssText|^length$|parentRule|hasLayout/, isSizeConstraint = /(max|min)(Width|Height)/,
isZeroClip = /rect\((0(px)?\, ){3}0(px?)\)/,
relativeSides = ['textIndent', 'paddingBottom', 'paddingLeft', 'paddingRight', 'paddingTop'];
 
var getCurrentStyle = window.getComputedStyle ? function(element){
return window.getComputedStyle(element, null);
} : function(element){
return element.currentStyle || element.style;
};
 
var getRelativeFontSize = function(element){
var z = getCurrentStyle(element).fontSize, u, i = 0;
if (hasUnit.test(z)) i = -2;
else if (isPercentage.test(z)) i = -1;
u = z.substr(z.length + i);
z = parseFloat(z.substr(0, z.length + i));
if (isRelative.test(u) && element.parentNode && element.parentNode != window.document.body){
var p = arguments.callee(element.parentNode);
z = (u == '%' ? z / 100 : (u == 'ex' ? z / 2 : z)) * p.size;
u = p.unit;
}
return { size: z, unit: u, value: z + u };
};
 
Element.implement({
 
cloneStyles: function(from){
var styles = getCurrentStyle(from);
for (var k in styles){
if (isDigits.test(k)) k = styles[k];
if (k == 'clip' && isZeroClip.test(styles[k]))
this.style['clip'] = 'auto';
else if (!isExcludedStyle.test(k) && styles[k] != '' && $type(styles[k]) != 'function'
&& (!isSizeConstraint.test(k) || styles[k] != '-1px'))
this.style[k] = styles[k];
}
return this;
},
 
snapshot: function(contents, keepid){
var clone = this.clone(contents, keepid);
if (!this.parentNode || this.parentNode.nodeType == 11) return clone;
 
var offsetParent = this;
while ((offsetParent = offsetParent.parentNode) && getCurrentStyle(offsetParent).display == 'inline');
 
var thisSize = this.getComputedSize(), parentWidth = document.id(offsetParent).getComputedSize().width;
 
var ce = clone.getElementsByTagName('*'), te = this.getElementsByTagName('*');
for (var i = ce.length; i--;) Element.cloneStyles(ce[i], te[i]);
 
var styles = getCurrentStyle(this);
relativeSides.each(function(style){
var value = styles[style];
if (isPercentage.test(value))
newElement.setStyle(style, parentWidth * parseFloat(value.substr(0, value.length - 1)) / 100);
});
 
return clone.setStyles({
'font-size': getRelativeFontSize(this).value,
'width': thisSize.width,
'height': thisSize.height
});
}
 
});
 
})(window);
Event.DataTransfer.Feedback.js #
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
/*
@import Event.DataTransfer.js
@import Element.Snapshot.js
 
Script: Event.DataTransfer.Feedback.js
Extends DataTransfer with methods to set image represenation of current drag operations.
 
License:
MIT-style license.
 
Authors:
Sebastian Markbåge
*/
 
(function(document){
 
var ghost, lastTarget;
 
var simulateDragEvent = Element.dispatchDragEvent;
 
var efpProp = Browser.Engine.webkit || Browser.Engine.presto ? 'page' : 'client',
offsetProp = Browser.Engine.trident4 ? 'page' : 'client',
positioning = Browser.Engine.trident4 ? 'absolute' : 'fixed',
emptyElement = new Element('div', { styles: { width: 1, height: 1, visibility: 'hidden' }});
 
var moveGhost = function(event){
if (!ghost.nativ)
ghost.element.setStyles({
left: event[offsetProp].x - ghost.offset.x,
top: event[offsetProp].y - ghost.offset.y
});
};
 
var endGhost = function(event){
ghost.nativ = false;
//moveGhost(lastDrag || event);
if (ghost.revert && event.dataTransfer.get('dropEffect') == 'none'){
ghost.revert.start(ghost.revertTarget).chain(cleanUpGhost);
} else {
cleanUpGhost();
}
};
 
var cleanUpGhost = function(){
if (!ghost) return;
if (ghost.effect) ghost.effect.cancel();
ghost.element.destroy();
document.body.removeEvents({ drag: moveGhost, dragend: endGhost });
ghost = undefined;
lastTarget = undefined;
};
 
var dragOverGhost = function(event){
event.stopPropagation();
var target = getTarget(event);
if (lastTarget != target){
if (simulateDragEvent(event.event, 'dragenter', target, lastTarget) === false) event.preventDefault();
if (lastTarget) simulateDragEvent(event.event, 'dragleave', lastTarget, target);
lastTarget = target;
}
else if (simulateDragEvent(event.event, 'dragover', target) === false) event.preventDefault();
};
 
var dropOnGhost = function(event){
event.stop();
simulateDragEvent(event, 'drop', getTarget(event));
};
 
var getTarget = document.elementFromPoint ? function(event){
var doc = event.target.ownerDocument, style = ghost.element.style;
var display = style.display;
style.display = 'none';
var target = doc.elementFromPoint(event[efpProp].x, event[efpProp].y);
style.display = display;
return target;
} : function(){
return document.body;
};
 
this.DataTransfer.implement({
 
setDragFeedback: function(element, options){
/*
options = {
opacity: 1,
class: '...',
styles: { ... },
revert: false,
container: false,
limit: false,
offset: { x: undefined, y: undefined },
wrapImage: true,
native: true
}
*/
 
element = document.id(element);
 
var dataTransfer = this.dataTransfer;
 
if (!element){
cleanUpGhost();
if (dataTransfer && dataTransfer.setDragImage)
dataTransfer.setDragImage(emptyElement, 0, 0);
return this;
}
 
if (!ghost)
document.body.addEvents({ drag: moveGhost, dragend: endGhost });
else
ghost.element.destroy();
 
ghost = {};
 
if (options.offset){
ghost.offset = options.offset;
} else if (this.event && element.getParent()){
var l = element.getPosition(), v = event.page, b = element.getDocument().body;
ghost.offset = { x: v.x - l.x - b.offsetLeft, y: v.y - l.y - b.offsetTop };
} else {
ghost.offset = { x: -1, y: -1 };
}
 
ghost.limit = document.id(options.container) ? options.container.getCoordinates() : options.limit;
//var size = ghost.element.getSize();
 
ghost.nativ = options['native'] !== false && !ghost.limit && (!Browser.Engine.webkit || Browser.Platform.mac) && !Browser.Engine.gecko && dataTransfer && dataTransfer.setDragImage;
 
if (element.getParent()) element = element.snapshot(true, false);
 
element.set({ styles: options.styles, 'class': options['class'], opacity: options.opacity });
 
if (ghost.nativ){
if (options.wrapImage !== false && element.get('tag') == 'img') element = new Element('div').adopt(element.setStyles({ margin: 0, left: 0, top: 0 }));
if (Browser.Engine.gecko) element.set('opacity', element.get('opacity') / 0.75);
}
 
if (options.revert && this.event && (!ghost.nativ || !Browser.Engine.webkit)){
ghost.revert = new Fx.Morph(element, $extend({duration: 250, link: 'cancel'}, options.revert));
ghost.revertTarget = {
left: this.event[offsetProp].x - ghost.offset.x,
top: this.event[offsetProp].y - ghost.offset.y
};
}
 
ghost.element = element.setStyles({
'position': positioning,
'z-index': 2000,
'left': -2000,
'top': 0,
'max-width': '100%',
'max-height': '100%',
'float': 'none',
'display': 'block',
'margin': 0
}).inject(document.body);
 
if (ghost.nativ){
dataTransfer.setDragImage(element, ghost.offset.x, ghost.offset.y);
} else {
if (dataTransfer && dataTransfer.setDragImage) dataTransfer.setDragImage(emptyElement, 0, 0);
element.addEvents({
dragenter: dragOverGhost,
dragleave: Event.stopPropagation,
dragover: dragOverGhost,
drop: dropOnGhost
});
}
return this;
},
 
setDragImage: function(image, x, y){
return this.setDragFeedback(image, { offset: { x: y, y: y }, wrapImage: false });
}
 
});
 
})(document);
Event.DataTransfer.js #
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
/*
Script: Event.DataTransfer.js
Contains the dataTransfer object attached to events during drag & drop operations in HTML 5.
 
License:
MIT-style license.
 
Authors:
Sebastian Markbåge
*/
 
(function(document){
 
var currentDrag;
 
var getCurrentDrag = function(){
if (currentDrag) return currentDrag;
document.body.addEvent('dragend', cleanUpDrag);
return currentDrag = { data: {} };
};
 
var cleanUpDrag = function(){
currentDrag = false;
document.body.removeEvent('dragend', cleanUpDrag);
};
 
var limitedFormats = Browser.Engine.trident || (Browser.Engine.webkit && !Browser.Platform.mac);
 
var formatMap = limitedFormats ? { 'text/plain': 'Text', 'text/uri-list': 'URL' }
: (Browser.Engine.gecko ? { 'text/uri-list' : 'text/x-moz-url' } : {});
 
this.DataTransfer.implement({
 
clearData: function(format){
if ($type(format) == 'object'){
this.get('types').each(this.clearData, this);
return this;
}
if (this.dataTransfer) this.dataTransfer.clearData(formatMap[format] || format);
if (currentDrag) delete currentDrag.data[format];
return this;
},
 
hasData: function(format){
var dataTransfer = this.dataTransfer;
if (dataTransfer && dataTransfer.types && dataTransfer.types.contains(formatMap[format] || format)) return true;
return currentDrag && currentDrag.data[format] != undefined;
},
 
setData: function(format, data){
if ($type(format) == 'object'){
for (var f in format) this.setData(f, format[f]);
return this;
}
var dataTransfer = this.dataTransfer;
if (dataTransfer && $type(data) == 'string'){
var map = formatMap[format];
if (map || !limitedFormats){
dataTransfer.setData(map || format, data);
if (currentDrag) delete currentDrag.data[format];
return this;
}
}
getCurrentDrag().data[format] = data;
},
 
getData: function(format){
if (!format){
var types = this.get('types');
return types.map(this.getData, this).associate(types);
}
if (currentDrag && currentDrag.data[format]) return currentDrag.data[format];
var dataTransfer = this.dataTransfer;
if (!dataTransfer) return undefined;
var map = formatMap[format];
return map || !limitedFormats ? dataTransfer.getData(map || format) : undefined;
},
 
get: function(name){
var dataTransfer = this.dataTransfer;
switch (name){
case 'data': return this.getData();
case 'types':
var types = dataTransfer && dataTransfer.types ? dataTransfer.types.map(Hash.keyOf, formatMap) :
(limitedFormats ? ['text/plain', 'text/uri-list'] : []);
if (currentDrag) for (var type in currentDrag.data) types.include(type);
return types;
case 'effectAllowed':
return dataTransfer ? dataTransfer[name] : (currentDrag ? currentDrag[name] : false) || 'uninitialized';
case 'dropEffect':
var current = dataTransfer || currentDrag;
if (current && current[name]) return current[name];
var allowed = this.get('effectAllowed');
if (allowed == 'none' || allowed == 'move') return allowed;
return allowed.substr(0, 4) == 'link' ? 'link' : 'copy';
case 'dropAllowed':
var allowed = this.get('effectAllowed');
return allowed == 'uninitialized' || allowed == 'all' || allowed.contains(this.get('dropEffect'));
default:
return currentDrag ? currentDrag[name] : undefined;
}
},
 
set: function(name, value){
switch (name){
case 'data': return this.setData(value);
case 'dropEffect':
// if (!(/^(none|copy|link|move)$/).test(type)) return this;
case 'effectAllowed':
// if (!(/^(none|copy|copyLink|copyMove|link|linkMove|move|all|uninitialized)$/).test(type)) return this;
if (this.dataTransfer){
this.dataTransfer[name] = value;
return this;
}
}
getCurrentDrag()[name] = value;
return this;
}
 
});
 
})(document);
Event.Drag.js #
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
/*
Script: Event.Drag.js
Extends the Event native to also cover drag and drop events.
 
License:
MIT-style license.
 
Authors:
Sebastian Markbåge
*/
 
// This logic should be moved to Core to avoid duplicate code and a cleaner model
 
(function(){
 
var originalConstructor = Event.prototype.constructor,
originalProperties = Event,
lastTarget;
 
this.Event = new Native({
 
legacy: Event,
 
name: 'Event',
 
initialize: function(event, win){
 
event = originalConstructor.apply(this, arguments);
win = win || window;
 
if (!event.type.match(/drag|drop/)) return event;
 
if (!event.relatedTarget && event.type.match(/enter|leave/)){
// TODO: Better relatedTarget solution for WebKit
var related = lastTarget || event.event.relatedTarget || event.event.fromElement || event.event.toElement;
if (!(function(){
while (related && related.nodeType == 3) related = related.parentNode;
return true;
}).create({ attempt: Browser.Engine.gecko })()) related = false;
event.relatedTarget = related;
lastTarget = event.target;
}
 
if (event.type == 'dragend') lastTarget = undefined;
 
if (!event.page){
var doc = win.document, e = event.event;
doc = (!doc.compatMode || doc.compatMode == 'CSS1Compat') ? doc.html : doc.body;
event.page = { x: e.pageX || e.clientX + doc.scrollLeft, y: e.pageY || e.clientY + doc.scrollTop };
event.client = { x: (e.pageX) ? e.pageX - win.pageXOffset : e.clientX, y: (e.pageY) ? e.pageY - win.pageYOffset : e.clientY };
}
 
if (!event.dataTransfer) event.dataTransfer = new DataTransfer(event.event.dataTransfer, event);
 
return event;
}
 
});
 
for (var prop in originalProperties) Event[prop] = originalProperties[prop];
 
$extend(Element.NativeEvents, { dragstart: 2, drag: 2, dragend: 2, dragenter: 2, dragover: 2, dragleave: 2, drop: 2 });
 
var $check = function(event){
var related = event.relatedTarget;
if(related == undefined) return true;
if(related === false) return false;
return $type(this) != 'document' && related != this && related.prefix != 'xul' && !this.hasChild(related);
};
 
$extend(Element.Events, {
 
dragend: Browser.Engine.webkit ? {
condition: function(event){
// Delay dragend event on webkit until after the drop event to conform with HTML 5 specs.
// Important for correct clean up order. Cannot be done in drop event since that may occur in a different window.
if (event.$delayed) return true;
event.$delayed = true;
this.fireEvent.delay(40, this, ['dragend', event]);
return false;
}
} : {},
drag: Browser.Engine.gecko19 ? {
condition: function(event){
return event.page.x != 0 || event.page.y != 0 || event.client.x != 0 || event.client.y != 0;
}
} : {},
dragenterself: { base: 'dragenter', condition: $check },
dragleaveself: { base: 'dragleave', condition: $check }
 
});
 
if (!document.createEvent){
 
//var fireEvent = document.createElement('div').fireEvent;
Element.dispatchDragEvent = function(original, type, target, relatedTarget){
var event = document.createEventObject(original.event || original);
//event.type = type;
//if (type == 'dragenter') event.fromElement = relatedTarget || original.relatedTarget;
//if (type == 'dragleave') event.toElement = relatedTarget || original.relatedTarget;
var t = document.id(target || original.target || original.srcElement);
if (!t) return true;
return t._fireEvent('on' + type, event);
};
 
} else if (this.DragEvent && this.DragEvent.prototype){
 
Element.dispatchDragEvent = function(original, type, target, relatedTarget){
var event;
 
if ((type == 'drag' && Browser.Engine.gecko19) || !original.dataTransfer){
event = document.createEvent('MouseEvents');
event.initMouseEvent(type, true, true, original.view, original.detail,
original.screenX, original.screenY, original.clientX, original.clientY,
original.ctrlKey, original.altKey, original.shiftKey, original.metaKey,
original.button, relatedTarget || original.relatedTarget);
} else {
event = document.createEvent('DragEvents');
event.initDragEvent(type, true, true, original.view, original.detail,
original.screenX, original.screenY, original.clientX, original.clientY,
original.ctrlKey, original.altKey, original.shiftKey, original.metaKey,
original.button, relatedTarget || original.relatedTarget, original.dataTransfer);
}
return (target || original.target).dispatchEvent(event);
};
 
} else {
 
var unsafeEvents = Browser.Engine.gecko18 ? ['dragover', 'dragenter', 'dragleave'] : false, safeSuffix = '-safe';
//unsafeEvents = Browser.Engine.presto ? ['drop'] : false;
 
if (unsafeEvents) // Workaround for dispatching native XUL events
unsafeEvents.each(function(event){
Element.Events[event] = { base: event + safeSuffix };
Element.NativeEvents[event + safeSuffix] = 2;
});
 
Element.dispatchDragEvent = function(original, type, target, relatedTarget){
if (unsafeEvents && unsafeEvents.contains(type)) type += safeSuffix;
 
var event = document.createEvent('MouseEvents');
event.initMouseEvent(type, true, true, original.view, original.detail,
original.screenX, original.screenY, original.clientX, original.clientY,
original.ctrlKey, original.altKey, original.shiftKey, original.metaKey,
original.button, relatedTarget || original.relatedTarget);
 
return (target || original.target).dispatchEvent(event);
};
 
}
 
this.DataTransfer = new Native({
 
name: 'DataTransfer',
 
initialize: function(dataTransfer, event){
this.event = event;
if (dataTransfer) this.dataTransfer = dataTransfer.dataTransfer || dataTransfer;
}
 
});
 
})();
SortableList.js #
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
/*
@import Element.Draggable.js
@import Class.Binds.js
@import Fx.Elements.js
*/
 
var SortableList = new Class({
 
Implements: [Events, Options],
 
Binds: ['onDragStart', 'onDragEnd', 'onDragOver', 'onDragLeave', 'onDrop', 'cleanUp'],
 
options: {
/*
onStart: $empty,
onComplete: $empty,
*/
/* SortableList Options
placeholderBase: false,
placeholderStyle: false,*/
direction: 'right',
moveRegion: .3,
fx: { duration: 200 }
},
 
/*
fx: false,
placeholder: false,
*/
placeholders: [],
 
initialize: function(element, options){
this.element = element;
this.setOptions(options);
this.attach();
},
 
attach: function(){
this.element
.set('draggable', true)
.addEvents({
dragstart: this.onDragStart,
dragend: this.onDragEnd,
dragover: this.onDragOver,
dragleaveself: this.onDragLeave,
drop: this.onDrop
});
},
 
detach: function(){
this.element
.set('draggable', 'auto')
.removeEvents({
dragstart: this.onDragStart,
dragend: this.onDragEnd,
dragover: this.onDragOver,
dragleaveself: this.onDragLeave,
drop: this.onDrop
});
},
 
onDragStart: function(event){
var item = event.target;
if (item == this.element){
event.preventDefault();
return;
}
while (item && item.parentNode != this.element) item = item.parentNode;
if (!item) return;
 
this.fireEvent('start', [item, event.dataTransfer]);
 
this.getPlaceholder(item, false, true);
this.changePlaceholder();
 
this.currentDragDisplay = item.getStyle('display');
this.currentDrag = item.setStyle('display', 'none');
},
 
onDragEnd: function(event){
this.fireEvent('end', [this.currentDrag, event.dataTransfer]);
 
if (this.currentDrag) this.currentDrag.setStyle('display', this.currentDragDisplay);
this.placeholder = this.currentDrag = this.currentDragDisplay = null;
this.cleanUp();
},
 
onDragOver: function(event){
var item = event.target;
if (!item) return;
if (item == this.element){
if (!this.placeholder) this.changePlaceholder(this.getPlaceholder());
return;
}
 
var dropTarget = false;
do {
var tmp;
if (item.retrieve && (tmp = item.retrieve('events')) && (tmp = tmp['drop']) && tmp.keys.length > 0)
dropTarget = true;
} while (item.parentNode != this.element && (item = item.parentNode));
 
if (item.retrieve && item.retrieve('isPlaceholder')){
this.changePlaceholder(item);
return;
}
 
var cord = document.id(item).getCoordinates(),
m = this.options.moveRegion,
od = this.options.direction,
h = (od == 'right' || od == 'left'),
x = h ? event.page.x - cord.left : event.page.y - cord.top,
w = h ? cord.width : cord.height,
d = x > w / 2 ? x - w : x;
if (od == 'right' || od == 'down') d = -d;
// If not dropzone
if (!dropTarget || Math.abs(d) <= (m < 1 ? (w * m) : m)){
var ph = this.getPlaceholder(item, d > 0);
//if (this.placeholder == ph) ph = this.getPlaceholder(item, d <= 0);
this.changePlaceholder(ph);
}
},
 
onDragLeave: function(event){
this.changePlaceholder();
},
 
onDrop: function(event){
if (this.fx) this.fx.cancel();
this.fireEvent('drop', [this.placeholder, event.dataTransfer]);
event.preventDefault();
this.placeholder = null;
this.cleanUp();
},
 
getStyles: function(style){
if (style) return $type(style) == 'string' ? Fx.CSS.prototype.search(style) : style;
return {};
},
 
getPlaceholder: function(item, next, expanded){
var h = (this.options.direction == 'right' || this.options.direction == 'left');
var nph = item;
if (!nph){
nph = this.element.lastChild;
while (nph && (nph.nodeType != 1 || nph.getStyle('display') == 'none')) nph = nph.previousSibling;
item = nph;
next = true;
} else {
while ((nph = (next ? nph.nextSibling : nph.previousSibling)) && (nph.nodeType != 1 || nph.getStyle('display') == 'none'));
}
// If no placeholder is found create a new one
if (!nph || !nph.retrieve || !nph.retrieve('isPlaceholder')){
nph = new Element('li').store('isPlaceholder', true)
if (item)
nph.inject(item, next ? 'after' : 'before');
else
this.element.appendChild(nph);
 
this.placeholders.push(nph);
 
var size;
if (item){
size = item.getSize();
size = {
width: size.x + item.getStyle('margin-left').toInt() + item.getStyle('margin-right').toInt(),
height: size.y + item.getStyle('margin-top').toInt() + item.getStyle('margin-bottom').toInt()
};
} else {
size = this.element.getComputedSize();
size = {
width: size.width < 10 ? 10 : size.width,
height: size.height < 10 ? 10 : size.height
};
}
nph.store('targetSize', size);
nph.setStyles({ background: 'transparent', border: 0, padding: 0, margin: 0 }).setStyles(size);
if (expanded) nph.setStyles(nph.getStyles(this.options.placeholderStyle));
else nph.setStyles(h ? { width: 0 } : { height: 0 }).setStyles(nph.getStyles(this.options.placeholderBase));
}
return nph;
},
 
changePlaceholder: function(nph){
if (nph && this.placeholder == nph) return;
var sz = nph ? nph.retrieve('targetSize') : {},
h = (this.options.direction == 'right' || this.options.direction == 'left'),
prop = {},
hide = $extend(h ? { width: 0 } : { height: 0 }, this.getStyles(this.options.placeholderBase)),
show = $extend(h ? { width: sz.width } : { height: sz.height }, this.getStyles(this.options.placeholderStyle));
this.placeholders.each(function(ph, i){ prop[i] = nph == ph ? show : hide; });
this.placeholder = nph;
if (this.fx){
this.fx.cancel();
this.fx.elements = this.fx.subject = this.placeholders;
} else {
this.fx = new Fx.Elements(this.placeholders, this.options.fx);
}
/*if (Browser.Engine.trident){
this.fx.set(prop);
this.cleanUp();
} else {*/
this.fx.start(prop).chain(this.cleanUp);
//}
},
 
cleanUp: function(){
var currentPlaceholder = this.placeholder;
this.placeholders.each(function(ph){ if(ph != currentPlaceholder) ph.destroy(); });
this.placeholders = currentPlaceholder ? [currentPlaceholder] : [];
}
 
});