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