splitpane.js 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918
  1. // Copyright 2007 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 Class for splitting two areas with draggable control for
  16. * changing size.
  17. *
  18. * The DOM that is created (or that can be decorated) looks like this:
  19. * <div class='goog-splitpane'>
  20. * <div class='goog-splitpane-first-container'></div>
  21. * <div class='goog-splitpane-second-container'></div>
  22. * <div class='goog-splitpane-handle'></div>
  23. * </div>
  24. *
  25. * The content to be split goes in the first and second DIVs, the third one
  26. * is for managing (and styling) the splitter handle.
  27. *
  28. * @see ../demos/splitpane.html
  29. */
  30. goog.provide('goog.ui.SplitPane');
  31. goog.provide('goog.ui.SplitPane.Orientation');
  32. goog.require('goog.asserts');
  33. goog.require('goog.dom');
  34. goog.require('goog.dom.TagName');
  35. goog.require('goog.dom.classlist');
  36. goog.require('goog.events.EventType');
  37. goog.require('goog.fx.Dragger');
  38. goog.require('goog.math.Rect');
  39. goog.require('goog.math.Size');
  40. goog.require('goog.style');
  41. goog.require('goog.ui.Component');
  42. goog.require('goog.userAgent');
  43. /**
  44. * A left/right up/down Container SplitPane.
  45. * Create SplitPane with two goog.ui.Component opjects to split.
  46. * TODO(user): Support minimum splitpane size.
  47. * TODO(user): Allow component change/orientation after init.
  48. * TODO(user): Support hiding either side of handle (plus handle).
  49. * TODO(user): Look at setBorderBoxSize fixes and revist borderwidth code.
  50. *
  51. * @param {goog.ui.Component} firstComponent Left or Top component.
  52. * @param {goog.ui.Component} secondComponent Right or Bottom component.
  53. * @param {goog.ui.SplitPane.Orientation} orientation SplitPane orientation.
  54. * @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper.
  55. * @extends {goog.ui.Component}
  56. * @constructor
  57. */
  58. goog.ui.SplitPane = function(
  59. firstComponent, secondComponent, orientation, opt_domHelper) {
  60. goog.ui.SplitPane.base(this, 'constructor', opt_domHelper);
  61. /**
  62. * The orientation of the containers.
  63. * @type {goog.ui.SplitPane.Orientation}
  64. * @private
  65. */
  66. this.orientation_ = orientation;
  67. /**
  68. * The left/top component.
  69. * @type {goog.ui.Component}
  70. * @private
  71. */
  72. this.firstComponent_ = firstComponent;
  73. this.addChild(firstComponent);
  74. /**
  75. * The right/bottom component.
  76. * @type {goog.ui.Component}
  77. * @private
  78. */
  79. this.secondComponent_ = secondComponent;
  80. this.addChild(secondComponent);
  81. /** @private {Element} */
  82. this.splitpaneHandle_ = null;
  83. };
  84. goog.inherits(goog.ui.SplitPane, goog.ui.Component);
  85. goog.tagUnsealableClass(goog.ui.SplitPane);
  86. /**
  87. * Events.
  88. * @enum {string}
  89. */
  90. goog.ui.SplitPane.EventType = {
  91. /**
  92. * Dispatched after handle drag.
  93. */
  94. HANDLE_DRAG: 'handle_drag',
  95. /**
  96. * Dispatched after handle drag end.
  97. */
  98. HANDLE_DRAG_END: 'handle_drag_end',
  99. /**
  100. * Dispatched after handle snap (double-click splitter).
  101. */
  102. HANDLE_SNAP: 'handle_snap'
  103. };
  104. /**
  105. * CSS class names for splitpane outer container.
  106. * @type {string}
  107. * @private
  108. */
  109. goog.ui.SplitPane.CLASS_NAME_ = goog.getCssName('goog-splitpane');
  110. /**
  111. * CSS class name for first splitpane container.
  112. * @type {string}
  113. * @private
  114. */
  115. goog.ui.SplitPane.FIRST_CONTAINER_CLASS_NAME_ =
  116. goog.getCssName('goog-splitpane-first-container');
  117. /**
  118. * CSS class name for second splitpane container.
  119. * @type {string}
  120. * @private
  121. */
  122. goog.ui.SplitPane.SECOND_CONTAINER_CLASS_NAME_ =
  123. goog.getCssName('goog-splitpane-second-container');
  124. /**
  125. * CSS class name for the splitpane handle.
  126. * @type {string}
  127. * @private
  128. */
  129. goog.ui.SplitPane.HANDLE_CLASS_NAME_ = goog.getCssName('goog-splitpane-handle');
  130. /**
  131. * CSS class name for the splitpane handle in horizontal orientation.
  132. * @type {string}
  133. * @private
  134. */
  135. goog.ui.SplitPane.HANDLE_CLASS_NAME_HORIZONTAL_ =
  136. goog.getCssName('goog-splitpane-handle-horizontal');
  137. /**
  138. * CSS class name for the splitpane handle in horizontal orientation.
  139. * @type {string}
  140. * @private
  141. */
  142. goog.ui.SplitPane.HANDLE_CLASS_NAME_VERTICAL_ =
  143. goog.getCssName('goog-splitpane-handle-vertical');
  144. /**
  145. * The dragger to move the drag handle.
  146. * @type {goog.fx.Dragger?}
  147. * @private
  148. */
  149. goog.ui.SplitPane.prototype.splitDragger_ = null;
  150. /**
  151. * The left/top component dom container.
  152. * @type {Element}
  153. * @private
  154. */
  155. goog.ui.SplitPane.prototype.firstComponentContainer_ = null;
  156. /**
  157. * The right/bottom component dom container.
  158. * @type {Element}
  159. * @private
  160. */
  161. goog.ui.SplitPane.prototype.secondComponentContainer_ = null;
  162. /**
  163. * The size (width or height) of the splitpane handle, default = 5.
  164. * @type {number}
  165. * @private
  166. */
  167. goog.ui.SplitPane.prototype.handleSize_ = 5;
  168. /**
  169. * The initial size (width or height) of the left or top component.
  170. * @type {?number}
  171. * @private
  172. */
  173. goog.ui.SplitPane.prototype.initialSize_ = null;
  174. /**
  175. * The saved size (width or height) of the left or top component on a
  176. * double-click (snap).
  177. * This needs to be saved so it can be restored after another double-click.
  178. * @type {?number}
  179. * @private
  180. */
  181. goog.ui.SplitPane.prototype.savedSnapSize_ = null;
  182. /**
  183. * The first component size, so we don't change it on a window resize.
  184. * @type {?number}
  185. * @private
  186. */
  187. goog.ui.SplitPane.prototype.firstComponentSize_ = null;
  188. /**
  189. * If we resize as they user moves the handle (default = true).
  190. * @type {boolean}
  191. * @private
  192. */
  193. goog.ui.SplitPane.prototype.continuousResize_ = true;
  194. /**
  195. * Iframe overlay to prevent iframes from grabbing events.
  196. * @type {Element}
  197. * @private
  198. */
  199. goog.ui.SplitPane.prototype.iframeOverlay_ = null;
  200. /**
  201. * Z indices for iframe overlay and splitter handle.
  202. * @enum {number}
  203. * @private
  204. */
  205. goog.ui.SplitPane.IframeOverlayIndex_ = {
  206. HIDDEN: -1,
  207. OVERLAY: 1,
  208. SPLITTER_HANDLE: 2
  209. };
  210. /**
  211. * Orientation values for the splitpane.
  212. * @enum {string}
  213. */
  214. goog.ui.SplitPane.Orientation = {
  215. /**
  216. * Horizontal orientation means splitter moves right-left.
  217. */
  218. HORIZONTAL: 'horizontal',
  219. /**
  220. * Vertical orientation means splitter moves up-down.
  221. */
  222. VERTICAL: 'vertical'
  223. };
  224. /**
  225. * Create the DOM node & text node needed for the splitpane.
  226. * @override
  227. */
  228. goog.ui.SplitPane.prototype.createDom = function() {
  229. var dom = this.getDomHelper();
  230. // Create the components.
  231. var firstContainer = dom.createDom(
  232. goog.dom.TagName.DIV, goog.ui.SplitPane.FIRST_CONTAINER_CLASS_NAME_);
  233. var secondContainer = dom.createDom(
  234. goog.dom.TagName.DIV, goog.ui.SplitPane.SECOND_CONTAINER_CLASS_NAME_);
  235. var splitterHandle =
  236. dom.createDom(goog.dom.TagName.DIV, goog.ui.SplitPane.HANDLE_CLASS_NAME_);
  237. // Create the primary element, a DIV that holds the two containers and handle.
  238. this.setElementInternal(
  239. dom.createDom(
  240. goog.dom.TagName.DIV, goog.ui.SplitPane.CLASS_NAME_, firstContainer,
  241. secondContainer, splitterHandle));
  242. this.firstComponentContainer_ = firstContainer;
  243. this.secondComponentContainer_ = secondContainer;
  244. this.splitpaneHandle_ = splitterHandle;
  245. this.setUpHandle_();
  246. this.finishSetup_();
  247. };
  248. /**
  249. * Determines if a given element can be decorated by this type of component.
  250. * @param {Element} element Element to decorate.
  251. * @return {boolean} True if the element can be decorated, false otherwise.
  252. * @override
  253. */
  254. goog.ui.SplitPane.prototype.canDecorate = function(element) {
  255. var className = goog.ui.SplitPane.FIRST_CONTAINER_CLASS_NAME_;
  256. var firstContainer = this.getElementToDecorate_(element, className);
  257. if (!firstContainer) {
  258. return false;
  259. }
  260. // Since we have this component, save it so we don't have to get it
  261. // again in decorateInternal. Same w/other components.
  262. this.firstComponentContainer_ = firstContainer;
  263. className = goog.ui.SplitPane.SECOND_CONTAINER_CLASS_NAME_;
  264. var secondContainer = this.getElementToDecorate_(element, className);
  265. if (!secondContainer) {
  266. return false;
  267. }
  268. this.secondComponentContainer_ = secondContainer;
  269. className = goog.ui.SplitPane.HANDLE_CLASS_NAME_;
  270. var splitpaneHandle = this.getElementToDecorate_(element, className);
  271. if (!splitpaneHandle) {
  272. return false;
  273. }
  274. this.splitpaneHandle_ = splitpaneHandle;
  275. // We found all the components we're looking for, so return true.
  276. return true;
  277. };
  278. /**
  279. * Obtains the element to be decorated by class name. If multiple such elements
  280. * are found, preference is given to those directly attached to the specified
  281. * root element.
  282. * @param {Element} rootElement The root element from which to retrieve the
  283. * element to be decorated.
  284. * @param {string} className The target class name.
  285. * @return {Element} The element to decorate.
  286. * @private
  287. */
  288. goog.ui.SplitPane.prototype.getElementToDecorate_ = function(
  289. rootElement, className) {
  290. // Decorate the root element's children, if available.
  291. var childElements = goog.dom.getChildren(rootElement);
  292. for (var i = 0; i < childElements.length; i++) {
  293. var childElement = goog.asserts.assertElement(childElements[i]);
  294. if (goog.dom.classlist.contains(childElement, className)) {
  295. return childElement;
  296. }
  297. }
  298. // Default to the first descendent element with the correct class.
  299. return goog.dom.getElementsByTagNameAndClass(null, className, rootElement)[0];
  300. };
  301. /**
  302. * Decorates the given HTML element as a SplitPane. Overrides {@link
  303. * goog.ui.Component#decorateInternal}. Considered protected.
  304. * @param {Element} element Element (SplitPane div) to decorate.
  305. * @protected
  306. * @override
  307. */
  308. goog.ui.SplitPane.prototype.decorateInternal = function(element) {
  309. goog.ui.SplitPane.base(this, 'decorateInternal', element);
  310. this.setUpHandle_();
  311. var elSize = goog.style.getBorderBoxSize(element);
  312. this.setSize(new goog.math.Size(elSize.width, elSize.height));
  313. this.finishSetup_();
  314. };
  315. /**
  316. * Parent the passed in components to the split containers. Call their
  317. * createDom methods if necessary.
  318. * @private
  319. */
  320. goog.ui.SplitPane.prototype.finishSetup_ = function() {
  321. var dom = this.getDomHelper();
  322. if (!this.firstComponent_.getElement()) {
  323. this.firstComponent_.createDom();
  324. }
  325. dom.appendChild(
  326. this.firstComponentContainer_, this.firstComponent_.getElement());
  327. if (!this.secondComponent_.getElement()) {
  328. this.secondComponent_.createDom();
  329. }
  330. dom.appendChild(
  331. this.secondComponentContainer_, this.secondComponent_.getElement());
  332. this.splitDragger_ =
  333. new goog.fx.Dragger(this.splitpaneHandle_, this.splitpaneHandle_);
  334. this.firstComponentContainer_.style.position = 'absolute';
  335. this.secondComponentContainer_.style.position = 'absolute';
  336. var handleStyle = this.splitpaneHandle_.style;
  337. handleStyle.position = 'absolute';
  338. handleStyle.overflow = 'hidden';
  339. handleStyle.zIndex = goog.ui.SplitPane.IframeOverlayIndex_.SPLITTER_HANDLE;
  340. };
  341. /**
  342. * Setup all events and do an initial resize.
  343. * @override
  344. */
  345. goog.ui.SplitPane.prototype.enterDocument = function() {
  346. goog.ui.SplitPane.base(this, 'enterDocument');
  347. // If position is not set in the inline style of the element, it is not
  348. // possible to get the element's real CSS position until the element is in
  349. // the document.
  350. // When position:relative is set in the CSS and the element is not in the
  351. // document, Safari, Chrome, and Opera always return the empty string; while
  352. // IE always return "static".
  353. // Do the final check to see if element's position is set as "relative",
  354. // "absolute" or "fixed".
  355. var element = this.getElement();
  356. if (goog.style.getComputedPosition(element) == 'static') {
  357. element.style.position = 'relative';
  358. }
  359. this.getHandler()
  360. .listen(
  361. this.splitpaneHandle_, goog.events.EventType.DBLCLICK,
  362. this.handleDoubleClick_)
  363. .listen(
  364. this.splitDragger_, goog.fx.Dragger.EventType.START,
  365. this.handleDragStart_)
  366. .listen(
  367. this.splitDragger_, goog.fx.Dragger.EventType.DRAG, this.handleDrag_)
  368. .listen(
  369. this.splitDragger_, goog.fx.Dragger.EventType.END,
  370. this.handleDragEnd_);
  371. this.setFirstComponentSize(this.initialSize_);
  372. };
  373. /**
  374. * Sets the initial size of the left or top component.
  375. * @param {number} size The size in Pixels of the container.
  376. */
  377. goog.ui.SplitPane.prototype.setInitialSize = function(size) {
  378. this.initialSize_ = size;
  379. };
  380. /**
  381. * Sets the SplitPane handle size.
  382. * TODO(user): Make sure this works after initialization.
  383. * @param {number} size The size of the handle in pixels.
  384. */
  385. goog.ui.SplitPane.prototype.setHandleSize = function(size) {
  386. this.handleSize_ = size;
  387. };
  388. /**
  389. * Sets whether we resize on handle drag.
  390. * @param {boolean} continuous The continuous resize value.
  391. */
  392. goog.ui.SplitPane.prototype.setContinuousResize = function(continuous) {
  393. this.continuousResize_ = continuous;
  394. };
  395. /**
  396. * Returns whether the orientation for the split pane is vertical
  397. * or not.
  398. * @return {boolean} True if the orientation is vertical, false otherwise.
  399. */
  400. goog.ui.SplitPane.prototype.isVertical = function() {
  401. return this.orientation_ == goog.ui.SplitPane.Orientation.VERTICAL;
  402. };
  403. /**
  404. * Initializes the handle by assigning the correct height/width and adding
  405. * the correct class as per the orientation.
  406. * @private
  407. */
  408. goog.ui.SplitPane.prototype.setUpHandle_ = function() {
  409. if (this.isVertical()) {
  410. this.splitpaneHandle_.style.height = this.handleSize_ + 'px';
  411. goog.dom.classlist.add(
  412. this.splitpaneHandle_, goog.ui.SplitPane.HANDLE_CLASS_NAME_VERTICAL_);
  413. } else {
  414. this.splitpaneHandle_.style.width = this.handleSize_ + 'px';
  415. goog.dom.classlist.add(
  416. this.splitpaneHandle_, goog.ui.SplitPane.HANDLE_CLASS_NAME_HORIZONTAL_);
  417. }
  418. };
  419. /**
  420. * Sets the orientation class for the split pane handle.
  421. * @protected
  422. */
  423. goog.ui.SplitPane.prototype.setOrientationClassForHandle = function() {
  424. goog.asserts.assert(this.splitpaneHandle_);
  425. if (this.isVertical()) {
  426. goog.dom.classlist.swap(
  427. this.splitpaneHandle_, goog.ui.SplitPane.HANDLE_CLASS_NAME_HORIZONTAL_,
  428. goog.ui.SplitPane.HANDLE_CLASS_NAME_VERTICAL_);
  429. } else {
  430. goog.dom.classlist.swap(
  431. this.splitpaneHandle_, goog.ui.SplitPane.HANDLE_CLASS_NAME_VERTICAL_,
  432. goog.ui.SplitPane.HANDLE_CLASS_NAME_HORIZONTAL_);
  433. }
  434. };
  435. /**
  436. * Sets the orientation of the split pane.
  437. * @param {goog.ui.SplitPane.Orientation} orientation SplitPane orientation.
  438. */
  439. goog.ui.SplitPane.prototype.setOrientation = function(orientation) {
  440. if (this.orientation_ != orientation) {
  441. this.orientation_ = orientation;
  442. var isVertical = this.isVertical();
  443. // If the split pane is already in document, then the positions and sizes
  444. // need to be adjusted.
  445. if (this.isInDocument()) {
  446. this.setOrientationClassForHandle();
  447. // TODO(user): Should handleSize_ and initialSize_ also be adjusted ?
  448. if (goog.isNumber(this.firstComponentSize_)) {
  449. var splitpaneSize = goog.style.getBorderBoxSize(this.getElement());
  450. var ratio = isVertical ? splitpaneSize.height / splitpaneSize.width :
  451. splitpaneSize.width / splitpaneSize.height;
  452. // TODO(user): Fix the behaviour for the case when the handle is
  453. // placed on either of the edges of the split pane. Also, similar
  454. // behaviour is present in {@link #setSize}. Probably need to modify
  455. // {@link #setFirstComponentSize}.
  456. this.setFirstComponentSize(this.firstComponentSize_ * ratio);
  457. } else {
  458. this.setFirstComponentSize();
  459. }
  460. }
  461. }
  462. };
  463. /**
  464. * Gets the orientation of the split pane.
  465. * @return {goog.ui.SplitPane.Orientation} The orientation.
  466. */
  467. goog.ui.SplitPane.prototype.getOrientation = function() {
  468. return this.orientation_;
  469. };
  470. /**
  471. * Move and resize a container. The sizing changes the BorderBoxSize.
  472. * @param {Element} element The element to move and size.
  473. * @param {goog.math.Rect} rect The top, left, width and height to change to.
  474. * @private
  475. */
  476. goog.ui.SplitPane.prototype.moveAndSize_ = function(element, rect) {
  477. goog.style.setPosition(element, rect.left, rect.top);
  478. // TODO(user): Add a goog.math.Size.max call for below.
  479. goog.style.setBorderBoxSize(
  480. element,
  481. new goog.math.Size(Math.max(rect.width, 0), Math.max(rect.height, 0)));
  482. };
  483. /**
  484. * @return {?number} The size of the left/top component.
  485. */
  486. goog.ui.SplitPane.prototype.getFirstComponentSize = function() {
  487. return this.firstComponentSize_;
  488. };
  489. /**
  490. * Set the size of the left/top component, and resize the other component based
  491. * on that size and handle size.
  492. * @param {?number=} opt_size The size of the top or left, in pixels. If
  493. * unspecified, leaves the size of the first component unchanged but adjusts
  494. * the size of the second component to fit the split pane size.
  495. */
  496. goog.ui.SplitPane.prototype.setFirstComponentSize = function(opt_size) {
  497. this.setFirstComponentSize_(
  498. goog.style.getBorderBoxSize(this.getElement()), opt_size);
  499. };
  500. /**
  501. * Set the size of the left/top component, and resize the other component based
  502. * on that size and handle size. Unlike the public method, this takes the
  503. * current pane size which avoids the expensive getBorderBoxSize() call
  504. * when we have the size available.
  505. *
  506. * @param {!goog.math.Size} splitpaneSize The current size of the splitpane.
  507. * @param {?number=} opt_size The size of the top or left, in pixels.
  508. * @private
  509. */
  510. goog.ui.SplitPane.prototype.setFirstComponentSize_ = function(
  511. splitpaneSize, opt_size) {
  512. var top = 0, left = 0;
  513. var isVertical = this.isVertical();
  514. // Figure out first component size; it's either passed in, taken from the
  515. // saved size, or is half of the total size.
  516. var firstComponentSize = goog.isNumber(opt_size) ?
  517. opt_size :
  518. goog.isNumber(this.firstComponentSize_) ?
  519. this.firstComponentSize_ :
  520. Math.floor((isVertical ? splitpaneSize.height : splitpaneSize.width) / 2);
  521. this.firstComponentSize_ = firstComponentSize;
  522. var firstComponentWidth;
  523. var firstComponentHeight;
  524. var secondComponentWidth;
  525. var secondComponentHeight;
  526. var handleWidth;
  527. var handleHeight;
  528. var secondComponentLeft;
  529. var secondComponentTop;
  530. var handleLeft;
  531. var handleTop;
  532. if (isVertical) {
  533. // Width for the handle and the first and second components will be the
  534. // width of the split pane. The height for the first component will be
  535. // the calculated first component size. The height for the second component
  536. // will be the total height minus the heights of the first component and
  537. // the handle.
  538. firstComponentHeight = firstComponentSize;
  539. firstComponentWidth = splitpaneSize.width;
  540. handleWidth = splitpaneSize.width;
  541. handleHeight = this.handleSize_;
  542. secondComponentHeight =
  543. splitpaneSize.height - firstComponentHeight - handleHeight;
  544. secondComponentWidth = splitpaneSize.width;
  545. handleTop = top + firstComponentHeight;
  546. handleLeft = left;
  547. secondComponentTop = handleTop + handleHeight;
  548. secondComponentLeft = left;
  549. } else {
  550. // Height for the handle and the first and second components will be the
  551. // height of the split pane. The width for the first component will be
  552. // the calculated first component size. The width for the second component
  553. // will be the total width minus the widths of the first component and
  554. // the handle.
  555. firstComponentWidth = firstComponentSize;
  556. firstComponentHeight = splitpaneSize.height;
  557. handleWidth = this.handleSize_;
  558. handleHeight = splitpaneSize.height;
  559. secondComponentWidth =
  560. splitpaneSize.width - firstComponentWidth - handleWidth;
  561. secondComponentHeight = splitpaneSize.height;
  562. handleLeft = left + firstComponentWidth;
  563. handleTop = top;
  564. secondComponentLeft = handleLeft + handleWidth;
  565. secondComponentTop = top;
  566. }
  567. // Now move and size the containers.
  568. this.moveAndSize_(
  569. this.firstComponentContainer_,
  570. new goog.math.Rect(left, top, firstComponentWidth, firstComponentHeight));
  571. if (typeof this.firstComponent_.resize == 'function') {
  572. this.firstComponent_.resize(
  573. new goog.math.Size(firstComponentWidth, firstComponentHeight));
  574. }
  575. this.moveAndSize_(
  576. this.splitpaneHandle_,
  577. new goog.math.Rect(handleLeft, handleTop, handleWidth, handleHeight));
  578. this.moveAndSize_(
  579. this.secondComponentContainer_,
  580. new goog.math.Rect(
  581. secondComponentLeft, secondComponentTop, secondComponentWidth,
  582. secondComponentHeight));
  583. if (typeof this.secondComponent_.resize == 'function') {
  584. this.secondComponent_.resize(
  585. new goog.math.Size(secondComponentWidth, secondComponentHeight));
  586. }
  587. // Fire a CHANGE event.
  588. this.dispatchEvent(goog.ui.Component.EventType.CHANGE);
  589. };
  590. /**
  591. * Set the size of the splitpane. This is usually called by the controlling
  592. * application. This will set the SplitPane BorderBoxSize.
  593. * @param {!goog.math.Size} size The size to set the splitpane.
  594. * @param {?number=} opt_firstComponentSize The size of the top or left
  595. * component, in pixels.
  596. */
  597. goog.ui.SplitPane.prototype.setSize = function(size, opt_firstComponentSize) {
  598. goog.style.setBorderBoxSize(this.getElement(), size);
  599. if (this.iframeOverlay_) {
  600. goog.style.setBorderBoxSize(this.iframeOverlay_, size);
  601. }
  602. this.setFirstComponentSize_(size, opt_firstComponentSize);
  603. };
  604. /**
  605. * Snap the container to the left or top on a Double-click.
  606. * @private
  607. */
  608. goog.ui.SplitPane.prototype.snapIt_ = function() {
  609. var handlePos = goog.style.getRelativePosition(
  610. this.splitpaneHandle_, this.firstComponentContainer_);
  611. var firstBorderBoxSize =
  612. goog.style.getBorderBoxSize(this.firstComponentContainer_);
  613. var firstContentBoxSize =
  614. goog.style.getContentBoxSize(this.firstComponentContainer_);
  615. var isVertical = this.isVertical();
  616. // Where do we snap the handle (what size to make the component) and what
  617. // is the current handle position.
  618. var snapSize;
  619. var handlePosition;
  620. if (isVertical) {
  621. snapSize = firstBorderBoxSize.height - firstContentBoxSize.height;
  622. handlePosition = handlePos.y;
  623. } else {
  624. snapSize = firstBorderBoxSize.width - firstContentBoxSize.width;
  625. handlePosition = handlePos.x;
  626. }
  627. if (snapSize == handlePosition) {
  628. // This means we're 'unsnapping', set it back to where it was.
  629. this.setFirstComponentSize(this.savedSnapSize_);
  630. } else {
  631. // This means we're 'snapping', set the size to snapSize, and hide the
  632. // first component.
  633. if (isVertical) {
  634. this.savedSnapSize_ =
  635. goog.style.getBorderBoxSize(this.firstComponentContainer_).height;
  636. } else {
  637. this.savedSnapSize_ =
  638. goog.style.getBorderBoxSize(this.firstComponentContainer_).width;
  639. }
  640. this.setFirstComponentSize(snapSize);
  641. }
  642. // Fire a SNAP event.
  643. this.dispatchEvent(goog.ui.SplitPane.EventType.HANDLE_SNAP);
  644. };
  645. /**
  646. * Handle the start drag event - set up the dragger.
  647. * @param {goog.events.Event} e The event.
  648. * @private
  649. */
  650. goog.ui.SplitPane.prototype.handleDragStart_ = function(e) {
  651. // Setup iframe overlay to prevent iframes from grabbing events.
  652. if (!this.iframeOverlay_) {
  653. // Create the overlay.
  654. var cssStyles = 'position: relative';
  655. if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('10')) {
  656. // IE doesn't look at this div unless it has a background, so we'll
  657. // put one on, but make it opaque.
  658. cssStyles += ';background-color: #000;filter: Alpha(Opacity=0)';
  659. }
  660. this.iframeOverlay_ = this.getDomHelper().createDom(
  661. goog.dom.TagName.DIV, {'style': cssStyles});
  662. this.getDomHelper().appendChild(this.getElement(), this.iframeOverlay_);
  663. }
  664. this.iframeOverlay_.style.zIndex =
  665. goog.ui.SplitPane.IframeOverlayIndex_.OVERLAY;
  666. goog.style.setBorderBoxSize(
  667. this.iframeOverlay_, goog.style.getBorderBoxSize(this.getElement()));
  668. var pos = goog.style.getPosition(this.firstComponentContainer_);
  669. // For the size of the limiting box, we add the container content box sizes
  670. // so that if the handle is placed all the way to the end or the start, the
  671. // border doesn't exceed the total size. For position, we add the difference
  672. // between the border box and content box sizes of the first container to the
  673. // position of the first container. The start position should be such that
  674. // there is no overlap of borders.
  675. var limitWidth = 0;
  676. var limitHeight = 0;
  677. var limitx = pos.x;
  678. var limity = pos.y;
  679. var firstBorderBoxSize =
  680. goog.style.getBorderBoxSize(this.firstComponentContainer_);
  681. var firstContentBoxSize =
  682. goog.style.getContentBoxSize(this.firstComponentContainer_);
  683. var secondContentBoxSize =
  684. goog.style.getContentBoxSize(this.secondComponentContainer_);
  685. if (this.isVertical()) {
  686. limitHeight = firstContentBoxSize.height + secondContentBoxSize.height;
  687. limity += firstBorderBoxSize.height - firstContentBoxSize.height;
  688. } else {
  689. limitWidth = firstContentBoxSize.width + secondContentBoxSize.width;
  690. limitx += firstBorderBoxSize.width - firstContentBoxSize.width;
  691. }
  692. var limits = new goog.math.Rect(limitx, limity, limitWidth, limitHeight);
  693. this.splitDragger_.setLimits(limits);
  694. };
  695. /**
  696. * Find the location relative to the splitpane.
  697. * @param {number} left The x location relative to the window.
  698. * @return {number} The relative x location.
  699. * @private
  700. */
  701. goog.ui.SplitPane.prototype.getRelativeLeft_ = function(left) {
  702. return left - goog.style.getPosition(this.firstComponentContainer_).x;
  703. };
  704. /**
  705. * Find the location relative to the splitpane.
  706. * @param {number} top The y location relative to the window.
  707. * @return {number} The relative y location.
  708. * @private
  709. */
  710. goog.ui.SplitPane.prototype.getRelativeTop_ = function(top) {
  711. return top - goog.style.getPosition(this.firstComponentContainer_).y;
  712. };
  713. /**
  714. * Handle the drag event. Move the containers.
  715. * @param {!goog.fx.DragEvent} e The event.
  716. * @private
  717. */
  718. goog.ui.SplitPane.prototype.handleDrag_ = function(e) {
  719. if (this.continuousResize_) {
  720. if (this.isVertical()) {
  721. var top = this.getRelativeTop_(e.top);
  722. this.setFirstComponentSize(top);
  723. } else {
  724. var left = this.getRelativeLeft_(e.left);
  725. this.setFirstComponentSize(left);
  726. }
  727. this.dispatchEvent(goog.ui.SplitPane.EventType.HANDLE_DRAG);
  728. }
  729. };
  730. /**
  731. * Handle the drag end event. If we're not doing continuous resize,
  732. * resize the component. If we're doing continuous resize, the component
  733. * is already the correct size.
  734. * @param {!goog.fx.DragEvent} e The event.
  735. * @private
  736. */
  737. goog.ui.SplitPane.prototype.handleDragEnd_ = function(e) {
  738. // Push iframe overlay down.
  739. this.iframeOverlay_.style.zIndex =
  740. goog.ui.SplitPane.IframeOverlayIndex_.HIDDEN;
  741. if (!this.continuousResize_) {
  742. if (this.isVertical()) {
  743. var top = this.getRelativeTop_(e.top);
  744. this.setFirstComponentSize(top);
  745. } else {
  746. var left = this.getRelativeLeft_(e.left);
  747. this.setFirstComponentSize(left);
  748. }
  749. }
  750. this.dispatchEvent(goog.ui.SplitPane.EventType.HANDLE_DRAG_END);
  751. };
  752. /**
  753. * Handle the Double-click. Call the snapIt method which snaps the container
  754. * to the top or left.
  755. * @param {goog.events.Event} e The event.
  756. * @private
  757. */
  758. goog.ui.SplitPane.prototype.handleDoubleClick_ = function(e) {
  759. this.snapIt_();
  760. };
  761. /** @override */
  762. goog.ui.SplitPane.prototype.disposeInternal = function() {
  763. goog.dispose(this.splitDragger_);
  764. this.splitDragger_ = null;
  765. goog.dom.removeNode(this.iframeOverlay_);
  766. this.iframeOverlay_ = null;
  767. goog.ui.SplitPane.base(this, 'disposeInternal');
  768. };