Created
October 22, 2009 18:25
-
-
Save sebmarkbage/216161 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
@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); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
@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); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
@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); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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; | |
} | |
}); | |
})(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
@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] : []; | |
} | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment