Fixed Rico.Corner.round to be compatible with latest browsers/CSS3 - in both Rico2...
[infodrom/rico3] / ricoClient / js / ricoDragDrop.js
1 /*
2  *  (c) 2005-2009 Richard Cowin (http://openrico.org)
3  *  (c) 2005-2009 Matt Brown (http://dowdybrown.com)
4  *
5  *  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
6  *  file except in compliance with the License. You may obtain a copy of the License at
7  *
8  *         http://www.apache.org/licenses/LICENSE-2.0
9  *
10  *  Unless required by applicable law or agreed to in writing, software distributed under the
11  *  License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
12  *  either express or implied. See the License for the specific language governing permissions
13  *  and limitations under the License.
14  */
15  
16 Rico.dndMgrList = [];
17
18 Rico.registerDraggable = function(aDraggable, mgrIdx) {
19   if (typeof mgrIdx != 'number') mgrIdx=0;
20   if (typeof Rico.dndMgrList[mgrIdx] != 'object')
21     Rico.dndMgrList[mgrIdx] = new Rico.dndMgr();
22   Rico.dndMgrList[mgrIdx].registerDraggable(aDraggable);
23 };
24
25 Rico.registerDropZone = function(aDropZone, mgrIdx) {
26   if (typeof mgrIdx != 'number') mgrIdx=0;
27   if (typeof Rico.dndMgrList[mgrIdx] != 'object')
28     Rico.dndMgrList[mgrIdx] = new Rico.dndMgr();
29   Rico.dndMgrList[mgrIdx].registerDropZone(aDropZone);
30 };
31
32 Rico.dndMgr = function() {
33   this.initialize();
34 };
35
36 Rico.dndMgr.prototype = {
37 /**
38  * @class Implements drag-n-drop manager -- a group of linked draggables and drop zones
39  * @constructs
40  */
41    initialize: function() {
42       this.dropZones                = [];
43       this.draggables               = [];
44       this.currentDragObjects       = [];
45       this.dragElement              = null;
46       this.lastSelectedDraggable    = null;
47       this.currentDragObjectVisible = false;
48       this.interestedInMotionEvents = false;
49       this._mouseDown = Rico.eventHandle(this,'_mouseDownHandler');
50       this._mouseMove = Rico.eventHandle(this,'_mouseMoveHandler');
51       this._mouseUp = Rico.eventHandle(this,'_mouseUpHandler');
52    },
53
54    registerDropZone: function(aDropZone) {
55       this.dropZones[ this.dropZones.length ] = aDropZone;
56    },
57
58    deregisterDropZone: function(aDropZone) {
59       var newDropZones = new Array();
60       var j = 0;
61       for ( var i = 0 ; i < this.dropZones.length ; i++ ) {
62          if ( this.dropZones[i] != aDropZone )
63             newDropZones[j++] = this.dropZones[i];
64       }
65
66       this.dropZones = newDropZones;
67    },
68
69    clearDropZones: function() {
70       this.dropZones = new Array();
71    },
72
73    registerDraggable: function( aDraggable ) {
74       this.draggables[ this.draggables.length ] = aDraggable;
75       var htmlElement = aDraggable.getMouseDownHTMLElement();
76       if ( htmlElement != null ) { 
77          htmlElement.ricoDraggable = aDraggable;
78          Rico.eventBind(htmlElement, "mousedown", Rico.eventHandle(this,'_attachEvents'));
79          Rico.eventBind(htmlElement, "mousedown", this._mouseDown);
80       }
81    },
82
83    clearSelection: function() {
84       for ( var i = 0 ; i < this.currentDragObjects.length ; i++ )
85          this.currentDragObjects[i].deselect();
86       this.currentDragObjects = new Array();
87       this.lastSelectedDraggable = null;
88    },
89
90    hasSelection: function() {
91       return this.currentDragObjects.length > 0;
92    },
93
94    setStartDragFromElement: function( e, mouseDownElement ) {
95       this.origPos = Rico.cumulativeOffset(mouseDownElement);
96       var coord=Rico.eventClient(e);
97       this.startx = coord.x - this.origPos.left;
98       this.starty = coord.y - this.origPos.top;
99
100       this.interestedInMotionEvents = this.hasSelection();
101       Rico.eventStop(e);
102    },
103
104    updateSelection: function( draggable, extendSelection ) {
105       if ( ! extendSelection )
106          this.clearSelection();
107
108       if ( draggable.isSelected() ) {
109          this.currentDragObjects=this.currentDragObjects.without(draggable);
110          draggable.deselect();
111          if ( draggable == this.lastSelectedDraggable )
112             this.lastSelectedDraggable = null;
113       }
114       else {
115          draggable.select();
116          if ( draggable.isSelected() ) {
117            this.currentDragObjects.push(draggable);
118            this.lastSelectedDraggable = draggable;
119          }
120       }
121    },
122
123    _mouseDownHandler: function(e) {
124       // if not button 1 ignore it...
125       if (!Rico.eventLeftClick(e)) return;
126
127       var eventTarget      = Rico.eventElement(e);
128       var draggableObject  = eventTarget.ricoDraggable;
129
130       var candidate = eventTarget;
131       while (draggableObject == null && candidate.parentNode) {
132          candidate = candidate.parentNode;
133          draggableObject = candidate.ricoDraggable;
134       }
135    
136       if ( draggableObject == null ) return;
137
138       this.updateSelection( draggableObject, e.ctrlKey );
139
140       // clear the drop zones postion cache...
141       if ( this.hasSelection() ) {
142          for ( var i = 0 ; i < this.dropZones.length ; i++ )
143             this.dropZones[i].clearPositionCache();
144       }
145       this.setStartDragFromElement( e, draggableObject.getMouseDownHTMLElement() );
146    },
147
148
149    _mouseMoveHandler: function(e) {
150       if ( !this.interestedInMotionEvents ) {
151          return;
152       }
153
154       if ( ! this.hasSelection() )
155          return;
156
157       if ( ! this.currentDragObjectVisible )
158          this._startDrag(e);
159
160       if ( !this.activatedDropZones )
161          this._activateRegisteredDropZones();
162
163       this._updateDraggableLocation(e);
164       this._updateDropZonesHover(e);
165
166       Rico.eventStop(e);
167    },
168
169    _makeDraggableObjectVisible: function(e) {
170       if ( !this.hasSelection() )
171          return;
172
173       var dragElement;
174       if ( this.currentDragObjects.length > 1 )
175          dragElement = this.currentDragObjects[0].getMultiObjectDragGUI(this.currentDragObjects);
176       else
177          dragElement = this.currentDragObjects[0].getSingleObjectDragGUI();
178
179       // go ahead and absolute position it...
180       this.dragElemPosition=Rico.getStyle(dragElement, "position");
181       if (this.dragElemPosition != "absolute")
182          dragElement.style.position = "absolute";
183
184       // need to parent him into the document...
185       if ( dragElement.parentNode == null || dragElement.parentNode.nodeType == 11 )
186          document.body.appendChild(dragElement);
187
188       this.dragElement = dragElement;
189       this._updateDraggableLocation(e);
190
191       this.currentDragObjectVisible = true;
192    },
193
194    _leftOffset: function(e) {
195            return e.offsetX ? document.body.scrollLeft : 0;
196         },
197
198    _topOffset: function(e) {
199            return e.offsetY ? document.body.scrollTop : 0;
200         },
201
202                 
203    _updateDraggableLocation: function(e) {
204       var dragObjectStyle = this.dragElement.style;
205       var coord=Rico.eventClient(e);
206       dragObjectStyle.left = (coord.x + this._leftOffset(e) - this.startx) + "px";
207       dragObjectStyle.top  = (coord.y + this._topOffset(e) - this.starty) + "px";
208    },
209
210    _updateDropZonesHover: function(e) {
211       var i,n = this.dropZones.length;
212       for ( i = 0 ; i < n ; i++ ) {
213          if ( ! this._mousePointInDropZone( e, this.dropZones[i] ) )
214             this.dropZones[i].hideHover();
215       }
216
217       for ( i = 0 ; i < n ; i++ ) {
218          if ( this._mousePointInDropZone( e, this.dropZones[i] ) ) {
219             if ( this.dropZones[i].canAccept(this.currentDragObjects) )
220                this.dropZones[i].showHover();
221          }
222       }
223    },
224
225    _startDrag: function(e) {
226       for ( var i = 0 ; i < this.currentDragObjects.length ; i++ )
227          this.currentDragObjects[i].startDrag();
228       this._makeDraggableObjectVisible(e);
229    },
230
231    _mouseUpHandler: function(e) {
232       if ( ! this.hasSelection() ) return;
233       if (!Rico.eventLeftClick(e)) return;
234
235       this.interestedInMotionEvents = false;
236
237       if ( this._placeDraggableInDropZone(e) )
238          this._completeDropOperation(e);
239       else if (this.dragElement != null) {
240          Rico.eventStop(e);
241          Rico.animate(this.dragElement, 
242                       {duration: 300, onEnd:Rico.bind(this,'_doCancelDragProcessing')}, 
243                       {left:this.origPos.left, top:this.origPos.top});
244       }
245
246      Rico.eventUnbind(document.body, "mousemove", this._mouseMove);
247      Rico.eventUnbind(document.body, "mouseup",  this._mouseUp);
248    },
249
250    _retTrue: function () {
251       return true;
252    },
253
254    _completeDropOperation: function(e) {
255       if ( this.dragElement != this.currentDragObjects[0].getMouseDownHTMLElement() ) {
256          if ( this.dragElement.parentNode != null )
257             this.dragElement.parentNode.removeChild(this.dragElement);
258       }
259
260       this._deactivateRegisteredDropZones();
261       this._endDrag();
262       this.clearSelection();
263       this.dragElement = null;
264       this.currentDragObjectVisible = false;
265       Rico.eventStop(e);
266    },
267
268    _doCancelDragProcessing: function() {
269       this._cancelDrag();
270       if ( this.dragElement == this.currentDragObjects[0].getMouseDownHTMLElement() ) {
271          this.dragElement.style.position=this.dragElemPosition;
272       } else {
273          if ( this.dragElement && this.dragElement.parentNode != null )
274             this.dragElement.parentNode.removeChild(this.dragElement);
275       }
276       this._deactivateRegisteredDropZones();
277       this.dragElement = null;
278       this.currentDragObjectVisible = false;
279    },
280
281    _placeDraggableInDropZone: function(e) {
282       var foundDropZone = false;
283       var n = this.dropZones.length;
284       for ( var i = 0 ; i < n ; i++ ) {
285          if ( this._mousePointInDropZone( e, this.dropZones[i] ) ) {
286             if ( this.dropZones[i].canAccept(this.currentDragObjects) ) {
287                this.dropZones[i].hideHover();
288                this.dropZones[i].accept(this.currentDragObjects);
289                foundDropZone = true;
290                break;
291             }
292          }
293       }
294
295       return foundDropZone;
296    },
297
298    _cancelDrag: function() {
299       for ( var i = 0 ; i < this.currentDragObjects.length ; i++ )
300          this.currentDragObjects[i].cancelDrag();
301    },
302
303    _endDrag: function() {
304       for ( var i = 0 ; i < this.currentDragObjects.length ; i++ )
305          this.currentDragObjects[i].endDrag();
306    },
307
308    _mousePointInDropZone: function( e, dropZone ) {
309
310       var absoluteRect = dropZone.getAbsoluteRect();
311       var coord=Rico.eventClient(e);
312
313       return coord.x  > absoluteRect.left + this._leftOffset(e) &&
314              coord.x  < absoluteRect.right + this._leftOffset(e) &&
315              coord.y  > absoluteRect.top + this._topOffset(e)   &&
316              coord.y  < absoluteRect.bottom + this._topOffset(e);
317    },
318
319    _activateRegisteredDropZones: function() {
320       var n = this.dropZones.length;
321       for ( var i = 0 ; i < n ; i++ ) {
322          var dropZone = this.dropZones[i];
323          if ( dropZone.canAccept(this.currentDragObjects) )
324             dropZone.activate();
325       }
326
327       this.activatedDropZones = true;
328    },
329
330    _deactivateRegisteredDropZones: function() {
331       var n = this.dropZones.length;
332       for ( var i = 0 ; i < n ; i++ )
333          this.dropZones[i].deactivate();
334       this.activatedDropZones = false;
335    },
336
337    _attachEvents: function () {
338      Rico.eventBind(document.body, "mousemove", this._mouseMove);
339      Rico.eventBind(document.body, "mouseup",  this._mouseUp);
340    }
341
342 };
343
344
345 Rico.Draggable = function(type, htmlElement) {
346   this.initialize(type, htmlElement);
347 };
348
349 Rico.Draggable.prototype = {
350 /**
351  * @class Implements behavior for a draggable element
352  * @constructs
353  */
354    initialize: function( type, htmlElement ) {
355       this.type          = type;
356       this.htmlElement   = Rico.$(htmlElement);
357       this.selected      = false;
358    },
359
360    /**
361     *   Returns the HTML element that should have a mouse down event
362     *   added to it in order to initiate a drag operation
363     **/
364    getMouseDownHTMLElement: function() {
365       return this.htmlElement;
366    },
367
368    select: function() {
369       this._select();
370    },
371
372    _select: function() {
373       this.selected = true;
374       if (this.showingSelected) return;
375       this.showingSelected = true;
376
377       var htmlElement = this.getMouseDownHTMLElement();
378       var color = Rico.Color.createColorFromBackground(htmlElement);
379       color.isBright() ? color.darken(0.033) : color.brighten(0.033);
380       this.saveBackground = Rico.getStyle(htmlElement, "backgroundColor", "background-color");
381       htmlElement.style.backgroundColor = color.asHex();
382    },
383
384    deselect: function() {
385       this.selected = false;
386       if (!this.showingSelected) return;
387       var htmlElement = this.getMouseDownHTMLElement();
388       htmlElement.style.backgroundColor = this.saveBackground;
389       this.showingSelected = false;
390    },
391
392    isSelected: function() {
393       return this.selected;
394    },
395
396    startDrag: function() {
397    },
398
399    cancelDrag: function() {
400    },
401
402    endDrag: function() {
403    },
404
405    getSingleObjectDragGUI: function() {
406       return this.htmlElement;
407    },
408
409    getMultiObjectDragGUI: function( draggables ) {
410       return this.htmlElement;
411    },
412
413    getDroppedGUI: function() {
414       return this.htmlElement;
415    },
416
417    toString: function() {
418       return this.type + ":" + this.htmlElement + ":";
419    }
420
421 };
422
423
424 Rico.LiveGridDraggable = function(grid, rownum, colnum) {
425   this.initialize(grid, rownum, colnum);
426 };
427
428 Rico.LiveGridDraggable.prototype = Rico.extend(new Rico.Draggable(), {
429 /**
430  * @class Enables draggable behavior for LiveGrid cells.
431  * Called by LiveGrid#appendBlankRow for columns where canDrag is true.
432  * @extends Rico.Draggable
433  * @constructs
434  */
435   initialize: function( grid, rownum, colnum) {\r
436     this.type        = 'RicoCell';\r
437     this.htmlElement = grid.cell(rownum,colnum);\r
438     this.liveGrid    = grid;\r
439     this.dragRow     = rownum;\r
440     this.dragCol     = colnum;\r
441   },\r
442   \r
443   select: function() {
444     if (this.dragRow >= this.liveGrid.buffer.totalRows) return;
445     this.selected = true;
446     this.showingSelected = true;
447   },
448
449   deselect: function() {
450     this.selected = false;
451     this.showingSelected = false;
452   },
453
454   getSingleObjectDragGUI: function() {\r
455     var div = document.createElement("div");\r
456     div.className = 'LiveGridDraggable';\r
457     div.style.width = (this.htmlElement.offsetWidth - 10) + "px";\r
458     div.innerHTML=this.htmlElement.innerHTML;
459     return div;\r
460   }\r
461 });\r
462
463
464 Rico.Dropzone = function(htmlElement) {
465   this.initialize(htmlElement);
466 };
467
468 Rico.Dropzone.prototype = {
469 /**
470  * @class Implements behavior for a drop zone
471  * @constructs
472  */
473    initialize: function( htmlElement ) {
474       this.htmlElement  = Rico.$(htmlElement);
475       this.absoluteRect = null;
476    },
477
478    getHTMLElement: function() {
479       return this.htmlElement;
480    },
481
482    clearPositionCache: function() {
483       this.absoluteRect = null;
484    },
485
486    getAbsoluteRect: function() {
487       if ( this.absoluteRect == null ) {
488          var htmlElement = this.getHTMLElement();
489          var pos = Rico.viewportOffset(htmlElement);
490
491          this.absoluteRect = {
492             top:    pos.top,
493             left:   pos.left,
494             bottom: pos.top + htmlElement.offsetHeight,
495             right:  pos.left + htmlElement.offsetWidth
496          };
497       }
498       return this.absoluteRect;
499    },
500
501    activate: function() {
502       var htmlElement = this.getHTMLElement();
503       if (htmlElement == null  || this.showingActive)
504          return;
505
506       this.showingActive = true;
507       this.saveBackgroundColor = htmlElement.style.backgroundColor;
508
509       var fallbackColor = "#ffea84";
510       var currentColor = Rico.Color.createColorFromBackground(htmlElement);
511       if ( currentColor == null )
512          htmlElement.style.backgroundColor = fallbackColor;
513       else {
514          currentColor.isBright() ? currentColor.darken(0.2) : currentColor.brighten(0.2);
515          htmlElement.style.backgroundColor = currentColor.asHex();
516       }
517    },
518
519    deactivate: function() {
520       var htmlElement = this.getHTMLElement();
521       if (htmlElement == null || !this.showingActive)
522          return;
523
524       htmlElement.style.backgroundColor = this.saveBackgroundColor;
525       this.showingActive = false;
526       this.saveBackgroundColor = null;
527    },
528
529    showHover: function() {
530       var htmlElement = this.getHTMLElement();
531       if ( htmlElement == null || this.showingHover )
532          return;
533
534       this.saveBorderWidth = htmlElement.style.borderWidth;
535       this.saveBorderStyle = htmlElement.style.borderStyle;
536       this.saveBorderColor = htmlElement.style.borderColor;
537
538       this.showingHover = true;
539       htmlElement.style.borderWidth = "1px";
540       htmlElement.style.borderStyle = "solid";
541       //htmlElement.style.borderColor = "#ff9900";
542       htmlElement.style.borderColor = "#ffff00";
543    },
544
545    hideHover: function() {
546       var htmlElement = this.getHTMLElement();
547       if ( htmlElement == null || !this.showingHover )
548          return;
549
550       htmlElement.style.borderWidth = this.saveBorderWidth;
551       htmlElement.style.borderStyle = this.saveBorderStyle;
552       htmlElement.style.borderColor = this.saveBorderColor;
553       this.showingHover = false;
554    },
555
556    canAccept: function(draggableObjects) {
557       return true;
558    },
559
560    accept: function(draggableObjects) {
561       var htmlElement = this.getHTMLElement();
562       if ( htmlElement == null )
563          return;
564
565       var n = draggableObjects.length;
566       for ( var i = 0 ; i < n ; i++ ) {
567          var theGUI = draggableObjects[i].getDroppedGUI();
568          if ( Rico.getStyle( theGUI, "position" ) == "absolute" ) {
569             theGUI.style.position = "static";
570             theGUI.style.top = "";
571             theGUI.style.top = "";
572          }
573          htmlElement.appendChild(theGUI);
574       }
575    }
576 };
577
578 Rico.includeLoaded('ricoDragDrop.js');