1 /** 2 * @namespace Provides drag and drop functionality. Not 100% complete. 3 */ 4 Jelo.DD = function() { 5 /** @private convenience */ 6 function pi(n) { 7 return parseInt(n, 10); 8 } 9 10 /** @private constants */ 11 var _ = { 12 minOffset : 5, // px until drag 13 zHigh : 20000, 14 zHigher : 20001, 15 isDragging : false, 16 item : null, 17 mouseX : null, 18 mouseY : null, 19 lastDragged : null, 20 dropTargets : [] 21 }; 22 23 /** @private whether drag-drop is on or off */ 24 var active = false; 25 26 /** @private */ 27 var zeroNaN = function(n) { 28 return isNaN(n) ? 0 : n; 29 }; 30 31 /** @private event handler */ 32 var mouseDown = function(t, e) { 33 if (!active) { 34 return true; 35 } 36 if (Jelo.Event.isFixed()) { 37 e = t; 38 } 39 t = this; 40 while (!t.jeloDragTarget && t !== null) { 41 t = t.parentNode || t.parentElement; 42 } 43 if (t === null) { 44 return false; 45 } 46 47 var d = t.jeloDragTarget; 48 49 // store orig state 50 var iPos = findPosition(d); 51 console.log(iPos); 52 d.jeloDragT = Jelo.css(d, "top"); 53 d.jeloDragL = Jelo.css(d, "left"); 54 d.jeloDragP = Jelo.css(d, "position"); 55 d.jeloDragO = Jelo.css(d, "opacity"); 56 d.jeloDragZ = Jelo.css(d, "z-index"); 57 58 // get data for new state 59 Jelo.css(d, "position", "absolute"); 60 Jelo.css(d, "top", iPos.y + "px"); 61 Jelo.css(d, "left", iPos.x + "px"); 62 d.jeloDragTop = pi(Jelo.css(d, "top")); 63 d.jeloDragLeft = pi(Jelo.css(d, "left")); 64 65 // return to orig state 66 Jelo.css(d, "position", d.jeloDragP); 67 Jelo.css(d, "left", d.jeloDragL); 68 Jelo.css(d, "top", d.jeloDragT); 69 70 Jelo.on(document, "mousemove", mouseMove); 71 Jelo.on(document, "mouseup", mouseUp); 72 _.item = d; 73 _.isDragging = true; 74 _.mouseX = e.clientX; 75 _.mouseY = e.clientY; 76 77 return false; 78 }; 79 80 /** @private event handler */ 81 var mouseMove = function(t, e) { 82 if (!active || !_.isDragging || !_.item) { 83 return true; 84 } 85 if (Jelo.Event.isFixed()) { 86 e = t; 87 } 88 var x = e.clientX - _.mouseX; 89 var y = e.clientY - _.mouseY; 90 var ax = Math.abs(e.clientX - _.mouseX); 91 var ay = Math.abs(e.clientY - _.mouseY); 92 if (Math.round((ax + ay) / 2) > _.minOffset) { 93 var d = _.item; 94 Jelo.css(d, "position", "absolute"); 95 Jelo.css(d, "opacity", 0.7); 96 Jelo.css(d, "z-index", _.zHigher); 97 Jelo.css(d, "top", pi(d.jeloDragTop + y) + "px"); 98 Jelo.css(d, "left", pi(d.jeloDragLeft + x) + "px"); 99 document.body.appendChild(d); 100 } 101 return false; 102 }; 103 104 /** @private event handler */ 105 var mouseUp = function(t, e) { 106 if (!active) { 107 return true; 108 } 109 if (Jelo.Event.isFixed()) { 110 e = t; 111 } 112 if (_.item) { 113 if (_.lastDragged != _.item) { 114 _.zHigh++; 115 _.zHigher++; 116 } 117 _.lastDragged = _.item; 118 Jelo.css(_.item, "opacity", _.item.jeloDragO); 119 Jelo.css(_.item, "z-index", _.zHigh); 120 if (typeof _.item.jeloDragOnMouseUp == "function") { 121 _.item.jeloDragOnMouseUp.call(_.item, _.item.jeloDragHandle); 122 } 123 124 // handle drops 125 Jelo.each(_.dropTargets, function() { 126 if (typeof this.jeloOnDrop == "function") { 127 var t = findPosition(_.item); 128 var p = findPosition(this); 129 var r = p.x + zeroNaN(pi(Jelo.css(this, "width"))); // right edge 130 var b = p.y + zeroNaN(pi(Jelo.css(this, "height"))); // bottom edge 131 var mx = e.clientX; 132 var my = e.clientY; 133 if (mx > p.x && mx < r && my > p.y && my < b) { 134 var d = _.item; 135 Jelo.css(d, "position", "static"); 136 Jelo.css(d, "left", "auto"); 137 Jelo.css(d, "top", "auto"); 138 this.appendChild(_.item); 139 this.jeloOnDrop.call(this, _.item, 140 _.item.jeloDragHandle); 141 } 142 } 143 }); 144 145 } 146 Jelo.un(document, "mousemove", mouseMove); 147 Jelo.un(document, "mouseup", mouseUp); 148 _.mouseX = null; 149 _.mouseY = null; 150 _.item = null; 151 _.isDragging = false; 152 }; 153 154 var detectDrop = function(t, e) { 155 if (Jelo.Event.isFixed()) { 156 e = t; 157 } 158 console.log('test'); 159 }; 160 161 /** @private utility */ 162 var findPosition = function(o) { 163 var offsetX = zeroNaN(pi(Jelo.css(o, "margin-left"))); 164 var offsetY = zeroNaN(pi(Jelo.css(o, "margin-top"))); 165 var curleft = 0, curtop = 0; 166 if (o.offsetParent) { 167 curleft = o.offsetLeft; 168 curtop = o.offsetTop; 169 while ((o = o.offsetParent)) { 170 curleft += o.offsetLeft; 171 curtop += o.offsetTop; 172 } 173 } 174 return { 175 "x" : curleft - offsetX, 176 "y" : curtop - offsetY 177 }; 178 }; 179 180 /** @scope Jelo.DD */ 181 return { 182 /** 183 * @param {HTMLElement} element The item to investigate. 184 * @returns {Boolean} True if element will respond to drag events. 185 */ 186 isDraggy : function(el) { 187 return (el && !!el.jeloDraggy); 188 }, 189 /** 190 * @param {HTMLElement} element The item to investigate. 191 * @returns {Boolean} True if element will respond to drop events. 192 */ 193 isDroppy : function(el) { 194 return (el && !!el.jeloDroppy); 195 }, 196 /** 197 * Starts or stops listening for drag events on a given element. 198 * 199 * @param {HTMLElement} element The item to affect. 200 * @param {Boolean} [bool=true] True to start listening for drag events, false 201 * to stop. 202 * @param {HTMLElement} [handle=element] The area that, when dragged, 203 * will move the element. Defaults to the whole element itself. 204 * @param {Function} [fn] Method to invoke when the element is dropped. 205 * The execution context ("this") will be the element itself, and the 206 * argument passed will be the handle object. 207 */ 208 setDraggy : function(el, bool, handle, fn) { 209 bool = (typeof bool == "boolean") ? bool : true; 210 handle = handle || el; 211 if (Jelo.Valid.isElement(el) && Jelo.Valid.isElement(handle)) { 212 handle.jeloDragTarget = el; 213 Jelo.on(handle, "mousedown", mouseDown); 214 Jelo.css(handle, "cursor", "move"); 215 el.jeloDraggy = bool; 216 el.jeloDragHandle = handle; 217 if (typeof fn == "function") { 218 el.jeloDragOnMouseUp = fn; 219 } 220 } 221 }, 222 /** 223 * Starts or stops listening for drop events on a given element. 224 * 225 * @param {HTMLElement} element The item to affect. 226 * @param {Boolean} [bool=true] True to start listening for drop events, false 227 * to stop. 228 * @param {HTMLElement} [handle=element] The area that, when dropped 229 * upon, will register the event. Defaults to the whole element itself. 230 * @param {Function} [fn] Method to invoke when something is dropped on 231 * this element. The execution context ("this") will be the drop zone 232 * element itself, and the arguments passed will be the dropped element 233 * and the drop zone's handle, respectively. 234 */ 235 setDroppy : function(el, bool, handle, fn) { 236 bool = (typeof bool == "boolean") ? bool : true; 237 handle = handle || el; 238 if (Jelo.Valid.isElement(el) && Jelo.Valid.isElement(handle)) { 239 handle.jeloDropTarget = el; 240 el.jeloDroppy = bool; 241 el.jeloDropHandle = handle; 242 if (typeof fn == "function") { 243 el.jeloOnDrop = fn; 244 } 245 _.dropTargets.push(el); 246 } 247 }, 248 /** 249 * Turns on drag-drop support. 250 */ 251 on : function() { 252 active = true; 253 }, 254 /** 255 * Turns on drag-drop support. Elements retain any registered drag 256 * handlers while drag-drop support is off, but no such events are 257 * triggered. 258 */ 259 off : function() { 260 active = false; 261 }, 262 /** 263 * Sets how far an element must be dragged before it "counts" as a drag. 264 * 265 * @param {Number} n How many pixels an element must be dragged before 266 * it "counts" as a drag. 267 */ 268 setMinimumDrag : function(n) { 269 n = pi(n); 270 if (!isNaN(n)) { 271 _.minOffset = n; 272 } 273 } 274 }; 275 }(); 276