drop.js | |
---|---|
(function ($) {
var event = $.event;
var eventNames = [
"dropover",
"dropon",
"dropout",
"dropinit",
"dropmove",
"dropend"];
$.Drop = function (callbacks, element) {
$.extend(this, callbacks);
this.element = $(element);
} | |
add the elements ... | $.each(eventNames, function () {
event.special[this] = {
add: function (handleObj) { |
add this element to the compiles list | var el = $(this),
current = (el.data("dropEventCount") || 0);
el.data("dropEventCount", current + 1)
if (current == 0) {
$.Drop.addElement(this);
}
},
remove: function () {
var el = $(this),
current = (el.data("dropEventCount") || 0);
el.data("dropEventCount", current - 1)
if (current <= 1) {
$.Drop.removeElement(this);
}
}
}
});
$.extend($.Drop, {
lowerName: "drop",
_rootElements: [], |
elements that are listening for drops | _elements: $(), |
elements that can be dropped on | last_active: [],
endName: "dropon", |
adds an element as a 'root' element this element might have events that we need to respond to | addElement: function (el) { |
check other elements | for (var i = 0; i < this._rootElements.length; i++) {
if (el == this._rootElements[i]) return;
}
this._rootElements.push(el);
},
removeElement: function (el) {
for (var i = 0; i < this._rootElements.length; i++) {
if (el == this._rootElements[i]) {
this._rootElements.splice(i, 1)
return;
}
}
},
sortByDeepestChild: function (a, b) { |
Use jQuery.compare to compare two elements | var compare = a.element.compare(b.element);
if (compare & 16 || compare & 4) return 1;
if (compare & 8 || compare & 2) return -1;
return 0;
},
isAffected: function (point, moveable, responder) {
return ((responder.element != moveable.element) && (responder.element.within(point[0], point[1], responder._cache).length == 1));
},
deactivate: function (responder, mover, event) {
mover.out(event, responder)
responder.callHandlers(this.lowerName + 'out', responder.element[0], event, mover)
},
activate: function (responder, mover, event) { //this is where we should call over
mover.over(event, responder)
responder.callHandlers(this.lowerName + 'over', responder.element[0], event, mover);
},
move: function (responder, mover, event) {
responder.callHandlers(this.lowerName + 'move', responder.element[0], event, mover)
},
compile: function (event, drag) { |
if we called compile w/o a current drag | if (!this.dragging && !drag) {
return;
} else if (!this.dragging) {
this.dragging = drag;
this.last_active = [];
}
var el, drops, selector, dropResponders, newEls = [],
dragging = this.dragging; |
go to each root element and look for drop elements | for (var i = 0; i < this._rootElements.length; i++) { //for each element
el = this._rootElements[i] |
gets something like {"": ["dropinit"], ".foo" : ["dropover","dropmove"] } | var drops = $.event.findBySelector(el, eventNames) |
get drop elements by selector | for (selector in drops) {
dropResponders = selector ? jQuery(selector, el) : [el]; |
for each drop element | for (var e = 0; e < dropResponders.length; e++) { |
add the callbacks to the element's Data there already might be data, so we merge it | if (this.addCallbacks(dropResponders[e], drops[selector], dragging)) {
newEls.push(dropResponders[e])
};
}
}
} |
once all callbacks are added, call init on everything ... | this.add(newEls, event, dragging)
}, |
adds the drag callbacks object to the element or merges other callbacks ... returns true or false if the element is new ... onlyNew lets only new elements add themselves | addCallbacks: function (el, callbacks, onlyNew) {
var origData = $.data(el, "_dropData");
if (!origData) {
$.data(el, "_dropData", new $.Drop(callbacks, el));
return true;
} else if (!onlyNew) {
var origCbs = origData; |
merge data | for (var eventName in callbacks) {
origCbs[eventName] = origCbs[eventName] ? origCbs[eventName].concat(callbacks[eventName]) : callbacks[eventName];
}
return false;
}
}, |
calls init on each element's drags. if its cancelled it's removed adds to the current elements ... | add: function (newEls, event, drag, dragging) {
var i = 0,
drop;
while (i < newEls.length) {
drop = $.data(newEls[i], "_dropData");
drop.callHandlers(this.lowerName + 'init', newEls[i], event, drag)
if (drop._canceled) {
newEls.splice(i, 1)
} else {
i++;
}
}
this._elements.push.apply(this._elements, newEls)
},
show: function (point, moveable, event) {
var element = moveable.element;
if (!this._elements.length) return;
var respondable, affected = [],
propagate = true,
i = 0,
j, la, toBeActivated, aff, oldLastActive = this.last_active,
responders = [],
self = this,
drag; |
what's still affected ... we can also move element out here | while (i < this._elements.length) {
drag = $.data(this._elements[i], "_dropData");
if (!drag) {
this._elements.splice(i, 1)
}
else {
i++;
if (self.isAffected(point, moveable, drag)) {
affected.push(drag);
}
}
} |
we should only trigger on lowest children | affected.sort(this.sortByDeepestChild);
event.stopRespondPropagate = function () {
propagate = false;
}
toBeActivated = affected.slice(); |
all these will be active | this.last_active = affected; |
deactivate everything in last_active that isn't active | for (j = 0; j < oldLastActive.length; j++) {
la = oldLastActive[j];
i = 0;
while ((aff = toBeActivated[i])) {
if (la == aff) {
toBeActivated.splice(i, 1);
break;
} else {
i++;
}
}
if (!aff) {
this.deactivate(la, moveable, event);
}
if (!propagate) return;
}
for (var i = 0; i < toBeActivated.length; i++) {
this.activate(toBeActivated[i], moveable, event);
if (!propagate) return;
} |
activate everything in affected that isn't in last_active | for (i = 0; i < affected.length; i++) {
this.move(affected[i], moveable, event);
if (!propagate) return;
}
},
end: function (event, moveable) {
var la, endName = this.lowerName + 'end',
onEvent = $.Event(this.endName, event),
dropData; |
call dropon go through the actives ... if you are over one, call dropped on it | for (var i = 0; i < this.last_active.length; i++) {
la = this.last_active[i]
if (this.isAffected(event.vector(), moveable, la) && la[this.endName]) {
la.callHandlers(this.endName, null, onEvent, moveable);
}
if (onEvent.isPropagationStopped()) {
break;
}
} |
call dropend | for (var r = 0; r < this._elements.length; r++) {
dropData = $.data(this._elements[r], "_dropData");
dropData && dropData.callHandlers(endName, null, event, moveable);
}
this.clear();
},
clear: function () {
this._elements.each(function () { |
remove temporary drop data | $.removeData(this, "_dropData")
})
this._elements = $();
delete this.dragging;
}
})
$.Drag.responder = $.Drop;
$.extend($.Drop.prototype, {
callHandlers: function (method, el, ev, drag) {
var length = this[method] ? this[method].length : 0
for (var i = 0; i < length; i++) {
this[method][i].call(el || this.element[0], ev, this, drag)
}
},
cache: function (value) {
this._cache = value != null ? value : true;
},
cancel: function () {
this._canceled = true;
}
});
return $;
})(jQuery);
|