2 * Copyright 2005 Sabre Airline Solutions
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
5 * file except in compliance with the License. You may obtain a copy of the License at
7 * http://www.apache.org/licenses/LICENSE-2.0
9 * Unless required by applicable law or agreed to in writing, software distributed under the
10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
11 * either express or implied. See the License for the specific language governing permissions
12 * and limitations under the License.
15 Rico.DragAndDrop = Class.create(
16 /** @lends Rico.DragAndDrop# */
19 * @class Implements drag-n-drop manager. Used by {@link dndMgr}.
22 initialize: function() {
23 this.dropZones = new Array();
24 this.draggables = new Array();
25 this.currentDragObjects = new Array();
26 this.dragElement = null;
27 this.lastSelectedDraggable = null;
28 this.currentDragObjectVisible = false;
29 this.interestedInMotionEvents = false;
30 this._mouseDown = this._mouseDownHandler.bindAsEventListener(this);
31 this._mouseMove = this._mouseMoveHandler.bindAsEventListener(this);
32 this._mouseUp = this._mouseUpHandler.bindAsEventListener(this);
35 registerDropZone: function(aDropZone) {
36 this.dropZones[ this.dropZones.length ] = aDropZone;
39 deregisterDropZone: function(aDropZone) {
40 var newDropZones = new Array();
42 for ( var i = 0 ; i < this.dropZones.length ; i++ ) {
43 if ( this.dropZones[i] != aDropZone )
44 newDropZones[j++] = this.dropZones[i];
47 this.dropZones = newDropZones;
50 clearDropZones: function() {
51 this.dropZones = new Array();
54 registerDraggable: function( aDraggable ) {
55 this.draggables[ this.draggables.length ] = aDraggable;
56 this._addMouseDownHandler( aDraggable );
59 clearSelection: function() {
60 for ( var i = 0 ; i < this.currentDragObjects.length ; i++ )
61 this.currentDragObjects[i].deselect();
62 this.currentDragObjects = new Array();
63 this.lastSelectedDraggable = null;
66 hasSelection: function() {
67 return this.currentDragObjects.length > 0;
70 setStartDragFromElement: function( e, mouseDownElement ) {
71 this.origPos = RicoUtil.toDocumentPosition(mouseDownElement);
72 this.startx = e.screenX - this.origPos.x;
73 this.starty = e.screenY - this.origPos.y;
75 this.interestedInMotionEvents = this.hasSelection();
76 this._terminateEvent(e);
79 updateSelection: function( draggable, extendSelection ) {
80 if ( ! extendSelection )
81 this.clearSelection();
83 if ( draggable.isSelected() ) {
84 this.currentDragObjects=this.currentDragObjects.without(draggable);
86 if ( draggable == this.lastSelectedDraggable )
87 this.lastSelectedDraggable = null;
91 if ( draggable.isSelected() ) {
92 this.currentDragObjects.push(draggable);
93 this.lastSelectedDraggable = draggable;
98 _mouseDownHandler: function(e) {
99 //Rico.writeDebugMsg("_mouseDownHandler");
100 if ( arguments.length == 0 )
103 // if not button 1 ignore it...
104 var nsEvent = e.which != undefined;
105 if ( (nsEvent && e.which != 1) || (!nsEvent && e.button != 1))
108 var eventTarget = e.target ? e.target : e.srcElement;
109 var draggableObject = eventTarget.ricoDraggable;
111 var candidate = eventTarget;
112 while (draggableObject == null && candidate.parentNode) {
113 candidate = candidate.parentNode;
114 draggableObject = candidate.ricoDraggable;
117 if ( draggableObject == null )
120 this.updateSelection( draggableObject, e.ctrlKey );
122 // clear the drop zones postion cache...
123 if ( this.hasSelection() ) {
124 for ( var i = 0 ; i < this.dropZones.length ; i++ )
125 this.dropZones[i].clearPositionCache();
127 this.setStartDragFromElement( e, draggableObject.getMouseDownHTMLElement() );
131 _mouseMoveHandler: function(e) {
132 var nsEvent = e.which != undefined;
133 if ( !this.interestedInMotionEvents ) {
134 //this._terminateEvent(e);
138 if ( ! this.hasSelection() )
141 if ( ! this.currentDragObjectVisible )
144 if ( !this.activatedDropZones )
145 this._activateRegisteredDropZones();
147 this._updateDraggableLocation(e);
148 this._updateDropZonesHover(e);
150 this._terminateEvent(e);
153 _makeDraggableObjectVisible: function(e)
155 if ( !this.hasSelection() )
159 if ( this.currentDragObjects.length > 1 )
160 dragElement = this.currentDragObjects[0].getMultiObjectDragGUI(this.currentDragObjects);
162 dragElement = this.currentDragObjects[0].getSingleObjectDragGUI();
164 // go ahead and absolute position it...
165 this.dragElemPosition=RicoUtil.getElementsComputedStyle(dragElement, "position");
166 if (this.dragElemPosition != "absolute")
167 dragElement.style.position = "absolute";
169 // need to parent him into the document...
170 if ( dragElement.parentNode == null || dragElement.parentNode.nodeType == 11 )
171 document.body.appendChild(dragElement);
173 this.dragElement = dragElement;
174 this._updateDraggableLocation(e);
176 this.currentDragObjectVisible = true;
179 _leftOffset: function(e) {
180 return e.offsetX ? document.body.scrollLeft : 0;
183 _topOffset: function(e) {
184 return e.offsetY ? document.body.scrollTop : 0;
188 _updateDraggableLocation: function(e) {
189 var dragObjectStyle = this.dragElement.style;
190 dragObjectStyle.left = (e.screenX + this._leftOffset(e) - this.startx) + "px";
191 dragObjectStyle.top = (e.screenY + this._topOffset(e) - this.starty) + "px";
194 _updateDropZonesHover: function(e) {
195 var i,n = this.dropZones.length;
196 for ( i = 0 ; i < n ; i++ ) {
197 if ( ! this._mousePointInDropZone( e, this.dropZones[i] ) )
198 this.dropZones[i].hideHover();
201 for ( i = 0 ; i < n ; i++ ) {
202 if ( this._mousePointInDropZone( e, this.dropZones[i] ) ) {
203 if ( this.dropZones[i].canAccept(this.currentDragObjects) )
204 this.dropZones[i].showHover();
209 _startDrag: function(e) {
210 for ( var i = 0 ; i < this.currentDragObjects.length ; i++ )
211 this.currentDragObjects[i].startDrag();
213 this._makeDraggableObjectVisible(e);
216 _mouseUpHandler: function(e) {
217 //Rico.writeDebugMsg("_mouseUpHandler");
218 if ( ! this.hasSelection() ) return;
220 var nsEvent = e.which != undefined;
221 if ( (nsEvent && e.which != 1) || (!nsEvent && e.button != 1))
224 this.interestedInMotionEvents = false;
226 if ( this._placeDraggableInDropZone(e) )
227 this._completeDropOperation(e);
228 else if (this.dragElement != null) {
229 this._terminateEvent(e);
230 Rico.animate(new Rico.Effect.Position( this.dragElement, this.origPos.x, this.origPos.y),
233 onFinish : this._doCancelDragProcessing.bind(this) } );
236 Event.stopObserving(document.body, "mousemove", this._mouseMove);
237 Event.stopObserving(document.body, "mouseup", this._mouseUp);
240 _retTrue: function () {
244 _completeDropOperation: function(e) {
245 if ( this.dragElement != this.currentDragObjects[0].getMouseDownHTMLElement() ) {
246 if ( this.dragElement.parentNode != null )
247 this.dragElement.parentNode.removeChild(this.dragElement);
250 this._deactivateRegisteredDropZones();
252 this.clearSelection();
253 this.dragElement = null;
254 this.currentDragObjectVisible = false;
255 this._terminateEvent(e);
258 _doCancelDragProcessing: function() {
260 if ( this.dragElement == this.currentDragObjects[0].getMouseDownHTMLElement() ) {
261 this.dragElement.style.position=this.dragElemPosition;
263 if ( this.dragElement && this.dragElement.parentNode != null )
264 this.dragElement.parentNode.removeChild(this.dragElement);
266 this._deactivateRegisteredDropZones();
267 this.dragElement = null;
268 this.currentDragObjectVisible = false;
271 _placeDraggableInDropZone: function(e) {
272 var foundDropZone = false;
273 var n = this.dropZones.length;
274 for ( var i = 0 ; i < n ; i++ ) {
275 if ( this._mousePointInDropZone( e, this.dropZones[i] ) ) {
276 if ( this.dropZones[i].canAccept(this.currentDragObjects) ) {
277 this.dropZones[i].hideHover();
278 this.dropZones[i].accept(this.currentDragObjects);
279 foundDropZone = true;
285 return foundDropZone;
288 _cancelDrag: function() {
289 for ( var i = 0 ; i < this.currentDragObjects.length ; i++ )
290 this.currentDragObjects[i].cancelDrag();
293 _endDrag: function() {
294 for ( var i = 0 ; i < this.currentDragObjects.length ; i++ )
295 this.currentDragObjects[i].endDrag();
298 _mousePointInDropZone: function( e, dropZone ) {
300 var absoluteRect = dropZone.getAbsoluteRect();
302 return e.clientX > absoluteRect.left + this._leftOffset(e) &&
303 e.clientX < absoluteRect.right + this._leftOffset(e) &&
304 e.clientY > absoluteRect.top + this._topOffset(e) &&
305 e.clientY < absoluteRect.bottom + this._topOffset(e);
308 _addMouseDownHandler: function( aDraggable )
310 var htmlElement = aDraggable.getMouseDownHTMLElement();
311 if ( htmlElement != null ) {
312 htmlElement.ricoDraggable = aDraggable;
313 Event.observe(htmlElement , "mousedown", this._onmousedown.bindAsEventListener(this));
314 Event.observe(htmlElement, "mousedown", this._mouseDown);
318 _activateRegisteredDropZones: function() {
319 var n = this.dropZones.length;
320 for ( var i = 0 ; i < n ; i++ ) {
321 var dropZone = this.dropZones[i];
322 if ( dropZone.canAccept(this.currentDragObjects) )
326 this.activatedDropZones = true;
329 _deactivateRegisteredDropZones: function() {
330 var n = this.dropZones.length;
331 for ( var i = 0 ; i < n ; i++ )
332 this.dropZones[i].deactivate();
333 this.activatedDropZones = false;
336 _onmousedown: function () {
337 //Rico.writeDebugMsg("_onmousedown (attaching events)");
338 Event.observe(document.body, "mousemove", this._mouseMove);
339 Event.observe(document.body, "mouseup", this._mouseUp);
342 _terminateEvent: function(e) {
347 initializeEventHandlers: function() {
348 if ( typeof document.implementation != "undefined" &&
349 document.implementation.hasFeature("HTML", "1.0") &&
350 document.implementation.hasFeature("Events", "2.0") &&
351 document.implementation.hasFeature("CSS", "2.0") ) {
352 document.addEventListener("mouseup", this._mouseUpHandler.bindAsEventListener(this), false);
353 document.addEventListener("mousemove", this._mouseMoveHandler.bindAsEventListener(this), false);
356 document.attachEvent( "onmouseup", this._mouseUpHandler.bindAsEventListener(this) );
357 document.attachEvent( "onmousemove", this._mouseMoveHandler.bindAsEventListener(this) );
363 /** @namespace Instance of {@link Rico.DragAndDrop} that manages the interaction of draggables to drop zones */
364 var dndMgr = new Rico.DragAndDrop();
367 Rico.Draggable = Class.create(
368 /** @lends Rico.Draggable# */
371 * @class Implements behavior for a draggable element
374 initialize: function( type, htmlElement ) {
376 this.htmlElement = $(htmlElement);
377 this.selected = false;
381 * Returns the HTML element that should have a mouse down event
382 * added to it in order to initiate a drag operation
384 getMouseDownHTMLElement: function() {
385 return this.htmlElement;
392 _select: function() {
393 this.selected = true;
394 if (this.showingSelected) return;
395 this.showingSelected = true;
397 var htmlElement = this.getMouseDownHTMLElement();
398 var color = Rico.Color.createColorFromBackground(htmlElement);
399 color.isBright() ? color.darken(0.033) : color.brighten(0.033);
400 this.saveBackground = RicoUtil.getElementsComputedStyle(htmlElement, "backgroundColor", "background-color");
401 htmlElement.style.backgroundColor = color.asHex();
404 deselect: function() {
405 this.selected = false;
406 if (!this.showingSelected) return;
407 var htmlElement = this.getMouseDownHTMLElement();
408 htmlElement.style.backgroundColor = this.saveBackground;
409 this.showingSelected = false;
412 isSelected: function() {
413 return this.selected;
416 startDrag: function() {
419 cancelDrag: function() {
422 endDrag: function() {
425 getSingleObjectDragGUI: function() {
426 return this.htmlElement;
429 getMultiObjectDragGUI: function( draggables ) {
430 return this.htmlElement;
433 getDroppedGUI: function() {
434 return this.htmlElement;
437 toString: function() {
438 return this.type + ":" + this.htmlElement + ":";
444 Rico.LiveGridDraggable = Class.create();
445 /** @lends Rico.LiveGridDraggable# */
446 Rico.LiveGridDraggable.prototype = Object.extend(new Rico.Draggable(), {
448 * @class Enables draggable behavior for LiveGrid cells.
449 * Called by LiveGrid#appendBlankRow for columns where canDrag is true.
450 * @extends Rico.Draggable
453 initialize: function( grid, rownum, colnum) {
454 this.type = 'RicoCell';
455 this.htmlElement = grid.cell(rownum,colnum);
456 this.liveGrid = grid;
457 this.dragRow = rownum;
458 this.dragCol = colnum;
462 if (this.dragRow >= this.liveGrid.buffer.totalRows) return;
463 this.selected = true;
464 this.showingSelected = true;
467 deselect: function() {
468 this.selected = false;
469 this.showingSelected = false;
472 getSingleObjectDragGUI: function() {
473 var div = document.createElement("div");
474 div.className = 'LiveGridDraggable';
475 div.style.width = (this.htmlElement.offsetWidth - 10) + "px";
476 Element.insert(div,this.htmlElement.innerHTML);
482 Rico.Dropzone = Class.create(
483 /** @lends Rico.Dropzone# */
486 * @class Implements behavior for a drop zone
489 initialize: function( htmlElement ) {
490 this.htmlElement = $(htmlElement);
491 this.absoluteRect = null;
494 getHTMLElement: function() {
495 return this.htmlElement;
498 clearPositionCache: function() {
499 this.absoluteRect = null;
502 getAbsoluteRect: function() {
503 if ( this.absoluteRect == null ) {
504 var htmlElement = this.getHTMLElement();
505 var pos = RicoUtil.toViewportPosition(htmlElement);
507 this.absoluteRect = {
510 bottom: pos.y + htmlElement.offsetHeight,
511 right: pos.x + htmlElement.offsetWidth
514 return this.absoluteRect;
517 activate: function() {
518 var htmlElement = this.getHTMLElement();
519 if (htmlElement == null || this.showingActive)
522 this.showingActive = true;
523 this.saveBackgroundColor = htmlElement.style.backgroundColor;
525 var fallbackColor = "#ffea84";
526 var currentColor = Rico.Color.createColorFromBackground(htmlElement);
527 if ( currentColor == null )
528 htmlElement.style.backgroundColor = fallbackColor;
530 currentColor.isBright() ? currentColor.darken(0.2) : currentColor.brighten(0.2);
531 htmlElement.style.backgroundColor = currentColor.asHex();
535 deactivate: function() {
536 var htmlElement = this.getHTMLElement();
537 if (htmlElement == null || !this.showingActive)
540 htmlElement.style.backgroundColor = this.saveBackgroundColor;
541 this.showingActive = false;
542 this.saveBackgroundColor = null;
545 showHover: function() {
546 var htmlElement = this.getHTMLElement();
547 if ( htmlElement == null || this.showingHover )
550 this.saveBorderWidth = htmlElement.style.borderWidth;
551 this.saveBorderStyle = htmlElement.style.borderStyle;
552 this.saveBorderColor = htmlElement.style.borderColor;
554 this.showingHover = true;
555 htmlElement.style.borderWidth = "1px";
556 htmlElement.style.borderStyle = "solid";
557 //htmlElement.style.borderColor = "#ff9900";
558 htmlElement.style.borderColor = "#ffff00";
561 hideHover: function() {
562 var htmlElement = this.getHTMLElement();
563 if ( htmlElement == null || !this.showingHover )
566 htmlElement.style.borderWidth = this.saveBorderWidth;
567 htmlElement.style.borderStyle = this.saveBorderStyle;
568 htmlElement.style.borderColor = this.saveBorderColor;
569 this.showingHover = false;
572 canAccept: function(draggableObjects) {
576 accept: function(draggableObjects) {
577 var htmlElement = this.getHTMLElement();
578 if ( htmlElement == null )
581 var n = draggableObjects.length;
582 for ( var i = 0 ; i < n ; i++ )
584 var theGUI = draggableObjects[i].getDroppedGUI();
585 /* if (Element.getStyle(theGUI,'position')=='absolute')*/
586 if ( RicoUtil.getElementsComputedStyle( theGUI, "position" ) == "absolute" )
588 theGUI.style.position = "static";
589 theGUI.style.top = "";
590 theGUI.style.top = "";
592 htmlElement.appendChild(theGUI);
597 RicoUtil = Object.extend(RicoUtil,
598 /** @lends RicoUtil# */
601 * @deprecated Use Prototype's Element#getStyle instead
603 getElementsComputedStyle: function ( htmlElement, cssProperty, mozillaEquivalentCSS) {
604 if ( arguments.length == 2 )
605 mozillaEquivalentCSS = cssProperty;
607 var el = $(htmlElement);
608 if ( el.currentStyle )
609 return el.currentStyle[cssProperty];
611 return document.defaultView.getComputedStyle(el, null).getPropertyValue(mozillaEquivalentCSS);
615 * @deprecated Use Prototype's Element#viewportOffset instead
617 toViewportPosition: function(element) {
618 return this._toAbsolute(element,true);
622 * @deprecated Use Prototype's Element#cumulativeOffset instead
624 toDocumentPosition: function(element) {
625 return this._toAbsolute(element,false);
629 * Compute the elements position in terms of the window viewport
630 * so that it can be compared to the position of the mouse (dnd)
631 * This is additions of all the offsetTop,offsetLeft values up the
632 * offsetParent hierarchy, ...taking into account any scrollTop,
633 * scrollLeft values along the way...
635 * IE has a bug reporting a correct offsetLeft of elements within a
636 * a relatively positioned parent!!!
638 _toAbsolute: function(element,accountForDocScroll) {
640 if ( !Prototype.Browser.IE && !Prototype.Browser.Opera )
641 return this._toAbsoluteMozilla(element,accountForDocScroll);
645 var parent = element;
648 var borderXOffset = 0;
649 var borderYOffset = 0;
650 if ( parent != element ) {
651 borderXOffset = parseInt(this.getElementsComputedStyle(parent, "borderLeftWidth" ),10);
652 borderYOffset = parseInt(this.getElementsComputedStyle(parent, "borderTopWidth" ),10);
653 borderXOffset = isNaN(borderXOffset) ? 0 : borderXOffset;
654 borderYOffset = isNaN(borderYOffset) ? 0 : borderYOffset;
657 x += parent.offsetLeft - parent.scrollLeft + borderXOffset;
658 y += parent.offsetTop - parent.scrollTop + borderYOffset;
659 parent = parent.offsetParent;
662 if ( accountForDocScroll ) {
663 x -= this.docScrollLeft();
664 y -= this.docScrollTop();
671 * Mozilla did not report all of the parents up the hierarchy via the
672 * offsetParent property that IE did. So for the calculation of the
673 * offsets we use the offsetParent property, but for the calculation of
674 * the scrollTop/scrollLeft adjustments we navigate up via the parentNode
675 * property instead so as to get the scroll offsets...
678 _toAbsoluteMozilla: function(element,accountForDocScroll) {
681 var parent = element;
683 x += parent.offsetLeft;
684 y += parent.offsetTop;
685 parent = parent.offsetParent;
690 parent != document.body &&
691 parent != document.documentElement ) {
692 if ( parent.scrollLeft )
693 x -= parent.scrollLeft;
694 if ( parent.scrollTop )
695 y -= parent.scrollTop;
696 parent = parent.parentNode;
699 if ( accountForDocScroll ) {
700 x -= this.docScrollLeft();
701 y -= this.docScrollTop();
709 Rico.includeLoaded('ricoDragDrop.js');