abstractdragdrop.js 48 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575
  1. // Copyright 2006 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. /**
  15. * @fileoverview Abstract Base Class for Drag and Drop.
  16. *
  17. * Provides functionality for implementing drag and drop classes. Also provides
  18. * support classes and events.
  19. *
  20. * @author eae@google.com (Emil A Eklund)
  21. */
  22. goog.provide('goog.fx.AbstractDragDrop');
  23. goog.provide('goog.fx.AbstractDragDrop.EventType');
  24. goog.provide('goog.fx.DragDropEvent');
  25. goog.provide('goog.fx.DragDropItem');
  26. goog.require('goog.array');
  27. goog.require('goog.asserts');
  28. goog.require('goog.dom');
  29. goog.require('goog.dom.classlist');
  30. goog.require('goog.events');
  31. goog.require('goog.events.Event');
  32. goog.require('goog.events.EventHandler');
  33. goog.require('goog.events.EventTarget');
  34. goog.require('goog.events.EventType');
  35. goog.require('goog.fx.Dragger');
  36. goog.require('goog.math.Box');
  37. goog.require('goog.math.Coordinate');
  38. goog.require('goog.style');
  39. /**
  40. * Abstract class that provides reusable functionality for implementing drag
  41. * and drop functionality.
  42. *
  43. * This class also allows clients to define their own subtargeting function
  44. * so that drop areas can have finer granularity then a singe element. This is
  45. * accomplished by using a client provided function to map from element and
  46. * coordinates to a subregion id.
  47. *
  48. * This class can also be made aware of scrollable containers that contain
  49. * drop targets by calling addScrollableContainer. This will cause dnd to
  50. * take changing scroll positions into account while a drag is occurring.
  51. *
  52. * @extends {goog.events.EventTarget}
  53. * @constructor
  54. * @struct
  55. */
  56. goog.fx.AbstractDragDrop = function() {
  57. goog.fx.AbstractDragDrop.base(this, 'constructor');
  58. /**
  59. * List of items that makes up the drag source or drop target.
  60. * @protected {Array<goog.fx.DragDropItem>}
  61. * @suppress {underscore|visibility}
  62. */
  63. this.items_ = [];
  64. /**
  65. * List of associated drop targets.
  66. * @private {Array<goog.fx.AbstractDragDrop>}
  67. */
  68. this.targets_ = [];
  69. /**
  70. * Scrollable containers to account for during drag
  71. * @private {Array<goog.fx.ScrollableContainer_>}
  72. */
  73. this.scrollableContainers_ = [];
  74. /**
  75. * Flag indicating if it's a drag source, set by addTarget.
  76. * @private {boolean}
  77. */
  78. this.isSource_ = false;
  79. /**
  80. * Flag indicating if it's a drop target, set when added as target to another
  81. * DragDrop object.
  82. * @private {boolean}
  83. */
  84. this.isTarget_ = false;
  85. /**
  86. * Subtargeting function accepting args:
  87. * (goog.fx.DragDropItem, goog.math.Box, number, number)
  88. * @private {?Function}
  89. */
  90. this.subtargetFunction_;
  91. /**
  92. * Last active subtarget.
  93. * @private {?Object}
  94. */
  95. this.activeSubtarget_;
  96. /**
  97. * Class name to add to source elements being dragged. Set by setDragClass.
  98. * @private {?string}
  99. */
  100. this.dragClass_;
  101. /**
  102. * Class name to add to source elements. Set by setSourceClass.
  103. * @private {?string}
  104. */
  105. this.sourceClass_;
  106. /**
  107. * Class name to add to target elements. Set by setTargetClass.
  108. * @private {?string}
  109. */
  110. this.targetClass_;
  111. /**
  112. * The SCROLL event target used to make drag element follow scrolling.
  113. * @private {?EventTarget}
  114. */
  115. this.scrollTarget_;
  116. /**
  117. * Dummy target, {@see maybeCreateDummyTargetForPosition_}.
  118. * @private {?goog.fx.ActiveDropTarget_}
  119. */
  120. this.dummyTarget_;
  121. /**
  122. * Whether the object has been initialized.
  123. * @private {boolean}
  124. */
  125. this.initialized_ = false;
  126. /** @private {?Element} */
  127. this.dragEl_;
  128. /** @private {?Array<!goog.fx.ActiveDropTarget_>} */
  129. this.targetList_;
  130. /** @private {?goog.math.Box} */
  131. this.targetBox_;
  132. /** @private {?goog.fx.ActiveDropTarget_} */
  133. this.activeTarget_;
  134. /** @private {?goog.fx.DragDropItem} */
  135. this.dragItem_;
  136. /** @private {?goog.fx.Dragger} */
  137. this.dragger_;
  138. };
  139. goog.inherits(goog.fx.AbstractDragDrop, goog.events.EventTarget);
  140. /**
  141. * Minimum size (in pixels) for a dummy target. If the box for the target is
  142. * less than the specified size it's not created.
  143. * @type {number}
  144. * @private
  145. */
  146. goog.fx.AbstractDragDrop.DUMMY_TARGET_MIN_SIZE_ = 10;
  147. /**
  148. * Constants for event names
  149. * @const
  150. */
  151. goog.fx.AbstractDragDrop.EventType = {
  152. DRAGOVER: 'dragover',
  153. DRAGOUT: 'dragout',
  154. DRAG: 'drag',
  155. DROP: 'drop',
  156. DRAGSTART: 'dragstart',
  157. DRAGEND: 'dragend'
  158. };
  159. /**
  160. * Constant for distance threshold, in pixels, an element has to be moved to
  161. * initiate a drag operation.
  162. * @type {number}
  163. */
  164. goog.fx.AbstractDragDrop.initDragDistanceThreshold = 5;
  165. /**
  166. * Set class to add to source elements being dragged.
  167. *
  168. * @param {string} className Class to be added. Must be a single, valid
  169. * classname.
  170. */
  171. goog.fx.AbstractDragDrop.prototype.setDragClass = function(className) {
  172. this.dragClass_ = className;
  173. };
  174. /**
  175. * Set class to add to source elements.
  176. *
  177. * @param {string} className Class to be added. Must be a single, valid
  178. * classname.
  179. */
  180. goog.fx.AbstractDragDrop.prototype.setSourceClass = function(className) {
  181. this.sourceClass_ = className;
  182. };
  183. /**
  184. * Set class to add to target elements.
  185. *
  186. * @param {string} className Class to be added. Must be a single, valid
  187. * classname.
  188. */
  189. goog.fx.AbstractDragDrop.prototype.setTargetClass = function(className) {
  190. this.targetClass_ = className;
  191. };
  192. /**
  193. * Whether the control has been initialized.
  194. *
  195. * @return {boolean} True if it's been initialized.
  196. */
  197. goog.fx.AbstractDragDrop.prototype.isInitialized = function() {
  198. return this.initialized_;
  199. };
  200. /**
  201. * Add item to drag object.
  202. *
  203. * @param {Element|string} element Dom Node, or string representation of node
  204. * id, to be used as drag source/drop target.
  205. * @throws Error Thrown if called on instance of abstract class
  206. */
  207. goog.fx.AbstractDragDrop.prototype.addItem = goog.abstractMethod;
  208. /**
  209. * Associate drop target with drag element.
  210. *
  211. * @param {goog.fx.AbstractDragDrop} target Target to add.
  212. */
  213. goog.fx.AbstractDragDrop.prototype.addTarget = function(target) {
  214. this.targets_.push(target);
  215. target.isTarget_ = true;
  216. this.isSource_ = true;
  217. };
  218. /**
  219. * Removes the specified target from the list of drop targets.
  220. *
  221. * @param {!goog.fx.AbstractDragDrop} target Target to remove.
  222. */
  223. goog.fx.AbstractDragDrop.prototype.removeTarget = function(target) {
  224. goog.array.remove(this.targets_, target);
  225. if (this.activeTarget_ && this.activeTarget_.target_ == target) {
  226. this.activeTarget_ = null;
  227. }
  228. this.recalculateDragTargets();
  229. };
  230. /**
  231. * Sets the SCROLL event target to make drag element follow scrolling.
  232. *
  233. * @param {EventTarget} scrollTarget The element that dispatches SCROLL events.
  234. */
  235. goog.fx.AbstractDragDrop.prototype.setScrollTarget = function(scrollTarget) {
  236. this.scrollTarget_ = scrollTarget;
  237. };
  238. /**
  239. * Initialize drag and drop functionality for sources/targets already added.
  240. * Sources/targets added after init has been called will initialize themselves
  241. * one by one.
  242. */
  243. goog.fx.AbstractDragDrop.prototype.init = function() {
  244. if (this.initialized_) {
  245. return;
  246. }
  247. for (var item, i = 0; item = this.items_[i]; i++) {
  248. this.initItem(item);
  249. }
  250. this.initialized_ = true;
  251. };
  252. /**
  253. * Initializes a single item.
  254. *
  255. * @param {goog.fx.DragDropItem} item Item to initialize.
  256. * @protected
  257. */
  258. goog.fx.AbstractDragDrop.prototype.initItem = function(item) {
  259. if (this.isSource_) {
  260. goog.events.listen(
  261. item.element, goog.events.EventType.MOUSEDOWN, item.mouseDown_, false,
  262. item);
  263. if (this.sourceClass_) {
  264. goog.dom.classlist.add(
  265. goog.asserts.assert(item.element), this.sourceClass_);
  266. }
  267. }
  268. if (this.isTarget_ && this.targetClass_) {
  269. goog.dom.classlist.add(
  270. goog.asserts.assert(item.element), this.targetClass_);
  271. }
  272. };
  273. /**
  274. * Called when removing an item. Removes event listeners and classes.
  275. *
  276. * @param {goog.fx.DragDropItem} item Item to dispose.
  277. * @protected
  278. */
  279. goog.fx.AbstractDragDrop.prototype.disposeItem = function(item) {
  280. if (this.isSource_) {
  281. goog.events.unlisten(
  282. item.element, goog.events.EventType.MOUSEDOWN, item.mouseDown_, false,
  283. item);
  284. if (this.sourceClass_) {
  285. goog.dom.classlist.remove(
  286. goog.asserts.assert(item.element), this.sourceClass_);
  287. }
  288. }
  289. if (this.isTarget_ && this.targetClass_) {
  290. goog.dom.classlist.remove(
  291. goog.asserts.assert(item.element), this.targetClass_);
  292. }
  293. item.dispose();
  294. };
  295. /**
  296. * Removes all items.
  297. */
  298. goog.fx.AbstractDragDrop.prototype.removeItems = function() {
  299. for (var item, i = 0; item = this.items_[i]; i++) {
  300. this.disposeItem(item);
  301. }
  302. this.items_.length = 0;
  303. };
  304. /**
  305. * Starts a drag event for an item if the mouse button stays pressed and the
  306. * cursor moves a few pixels. Allows dragging of items without first having to
  307. * register them with addItem.
  308. *
  309. * @param {goog.events.BrowserEvent} event Mouse down event.
  310. * @param {goog.fx.DragDropItem} item Item that's being dragged.
  311. */
  312. goog.fx.AbstractDragDrop.prototype.maybeStartDrag = function(event, item) {
  313. item.maybeStartDrag_(event, item.element);
  314. };
  315. /**
  316. * Event handler that's used to start drag.
  317. *
  318. * @param {goog.events.BrowserEvent} event Mouse move event.
  319. * @param {goog.fx.DragDropItem} item Item that's being dragged.
  320. */
  321. goog.fx.AbstractDragDrop.prototype.startDrag = function(event, item) {
  322. // Prevent a new drag operation from being started if another one is already
  323. // in progress (could happen if the mouse was released outside of the
  324. // document).
  325. if (this.dragItem_) {
  326. return;
  327. }
  328. this.dragItem_ = item;
  329. // Dispatch DRAGSTART event
  330. var dragStartEvent = new goog.fx.DragDropEvent(
  331. goog.fx.AbstractDragDrop.EventType.DRAGSTART, this, this.dragItem_,
  332. undefined, // opt_target
  333. undefined, // opt_targetItem
  334. undefined, // opt_targetElement
  335. undefined, // opt_clientX
  336. undefined, // opt_clientY
  337. undefined, // opt_x
  338. undefined, // opt_y
  339. undefined, // opt_subtarget
  340. event);
  341. if (this.dispatchEvent(dragStartEvent) == false) {
  342. this.dragItem_ = null;
  343. return;
  344. }
  345. // Get the source element and create a drag element for it.
  346. var el = item.getCurrentDragElement();
  347. this.dragEl_ = this.createDragElement(el);
  348. var doc = goog.dom.getOwnerDocument(el);
  349. doc.body.appendChild(this.dragEl_);
  350. this.dragger_ = this.createDraggerFor(el, this.dragEl_, event);
  351. this.dragger_.setScrollTarget(this.scrollTarget_);
  352. goog.events.listen(
  353. this.dragger_, goog.fx.Dragger.EventType.DRAG, this.moveDrag_, false,
  354. this);
  355. goog.events.listen(
  356. this.dragger_, goog.fx.Dragger.EventType.END, this.endDrag, false, this);
  357. // IE may issue a 'selectstart' event when dragging over an iframe even when
  358. // default mousemove behavior is suppressed. If the default selectstart
  359. // behavior is not suppressed, elements dragged over will show as selected.
  360. goog.events.listen(
  361. doc.body, goog.events.EventType.SELECTSTART, this.suppressSelect_);
  362. this.recalculateDragTargets();
  363. this.recalculateScrollableContainers();
  364. this.activeTarget_ = null;
  365. this.initScrollableContainerListeners_();
  366. this.dragger_.startDrag(event);
  367. event.preventDefault();
  368. };
  369. /**
  370. * Recalculates the geometry of this source's drag targets. Call this
  371. * if the position or visibility of a drag target has changed during
  372. * a drag, or if targets are added or removed.
  373. *
  374. * TODO(user): this is an expensive operation; more efficient APIs
  375. * may be necessary.
  376. */
  377. goog.fx.AbstractDragDrop.prototype.recalculateDragTargets = function() {
  378. this.targetList_ = [];
  379. for (var target, i = 0; target = this.targets_[i]; i++) {
  380. for (var itm, j = 0; itm = target.items_[j]; j++) {
  381. this.addDragTarget_(target, itm);
  382. }
  383. }
  384. if (!this.targetBox_) {
  385. this.targetBox_ = new goog.math.Box(0, 0, 0, 0);
  386. }
  387. };
  388. /**
  389. * Recalculates the current scroll positions of scrollable containers and
  390. * allocates targets. Call this if the position of a container changed or if
  391. * targets are added or removed.
  392. */
  393. goog.fx.AbstractDragDrop.prototype.recalculateScrollableContainers =
  394. function() {
  395. var container, i, j, target;
  396. for (i = 0; container = this.scrollableContainers_[i]; i++) {
  397. container.containedTargets_ = [];
  398. container.savedScrollLeft_ = container.element_.scrollLeft;
  399. container.savedScrollTop_ = container.element_.scrollTop;
  400. var pos = goog.style.getPageOffset(container.element_);
  401. var size = goog.style.getSize(container.element_);
  402. container.box_ = new goog.math.Box(
  403. pos.y, pos.x + size.width, pos.y + size.height, pos.x);
  404. }
  405. for (i = 0; target = this.targetList_[i]; i++) {
  406. for (j = 0; container = this.scrollableContainers_[j]; j++) {
  407. if (goog.dom.contains(container.element_, target.element_)) {
  408. container.containedTargets_.push(target);
  409. target.scrollableContainer_ = container;
  410. }
  411. }
  412. }
  413. };
  414. /**
  415. * Creates the Dragger for the drag element.
  416. * @param {Element} sourceEl Drag source element.
  417. * @param {Element} el the element created by createDragElement().
  418. * @param {goog.events.BrowserEvent} event Mouse down event for start of drag.
  419. * @return {!goog.fx.Dragger} The new Dragger.
  420. * @protected
  421. */
  422. goog.fx.AbstractDragDrop.prototype.createDraggerFor = function(
  423. sourceEl, el, event) {
  424. // Position the drag element.
  425. var pos = this.getDragElementPosition(sourceEl, el, event);
  426. el.style.position = 'absolute';
  427. el.style.left = pos.x + 'px';
  428. el.style.top = pos.y + 'px';
  429. return new goog.fx.Dragger(el);
  430. };
  431. /**
  432. * Event handler that's used to stop drag. Fires a drop event if over a valid
  433. * target.
  434. *
  435. * @param {goog.fx.DragEvent} event Drag event.
  436. */
  437. goog.fx.AbstractDragDrop.prototype.endDrag = function(event) {
  438. var activeTarget = event.dragCanceled ? null : this.activeTarget_;
  439. if (activeTarget && activeTarget.target_) {
  440. var clientX = event.clientX;
  441. var clientY = event.clientY;
  442. var scroll = this.getScrollPos();
  443. var x = clientX + scroll.x;
  444. var y = clientY + scroll.y;
  445. var subtarget;
  446. // If a subtargeting function is enabled get the current subtarget
  447. if (this.subtargetFunction_) {
  448. subtarget =
  449. this.subtargetFunction_(activeTarget.item_, activeTarget.box_, x, y);
  450. }
  451. var dragEvent = new goog.fx.DragDropEvent(
  452. goog.fx.AbstractDragDrop.EventType.DRAG, this, this.dragItem_,
  453. activeTarget.target_, activeTarget.item_, activeTarget.element_,
  454. clientX, clientY, x, y);
  455. this.dispatchEvent(dragEvent);
  456. var dropEvent = new goog.fx.DragDropEvent(
  457. goog.fx.AbstractDragDrop.EventType.DROP, this, this.dragItem_,
  458. activeTarget.target_, activeTarget.item_, activeTarget.element_,
  459. clientX, clientY, x, y, subtarget, event.browserEvent);
  460. activeTarget.target_.dispatchEvent(dropEvent);
  461. }
  462. var dragEndEvent = new goog.fx.DragDropEvent(
  463. goog.fx.AbstractDragDrop.EventType.DRAGEND, this, this.dragItem_,
  464. activeTarget ? activeTarget.target_ : undefined,
  465. activeTarget ? activeTarget.item_ : undefined,
  466. activeTarget ? activeTarget.element_ : undefined);
  467. this.dispatchEvent(dragEndEvent);
  468. goog.events.unlisten(
  469. this.dragger_, goog.fx.Dragger.EventType.DRAG, this.moveDrag_, false,
  470. this);
  471. goog.events.unlisten(
  472. this.dragger_, goog.fx.Dragger.EventType.END, this.endDrag, false, this);
  473. var doc = goog.dom.getOwnerDocument(this.dragItem_.getCurrentDragElement());
  474. goog.events.unlisten(
  475. doc.body, goog.events.EventType.SELECTSTART, this.suppressSelect_);
  476. this.afterEndDrag(this.activeTarget_ ? this.activeTarget_.item_ : null);
  477. };
  478. /**
  479. * Called after a drag operation has finished.
  480. *
  481. * @param {goog.fx.DragDropItem=} opt_dropTarget Target for successful drop.
  482. * @protected
  483. */
  484. goog.fx.AbstractDragDrop.prototype.afterEndDrag = function(opt_dropTarget) {
  485. this.disposeDrag();
  486. };
  487. /**
  488. * Called once a drag operation has finished. Removes event listeners and
  489. * elements.
  490. *
  491. * @protected
  492. */
  493. goog.fx.AbstractDragDrop.prototype.disposeDrag = function() {
  494. this.disposeScrollableContainerListeners_();
  495. this.dragger_.dispose();
  496. goog.dom.removeNode(this.dragEl_);
  497. delete this.dragItem_;
  498. delete this.dragEl_;
  499. delete this.dragger_;
  500. delete this.targetList_;
  501. delete this.activeTarget_;
  502. };
  503. /**
  504. * Event handler for drag events. Determines the active drop target, if any, and
  505. * fires dragover and dragout events appropriately.
  506. *
  507. * @param {goog.fx.DragEvent} event Drag event.
  508. * @private
  509. */
  510. goog.fx.AbstractDragDrop.prototype.moveDrag_ = function(event) {
  511. var position = this.getEventPosition(event);
  512. var x = position.x;
  513. var y = position.y;
  514. var activeTarget = this.activeTarget_;
  515. this.dispatchEvent(
  516. new goog.fx.DragDropEvent(
  517. goog.fx.AbstractDragDrop.EventType.DRAG, this, this.dragItem_,
  518. activeTarget ? activeTarget.target_ : undefined,
  519. activeTarget ? activeTarget.item_ : undefined,
  520. activeTarget ? activeTarget.element_ : undefined, event.clientX,
  521. event.clientY, x, y));
  522. // Check if we're still inside the bounds of the active target, if not fire
  523. // a dragout event and proceed to find a new target.
  524. var subtarget;
  525. if (activeTarget) {
  526. // If a subtargeting function is enabled get the current subtarget
  527. if (this.subtargetFunction_ && activeTarget.target_) {
  528. subtarget =
  529. this.subtargetFunction_(activeTarget.item_, activeTarget.box_, x, y);
  530. }
  531. if (activeTarget.box_.contains(position) &&
  532. subtarget == this.activeSubtarget_) {
  533. return;
  534. }
  535. if (activeTarget.target_) {
  536. var sourceDragOutEvent = new goog.fx.DragDropEvent(
  537. goog.fx.AbstractDragDrop.EventType.DRAGOUT, this, this.dragItem_,
  538. activeTarget.target_, activeTarget.item_, activeTarget.element_);
  539. this.dispatchEvent(sourceDragOutEvent);
  540. // The event should be dispatched the by target DragDrop so that the
  541. // target DragDrop can manage these events without having to know what
  542. // sources this is a target for.
  543. var targetDragOutEvent = new goog.fx.DragDropEvent(
  544. goog.fx.AbstractDragDrop.EventType.DRAGOUT, this, this.dragItem_,
  545. activeTarget.target_, activeTarget.item_, activeTarget.element_,
  546. undefined, undefined, undefined, undefined, this.activeSubtarget_);
  547. activeTarget.target_.dispatchEvent(targetDragOutEvent);
  548. }
  549. this.activeSubtarget_ = subtarget;
  550. this.activeTarget_ = null;
  551. }
  552. // Check if inside target box
  553. if (this.targetBox_.contains(position)) {
  554. // Search for target and fire a dragover event if found
  555. activeTarget = this.activeTarget_ = this.getTargetFromPosition_(position);
  556. if (activeTarget && activeTarget.target_) {
  557. // If a subtargeting function is enabled get the current subtarget
  558. if (this.subtargetFunction_) {
  559. subtarget = this.subtargetFunction_(
  560. activeTarget.item_, activeTarget.box_, x, y);
  561. }
  562. var sourceDragOverEvent = new goog.fx.DragDropEvent(
  563. goog.fx.AbstractDragDrop.EventType.DRAGOVER, this, this.dragItem_,
  564. activeTarget.target_, activeTarget.item_, activeTarget.element_);
  565. sourceDragOverEvent.subtarget = subtarget;
  566. this.dispatchEvent(sourceDragOverEvent);
  567. // The event should be dispatched by the target DragDrop so that the
  568. // target DragDrop can manage these events without having to know what
  569. // sources this is a target for.
  570. var targetDragOverEvent = new goog.fx.DragDropEvent(
  571. goog.fx.AbstractDragDrop.EventType.DRAGOVER, this, this.dragItem_,
  572. activeTarget.target_, activeTarget.item_, activeTarget.element_,
  573. event.clientX, event.clientY, undefined, undefined, subtarget);
  574. activeTarget.target_.dispatchEvent(targetDragOverEvent);
  575. } else if (!activeTarget) {
  576. // If no target was found create a dummy one so we won't have to iterate
  577. // over all possible targets for every move event.
  578. this.activeTarget_ = this.maybeCreateDummyTargetForPosition_(x, y);
  579. }
  580. }
  581. };
  582. /**
  583. * Event handler for suppressing selectstart events. Selecting should be
  584. * disabled while dragging.
  585. *
  586. * @param {goog.events.Event} event The selectstart event to suppress.
  587. * @return {boolean} Whether to perform default behavior.
  588. * @private
  589. */
  590. goog.fx.AbstractDragDrop.prototype.suppressSelect_ = function(event) {
  591. return false;
  592. };
  593. /**
  594. * Sets up listeners for the scrollable containers that keep track of their
  595. * scroll positions.
  596. * @private
  597. */
  598. goog.fx.AbstractDragDrop.prototype.initScrollableContainerListeners_ =
  599. function() {
  600. var container, i;
  601. for (i = 0; container = this.scrollableContainers_[i]; i++) {
  602. goog.events.listen(
  603. container.element_, goog.events.EventType.SCROLL,
  604. this.containerScrollHandler_, false, this);
  605. }
  606. };
  607. /**
  608. * Cleans up the scrollable container listeners.
  609. * @private
  610. */
  611. goog.fx.AbstractDragDrop.prototype.disposeScrollableContainerListeners_ =
  612. function() {
  613. for (var i = 0, container; container = this.scrollableContainers_[i]; i++) {
  614. goog.events.unlisten(
  615. container.element_, 'scroll', this.containerScrollHandler_, false,
  616. this);
  617. container.containedTargets_ = [];
  618. }
  619. };
  620. /**
  621. * Makes drag and drop aware of a target container that could scroll mid drag.
  622. * @param {Element} element The scroll container.
  623. */
  624. goog.fx.AbstractDragDrop.prototype.addScrollableContainer = function(element) {
  625. this.scrollableContainers_.push(new goog.fx.ScrollableContainer_(element));
  626. };
  627. /**
  628. * Removes all scrollable containers.
  629. */
  630. goog.fx.AbstractDragDrop.prototype.removeAllScrollableContainers = function() {
  631. this.disposeScrollableContainerListeners_();
  632. this.scrollableContainers_ = [];
  633. };
  634. /**
  635. * Event handler for containers scrolling.
  636. * @param {goog.events.BrowserEvent} e The event.
  637. * @suppress {visibility} TODO(martone): update dependent projects.
  638. * @private
  639. */
  640. goog.fx.AbstractDragDrop.prototype.containerScrollHandler_ = function(e) {
  641. for (var i = 0, container; container = this.scrollableContainers_[i]; i++) {
  642. if (e.target == container.element_) {
  643. var deltaTop = container.savedScrollTop_ - container.element_.scrollTop;
  644. var deltaLeft =
  645. container.savedScrollLeft_ - container.element_.scrollLeft;
  646. container.savedScrollTop_ = container.element_.scrollTop;
  647. container.savedScrollLeft_ = container.element_.scrollLeft;
  648. // When the container scrolls, it's possible that one of the targets will
  649. // move to the region contained by the dummy target. Since we don't know
  650. // which sides (if any) of the dummy target are defined by targets
  651. // contained by this container, we are conservative and just shrink it.
  652. if (this.dummyTarget_ && this.activeTarget_ == this.dummyTarget_) {
  653. if (deltaTop > 0) {
  654. this.dummyTarget_.box_.top += deltaTop;
  655. } else {
  656. this.dummyTarget_.box_.bottom += deltaTop;
  657. }
  658. if (deltaLeft > 0) {
  659. this.dummyTarget_.box_.left += deltaLeft;
  660. } else {
  661. this.dummyTarget_.box_.right += deltaLeft;
  662. }
  663. }
  664. for (var j = 0, target; target = container.containedTargets_[j]; j++) {
  665. var box = target.box_;
  666. box.top += deltaTop;
  667. box.left += deltaLeft;
  668. box.bottom += deltaTop;
  669. box.right += deltaLeft;
  670. this.calculateTargetBox_(box);
  671. }
  672. }
  673. }
  674. this.dragger_.onScroll_(e);
  675. };
  676. /**
  677. * Set a function that provides subtargets. A subtargeting function
  678. * returns an arbitrary identifier for each subtarget of an element.
  679. * DnD code will generate additional drag over / out events when
  680. * switching from subtarget to subtarget. This is useful for instance
  681. * if you are interested if you are on the top half or the bottom half
  682. * of the element.
  683. * The provided function will be given the DragDropItem, box, x, y
  684. * box is the current window coordinates occupied by element
  685. * x, y is the mouse position in window coordinates
  686. *
  687. * @param {Function} f The new subtarget function.
  688. */
  689. goog.fx.AbstractDragDrop.prototype.setSubtargetFunction = function(f) {
  690. this.subtargetFunction_ = f;
  691. };
  692. /**
  693. * Creates an element for the item being dragged.
  694. *
  695. * @param {Element} sourceEl Drag source element.
  696. * @return {Element} The new drag element.
  697. */
  698. goog.fx.AbstractDragDrop.prototype.createDragElement = function(sourceEl) {
  699. var dragEl = this.createDragElementInternal(sourceEl);
  700. goog.asserts.assert(dragEl);
  701. if (this.dragClass_) {
  702. goog.dom.classlist.add(dragEl, this.dragClass_);
  703. }
  704. return dragEl;
  705. };
  706. /**
  707. * Returns the position for the drag element.
  708. *
  709. * @param {Element} el Drag source element.
  710. * @param {Element} dragEl The dragged element created by createDragElement().
  711. * @param {goog.events.BrowserEvent} event Mouse down event for start of drag.
  712. * @return {!goog.math.Coordinate} The position for the drag element.
  713. */
  714. goog.fx.AbstractDragDrop.prototype.getDragElementPosition = function(
  715. el, dragEl, event) {
  716. var pos = goog.style.getPageOffset(el);
  717. // Subtract margin from drag element position twice, once to adjust the
  718. // position given by the original node and once for the drag node.
  719. var marginBox = goog.style.getMarginBox(el);
  720. pos.x -= (marginBox.left || 0) * 2;
  721. pos.y -= (marginBox.top || 0) * 2;
  722. return pos;
  723. };
  724. /**
  725. * Returns the dragger object.
  726. *
  727. * @return {goog.fx.Dragger} The dragger object used by this drag and drop
  728. * instance.
  729. */
  730. goog.fx.AbstractDragDrop.prototype.getDragger = function() {
  731. return this.dragger_;
  732. };
  733. /**
  734. * Creates copy of node being dragged.
  735. *
  736. * @param {Element} sourceEl Element to copy.
  737. * @return {!Element} The clone of {@code sourceEl}.
  738. * @deprecated Use goog.fx.Dragger.cloneNode().
  739. * @private
  740. */
  741. goog.fx.AbstractDragDrop.prototype.cloneNode_ = function(sourceEl) {
  742. return goog.fx.Dragger.cloneNode(sourceEl);
  743. };
  744. /**
  745. * Generates an element to follow the cursor during dragging, given a drag
  746. * source element. The default behavior is simply to clone the source element,
  747. * but this may be overridden in subclasses. This method is called by
  748. * {@code createDragElement()} before the drag class is added.
  749. *
  750. * @param {Element} sourceEl Drag source element.
  751. * @return {!Element} The new drag element.
  752. * @protected
  753. * @suppress {deprecated}
  754. */
  755. goog.fx.AbstractDragDrop.prototype.createDragElementInternal = function(
  756. sourceEl) {
  757. return this.cloneNode_(sourceEl);
  758. };
  759. /**
  760. * Add possible drop target for current drag operation.
  761. *
  762. * @param {goog.fx.AbstractDragDrop} target Drag handler.
  763. * @param {goog.fx.DragDropItem} item Item that's being dragged.
  764. * @private
  765. */
  766. goog.fx.AbstractDragDrop.prototype.addDragTarget_ = function(target, item) {
  767. // Get all the draggable elements and add each one.
  768. var draggableElements = item.getDraggableElements();
  769. for (var i = 0; i < draggableElements.length; i++) {
  770. var draggableElement = draggableElements[i];
  771. // Determine target position and dimension
  772. var box = this.getElementBox(item, draggableElement);
  773. this.targetList_.push(
  774. new goog.fx.ActiveDropTarget_(box, target, item, draggableElement));
  775. this.calculateTargetBox_(box);
  776. }
  777. };
  778. /**
  779. * Calculates the position and dimension of a draggable element.
  780. *
  781. * @param {goog.fx.DragDropItem} item Item that's being dragged.
  782. * @param {Element} element The element to calculate the box.
  783. *
  784. * @return {!goog.math.Box} Box describing the position and dimension
  785. * of element.
  786. * @protected
  787. */
  788. goog.fx.AbstractDragDrop.prototype.getElementBox = function(item, element) {
  789. var pos = goog.style.getPageOffset(element);
  790. var size = goog.style.getSize(element);
  791. return new goog.math.Box(
  792. pos.y, pos.x + size.width, pos.y + size.height, pos.x);
  793. };
  794. /**
  795. * Calculate the outer bounds (the region all targets are inside).
  796. *
  797. * @param {goog.math.Box} box Box describing the position and dimension
  798. * of a drag target.
  799. * @private
  800. */
  801. goog.fx.AbstractDragDrop.prototype.calculateTargetBox_ = function(box) {
  802. if (this.targetList_.length == 1) {
  803. this.targetBox_ =
  804. new goog.math.Box(box.top, box.right, box.bottom, box.left);
  805. } else {
  806. var tb = this.targetBox_;
  807. tb.left = Math.min(box.left, tb.left);
  808. tb.right = Math.max(box.right, tb.right);
  809. tb.top = Math.min(box.top, tb.top);
  810. tb.bottom = Math.max(box.bottom, tb.bottom);
  811. }
  812. };
  813. /**
  814. * Creates a dummy target for the given cursor position. The assumption is to
  815. * create as big dummy target box as possible, the only constraints are:
  816. * - The dummy target box cannot overlap any of real target boxes.
  817. * - The dummy target has to contain a point with current mouse coordinates.
  818. *
  819. * NOTE: For performance reasons the box construction algorithm is kept simple
  820. * and it is not optimal (see example below). Currently it is O(n) in regard to
  821. * the number of real drop target boxes, but its result depends on the order
  822. * of those boxes being processed (the order in which they're added to the
  823. * targetList_ collection).
  824. *
  825. * The algorithm.
  826. * a) Assumptions
  827. * - Mouse pointer is in the bounding box of real target boxes.
  828. * - None of the boxes have negative coordinate values.
  829. * - Mouse pointer is not contained by any of "real target" boxes.
  830. * - For targets inside a scrollable container, the box used is the
  831. * intersection of the scrollable container's box and the target's box.
  832. * This is because the part of the target that extends outside the scrollable
  833. * container should not be used in the clipping calculations.
  834. *
  835. * b) Outline
  836. * - Initialize the fake target to the bounding box of real targets.
  837. * - For each real target box - clip the fake target box so it does not contain
  838. * that target box, but does contain the mouse pointer.
  839. * -- Project the real target box, mouse pointer and fake target box onto
  840. * both axes and calculate the clipping coordinates.
  841. * -- Only one coordinate is used to clip the fake target box to keep the
  842. * fake target as big as possible.
  843. * -- If the projection of the real target box contains the mouse pointer,
  844. * clipping for a given axis is not possible.
  845. * -- If both clippings are possible, the clipping more distant from the
  846. * mouse pointer is selected to keep bigger fake target area.
  847. * - Save the created fake target only if it has a big enough area.
  848. *
  849. *
  850. * c) Example
  851. * <pre>
  852. * Input: Algorithm created box: Maximum box:
  853. * +---------------------+ +---------------------+ +---------------------+
  854. * | B1 | B2 | | B1 B2 | | B1 B2 |
  855. * | | | | +-------------+ | |+-------------------+|
  856. * |---------x-----------| | | | | || ||
  857. * | | | | | | | || ||
  858. * | | | | | | | || ||
  859. * | | | | | | | || ||
  860. * | | | | | | | || ||
  861. * | | | | +-------------+ | |+-------------------+|
  862. * | B4 | B3 | | B4 B3 | | B4 B3 |
  863. * +---------------------+ +---------------------+ +---------------------+
  864. * </pre>
  865. *
  866. * @param {number} x Cursor position on the x-axis.
  867. * @param {number} y Cursor position on the y-axis.
  868. * @return {goog.fx.ActiveDropTarget_} Dummy drop target.
  869. * @private
  870. */
  871. goog.fx.AbstractDragDrop.prototype.maybeCreateDummyTargetForPosition_ =
  872. function(x, y) {
  873. if (!this.dummyTarget_) {
  874. this.dummyTarget_ = new goog.fx.ActiveDropTarget_(this.targetBox_.clone());
  875. }
  876. var fakeTargetBox = this.dummyTarget_.box_;
  877. // Initialize the fake target box to the bounding box of DnD targets.
  878. fakeTargetBox.top = this.targetBox_.top;
  879. fakeTargetBox.right = this.targetBox_.right;
  880. fakeTargetBox.bottom = this.targetBox_.bottom;
  881. fakeTargetBox.left = this.targetBox_.left;
  882. // Clip the fake target based on mouse position and DnD target boxes.
  883. for (var i = 0, target; target = this.targetList_[i]; i++) {
  884. var box = target.box_;
  885. if (target.scrollableContainer_) {
  886. // If the target has a scrollable container, use the intersection of that
  887. // container's box and the target's box.
  888. var scrollBox = target.scrollableContainer_.box_;
  889. box = new goog.math.Box(
  890. Math.max(box.top, scrollBox.top),
  891. Math.min(box.right, scrollBox.right),
  892. Math.min(box.bottom, scrollBox.bottom),
  893. Math.max(box.left, scrollBox.left));
  894. }
  895. // Calculate clipping coordinates for horizontal and vertical axis.
  896. // The clipping coordinate is calculated by projecting fake target box,
  897. // the mouse pointer and DnD target box onto an axis and checking how
  898. // box projections overlap and if the projected DnD target box contains
  899. // mouse pointer. The clipping coordinate cannot be computed and is set to
  900. // a negative value if the projected DnD target contains the mouse pointer.
  901. var horizontalClip = null; // Assume mouse is above or below the DnD box.
  902. if (x >= box.right) { // Mouse is to the right of the DnD box.
  903. // Clip the fake box only if the DnD box overlaps it.
  904. horizontalClip =
  905. box.right > fakeTargetBox.left ? box.right : fakeTargetBox.left;
  906. } else if (x < box.left) { // Mouse is to the left of the DnD box.
  907. // Clip the fake box only if the DnD box overlaps it.
  908. horizontalClip =
  909. box.left < fakeTargetBox.right ? box.left : fakeTargetBox.right;
  910. }
  911. var verticalClip = null;
  912. if (y >= box.bottom) {
  913. verticalClip =
  914. box.bottom > fakeTargetBox.top ? box.bottom : fakeTargetBox.top;
  915. } else if (y < box.top) {
  916. verticalClip =
  917. box.top < fakeTargetBox.bottom ? box.top : fakeTargetBox.bottom;
  918. }
  919. // If both clippings are possible, choose one that gives us larger distance
  920. // to mouse pointer (mark the shorter clipping as impossible, by setting it
  921. // to null).
  922. if (!goog.isNull(horizontalClip) && !goog.isNull(verticalClip)) {
  923. if (Math.abs(horizontalClip - x) > Math.abs(verticalClip - y)) {
  924. verticalClip = null;
  925. } else {
  926. horizontalClip = null;
  927. }
  928. }
  929. // Clip none or one of fake target box sides (at most one clipping
  930. // coordinate can be active).
  931. if (!goog.isNull(horizontalClip)) {
  932. if (horizontalClip <= x) {
  933. fakeTargetBox.left = horizontalClip;
  934. } else {
  935. fakeTargetBox.right = horizontalClip;
  936. }
  937. } else if (!goog.isNull(verticalClip)) {
  938. if (verticalClip <= y) {
  939. fakeTargetBox.top = verticalClip;
  940. } else {
  941. fakeTargetBox.bottom = verticalClip;
  942. }
  943. }
  944. }
  945. // Only return the new fake target if it is big enough.
  946. return (fakeTargetBox.right - fakeTargetBox.left) *
  947. (fakeTargetBox.bottom - fakeTargetBox.top) >=
  948. goog.fx.AbstractDragDrop.DUMMY_TARGET_MIN_SIZE_ ?
  949. this.dummyTarget_ :
  950. null;
  951. };
  952. /**
  953. * Returns the target for a given cursor position.
  954. *
  955. * @param {goog.math.Coordinate} position Cursor position.
  956. * @return {goog.fx.ActiveDropTarget_} Target for position or null if no target
  957. * was defined for the given position.
  958. * @private
  959. */
  960. goog.fx.AbstractDragDrop.prototype.getTargetFromPosition_ = function(position) {
  961. for (var target, i = 0; target = this.targetList_[i]; i++) {
  962. if (target.box_.contains(position)) {
  963. if (target.scrollableContainer_) {
  964. // If we have a scrollable container we will need to make sure
  965. // we account for clipping of the scroll area
  966. var box = target.scrollableContainer_.box_;
  967. if (box.contains(position)) {
  968. return target;
  969. }
  970. } else {
  971. return target;
  972. }
  973. }
  974. }
  975. return null;
  976. };
  977. /**
  978. * Checks whatever a given point is inside a given box.
  979. *
  980. * @param {number} x Cursor position on the x-axis.
  981. * @param {number} y Cursor position on the y-axis.
  982. * @param {goog.math.Box} box Box to check position against.
  983. * @return {boolean} Whether the given point is inside {@code box}.
  984. * @protected
  985. * @deprecated Use goog.math.Box.contains.
  986. */
  987. goog.fx.AbstractDragDrop.prototype.isInside = function(x, y, box) {
  988. return x >= box.left && x < box.right && y >= box.top && y < box.bottom;
  989. };
  990. /**
  991. * Gets the scroll distance as a coordinate object, using
  992. * the window of the current drag element's dom.
  993. * @return {!goog.math.Coordinate} Object with scroll offsets 'x' and 'y'.
  994. * @protected
  995. */
  996. goog.fx.AbstractDragDrop.prototype.getScrollPos = function() {
  997. return goog.dom.getDomHelper(this.dragEl_).getDocumentScroll();
  998. };
  999. /**
  1000. * Get the position of a drag event.
  1001. * @param {goog.fx.DragEvent} event Drag event.
  1002. * @return {!goog.math.Coordinate} Position of the event.
  1003. * @protected
  1004. */
  1005. goog.fx.AbstractDragDrop.prototype.getEventPosition = function(event) {
  1006. var scroll = this.getScrollPos();
  1007. return new goog.math.Coordinate(
  1008. event.clientX + scroll.x, event.clientY + scroll.y);
  1009. };
  1010. /** @override */
  1011. goog.fx.AbstractDragDrop.prototype.disposeInternal = function() {
  1012. goog.fx.AbstractDragDrop.base(this, 'disposeInternal');
  1013. this.removeItems();
  1014. };
  1015. /**
  1016. * Object representing a drag and drop event.
  1017. *
  1018. * @param {string} type Event type.
  1019. * @param {goog.fx.AbstractDragDrop} source Source drag drop object.
  1020. * @param {goog.fx.DragDropItem} sourceItem Source item.
  1021. * @param {goog.fx.AbstractDragDrop=} opt_target Target drag drop object.
  1022. * @param {goog.fx.DragDropItem=} opt_targetItem Target item.
  1023. * @param {Element=} opt_targetElement Target element.
  1024. * @param {number=} opt_clientX X-Position relative to the screen.
  1025. * @param {number=} opt_clientY Y-Position relative to the screen.
  1026. * @param {number=} opt_x X-Position relative to the viewport.
  1027. * @param {number=} opt_y Y-Position relative to the viewport.
  1028. * @param {Object=} opt_subtarget The currently active subtarget.
  1029. * @param {goog.events.BrowserEvent=} opt_browserEvent The browser event
  1030. * that caused this dragdrop event.
  1031. * @extends {goog.events.Event}
  1032. * @constructor
  1033. * @struct
  1034. */
  1035. goog.fx.DragDropEvent = function(
  1036. type, source, sourceItem, opt_target, opt_targetItem, opt_targetElement,
  1037. opt_clientX, opt_clientY, opt_x, opt_y, opt_subtarget, opt_browserEvent) {
  1038. // TODO(eae): Get rid of all the optional parameters and have the caller set
  1039. // the fields directly instead.
  1040. goog.fx.DragDropEvent.base(this, 'constructor', type);
  1041. /**
  1042. * Reference to the source goog.fx.AbstractDragDrop object.
  1043. * @type {goog.fx.AbstractDragDrop}
  1044. */
  1045. this.dragSource = source;
  1046. /**
  1047. * Reference to the source goog.fx.DragDropItem object.
  1048. * @type {goog.fx.DragDropItem}
  1049. */
  1050. this.dragSourceItem = sourceItem;
  1051. /**
  1052. * Reference to the target goog.fx.AbstractDragDrop object.
  1053. * @type {goog.fx.AbstractDragDrop|undefined}
  1054. */
  1055. this.dropTarget = opt_target;
  1056. /**
  1057. * Reference to the target goog.fx.DragDropItem object.
  1058. * @type {goog.fx.DragDropItem|undefined}
  1059. */
  1060. this.dropTargetItem = opt_targetItem;
  1061. /**
  1062. * The actual element of the drop target that is the target for this event.
  1063. * @type {Element|undefined}
  1064. */
  1065. this.dropTargetElement = opt_targetElement;
  1066. /**
  1067. * X-Position relative to the screen.
  1068. * @type {number|undefined}
  1069. */
  1070. this.clientX = opt_clientX;
  1071. /**
  1072. * Y-Position relative to the screen.
  1073. * @type {number|undefined}
  1074. */
  1075. this.clientY = opt_clientY;
  1076. /**
  1077. * X-Position relative to the viewport.
  1078. * @type {number|undefined}
  1079. */
  1080. this.viewportX = opt_x;
  1081. /**
  1082. * Y-Position relative to the viewport.
  1083. * @type {number|undefined}
  1084. */
  1085. this.viewportY = opt_y;
  1086. /**
  1087. * The subtarget that is currently active if a subtargeting function
  1088. * is supplied.
  1089. * @type {Object|undefined}
  1090. */
  1091. this.subtarget = opt_subtarget;
  1092. /**
  1093. * The browser event that caused this dragdrop event.
  1094. * @const
  1095. */
  1096. this.browserEvent = opt_browserEvent;
  1097. };
  1098. goog.inherits(goog.fx.DragDropEvent, goog.events.Event);
  1099. /**
  1100. * Class representing a source or target element for drag and drop operations.
  1101. *
  1102. * @param {Element|string} element Dom Node, or string representation of node
  1103. * id, to be used as drag source/drop target.
  1104. * @param {Object=} opt_data Data associated with the source/target.
  1105. * @throws Error If no element argument is provided or if the type is invalid
  1106. * @extends {goog.events.EventTarget}
  1107. * @constructor
  1108. * @struct
  1109. */
  1110. goog.fx.DragDropItem = function(element, opt_data) {
  1111. goog.fx.DragDropItem.base(this, 'constructor');
  1112. /**
  1113. * Reference to drag source/target element
  1114. * @type {Element}
  1115. */
  1116. this.element = goog.dom.getElement(element);
  1117. /**
  1118. * Data associated with element.
  1119. * @type {Object|undefined}
  1120. */
  1121. this.data = opt_data;
  1122. /**
  1123. * Drag object the item belongs to.
  1124. * @type {goog.fx.AbstractDragDrop?}
  1125. * @private
  1126. */
  1127. this.parent_ = null;
  1128. /**
  1129. * Event handler for listeners on events that can initiate a drag.
  1130. * @type {!goog.events.EventHandler<!goog.fx.DragDropItem>}
  1131. * @private
  1132. */
  1133. this.eventHandler_ = new goog.events.EventHandler(this);
  1134. this.registerDisposable(this.eventHandler_);
  1135. /**
  1136. * The current element being dragged. This is needed because a DragDropItem
  1137. * can have multiple elements that can be dragged.
  1138. * @private {?Element}
  1139. */
  1140. this.currentDragElement_ = null;
  1141. /** @private {?goog.math.Coordinate} */
  1142. this.startPosition_;
  1143. if (!this.element) {
  1144. throw Error('Invalid argument');
  1145. }
  1146. };
  1147. goog.inherits(goog.fx.DragDropItem, goog.events.EventTarget);
  1148. /**
  1149. * Get the data associated with the source/target.
  1150. * @return {Object|null|undefined} Data associated with the source/target.
  1151. */
  1152. goog.fx.DragDropItem.prototype.getData = function() {
  1153. return this.data;
  1154. };
  1155. /**
  1156. * Gets the element that is actually draggable given that the given target was
  1157. * attempted to be dragged. This should be overriden when the element that was
  1158. * given actually contains many items that can be dragged. From the target, you
  1159. * can determine what element should actually be dragged.
  1160. *
  1161. * @param {Element} target The target that was attempted to be dragged.
  1162. * @return {Element} The element that is draggable given the target. If
  1163. * none are draggable, this will return null.
  1164. */
  1165. goog.fx.DragDropItem.prototype.getDraggableElement = function(target) {
  1166. return target;
  1167. };
  1168. /**
  1169. * Gets the element that is currently being dragged.
  1170. *
  1171. * @return {Element} The element that is currently being dragged.
  1172. */
  1173. goog.fx.DragDropItem.prototype.getCurrentDragElement = function() {
  1174. return this.currentDragElement_;
  1175. };
  1176. /**
  1177. * Gets all the elements of this item that are potentially draggable/
  1178. *
  1179. * @return {!Array<Element>} The draggable elements.
  1180. */
  1181. goog.fx.DragDropItem.prototype.getDraggableElements = function() {
  1182. return [this.element];
  1183. };
  1184. /**
  1185. * Event handler for mouse down.
  1186. *
  1187. * @param {goog.events.BrowserEvent} event Mouse down event.
  1188. * @private
  1189. */
  1190. goog.fx.DragDropItem.prototype.mouseDown_ = function(event) {
  1191. if (!event.isMouseActionButton()) {
  1192. return;
  1193. }
  1194. // Get the draggable element for the target.
  1195. var element = this.getDraggableElement(/** @type {Element} */ (event.target));
  1196. if (element) {
  1197. this.maybeStartDrag_(event, element);
  1198. }
  1199. };
  1200. /**
  1201. * Sets the dragdrop to which this item belongs.
  1202. * @param {goog.fx.AbstractDragDrop} parent The parent dragdrop.
  1203. */
  1204. goog.fx.DragDropItem.prototype.setParent = function(parent) {
  1205. this.parent_ = parent;
  1206. };
  1207. /**
  1208. * Adds mouse move, mouse out and mouse up handlers.
  1209. *
  1210. * @param {goog.events.BrowserEvent} event Mouse down event.
  1211. * @param {Element} element Element.
  1212. * @private
  1213. */
  1214. goog.fx.DragDropItem.prototype.maybeStartDrag_ = function(event, element) {
  1215. var eventType = goog.events.EventType;
  1216. this.eventHandler_
  1217. .listen(element, eventType.MOUSEMOVE, this.mouseMove_, false)
  1218. .listen(element, eventType.MOUSEOUT, this.mouseMove_, false);
  1219. // Capture the MOUSEUP on the document to ensure that we cancel the start
  1220. // drag handlers even if the mouse up occurs on some other element. This can
  1221. // happen for instance when the mouse down changes the geometry of the element
  1222. // clicked on (e.g. through changes in activation styling) such that the mouse
  1223. // up occurs outside the original element.
  1224. var doc = goog.dom.getOwnerDocument(element);
  1225. this.eventHandler_.listen(doc, eventType.MOUSEUP, this.mouseUp_, true);
  1226. this.currentDragElement_ = element;
  1227. this.startPosition_ = new goog.math.Coordinate(event.clientX, event.clientY);
  1228. };
  1229. /**
  1230. * Event handler for mouse move. Starts drag operation if moved more than the
  1231. * threshold value.
  1232. *
  1233. * @param {goog.events.BrowserEvent} event Mouse move or mouse out event.
  1234. * @private
  1235. */
  1236. goog.fx.DragDropItem.prototype.mouseMove_ = function(event) {
  1237. var distance = Math.abs(event.clientX - this.startPosition_.x) +
  1238. Math.abs(event.clientY - this.startPosition_.y);
  1239. // Fire dragStart event if the drag distance exceeds the threshold or if the
  1240. // mouse leave the dragged element.
  1241. // TODO(user): Consider using the goog.fx.Dragger to track the distance
  1242. // even after the mouse leaves the dragged element.
  1243. var currentDragElement = this.currentDragElement_;
  1244. var distanceAboveThreshold =
  1245. distance > goog.fx.AbstractDragDrop.initDragDistanceThreshold;
  1246. var mouseOutOnDragElement = event.type == goog.events.EventType.MOUSEOUT &&
  1247. event.target == currentDragElement;
  1248. if (distanceAboveThreshold || mouseOutOnDragElement) {
  1249. this.eventHandler_.removeAll();
  1250. this.parent_.startDrag(event, this);
  1251. }
  1252. // Prevent text selection while dragging an element.
  1253. event.preventDefault();
  1254. };
  1255. /**
  1256. * Event handler for mouse up. Removes mouse move, mouse out and mouse up event
  1257. * handlers.
  1258. *
  1259. * @param {goog.events.BrowserEvent} event Mouse up event.
  1260. * @private
  1261. */
  1262. goog.fx.DragDropItem.prototype.mouseUp_ = function(event) {
  1263. this.eventHandler_.removeAll();
  1264. delete this.startPosition_;
  1265. this.currentDragElement_ = null;
  1266. };
  1267. /**
  1268. * Class representing an active drop target
  1269. *
  1270. * @param {goog.math.Box} box Box describing the position and dimension of the
  1271. * target item.
  1272. * @param {goog.fx.AbstractDragDrop=} opt_target Target that contains the item
  1273. associated with position.
  1274. * @param {goog.fx.DragDropItem=} opt_item Item associated with position.
  1275. * @param {Element=} opt_element Element of item associated with position.
  1276. * @constructor
  1277. * @struct
  1278. * @private
  1279. */
  1280. goog.fx.ActiveDropTarget_ = function(box, opt_target, opt_item, opt_element) {
  1281. /**
  1282. * Box describing the position and dimension of the target item
  1283. * @type {goog.math.Box}
  1284. * @private
  1285. */
  1286. this.box_ = box;
  1287. /**
  1288. * Target that contains the item associated with position
  1289. * @type {goog.fx.AbstractDragDrop|undefined}
  1290. * @private
  1291. */
  1292. this.target_ = opt_target;
  1293. /**
  1294. * Item associated with position
  1295. * @type {goog.fx.DragDropItem|undefined}
  1296. * @private
  1297. */
  1298. this.item_ = opt_item;
  1299. /**
  1300. * The draggable element of the item associated with position.
  1301. * @type {Element}
  1302. * @private
  1303. */
  1304. this.element_ = opt_element || null;
  1305. /**
  1306. * If this target is in a scrollable container this is it.
  1307. * @private {?goog.fx.ScrollableContainer_}
  1308. */
  1309. this.scrollableContainer_ = null;
  1310. };
  1311. /**
  1312. * Class for representing a scrollable container
  1313. * @param {Element} element the scrollable element.
  1314. * @constructor
  1315. * @private
  1316. */
  1317. goog.fx.ScrollableContainer_ = function(element) {
  1318. /**
  1319. * The targets that lie within this container.
  1320. * @type {Array<goog.fx.ActiveDropTarget_>}
  1321. * @private
  1322. */
  1323. this.containedTargets_ = [];
  1324. /**
  1325. * The element that is this container
  1326. * @type {Element}
  1327. * @private
  1328. */
  1329. this.element_ = element;
  1330. /**
  1331. * The saved scroll left location for calculating deltas.
  1332. * @type {number}
  1333. * @private
  1334. */
  1335. this.savedScrollLeft_ = 0;
  1336. /**
  1337. * The saved scroll top location for calculating deltas.
  1338. * @type {number}
  1339. * @private
  1340. */
  1341. this.savedScrollTop_ = 0;
  1342. /**
  1343. * The space occupied by the container.
  1344. * @type {goog.math.Box}
  1345. * @private
  1346. */
  1347. this.box_ = null;
  1348. };