2 * (c) 2005-2009 Richard Cowin (http://openrico.org)
3 * (c) 2005-2009 Matt Brown (http://dowdybrown.com)
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
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);
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);
32 Rico.dndMgr = function() {
36 Rico.dndMgr.prototype = {
38 * @class Implements drag-n-drop manager -- a group of linked draggables and drop zones
41 initialize: function() {
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');
54 registerDropZone: function(aDropZone) {
55 this.dropZones[ this.dropZones.length ] = aDropZone;
58 deregisterDropZone: function(aDropZone) {
59 var newDropZones = new Array();
61 for ( var i = 0 ; i < this.dropZones.length ; i++ ) {
62 if ( this.dropZones[i] != aDropZone )
63 newDropZones[j++] = this.dropZones[i];
66 this.dropZones = newDropZones;
69 clearDropZones: function() {
70 this.dropZones = new Array();
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);
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;
90 hasSelection: function() {
91 return this.currentDragObjects.length > 0;
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;
100 this.interestedInMotionEvents = this.hasSelection();
104 updateSelection: function( draggable, extendSelection ) {
105 if ( ! extendSelection )
106 this.clearSelection();
108 if ( draggable.isSelected() ) {
109 this.currentDragObjects=this.currentDragObjects.without(draggable);
110 draggable.deselect();
111 if ( draggable == this.lastSelectedDraggable )
112 this.lastSelectedDraggable = null;
116 if ( draggable.isSelected() ) {
117 this.currentDragObjects.push(draggable);
118 this.lastSelectedDraggable = draggable;
123 _mouseDownHandler: function(e) {
124 // if not button 1 ignore it...
125 if (!Rico.eventLeftClick(e)) return;
127 var eventTarget = Rico.eventElement(e);
128 var draggableObject = eventTarget.ricoDraggable;
130 var candidate = eventTarget;
131 while (draggableObject == null && candidate.parentNode) {
132 candidate = candidate.parentNode;
133 draggableObject = candidate.ricoDraggable;
136 if ( draggableObject == null ) return;
138 this.updateSelection( draggableObject, e.ctrlKey );
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();
145 this.setStartDragFromElement( e, draggableObject.getMouseDownHTMLElement() );
149 _mouseMoveHandler: function(e) {
150 if ( !this.interestedInMotionEvents ) {
154 if ( ! this.hasSelection() )
157 if ( ! this.currentDragObjectVisible )
160 if ( !this.activatedDropZones )
161 this._activateRegisteredDropZones();
163 this._updateDraggableLocation(e);
164 this._updateDropZonesHover(e);
169 _makeDraggableObjectVisible: function(e) {
170 if ( !this.hasSelection() )
174 if ( this.currentDragObjects.length > 1 )
175 dragElement = this.currentDragObjects[0].getMultiObjectDragGUI(this.currentDragObjects);
177 dragElement = this.currentDragObjects[0].getSingleObjectDragGUI();
179 // go ahead and absolute position it...
180 this.dragElemPosition=Rico.getStyle(dragElement, "position");
181 if (this.dragElemPosition != "absolute")
182 dragElement.style.position = "absolute";
184 // need to parent him into the document...
185 if ( dragElement.parentNode == null || dragElement.parentNode.nodeType == 11 )
186 document.body.appendChild(dragElement);
188 this.dragElement = dragElement;
189 this._updateDraggableLocation(e);
191 this.currentDragObjectVisible = true;
194 _leftOffset: function(e) {
195 return e.offsetX ? document.body.scrollLeft : 0;
198 _topOffset: function(e) {
199 return e.offsetY ? document.body.scrollTop : 0;
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";
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();
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();
225 _startDrag: function(e) {
226 for ( var i = 0 ; i < this.currentDragObjects.length ; i++ )
227 this.currentDragObjects[i].startDrag();
228 this._makeDraggableObjectVisible(e);
231 _mouseUpHandler: function(e) {
232 if ( ! this.hasSelection() ) return;
233 if (!Rico.eventLeftClick(e)) return;
235 this.interestedInMotionEvents = false;
237 if ( this._placeDraggableInDropZone(e) )
238 this._completeDropOperation(e);
239 else if (this.dragElement != null) {
241 Rico.animate(this.dragElement,
242 {duration: 300, onEnd:Rico.bind(this,'_doCancelDragProcessing')},
243 {left:this.origPos.left, top:this.origPos.top});
246 Rico.eventUnbind(document.body, "mousemove", this._mouseMove);
247 Rico.eventUnbind(document.body, "mouseup", this._mouseUp);
250 _retTrue: function () {
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);
260 this._deactivateRegisteredDropZones();
262 this.clearSelection();
263 this.dragElement = null;
264 this.currentDragObjectVisible = false;
268 _doCancelDragProcessing: function() {
270 if ( this.dragElement == this.currentDragObjects[0].getMouseDownHTMLElement() ) {
271 this.dragElement.style.position=this.dragElemPosition;
273 if ( this.dragElement && this.dragElement.parentNode != null )
274 this.dragElement.parentNode.removeChild(this.dragElement);
276 this._deactivateRegisteredDropZones();
277 this.dragElement = null;
278 this.currentDragObjectVisible = false;
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;
295 return foundDropZone;
298 _cancelDrag: function() {
299 for ( var i = 0 ; i < this.currentDragObjects.length ; i++ )
300 this.currentDragObjects[i].cancelDrag();
303 _endDrag: function() {
304 for ( var i = 0 ; i < this.currentDragObjects.length ; i++ )
305 this.currentDragObjects[i].endDrag();
308 _mousePointInDropZone: function( e, dropZone ) {
310 var absoluteRect = dropZone.getAbsoluteRect();
311 var coord=Rico.eventClient(e);
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);
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) )
327 this.activatedDropZones = true;
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;
337 _attachEvents: function () {
338 Rico.eventBind(document.body, "mousemove", this._mouseMove);
339 Rico.eventBind(document.body, "mouseup", this._mouseUp);
345 Rico.Draggable = function(type, htmlElement) {
346 this.initialize(type, htmlElement);
349 Rico.Draggable.prototype = {
351 * @class Implements behavior for a draggable element
354 initialize: function( type, htmlElement ) {
356 this.htmlElement = Rico.$(htmlElement);
357 this.selected = false;
361 * Returns the HTML element that should have a mouse down event
362 * added to it in order to initiate a drag operation
364 getMouseDownHTMLElement: function() {
365 return this.htmlElement;
372 _select: function() {
373 this.selected = true;
374 if (this.showingSelected) return;
375 this.showingSelected = true;
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();
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;
392 isSelected: function() {
393 return this.selected;
396 startDrag: function() {
399 cancelDrag: function() {
402 endDrag: function() {
405 getSingleObjectDragGUI: function() {
406 return this.htmlElement;
409 getMultiObjectDragGUI: function( draggables ) {
410 return this.htmlElement;
413 getDroppedGUI: function() {
414 return this.htmlElement;
417 toString: function() {
418 return this.type + ":" + this.htmlElement + ":";
424 Rico.LiveGridDraggable = function(grid, rownum, colnum) {
425 this.initialize(grid, rownum, colnum);
428 Rico.LiveGridDraggable.prototype = Rico.extend(new Rico.Draggable(), {
430 * @class Enables draggable behavior for LiveGrid cells.
431 * Called by LiveGrid#appendBlankRow for columns where canDrag is true.
432 * @extends Rico.Draggable
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
444 if (this.dragRow >= this.liveGrid.buffer.totalRows) return;
445 this.selected = true;
446 this.showingSelected = true;
449 deselect: function() {
450 this.selected = false;
451 this.showingSelected = false;
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;
464 Rico.Dropzone = function(htmlElement) {
465 this.initialize(htmlElement);
468 Rico.Dropzone.prototype = {
470 * @class Implements behavior for a drop zone
473 initialize: function( htmlElement ) {
474 this.htmlElement = Rico.$(htmlElement);
475 this.absoluteRect = null;
478 getHTMLElement: function() {
479 return this.htmlElement;
482 clearPositionCache: function() {
483 this.absoluteRect = null;
486 getAbsoluteRect: function() {
487 if ( this.absoluteRect == null ) {
488 var htmlElement = this.getHTMLElement();
489 var pos = Rico.viewportOffset(htmlElement);
491 this.absoluteRect = {
494 bottom: pos.top + htmlElement.offsetHeight,
495 right: pos.left + htmlElement.offsetWidth
498 return this.absoluteRect;
501 activate: function() {
502 var htmlElement = this.getHTMLElement();
503 if (htmlElement == null || this.showingActive)
506 this.showingActive = true;
507 this.saveBackgroundColor = htmlElement.style.backgroundColor;
509 var fallbackColor = "#ffea84";
510 var currentColor = Rico.Color.createColorFromBackground(htmlElement);
511 if ( currentColor == null )
512 htmlElement.style.backgroundColor = fallbackColor;
514 currentColor.isBright() ? currentColor.darken(0.2) : currentColor.brighten(0.2);
515 htmlElement.style.backgroundColor = currentColor.asHex();
519 deactivate: function() {
520 var htmlElement = this.getHTMLElement();
521 if (htmlElement == null || !this.showingActive)
524 htmlElement.style.backgroundColor = this.saveBackgroundColor;
525 this.showingActive = false;
526 this.saveBackgroundColor = null;
529 showHover: function() {
530 var htmlElement = this.getHTMLElement();
531 if ( htmlElement == null || this.showingHover )
534 this.saveBorderWidth = htmlElement.style.borderWidth;
535 this.saveBorderStyle = htmlElement.style.borderStyle;
536 this.saveBorderColor = htmlElement.style.borderColor;
538 this.showingHover = true;
539 htmlElement.style.borderWidth = "1px";
540 htmlElement.style.borderStyle = "solid";
541 //htmlElement.style.borderColor = "#ff9900";
542 htmlElement.style.borderColor = "#ffff00";
545 hideHover: function() {
546 var htmlElement = this.getHTMLElement();
547 if ( htmlElement == null || !this.showingHover )
550 htmlElement.style.borderWidth = this.saveBorderWidth;
551 htmlElement.style.borderStyle = this.saveBorderStyle;
552 htmlElement.style.borderColor = this.saveBorderColor;
553 this.showingHover = false;
556 canAccept: function(draggableObjects) {
560 accept: function(draggableObjects) {
561 var htmlElement = this.getHTMLElement();
562 if ( htmlElement == null )
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 = "";
573 htmlElement.appendChild(theGUI);
578 Rico.includeLoaded('ricoDragDrop.js');