jquery.ui.droppable.js (9438B)
1 /* 2 * jQuery UI Droppable 1.8 3 * 4 * Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about) 5 * Dual licensed under the MIT (MIT-LICENSE.txt) 6 * and GPL (GPL-LICENSE.txt) licenses. 7 * 8 * http://docs.jquery.com/UI/Droppables 9 * 10 * Depends: 11 * jquery.ui.core.js 12 * jquery.ui.widget.js 13 * jquery.ui.mouse.js 14 * jquery.ui.draggable.js 15 */ 16 (function($) { 17 18 $.widget("ui.droppable", { 19 widgetEventPrefix: "drop", 20 options: { 21 accept: '*', 22 activeClass: false, 23 addClasses: true, 24 greedy: false, 25 hoverClass: false, 26 scope: 'default', 27 tolerance: 'intersect' 28 }, 29 _create: function() { 30 31 var o = this.options, accept = o.accept; 32 this.isover = 0; this.isout = 1; 33 34 this.accept = $.isFunction(accept) ? accept : function(d) { 35 return d.is(accept); 36 }; 37 38 //Store the droppable's proportions 39 this.proportions = { width: this.element[0].offsetWidth, height: this.element[0].offsetHeight }; 40 41 // Add the reference and positions to the manager 42 $.ui.ddmanager.droppables[o.scope] = $.ui.ddmanager.droppables[o.scope] || []; 43 $.ui.ddmanager.droppables[o.scope].push(this); 44 45 (o.addClasses && this.element.addClass("ui-droppable")); 46 47 }, 48 49 destroy: function() { 50 var drop = $.ui.ddmanager.droppables[this.options.scope]; 51 for ( var i = 0; i < drop.length; i++ ) 52 if ( drop[i] == this ) 53 drop.splice(i, 1); 54 55 this.element 56 .removeClass("ui-droppable ui-droppable-disabled") 57 .removeData("droppable") 58 .unbind(".droppable"); 59 60 return this; 61 }, 62 63 _setOption: function(key, value) { 64 65 if(key == 'accept') { 66 this.accept = $.isFunction(value) ? value : function(d) { 67 return d.is(value); 68 }; 69 } 70 $.Widget.prototype._setOption.apply(this, arguments); 71 }, 72 73 _activate: function(event) { 74 var draggable = $.ui.ddmanager.current; 75 if(this.options.activeClass) this.element.addClass(this.options.activeClass); 76 (draggable && this._trigger('activate', event, this.ui(draggable))); 77 }, 78 79 _deactivate: function(event) { 80 var draggable = $.ui.ddmanager.current; 81 if(this.options.activeClass) this.element.removeClass(this.options.activeClass); 82 (draggable && this._trigger('deactivate', event, this.ui(draggable))); 83 }, 84 85 _over: function(event) { 86 87 var draggable = $.ui.ddmanager.current; 88 if (!draggable || (draggable.currentItem || draggable.element)[0] == this.element[0]) return; // Bail if draggable and droppable are same element 89 90 if (this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) { 91 if(this.options.hoverClass) this.element.addClass(this.options.hoverClass); 92 this._trigger('over', event, this.ui(draggable)); 93 } 94 95 }, 96 97 _out: function(event) { 98 99 var draggable = $.ui.ddmanager.current; 100 if (!draggable || (draggable.currentItem || draggable.element)[0] == this.element[0]) return; // Bail if draggable and droppable are same element 101 102 if (this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) { 103 if(this.options.hoverClass) this.element.removeClass(this.options.hoverClass); 104 this._trigger('out', event, this.ui(draggable)); 105 } 106 107 }, 108 109 _drop: function(event,custom) { 110 111 var draggable = custom || $.ui.ddmanager.current; 112 if (!draggable || (draggable.currentItem || draggable.element)[0] == this.element[0]) return false; // Bail if draggable and droppable are same element 113 114 var childrenIntersection = false; 115 this.element.find(":data(droppable)").not(".ui-draggable-dragging").each(function() { 116 var inst = $.data(this, 'droppable'); 117 if( 118 inst.options.greedy 119 && !inst.options.disabled 120 && inst.options.scope == draggable.options.scope 121 && inst.accept.call(inst.element[0], (draggable.currentItem || draggable.element)) 122 && $.ui.intersect(draggable, $.extend(inst, { offset: inst.element.offset() }), inst.options.tolerance) 123 ) { childrenIntersection = true; return false; } 124 }); 125 if(childrenIntersection) return false; 126 127 if(this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) { 128 if(this.options.activeClass) this.element.removeClass(this.options.activeClass); 129 if(this.options.hoverClass) this.element.removeClass(this.options.hoverClass); 130 this._trigger('drop', event, this.ui(draggable)); 131 return this.element; 132 } 133 134 return false; 135 136 }, 137 138 ui: function(c) { 139 return { 140 draggable: (c.currentItem || c.element), 141 helper: c.helper, 142 position: c.position, 143 offset: c.positionAbs 144 }; 145 } 146 147 }); 148 149 $.extend($.ui.droppable, { 150 version: "1.8" 151 }); 152 153 $.ui.intersect = function(draggable, droppable, toleranceMode) { 154 155 if (!droppable.offset) return false; 156 157 var x1 = (draggable.positionAbs || draggable.position.absolute).left, x2 = x1 + draggable.helperProportions.width, 158 y1 = (draggable.positionAbs || draggable.position.absolute).top, y2 = y1 + draggable.helperProportions.height; 159 var l = droppable.offset.left, r = l + droppable.proportions.width, 160 t = droppable.offset.top, b = t + droppable.proportions.height; 161 162 switch (toleranceMode) { 163 case 'fit': 164 return (l < x1 && x2 < r 165 && t < y1 && y2 < b); 166 break; 167 case 'intersect': 168 return (l < x1 + (draggable.helperProportions.width / 2) // Right Half 169 && x2 - (draggable.helperProportions.width / 2) < r // Left Half 170 && t < y1 + (draggable.helperProportions.height / 2) // Bottom Half 171 && y2 - (draggable.helperProportions.height / 2) < b ); // Top Half 172 break; 173 case 'pointer': 174 var draggableLeft = ((draggable.positionAbs || draggable.position.absolute).left + (draggable.clickOffset || draggable.offset.click).left), 175 draggableTop = ((draggable.positionAbs || draggable.position.absolute).top + (draggable.clickOffset || draggable.offset.click).top), 176 isOver = $.ui.isOver(draggableTop, draggableLeft, t, l, droppable.proportions.height, droppable.proportions.width); 177 return isOver; 178 break; 179 case 'touch': 180 return ( 181 (y1 >= t && y1 <= b) || // Top edge touching 182 (y2 >= t && y2 <= b) || // Bottom edge touching 183 (y1 < t && y2 > b) // Surrounded vertically 184 ) && ( 185 (x1 >= l && x1 <= r) || // Left edge touching 186 (x2 >= l && x2 <= r) || // Right edge touching 187 (x1 < l && x2 > r) // Surrounded horizontally 188 ); 189 break; 190 default: 191 return false; 192 break; 193 } 194 195 }; 196 197 /* 198 This manager tracks offsets of draggables and droppables 199 */ 200 $.ui.ddmanager = { 201 current: null, 202 droppables: { 'default': [] }, 203 prepareOffsets: function(t, event) { 204 205 var m = $.ui.ddmanager.droppables[t.options.scope] || []; 206 var type = event ? event.type : null; // workaround for #2317 207 var list = (t.currentItem || t.element).find(":data(droppable)").andSelf(); 208 209 droppablesLoop: for (var i = 0; i < m.length; i++) { 210 211 if(m[i].options.disabled || (t && !m[i].accept.call(m[i].element[0],(t.currentItem || t.element)))) continue; //No disabled and non-accepted 212 for (var j=0; j < list.length; j++) { if(list[j] == m[i].element[0]) { m[i].proportions.height = 0; continue droppablesLoop; } }; //Filter out elements in the current dragged item 213 m[i].visible = m[i].element.css("display") != "none"; if(!m[i].visible) continue; //If the element is not visible, continue 214 215 m[i].offset = m[i].element.offset(); 216 m[i].proportions = { width: m[i].element[0].offsetWidth, height: m[i].element[0].offsetHeight }; 217 218 if(type == "mousedown") m[i]._activate.call(m[i], event); //Activate the droppable if used directly from draggables 219 220 } 221 222 }, 223 drop: function(draggable, event) { 224 225 var dropped = false; 226 $.each($.ui.ddmanager.droppables[draggable.options.scope] || [], function() { 227 228 if(!this.options) return; 229 if (!this.options.disabled && this.visible && $.ui.intersect(draggable, this, this.options.tolerance)) 230 dropped = dropped || this._drop.call(this, event); 231 232 if (!this.options.disabled && this.visible && this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) { 233 this.isout = 1; this.isover = 0; 234 this._deactivate.call(this, event); 235 } 236 237 }); 238 return dropped; 239 240 }, 241 drag: function(draggable, event) { 242 243 //If you have a highly dynamic page, you might try this option. It renders positions every time you move the mouse. 244 if(draggable.options.refreshPositions) $.ui.ddmanager.prepareOffsets(draggable, event); 245 246 //Run through all droppables and check their positions based on specific tolerance options 247 $.each($.ui.ddmanager.droppables[draggable.options.scope] || [], function() { 248 249 if(this.options.disabled || this.greedyChild || !this.visible) return; 250 var intersects = $.ui.intersect(draggable, this, this.options.tolerance); 251 252 var c = !intersects && this.isover == 1 ? 'isout' : (intersects && this.isover == 0 ? 'isover' : null); 253 if(!c) return; 254 255 var parentInstance; 256 if (this.options.greedy) { 257 var parent = this.element.parents(':data(droppable):eq(0)'); 258 if (parent.length) { 259 parentInstance = $.data(parent[0], 'droppable'); 260 parentInstance.greedyChild = (c == 'isover' ? 1 : 0); 261 } 262 } 263 264 // we just moved into a greedy child 265 if (parentInstance && c == 'isover') { 266 parentInstance['isover'] = 0; 267 parentInstance['isout'] = 1; 268 parentInstance._out.call(parentInstance, event); 269 } 270 271 this[c] = 1; this[c == 'isout' ? 'isover' : 'isout'] = 0; 272 this[c == "isover" ? "_over" : "_out"].call(this, event); 273 274 // we just moved out of a greedy child 275 if (parentInstance && c == 'isout') { 276 parentInstance['isout'] = 0; 277 parentInstance['isover'] = 1; 278 parentInstance._over.call(parentInstance, event); 279 } 280 }); 281 282 } 283 }; 284 285 })(jQuery);