Path: blob/trunk/third_party/closure/goog/fx/draglistgroup.js
2868 views
// Copyright 2008 The Closure Library Authors. All Rights Reserved.1//2// Licensed under the Apache License, Version 2.0 (the "License");3// you may not use this file except in compliance with the License.4// You may obtain a copy of the License at5//6// http://www.apache.org/licenses/LICENSE-2.07//8// Unless required by applicable law or agreed to in writing, software9// distributed under the License is distributed on an "AS-IS" BASIS,10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11// See the License for the specific language governing permissions and12// limitations under the License.1314/**15* @fileoverview A DragListGroup is a class representing a group of one or more16* "drag lists" with items that can be dragged within them and between them.17*18* @see ../demos/draglistgroup.html19*/202122goog.provide('goog.fx.DragListDirection');23goog.provide('goog.fx.DragListGroup');24goog.provide('goog.fx.DragListGroup.EventType');25goog.provide('goog.fx.DragListGroupEvent');2627goog.require('goog.array');28goog.require('goog.asserts');29goog.require('goog.dom');30goog.require('goog.dom.classlist');31goog.require('goog.events');32goog.require('goog.events.Event');33goog.require('goog.events.EventHandler');34goog.require('goog.events.EventId');35goog.require('goog.events.EventTarget');36goog.require('goog.events.EventType');37goog.require('goog.fx.Dragger');38goog.require('goog.math.Coordinate');39goog.require('goog.string');40goog.require('goog.style');41424344/**45* A class representing a group of one or more "drag lists" with items that can46* be dragged within them and between them.47*48* Example usage:49* var dragListGroup = new goog.fx.DragListGroup();50* dragListGroup.setDragItemHandleHoverClass(className1, className2);51* dragListGroup.setDraggerElClass(className3);52* dragListGroup.addDragList(vertList, goog.fx.DragListDirection.DOWN);53* dragListGroup.addDragList(horizList, goog.fx.DragListDirection.RIGHT);54* dragListGroup.init();55*56* @extends {goog.events.EventTarget}57* @constructor58* @struct59*/60goog.fx.DragListGroup = function() {61goog.fx.DragListGroup.base(this, 'constructor');6263/**64* The user-supplied CSS classes to add to a drag item on hover (not during a65* drag action).66* @private {Array|undefined}67*/68this.dragItemHoverClasses_;6970/**71* The user-supplied CSS classes to add to a drag item handle on hover (not72* during a drag action).73* @private {Array|undefined}74*/75this.dragItemHandleHoverClasses_;7677/**78* The user-supplied CSS classes to add to the current drag item (during a79* drag action).80* @private {Array|undefined}81*/82this.currDragItemClasses_;8384/**85* The user-supplied CSS classes to add to the clone of the current drag item86* that's actually being dragged around (during a drag action).87* @private {Array<string>|undefined}88*/89this.draggerElClasses_;9091/**92* The current drag item being moved.93* Note: This is only defined while a drag action is happening.94* @private {Element}95*/96this.currDragItem_;9798/**99* The drag list that {@code this.currDragItem_} is currently hovering over,100* or null if it is not hovering over a list.101* @private {Element}102*/103this.currHoverList_;104105/**106* The original drag list that the current drag item came from. We need to107* remember this in case the user drops the item outside of any lists, in108* which case we return the item to its original location.109* Note: This is only defined while a drag action is happening.110* @private {Element}111*/112this.origList_;113114/**115* The original next item in the original list that the current drag item came116* from. We need to remember this in case the user drops the item outside of117* any lists, in which case we return the item to its original location.118* Note: This is only defined while a drag action is happening.119* @private {Element}120*/121this.origNextItem_;122123/**124* The current item in the list we are hovering over. We need to remember125* this in case we do not update the position of the current drag item while126* dragging (see {@code updateWhileDragging_}). In this case the current drag127* item will be inserted into the list before this element when the drag ends.128* @private {Element}129*/130this.currHoverItem_;131132/**133* The clone of the current drag item that's actually being dragged around.134* Note: This is only defined while a drag action is happening.135* @private {HTMLElement}136*/137this.draggerEl_;138139/**140* The dragger object.141* Note: This is only defined while a drag action is happening.142* @private {goog.fx.Dragger}143*/144this.dragger_;145146/**147* The amount of distance, in pixels, after which a mousedown or touchstart is148* considered a drag.149* @private {number}150*/151this.hysteresisDistance_ = 0;152153154/**155* The drag lists.156* @private {Array<Element>}157*/158this.dragLists_ = [];159160/**161* All the drag items. Set by init().162* @private {Array<Element>}163*/164this.dragItems_ = [];165166/**167* Which drag item corresponds to a given handle. Set by init().168* Specifically, this maps from the unique ID (as given by goog.getUid)169* of the handle to the drag item.170* @private {Object}171*/172this.dragItemForHandle_ = {};173174/**175* The event handler for this instance.176* @private {goog.events.EventHandler<!goog.fx.DragListGroup>}177*/178this.eventHandler_ = new goog.events.EventHandler(this);179180/**181* Whether the setup has been done to make all items in all lists draggable.182* @private {boolean}183*/184this.isInitialized_ = false;185186/**187* Whether the currDragItem is always displayed. By default the list188* collapses, the currDragItem's display is set to none, when we do not189* hover over a draglist.190* @private {boolean}191*/192this.isCurrDragItemAlwaysDisplayed_ = false;193194/**195* Whether to update the position of the currDragItem as we drag, i.e.,196* insert the currDragItem each time to the position where it would land if197* we were to end the drag at that point. Defaults to true.198* @private {boolean}199*/200this.updateWhileDragging_ = true;201};202goog.inherits(goog.fx.DragListGroup, goog.events.EventTarget);203204205/**206* Enum to indicate the direction that a drag list grows.207* @enum {number}208*/209goog.fx.DragListDirection = {210DOWN: 0, // common211RIGHT: 2, // common212LEFT: 3, // uncommon (except perhaps for right-to-left interfaces)213RIGHT_2D: 4, // common + handles multiple lines if items are wrapped214LEFT_2D: 5 // for rtl languages215};216217218/**219* Events dispatched by this class.220* @enum {!goog.events.EventId<!goog.fx.DragListGroupEvent>}221*/222goog.fx.DragListGroup.EventType = {223BEFOREDRAGSTART: new goog.events.EventId('beforedragstart'),224DRAGSTART: new goog.events.EventId('dragstart'),225BEFOREDRAGMOVE: new goog.events.EventId('beforedragmove'),226DRAGMOVE: new goog.events.EventId('dragmove'),227BEFOREDRAGEND: new goog.events.EventId('beforedragend'),228DRAGEND: new goog.events.EventId('dragend')229};230231232/**233* Sets the property of the currDragItem that it is always displayed in the234* list.235*/236goog.fx.DragListGroup.prototype.setIsCurrDragItemAlwaysDisplayed = function() {237this.isCurrDragItemAlwaysDisplayed_ = true;238};239240241/**242* Sets the private property updateWhileDragging_ to false. This disables the243* update of the position of the currDragItem while dragging. It will only be244* placed to its new location once the drag ends.245*/246goog.fx.DragListGroup.prototype.setNoUpdateWhileDragging = function() {247this.updateWhileDragging_ = false;248};249250251/**252* Sets the distance the user has to drag the element before a drag operation253* is started.254* @param {number} distance The number of pixels after which a mousedown and255* move is considered a drag.256*/257goog.fx.DragListGroup.prototype.setHysteresis = function(distance) {258this.hysteresisDistance_ = distance;259};260261262/**263* @return {number} distance The number of pixels after which a mousedown and264* move is considered a drag.265*/266goog.fx.DragListGroup.prototype.getHysteresis = function() {267return this.hysteresisDistance_;268};269270271/** @return {boolean} true if the user is currently dragging an element. */272goog.fx.DragListGroup.prototype.isDragging = function() {273return !!this.dragger_;274};275276277/**278* Adds a drag list to this DragListGroup.279* All calls to this method must happen before the call to init().280* Remember that all child nodes (except text nodes) will be made draggable to281* any other drag list in this group.282*283* @param {Element} dragListElement Must be a container for a list of items284* that should all be made draggable.285* @param {goog.fx.DragListDirection} growthDirection The direction that this286* drag list grows in (i.e. if an item is appended to the DOM, the list's287* bounding box expands in this direction).288* @param {boolean=} opt_unused Unused argument.289* @param {string=} opt_dragHoverClass CSS class to apply to this drag list when290* the draggerEl hovers over it during a drag action. If present, must be a291* single, valid classname (not a string of space-separated classnames).292*/293goog.fx.DragListGroup.prototype.addDragList = function(294dragListElement, growthDirection, opt_unused, opt_dragHoverClass) {295goog.asserts.assert(!this.isInitialized_);296297dragListElement.dlgGrowthDirection_ = growthDirection;298dragListElement.dlgDragHoverClass_ = opt_dragHoverClass;299this.dragLists_.push(dragListElement);300};301302303/**304* Sets a user-supplied function used to get the "handle" element for a drag305* item. The function must accept exactly one argument. The argument may be306* any drag item element.307*308* If not set, the default implementation uses the whole drag item as the309* handle.310*311* @param {function(!Element): Element} getHandleForDragItemFn A function that,312* given any drag item, returns a reference to its "handle" element313* (which may be the drag item element itself).314*/315goog.fx.DragListGroup.prototype.setFunctionToGetHandleForDragItem = function(316getHandleForDragItemFn) {317goog.asserts.assert(!this.isInitialized_);318this.getHandleForDragItem_ = getHandleForDragItemFn;319};320321322/**323* Sets a user-supplied CSS class to add to a drag item on hover (not during a324* drag action).325* @param {...string} var_args The CSS class or classes.326*/327goog.fx.DragListGroup.prototype.setDragItemHoverClass = function(var_args) {328goog.asserts.assert(!this.isInitialized_);329this.dragItemHoverClasses_ = goog.array.slice(arguments, 0);330};331332333/**334* Sets a user-supplied CSS class to add to a drag item handle on hover (not335* during a drag action).336* @param {...string} var_args The CSS class or classes.337*/338goog.fx.DragListGroup.prototype.setDragItemHandleHoverClass = function(339var_args) {340goog.asserts.assert(!this.isInitialized_);341this.dragItemHandleHoverClasses_ = goog.array.slice(arguments, 0);342};343344345/**346* Sets a user-supplied CSS class to add to the current drag item (during a347* drag action).348*349* If not set, the default behavior adds visibility:hidden to the current drag350* item so that it is a block of empty space in the hover drag list (if any).351* If this class is set by the user, then the default behavior does not happen352* (unless, of course, the class also contains visibility:hidden).353*354* @param {...string} var_args The CSS class or classes.355*/356goog.fx.DragListGroup.prototype.setCurrDragItemClass = function(var_args) {357goog.asserts.assert(!this.isInitialized_);358this.currDragItemClasses_ = goog.array.slice(arguments, 0);359};360361362/**363* Sets a user-supplied CSS class to add to the clone of the current drag item364* that's actually being dragged around (during a drag action).365* @param {string} draggerElClass The CSS class.366*/367goog.fx.DragListGroup.prototype.setDraggerElClass = function(draggerElClass) {368goog.asserts.assert(!this.isInitialized_);369// Split space-separated classes up into an array.370this.draggerElClasses_ = goog.string.trim(draggerElClass).split(' ');371};372373374/**375* Performs the initial setup to make all items in all lists draggable.376*/377goog.fx.DragListGroup.prototype.init = function() {378if (this.isInitialized_) {379return;380}381382for (var i = 0, numLists = this.dragLists_.length; i < numLists; i++) {383var dragList = this.dragLists_[i];384385var dragItems = goog.dom.getChildren(dragList);386for (var j = 0, numItems = dragItems.length; j < numItems; ++j) {387this.listenForDragEvents(dragItems[j]);388}389}390391this.isInitialized_ = true;392};393394395/**396* Adds a single item to the given drag list and sets up the drag listeners for397* it.398* If opt_index is specified the item is inserted at this index, otherwise the399* item is added as the last child of the list.400*401* @param {!Element} list The drag list where to add item to.402* @param {!Element} item The new element to add.403* @param {number=} opt_index Index where to insert the item in the list. If not404* specified item is inserted as the last child of list.405*/406goog.fx.DragListGroup.prototype.addItemToDragList = function(407list, item, opt_index) {408if (goog.isDef(opt_index)) {409goog.dom.insertChildAt(list, item, opt_index);410} else {411goog.dom.appendChild(list, item);412}413this.listenForDragEvents(item);414};415416417/** @override */418goog.fx.DragListGroup.prototype.disposeInternal = function() {419this.eventHandler_.dispose();420421for (var i = 0, n = this.dragLists_.length; i < n; i++) {422var dragList = this.dragLists_[i];423// Note: IE doesn't allow 'delete' for fields on HTML elements (because424// they're not real JS objects in IE), so we just set them to undefined.425dragList.dlgGrowthDirection_ = undefined;426dragList.dlgDragHoverClass_ = undefined;427}428429this.dragLists_.length = 0;430this.dragItems_.length = 0;431this.dragItemForHandle_ = null;432433// In the case where a drag event is currently in-progress and dispose is434// called, this cleans up the extra state.435this.cleanupDragDom_();436437goog.fx.DragListGroup.superClass_.disposeInternal.call(this);438};439440441/**442* Caches the heights of each drag list and drag item, except for the current443* drag item.444*445*/446goog.fx.DragListGroup.prototype.recacheListAndItemBounds = function() {447this.recacheListAndItemBounds_(this.currDragItem_);448};449450451/**452* Caches the heights of each drag list and drag item, except for the current453* drag item.454*455* @param {Element} currDragItem The item currently being dragged.456* @private457*/458goog.fx.DragListGroup.prototype.recacheListAndItemBounds_ = function(459currDragItem) {460for (var i = 0, n = this.dragLists_.length; i < n; i++) {461var dragList = this.dragLists_[i];462dragList.dlgBounds_ = goog.style.getBounds(dragList);463}464465for (var i = 0, n = this.dragItems_.length; i < n; i++) {466var dragItem = this.dragItems_[i];467if (dragItem != currDragItem) {468dragItem.dlgBounds_ = goog.style.getBounds(dragItem);469}470}471};472473474/**475* Listens for drag events on the given drag item. This method is currently used476* to initialize drag items.477*478* @param {!Element} dragItem the element to initialize. This element has to be479* in one of the drag lists.480* @protected481*/482goog.fx.DragListGroup.prototype.listenForDragEvents = function(dragItem) {483var dragItemHandle = this.getHandleForDragItem_(dragItem);484var uid = goog.getUid(dragItemHandle);485this.dragItemForHandle_[uid] = dragItem;486487if (this.dragItemHoverClasses_) {488this.eventHandler_.listen(489dragItem, goog.events.EventType.MOUSEOVER,490this.handleDragItemMouseover_);491this.eventHandler_.listen(492dragItem, goog.events.EventType.MOUSEOUT, this.handleDragItemMouseout_);493}494if (this.dragItemHandleHoverClasses_) {495this.eventHandler_.listen(496dragItemHandle, goog.events.EventType.MOUSEOVER,497this.handleDragItemHandleMouseover_);498this.eventHandler_.listen(499dragItemHandle, goog.events.EventType.MOUSEOUT,500this.handleDragItemHandleMouseout_);501}502503this.dragItems_.push(dragItem);504this.eventHandler_.listen(505dragItemHandle,506[goog.events.EventType.MOUSEDOWN, goog.events.EventType.TOUCHSTART],507this.handlePotentialDragStart_);508};509510511/**512* Handles mouse and touch events which may start a drag action.513* @param {!goog.events.BrowserEvent} e MOUSEDOWN or TOUCHSTART event.514* @private515*/516goog.fx.DragListGroup.prototype.handlePotentialDragStart_ = function(e) {517var uid = goog.getUid(/** @type {Node} */ (e.currentTarget));518this.currDragItem_ = /** @type {Element} */ (this.dragItemForHandle_[uid]);519520this.draggerEl_ = /** @type {!HTMLElement} */ (521this.createDragElementInternal(this.currDragItem_));522if (this.draggerElClasses_) {523// Add CSS class for the clone, if any.524goog.dom.classlist.addAll(525goog.asserts.assert(this.draggerEl_), this.draggerElClasses_ || []);526}527528// Place the clone (i.e. draggerEl) at the same position as the actual529// current drag item. This is a bit tricky since530// goog.style.getPageOffset() gets the left-top pos of the border, but531// goog.style.setPageOffset() sets the left-top pos of the margin.532// It's difficult to adjust for the margins of the clone because it's533// difficult to read it: goog.style.getComputedStyle() doesn't work for IE.534// Instead, our workaround is simply to set the clone's margins to 0px.535this.draggerEl_.style.margin = '0';536this.draggerEl_.style.position = 'absolute';537this.draggerEl_.style.visibility = 'hidden';538var doc = goog.dom.getOwnerDocument(this.currDragItem_);539doc.body.appendChild(this.draggerEl_);540541// Important: goog.style.setPageOffset() only works correctly for IE when the542// element is already in the document.543var currDragItemPos = goog.style.getPageOffset(this.currDragItem_);544goog.style.setPageOffset(this.draggerEl_, currDragItemPos);545546this.dragger_ = new goog.fx.Dragger(this.draggerEl_);547this.dragger_.setHysteresis(this.hysteresisDistance_);548549// Listen to events on the dragger. These handlers will be unregistered at550// DRAGEND, when the dragger is disposed of. We can't use eventHandler_,551// because it creates new references to the handler functions at each552// dragging action, and keeps them until DragListGroup is disposed of.553goog.events.listen(554this.dragger_, goog.fx.Dragger.EventType.START, this.handleDragStart_,555false, this);556goog.events.listen(557this.dragger_, goog.fx.Dragger.EventType.END, this.handleDragEnd_, false,558this);559goog.events.listen(560this.dragger_, goog.fx.Dragger.EventType.EARLY_CANCEL, this.cleanup_,561false, this);562this.dragger_.startDrag(e);563};564565566/**567* Creates copy of node being dragged.568*569* @param {Element} sourceEl Element to copy.570* @return {!Element} The clone of {@code sourceEl}.571* @deprecated Use goog.fx.Dragger.cloneNode().572* @private573*/574goog.fx.DragListGroup.prototype.cloneNode_ = function(sourceEl) {575return goog.fx.Dragger.cloneNode(sourceEl);576};577578579/**580* Generates an element to follow the cursor during dragging, given a drag581* source element. The default behavior is simply to clone the source element,582* but this may be overridden in subclasses. This method is called by583* {@code createDragElement()} before the drag class is added.584*585* @param {Element} sourceEl Drag source element.586* @return {!Element} The new drag element.587* @protected588* @suppress {deprecated}589*/590goog.fx.DragListGroup.prototype.createDragElementInternal = function(sourceEl) {591return this.cloneNode_(sourceEl);592};593594595/**596* Handles the start of a drag action.597* @param {!goog.fx.DragEvent} e goog.fx.Dragger.EventType.START event.598* @private599*/600goog.fx.DragListGroup.prototype.handleDragStart_ = function(e) {601if (!this.dispatchEvent(602new goog.fx.DragListGroupEvent(603goog.fx.DragListGroup.EventType.BEFOREDRAGSTART, this,604e.browserEvent, this.currDragItem_, null, null))) {605e.preventDefault();606this.cleanup_();607return;608}609610// Record the original location of the current drag item.611// Note: this.origNextItem_ may be null.612this.origList_ = /** @type {Element} */ (this.currDragItem_.parentNode);613this.origNextItem_ = goog.dom.getNextElementSibling(this.currDragItem_);614this.currHoverItem_ = this.origNextItem_;615this.currHoverList_ = this.origList_;616617// If there's a CSS class specified for the current drag item, add it.618// Otherwise, make the actual current drag item hidden (takes up space).619if (this.currDragItemClasses_) {620goog.dom.classlist.addAll(621goog.asserts.assert(this.currDragItem_),622this.currDragItemClasses_ || []);623} else {624this.currDragItem_.style.visibility = 'hidden';625}626627// Precompute distances from top-left corner to center for efficiency.628var draggerElSize = goog.style.getSize(this.draggerEl_);629this.draggerEl_.halfWidth = draggerElSize.width / 2;630this.draggerEl_.halfHeight = draggerElSize.height / 2;631632this.draggerEl_.style.visibility = '';633634// Record the bounds of all the drag lists and all the other drag items. This635// caching is for efficiency, so that we don't have to recompute the bounds on636// each drag move. Do this in the state where the current drag item is not in637// any of the lists, except when update while dragging is disabled, as in this638// case the current drag item does not get removed until drag ends.639if (this.updateWhileDragging_) {640this.currDragItem_.style.display = 'none';641}642this.recacheListAndItemBounds_(this.currDragItem_);643this.currDragItem_.style.display = '';644645// Listen to events on the dragger.646goog.events.listen(647this.dragger_, goog.fx.Dragger.EventType.DRAG, this.handleDragMove_,648false, this);649650this.dispatchEvent(651new goog.fx.DragListGroupEvent(652goog.fx.DragListGroup.EventType.DRAGSTART, this, e.browserEvent,653this.currDragItem_, this.draggerEl_, this.dragger_));654};655656657/**658* Handles a drag movement (i.e. DRAG event fired by the dragger).659*660* @param {goog.fx.DragEvent} dragEvent Event object fired by the dragger.661* @return {boolean} The return value for the event.662* @private663*/664goog.fx.DragListGroup.prototype.handleDragMove_ = function(dragEvent) {665666// Compute the center of the dragger element (i.e. the cloned drag item).667var draggerElPos = goog.style.getPageOffset(this.draggerEl_);668var draggerElCenter = new goog.math.Coordinate(669draggerElPos.x + this.draggerEl_.halfWidth,670draggerElPos.y + this.draggerEl_.halfHeight);671672// Check whether the center is hovering over one of the drag lists.673var hoverList = this.getHoverDragList_(draggerElCenter);674675// If hovering over a list, find the next item (if drag were to end now).676var hoverNextItem =677hoverList ? this.getHoverNextItem_(hoverList, draggerElCenter) : null;678679var rv = this.dispatchEvent(680new goog.fx.DragListGroupEvent(681goog.fx.DragListGroup.EventType.BEFOREDRAGMOVE, this, dragEvent,682this.currDragItem_, this.draggerEl_, this.dragger_, draggerElCenter,683hoverList, hoverNextItem));684if (!rv) {685return false;686}687688if (hoverList) {689if (this.updateWhileDragging_) {690this.insertCurrDragItem_(hoverList, hoverNextItem);691} else {692// If update while dragging is disabled do not insert693// the dragged item, but update the hovered item instead.694this.updateCurrHoverItem(hoverNextItem, draggerElCenter);695}696this.currDragItem_.style.display = '';697// Add drag list's hover class (if any).698if (hoverList.dlgDragHoverClass_) {699goog.dom.classlist.add(700goog.asserts.assert(hoverList), hoverList.dlgDragHoverClass_);701}702703} else {704// Not hovering over a drag list, so remove the item altogether unless705// specified otherwise by the user.706if (!this.isCurrDragItemAlwaysDisplayed_) {707this.currDragItem_.style.display = 'none';708}709710// Remove hover classes (if any) from all drag lists.711for (var i = 0, n = this.dragLists_.length; i < n; i++) {712var dragList = this.dragLists_[i];713if (dragList.dlgDragHoverClass_) {714goog.dom.classlist.remove(715goog.asserts.assert(dragList), dragList.dlgDragHoverClass_);716}717}718}719720// If the current hover list is different than the last, the lists may have721// shrunk, so we should recache the bounds.722if (hoverList != this.currHoverList_) {723this.currHoverList_ = hoverList;724this.recacheListAndItemBounds_(this.currDragItem_);725}726727this.dispatchEvent(728new goog.fx.DragListGroupEvent(729goog.fx.DragListGroup.EventType.DRAGMOVE, this, dragEvent,730/** @type {Element} */ (this.currDragItem_), this.draggerEl_,731this.dragger_, draggerElCenter, hoverList, hoverNextItem));732733// Return false to prevent selection due to mouse drag.734return false;735};736737738/**739* Clear all our temporary fields that are only defined while dragging, and740* all the bounds info stored on the drag lists and drag elements.741* @param {!goog.events.Event=} opt_e EARLY_CANCEL event from the dragger if742* cleanup_ was called as an event handler.743* @private744*/745goog.fx.DragListGroup.prototype.cleanup_ = function(opt_e) {746this.cleanupDragDom_();747748this.currDragItem_ = null;749this.currHoverList_ = null;750this.origList_ = null;751this.origNextItem_ = null;752this.draggerEl_ = null;753this.dragger_ = null;754755// Note: IE doesn't allow 'delete' for fields on HTML elements (because756// they're not real JS objects in IE), so we just set them to null.757for (var i = 0, n = this.dragLists_.length; i < n; i++) {758this.dragLists_[i].dlgBounds_ = null;759}760for (var i = 0, n = this.dragItems_.length; i < n; i++) {761this.dragItems_[i].dlgBounds_ = null;762}763};764765766/**767* Handles the end or the cancellation of a drag action, i.e. END or CLEANUP768* event fired by the dragger.769*770* @param {!goog.fx.DragEvent} dragEvent Event object fired by the dragger.771* @return {boolean} Whether the event was handled.772* @private773*/774goog.fx.DragListGroup.prototype.handleDragEnd_ = function(dragEvent) {775var rv = this.dispatchEvent(776new goog.fx.DragListGroupEvent(777goog.fx.DragListGroup.EventType.BEFOREDRAGEND, this, dragEvent,778/** @type {Element} */ (this.currDragItem_), this.draggerEl_,779this.dragger_));780if (!rv) {781return false;782}783784// If update while dragging is disabled insert the current drag item into785// its intended location.786if (!this.updateWhileDragging_) {787this.insertCurrHoverItem();788}789790// The DRAGEND handler may need the new order of the list items. Clean up the791// garbage.792// TODO(user): Regression test.793this.cleanupDragDom_();794795this.dispatchEvent(796new goog.fx.DragListGroupEvent(797goog.fx.DragListGroup.EventType.DRAGEND, this, dragEvent,798this.currDragItem_, this.draggerEl_, this.dragger_));799800this.cleanup_();801802return true;803};804805806/**807* Cleans up DOM changes that are made by the {@code handleDrag*} methods.808* @private809*/810goog.fx.DragListGroup.prototype.cleanupDragDom_ = function() {811// Disposes of the dragger and remove the cloned drag item.812goog.dispose(this.dragger_);813if (this.draggerEl_) {814goog.dom.removeNode(this.draggerEl_);815}816817// If the current drag item is not in any list, put it back in its original818// location.819if (this.currDragItem_ && this.currDragItem_.style.display == 'none') {820// Note: this.origNextItem_ may be null, but insertBefore() still works.821this.origList_.insertBefore(this.currDragItem_, this.origNextItem_);822this.currDragItem_.style.display = '';823}824825// If there's a CSS class specified for the current drag item, remove it.826// Otherwise, make the current drag item visible (instead of empty space).827if (this.currDragItemClasses_ && this.currDragItem_) {828goog.dom.classlist.removeAll(829goog.asserts.assert(this.currDragItem_),830this.currDragItemClasses_ || []);831} else if (this.currDragItem_) {832this.currDragItem_.style.visibility = '';833}834835// Remove hover classes (if any) from all drag lists.836for (var i = 0, n = this.dragLists_.length; i < n; i++) {837var dragList = this.dragLists_[i];838if (dragList.dlgDragHoverClass_) {839goog.dom.classlist.remove(840goog.asserts.assert(dragList), dragList.dlgDragHoverClass_);841}842}843};844845846/**847* Default implementation of the function to get the "handle" element for a848* drag item. By default, we use the whole drag item as the handle. Users can849* change this by calling setFunctionToGetHandleForDragItem().850*851* @param {!Element} dragItem The drag item to get the handle for.852* @return {Element} The dragItem element itself.853* @private854*/855goog.fx.DragListGroup.prototype.getHandleForDragItem_ = function(dragItem) {856return dragItem;857};858859860/**861* Handles a MOUSEOVER event fired on a drag item.862* @param {goog.events.BrowserEvent} e The event.863* @private864*/865goog.fx.DragListGroup.prototype.handleDragItemMouseover_ = function(e) {866var targetEl = goog.asserts.assertElement(e.currentTarget);867goog.dom.classlist.addAll(targetEl, this.dragItemHoverClasses_ || []);868};869870871/**872* Handles a MOUSEOUT event fired on a drag item.873* @param {goog.events.BrowserEvent} e The event.874* @private875*/876goog.fx.DragListGroup.prototype.handleDragItemMouseout_ = function(e) {877var targetEl = goog.asserts.assertElement(e.currentTarget);878goog.dom.classlist.removeAll(targetEl, this.dragItemHoverClasses_ || []);879};880881882/**883* Handles a MOUSEOVER event fired on the handle element of a drag item.884* @param {goog.events.BrowserEvent} e The event.885* @private886*/887goog.fx.DragListGroup.prototype.handleDragItemHandleMouseover_ = function(e) {888var targetEl = goog.asserts.assertElement(e.currentTarget);889goog.dom.classlist.addAll(targetEl, this.dragItemHandleHoverClasses_ || []);890};891892893/**894* Handles a MOUSEOUT event fired on the handle element of a drag item.895* @param {goog.events.BrowserEvent} e The event.896* @private897*/898goog.fx.DragListGroup.prototype.handleDragItemHandleMouseout_ = function(e) {899var targetEl = goog.asserts.assertElement(e.currentTarget);900goog.dom.classlist.removeAll(901targetEl, this.dragItemHandleHoverClasses_ || []);902};903904905/**906* Helper for handleDragMove_().907* Given the position of the center of the dragger element, figures out whether908* it's currently hovering over any of the drag lists.909*910* @param {goog.math.Coordinate} draggerElCenter The center position of the911* dragger element.912* @return {Element} If currently hovering over a drag list, returns the drag913* list element. Else returns null.914* @private915*/916goog.fx.DragListGroup.prototype.getHoverDragList_ = function(draggerElCenter) {917918// If the current drag item was in a list last time we did this, then check919// that same list first.920var prevHoverList = null;921if (this.currDragItem_.style.display != 'none') {922prevHoverList = /** @type {Element} */ (this.currDragItem_.parentNode);923// Important: We can't use the cached bounds for this list because the924// cached bounds are based on the case where the current drag item is not925// in the list. Since the current drag item is known to be in this list, we926// must recompute the list's bounds.927var prevHoverListBounds = goog.style.getBounds(prevHoverList);928if (this.isInRect_(draggerElCenter, prevHoverListBounds)) {929return prevHoverList;930}931}932933for (var i = 0, n = this.dragLists_.length; i < n; i++) {934var dragList = this.dragLists_[i];935if (dragList == prevHoverList) {936continue;937}938if (this.isInRect_(draggerElCenter, dragList.dlgBounds_)) {939return dragList;940}941}942943return null;944};945946947/**948* Checks whether a coordinate position resides inside a rectangle.949* @param {goog.math.Coordinate} pos The coordinate position.950* @param {goog.math.Rect} rect The rectangle.951* @return {boolean} True if 'pos' is within the bounds of 'rect'.952* @private953*/954goog.fx.DragListGroup.prototype.isInRect_ = function(pos, rect) {955return pos.x > rect.left && pos.x < rect.left + rect.width &&956pos.y > rect.top && pos.y < rect.top + rect.height;957};958959960/**961* Updates the value of currHoverItem_.962*963* This method is used for insertion only when updateWhileDragging_ is false.964* The below implementation is the basic one. This method can be extended by965* a subclass to support changes to hovered item (eg: highlighting). Parametr966* opt_draggerElCenter can be used for more sophisticated effects.967*968* @param {Element} hoverNextItem element of the list that is hovered over.969* @param {goog.math.Coordinate=} opt_draggerElCenter current position of970* the dragged element.971* @protected972*/973goog.fx.DragListGroup.prototype.updateCurrHoverItem = function(974hoverNextItem, opt_draggerElCenter) {975if (hoverNextItem) {976this.currHoverItem_ = hoverNextItem;977}978};979980981/**982* Inserts the currently dragged item in its new place.983*984* This method is used for insertion only when updateWhileDragging_ is false985* (otherwise there is no need for that). In the basic implementation986* the element is inserted before the currently hovered over item (this can987* be changed by overriding the method in subclasses).988*989* @protected990*/991goog.fx.DragListGroup.prototype.insertCurrHoverItem = function() {992this.origList_.insertBefore(this.currDragItem_, this.currHoverItem_);993};994995996/**997* Helper for handleDragMove_().998* Given the position of the center of the dragger element, plus the drag list999* that it's currently hovering over, figures out the next drag item in the1000* list that follows the current position of the dragger element. (I.e. if1001* the drag action ends right now, it would become the item after the current1002* drag item.)1003*1004* @param {Element} hoverList The drag list that we're hovering over.1005* @param {goog.math.Coordinate} draggerElCenter The center position of the1006* dragger element.1007* @return {Element} Returns the earliest item in the hover list that belongs1008* after the current position of the dragger element. If all items in the1009* list should come before the current drag item, then returns null.1010* @private1011*/1012goog.fx.DragListGroup.prototype.getHoverNextItem_ = function(1013hoverList, draggerElCenter) {1014if (hoverList == null) {1015throw Error('getHoverNextItem_ called with null hoverList.');1016}10171018// The definition of what it means for the draggerEl to be "before" a given1019// item in the hover drag list is not always the same. It changes based on1020// the growth direction of the hover drag list in question.1021/** @type {number} */1022var relevantCoord = 0;1023var getRelevantBoundFn;1024var isBeforeFn;1025var pickClosestRow = false;1026var distanceToClosestRow = undefined;1027switch (hoverList.dlgGrowthDirection_) {1028case goog.fx.DragListDirection.DOWN:1029// "Before" means draggerElCenter.y is less than item's bottom y-value.1030relevantCoord = draggerElCenter.y;1031getRelevantBoundFn = goog.fx.DragListGroup.getBottomBound_;1032isBeforeFn = goog.fx.DragListGroup.isLessThan_;1033break;1034case goog.fx.DragListDirection.RIGHT_2D:1035pickClosestRow = true;1036case goog.fx.DragListDirection.RIGHT:1037// "Before" means draggerElCenter.x is less than item's right x-value.1038relevantCoord = draggerElCenter.x;1039getRelevantBoundFn = goog.fx.DragListGroup.getRightBound_;1040isBeforeFn = goog.fx.DragListGroup.isLessThan_;1041break;1042case goog.fx.DragListDirection.LEFT_2D:1043pickClosestRow = true;1044case goog.fx.DragListDirection.LEFT:1045// "Before" means draggerElCenter.x is greater than item's left x-value.1046relevantCoord = draggerElCenter.x;1047getRelevantBoundFn = goog.fx.DragListGroup.getLeftBound_;1048isBeforeFn = goog.fx.DragListGroup.isGreaterThan_;1049break;1050}10511052// This holds the earliest drag item found so far that should come after1053// this.currDragItem_ in the hover drag list (based on draggerElCenter).1054var earliestAfterItem = null;1055// This is the position of the relevant bound for the earliestAfterItem,1056// where "relevant" is determined by the growth direction of hoverList.1057var earliestAfterItemRelevantBound;10581059var hoverListItems = goog.dom.getChildren(hoverList);1060for (var i = 0, n = hoverListItems.length; i < n; i++) {1061var item = hoverListItems[i];1062if (item == this.currDragItem_) {1063continue;1064}10651066var relevantBound = getRelevantBoundFn(item.dlgBounds_);1067// When the hoverlist is broken into multiple rows (i.e., in the case of1068// LEFT_2D and RIGHT_2D) it is no longer enough to only look at the1069// x-coordinate alone in order to find the {@earliestAfterItem} in the1070// hoverlist. Make sure it is chosen from the row closest to the1071// {@code draggerElCenter}.1072if (pickClosestRow) {1073var distanceToRow = goog.fx.DragListGroup.verticalDistanceFromItem_(1074item, draggerElCenter);1075// Initialize the distance to the closest row to the current value if1076// undefined.1077if (!goog.isDef(distanceToClosestRow)) {1078distanceToClosestRow = distanceToRow;1079}1080if (isBeforeFn(relevantCoord, relevantBound) &&1081(earliestAfterItemRelevantBound == undefined ||1082(distanceToRow < distanceToClosestRow) ||1083((distanceToRow == distanceToClosestRow) &&1084(isBeforeFn(relevantBound, earliestAfterItemRelevantBound) ||1085relevantBound == earliestAfterItemRelevantBound)))) {1086earliestAfterItem = item;1087earliestAfterItemRelevantBound = relevantBound;1088}1089// Update distance to closest row.1090if (distanceToRow < distanceToClosestRow) {1091distanceToClosestRow = distanceToRow;1092}1093} else if (1094isBeforeFn(relevantCoord, relevantBound) &&1095(earliestAfterItemRelevantBound == undefined ||1096isBeforeFn(relevantBound, earliestAfterItemRelevantBound))) {1097earliestAfterItem = item;1098earliestAfterItemRelevantBound = relevantBound;1099}1100}1101// If we ended up picking an element that is not in the closest row it can1102// only happen if we should have picked the last one in which case there is1103// no consecutive element.1104if (!goog.isNull(earliestAfterItem) &&1105goog.fx.DragListGroup.verticalDistanceFromItem_(1106earliestAfterItem, draggerElCenter) > distanceToClosestRow) {1107return null;1108} else {1109return earliestAfterItem;1110}1111};111211131114/**1115* Private helper for getHoverNextItem().1116* Given an item and a target determine the vertical distance from the item's1117* center to the target.1118* @param {Element} item The item to measure the distance from.1119* @param {goog.math.Coordinate} target The (x,y) coordinate of the target1120* to measure the distance to.1121* @return {number} The vertical distance between the center of the item and1122* the target.1123* @private1124*/1125goog.fx.DragListGroup.verticalDistanceFromItem_ = function(item, target) {1126var itemBounds = item.dlgBounds_;1127var itemCenterY = itemBounds.top + (itemBounds.height - 1) / 2;1128return Math.abs(target.y - itemCenterY);1129};113011311132/**1133* Private helper for getHoverNextItem_().1134* Given the bounds of an item, computes the item's bottom y-value.1135* @param {goog.math.Rect} itemBounds The bounds of the item.1136* @return {number} The item's bottom y-value.1137* @private1138*/1139goog.fx.DragListGroup.getBottomBound_ = function(itemBounds) {1140return itemBounds.top + itemBounds.height - 1;1141};114211431144/**1145* Private helper for getHoverNextItem_().1146* Given the bounds of an item, computes the item's right x-value.1147* @param {goog.math.Rect} itemBounds The bounds of the item.1148* @return {number} The item's right x-value.1149* @private1150*/1151goog.fx.DragListGroup.getRightBound_ = function(itemBounds) {1152return itemBounds.left + itemBounds.width - 1;1153};115411551156/**1157* Private helper for getHoverNextItem_().1158* Given the bounds of an item, computes the item's left x-value.1159* @param {goog.math.Rect} itemBounds The bounds of the item.1160* @return {number} The item's left x-value.1161* @private1162*/1163goog.fx.DragListGroup.getLeftBound_ = function(itemBounds) {1164return itemBounds.left || 0;1165};116611671168/**1169* Private helper for getHoverNextItem_().1170* @param {number} a Number to compare.1171* @param {number} b Number to compare.1172* @return {boolean} Whether a is less than b.1173* @private1174*/1175goog.fx.DragListGroup.isLessThan_ = function(a, b) {1176return a < b;1177};117811791180/**1181* Private helper for getHoverNextItem_().1182* @param {number} a Number to compare.1183* @param {number} b Number to compare.1184* @return {boolean} Whether a is greater than b.1185* @private1186*/1187goog.fx.DragListGroup.isGreaterThan_ = function(a, b) {1188return a > b;1189};119011911192/**1193* Inserts the current drag item to the appropriate location in the drag list1194* that we're hovering over (if the current drag item is not already there).1195*1196* @param {Element} hoverList The drag list we're hovering over.1197* @param {Element} hoverNextItem The next item in the hover drag list.1198* @private1199*/1200goog.fx.DragListGroup.prototype.insertCurrDragItem_ = function(1201hoverList, hoverNextItem) {1202if (this.currDragItem_.parentNode != hoverList ||1203goog.dom.getNextElementSibling(this.currDragItem_) != hoverNextItem) {1204// The current drag item is not in the correct location, so we move it.1205// Note: hoverNextItem may be null, but insertBefore() still works.1206hoverList.insertBefore(this.currDragItem_, hoverNextItem);1207}1208};1209121012111212/**1213* The event object dispatched by DragListGroup.1214* The fields draggerElCenter, hoverList, and hoverNextItem are only available1215* for the BEFOREDRAGMOVE and DRAGMOVE events.1216*1217* @param {goog.fx.DragListGroup.EventType} type1218* @param {goog.fx.DragListGroup} dragListGroup A reference to the associated1219* DragListGroup object.1220* @param {goog.events.BrowserEvent|goog.fx.DragEvent} event The event fired1221* by the browser or fired by the dragger.1222* @param {Element} currDragItem The current drag item being moved.1223* @param {Element} draggerEl The clone of the current drag item that's actually1224* being dragged around.1225* @param {goog.fx.Dragger} dragger The dragger object.1226* @param {goog.math.Coordinate=} opt_draggerElCenter The current center1227* position of the draggerEl.1228* @param {Element=} opt_hoverList The current drag list that's being hovered1229* over, or null if the center of draggerEl is outside of any drag lists.1230* If not null and the drag action ends right now, then currDragItem will1231* end up in this list.1232* @param {Element=} opt_hoverNextItem The current next item in the hoverList1233* that the draggerEl is hovering over. (I.e. If the drag action ends1234* right now, then this item would become the next item after the new1235* location of currDragItem.) May be null if not applicable or if1236* currDragItem would be added to the end of hoverList.1237* @constructor1238* @struct1239* @extends {goog.events.Event}1240*/1241goog.fx.DragListGroupEvent = function(1242type, dragListGroup, event, currDragItem, draggerEl, dragger,1243opt_draggerElCenter, opt_hoverList, opt_hoverNextItem) {1244goog.events.Event.call(this, type);12451246/**1247* A reference to the associated DragListGroup object.1248* @type {goog.fx.DragListGroup}1249*/1250this.dragListGroup = dragListGroup;12511252/**1253* The event fired by the browser or fired by the dragger.1254* @type {goog.events.BrowserEvent|goog.fx.DragEvent}1255*/1256this.event = event;12571258/**1259* The current drag item being move.1260* @type {Element}1261*/1262this.currDragItem = currDragItem;12631264/**1265* The clone of the current drag item that's actually being dragged around.1266* @type {Element}1267*/1268this.draggerEl = draggerEl;12691270/**1271* The dragger object.1272* @type {goog.fx.Dragger}1273*/1274this.dragger = dragger;12751276/**1277* The current center position of the draggerEl.1278* @type {goog.math.Coordinate|undefined}1279*/1280this.draggerElCenter = opt_draggerElCenter;12811282/**1283* The current drag list that's being hovered over, or null if the center of1284* draggerEl is outside of any drag lists. (I.e. If not null and the drag1285* action ends right now, then currDragItem will end up in this list.)1286* @type {Element|undefined}1287*/1288this.hoverList = opt_hoverList;12891290/**1291* The current next item in the hoverList that the draggerEl is hovering over.1292* (I.e. If the drag action ends right now, then this item would become the1293* next item after the new location of currDragItem.) May be null if not1294* applicable or if currDragItem would be added to the end of hoverList.1295* @type {Element|undefined}1296*/1297this.hoverNextItem = opt_hoverNextItem;1298};1299goog.inherits(goog.fx.DragListGroupEvent, goog.events.Event);130013011302