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) {
242 Rico.animate(this.dragElement,
243 {duration: 300, onEnd: function() { self._doCancelDragProcessing(); } },
244 {left:this.origPos.left, top:this.origPos.top});
247 Rico.eventUnbind(document.body, "mousemove", this._mouseMove);
248 Rico.eventUnbind(document.body, "mouseup", this._mouseUp);
251 _retTrue: function () {
255 _completeDropOperation: function(e) {
256 if ( this.dragElement != this.currentDragObjects[0].getMouseDownHTMLElement() ) {
257 if ( this.dragElement.parentNode != null )
258 this.dragElement.parentNode.removeChild(this.dragElement);
261 this._deactivateRegisteredDropZones();
263 this.clearSelection();
264 this.dragElement = null;
265 this.currentDragObjectVisible = false;
269 _doCancelDragProcessing: function() {
271 if ( this.dragElement == this.currentDragObjects[0].getMouseDownHTMLElement() ) {
272 this.dragElement.style.position=this.dragElemPosition;
274 if ( this.dragElement && this.dragElement.parentNode != null )
275 this.dragElement.parentNode.removeChild(this.dragElement);
277 this._deactivateRegisteredDropZones();
278 this.dragElement = null;
279 this.currentDragObjectVisible = false;
282 _placeDraggableInDropZone: function(e) {
283 var foundDropZone = false;
284 var n = this.dropZones.length;
285 for ( var i = 0 ; i < n ; i++ ) {
286 if ( this._mousePointInDropZone( e, this.dropZones[i] ) ) {
287 if ( this.dropZones[i].canAccept(this.currentDragObjects) ) {
288 this.dropZones[i].hideHover();
289 this.dropZones[i].accept(this.currentDragObjects);
290 foundDropZone = true;
296 return foundDropZone;
299 _cancelDrag: function() {
300 for ( var i = 0 ; i < this.currentDragObjects.length ; i++ )
301 this.currentDragObjects[i].cancelDrag();
304 _endDrag: function() {
305 for ( var i = 0 ; i < this.currentDragObjects.length ; i++ )
306 this.currentDragObjects[i].endDrag();
309 _mousePointInDropZone: function( e, dropZone ) {
311 var absoluteRect = dropZone.getAbsoluteRect();
312 var coord=Rico.eventClient(e);
314 return coord.x > absoluteRect.left + this._leftOffset(e) &&
315 coord.x < absoluteRect.right + this._leftOffset(e) &&
316 coord.y > absoluteRect.top + this._topOffset(e) &&
317 coord.y < absoluteRect.bottom + this._topOffset(e);
320 _activateRegisteredDropZones: function() {
321 var n = this.dropZones.length;
322 for ( var i = 0 ; i < n ; i++ ) {
323 var dropZone = this.dropZones[i];
324 if ( dropZone.canAccept(this.currentDragObjects) )
328 this.activatedDropZones = true;
331 _deactivateRegisteredDropZones: function() {
332 var n = this.dropZones.length;
333 for ( var i = 0 ; i < n ; i++ )
334 this.dropZones[i].deactivate();
335 this.activatedDropZones = false;
338 _attachEvents: function () {
339 Rico.eventBind(document.body, "mousemove", this._mouseMove);
340 Rico.eventBind(document.body, "mouseup", this._mouseUp);
346 Rico.Draggable = function(type, htmlElement) {
347 this.initialize(type, htmlElement);
350 Rico.Draggable.prototype = {
352 * @class Implements behavior for a draggable element
355 initialize: function( type, htmlElement ) {
357 this.htmlElement = Rico.$(htmlElement);
358 this.selected = false;
362 * Returns the HTML element that should have a mouse down event
363 * added to it in order to initiate a drag operation
365 getMouseDownHTMLElement: function() {
366 return this.htmlElement;
373 _select: function() {
374 this.selected = true;
375 if (this.showingSelected) return;
376 this.showingSelected = true;
378 var htmlElement = this.getMouseDownHTMLElement();
379 var color = Rico.Color.createColorFromBackground(htmlElement);
380 color.isBright() ? color.darken(0.033) : color.brighten(0.033);
381 this.saveBackground = Rico.getStyle(htmlElement, "backgroundColor", "background-color");
382 htmlElement.style.backgroundColor = color.asHex();
385 deselect: function() {
386 this.selected = false;
387 if (!this.showingSelected) return;
388 var htmlElement = this.getMouseDownHTMLElement();
389 htmlElement.style.backgroundColor = this.saveBackground;
390 this.showingSelected = false;
393 isSelected: function() {
394 return this.selected;
397 startDrag: function() {
400 cancelDrag: function() {
403 endDrag: function() {
406 getSingleObjectDragGUI: function() {
407 return this.htmlElement;
410 getMultiObjectDragGUI: function( draggables ) {
411 return this.htmlElement;
414 getDroppedGUI: function() {
415 return this.htmlElement;
418 toString: function() {
419 return this.type + ":" + this.htmlElement + ":";
425 Rico.LiveGridDraggable = function(grid, rownum, colnum) {
426 this.initialize(grid, rownum, colnum);
429 Rico.LiveGridDraggable.prototype = Rico.extend(new Rico.Draggable(), {
431 * @class Enables draggable behavior for LiveGrid cells.
432 * Called by LiveGrid#appendBlankRow for columns where canDrag is true.
433 * @extends Rico.Draggable
436 initialize: function( grid, rownum, colnum) {
\r
437 this.type = 'RicoCell';
\r
438 this.htmlElement = grid.cell(rownum,colnum);
\r
439 this.liveGrid = grid;
\r
440 this.dragRow = rownum;
\r
441 this.dragCol = colnum;
\r
445 if (this.dragRow >= this.liveGrid.buffer.totalRows) return;
446 this.selected = true;
447 this.showingSelected = true;
450 deselect: function() {
451 this.selected = false;
452 this.showingSelected = false;
455 getSingleObjectDragGUI: function() {
\r
456 var div = document.createElement("div");
\r
457 div.className = 'LiveGridDraggable';
\r
458 div.style.width = (this.htmlElement.offsetWidth - 10) + "px";
\r
459 div.innerHTML=this.htmlElement.innerHTML;
465 Rico.Dropzone = function(htmlElement) {
466 this.initialize(htmlElement);
469 Rico.Dropzone.prototype = {
471 * @class Implements behavior for a drop zone
474 initialize: function( htmlElement ) {
475 this.htmlElement = Rico.$(htmlElement);
476 this.absoluteRect = null;
479 getHTMLElement: function() {
480 return this.htmlElement;
483 clearPositionCache: function() {
484 this.absoluteRect = null;
487 getAbsoluteRect: function() {
488 if ( this.absoluteRect == null ) {
489 var htmlElement = this.getHTMLElement();
490 var pos = Rico.viewportOffset(htmlElement);
492 this.absoluteRect = {
495 bottom: pos.top + htmlElement.offsetHeight,
496 right: pos.left + htmlElement.offsetWidth
499 return this.absoluteRect;
502 activate: function() {
503 var htmlElement = this.getHTMLElement();
504 if (htmlElement == null || this.showingActive)
507 this.showingActive = true;
508 this.saveBackgroundColor = htmlElement.style.backgroundColor;
510 var fallbackColor = "#ffea84";
511 var currentColor = Rico.Color.createColorFromBackground(htmlElement);
512 if ( currentColor == null )
513 htmlElement.style.backgroundColor = fallbackColor;
515 currentColor.isBright() ? currentColor.darken(0.2) : currentColor.brighten(0.2);
516 htmlElement.style.backgroundColor = currentColor.asHex();
520 deactivate: function() {
521 var htmlElement = this.getHTMLElement();
522 if (htmlElement == null || !this.showingActive)
525 htmlElement.style.backgroundColor = this.saveBackgroundColor;
526 this.showingActive = false;
527 this.saveBackgroundColor = null;
530 showHover: function() {
531 var htmlElement = this.getHTMLElement();
532 if ( htmlElement == null || this.showingHover )
535 this.saveBorderWidth = htmlElement.style.borderWidth;
536 this.saveBorderStyle = htmlElement.style.borderStyle;
537 this.saveBorderColor = htmlElement.style.borderColor;
539 this.showingHover = true;
540 htmlElement.style.borderWidth = "1px";
541 htmlElement.style.borderStyle = "solid";
542 //htmlElement.style.borderColor = "#ff9900";
543 htmlElement.style.borderColor = "#ffff00";
546 hideHover: function() {
547 var htmlElement = this.getHTMLElement();
548 if ( htmlElement == null || !this.showingHover )
551 htmlElement.style.borderWidth = this.saveBorderWidth;
552 htmlElement.style.borderStyle = this.saveBorderStyle;
553 htmlElement.style.borderColor = this.saveBorderColor;
554 this.showingHover = false;
557 canAccept: function(draggableObjects) {
561 accept: function(draggableObjects) {
562 var htmlElement = this.getHTMLElement();
563 if ( htmlElement == null )
566 var n = draggableObjects.length;
567 for ( var i = 0 ; i < n ; i++ ) {
568 var theGUI = draggableObjects[i].getDroppedGUI();
569 if ( Rico.getStyle( theGUI, "position" ) == "absolute" ) {
570 theGUI.style.position = "static";
571 theGUI.style.top = "";
572 theGUI.style.top = "";
574 htmlElement.appendChild(theGUI);