Reusable Accessible Mapping Platform

API Docs for: 5.0.0
Show:

File: src\js\RAMP\Modules\gui.js

  1. /*global define, $, window, TweenLite, TimelineLite, tmpl, i18n, console, jQuery, RAMP */
  2. /*jslint white: true */
  3.  
  4. /**
  5. * UI submodule
  6. *
  7. * @module RAMP
  8. * @submodule UI
  9. * @main UI
  10. */
  11.  
  12. /**
  13. * A class for handling most of the GUI on the page.
  14. *
  15. * @class GUI
  16. * @static
  17. * @uses dojo/_base/array
  18. * @uses dojo/topic
  19. * @uses dojo/_base/lang
  20. * @uses dojo/Deferred
  21. * @uses dojo/domReady!
  22. * @uses GlobalStorage
  23. * @uses EventManager
  24. * @uses Theme
  25. * @uses templates/sub_panel_Template.html
  26. * @uses templates/sub_panel_template.json
  27. * @uses templates/sub_panel_content_Template.html
  28. * @uses Util
  29. * @uses Dictionary
  30. * @uses PopupManager
  31. * @uses TmplHelper
  32. * @uses dojo/domReady!
  33. */
  34. define([
  35. // Dojo
  36. "dojo/_base/array", "dojo/topic", "dojo/_base/lang", "dojo/Deferred",
  37.  
  38. // Ramp
  39. "ramp/globalStorage", "ramp/eventManager",
  40.  
  41. "ramp/theme",
  42.  
  43. // Text
  44. "dojo/text!./templates/sub_panel_template.json",
  45.  
  46. // Util
  47. "utils/util", "utils/dictionary", "utils/popupManager", "utils/tmplHelper",
  48.  
  49. // Dom Ready
  50. "dojo/domReady!"
  51. ],
  52.  
  53. function (
  54. // Dojo
  55. dojoArray, topic, dojoLang, Deferred,
  56.  
  57. // Ramp
  58. GlobalStorage, EventManager,
  59.  
  60. Theme,
  61.  
  62. // Text
  63. subPanelTemplate,
  64.  
  65. // Util
  66. UtilMisc, utilDict, popupManager, tmplHelper) {
  67. "use strict";
  68.  
  69. var jWindow = $(window),
  70.  
  71. sidePanelWbTabs = $("#panel-div > .wb-tabs"),
  72. sidePanelTabList = sidePanelWbTabs.find(" > ul[role=tablist]"),
  73. sidePanelTabPanels = sidePanelWbTabs.find(" > .tabpanels"),
  74. //panelTabs =
  75.  
  76. mapContent = $("#mapContent"),
  77. loadIndicator = mapContent.find("#map-load-indicator"),
  78.  
  79. subPanels = {},
  80.  
  81. // subPanelAttribute definition
  82.  
  83. /**
  84. * A class holding properties of the SubPanel.
  85. *
  86. * @class SubPanelSettings
  87. * @for SubPanel
  88. */
  89. subPanelAttr = {
  90. /**
  91. * A name used to identify the subpanel being opened (e.g. "Details", "Metadata")
  92. *
  93. * @property panelName
  94. * @for SubPanelSettings
  95. * @type {String}
  96. * @default ""
  97. */
  98.  
  99. panelName: "",
  100. /**
  101. * Title of the content to be displayed on the SubPanel (e.g. "CESI Water Quality Indicators")
  102. *
  103. * @property title
  104. * @type {String}
  105. * @default ""
  106. */
  107.  
  108. title: "",
  109. /**
  110. * The text inside the subpanel. Can be String or a jQuery object. All nodes sporting CSS class
  111. * ".shorten-candidate" are treated to the shortening procedure - long strings are curtailed, and [more/less] links are placed at their ends.
  112. *
  113. * @property content
  114. * @type {String | jObject}
  115. * @default null
  116. */
  117.  
  118. content: null,
  119.  
  120. templateKey: "summary_sub_panel_container",
  121. /**
  122. * The node after which the panel will be inserted (e.g. node.find(".layer-details")).
  123. *
  124. * @property target
  125. * @type {jObject}
  126. * @default null
  127. */
  128.  
  129. target: null,
  130. /**
  131. * The name of the module that requested to open the SubPanel (e.g. "filterManager"). Used for identification of the panel's loyalty.
  132. *
  133. * @property origin
  134. * @type {String}
  135. * @default ""
  136. */
  137.  
  138. origin: "",
  139. /**
  140. * A unique id of the SubPanel. If none provided, a random one is generated. It is used to determine the animation and update function
  141. * to run on content update.
  142. *
  143. * @property guid
  144. * @type {String}
  145. * @default ""
  146. */
  147.  
  148. guid: "",
  149. /**
  150. * Indicates that the open SubPanel request is a content update to the already opened SubPanel.
  151. * Does not trigger any of the `doOn-` or `doAfter-` functions.
  152. *
  153. * __Use case:__ the uses clicks on the metadata button.
  154. * 1. A request to open a SubPanel is sent with only the title since the metadata content is not yet available
  155. * 2. Metadata is fetched from the server
  156. * 3. A second request to open a SubPanel is sent having `update` set to `true` and featuring the __same__ `guid` as the first request
  157. * 4. Only the content of the SubPanel is updated; no extra animations are triggered
  158. *
  159. * @property update
  160. * @type {Boolean}
  161. * @default false
  162. */
  163.  
  164. update: false,
  165. /**
  166. * The callback function when the panel starts the opening animation; also triggered by updating panel content; can be triggered many times.
  167. *
  168. * @property doOnOpen
  169. * @type {Function}
  170. * @default null
  171. */
  172.  
  173. doOnOpen: null,
  174. /**
  175. * The callback function when the panel finishes the opening animation; also triggered by updating panel content; can be triggered many times.
  176. *
  177. * @property doAfterOpen
  178. * @type {Function}
  179. * @default null
  180. */
  181.  
  182. doAfterOpen: null,
  183. /**
  184. * The callback function when the panel starts the closing animation; also triggered by updating panel content; can be triggered many times.
  185. *
  186. * @property doOnHide
  187. * @type {Function}
  188. * @default null
  189. */
  190.  
  191. doOnHide: null,
  192. /**
  193. * The callback function when the panel becomes hidden; also triggered by updating panel content; can be triggered many times.
  194. *
  195. * @property doAfterHide
  196. * @type {Function}
  197. * @default null
  198. */
  199.  
  200. doAfterHide: null,
  201. /**
  202. * The callback function when the panel is completely closed, its nodes destroyed; can be triggered only once in a lifespan of the panel.
  203. *
  204. * @property doOnDestroy
  205. * @type {Function}
  206. * @default null
  207. */
  208.  
  209. doOnDestroy: null,
  210. /**
  211. * The callback function executed after the SubPanel content is updated.
  212. * __Doesn't work correctly yet.__
  213. *
  214. * @property doAfterUpdate
  215. * @type {Function}
  216. * @default null
  217. */
  218.  
  219. doAfterUpdate: null,
  220. /**
  221. * The target number of chars after which a content text node will be shortened.
  222. *
  223. * @property showChars
  224. * @type {Number}
  225. * @default 170
  226. */
  227.  
  228. showChars: 170
  229. },
  230.  
  231. // Panel Prototype
  232.  
  233. /**
  234. * [subPanelPrototype description]
  235. *
  236. * @class SubPanel
  237. * @constructor
  238. * @for GUI
  239. */
  240. subPanelPrototype = {
  241. /**
  242. * Indicates if the closing animation is under way.
  243. *
  244. * @property _closing
  245. * @private
  246. * @for SubPanel
  247. * @type {Boolean}
  248. * @default false
  249. */
  250.  
  251. _closing: false,
  252. /**
  253. * Holds a deferred that would destroy the panel after the closing animation completes. May be interrupted.
  254. *
  255. * @property _destroyDeferred
  256. * @type {Deferred}
  257. * @private
  258. * @default null
  259. */
  260.  
  261. _destroyDeferred: null,
  262. /**
  263. * SubPanel attributes
  264. *
  265. * @property _attr
  266. * @private
  267. * @default null
  268. * @type {SubPanelSettings}
  269. */
  270.  
  271. _attr: null,
  272. /**
  273. * Indicates if the SubPanel is visible at the moment. Doesn't make the panel visible or invisible, just prevents animations on the content
  274. * to run when it is set to `true`.
  275. *
  276. * @property _visible
  277. * @private
  278. * @default false
  279. * @type {Boolean}
  280. */
  281.  
  282. _visible: false,
  283. /**
  284. * The outermost `div` of the SubPanel.
  285. *
  286. * @property container
  287. * @default null
  288. * @type {jObject}
  289. */
  290.  
  291. container: null,
  292. /**
  293. * The inner `div` of the SubPanel. Closing and opening animations are run on this `div`.
  294. *
  295. * @property panel
  296. * @default null
  297. * @type {jQobject}
  298. */
  299.  
  300. panel: null,
  301. /**
  302. * `div` housing the content of the SubPanel, including its title.
  303. *
  304. * @property _subPanelContentDiv
  305. * @private
  306. * @default null
  307. * @type {jObject}
  308. */
  309.  
  310. _subPanelContentDiv: null,
  311. /**
  312. * Heading of the content in the SubPanel.
  313. *
  314. * @property _panelTitle
  315. * @private
  316. * @default null
  317. * @type {jObject}
  318. */
  319.  
  320. _panelTitle: null,
  321. /**
  322. * `div` housing the content of the SubPanel, excluding its title.
  323. *
  324. * @property _panelContentDiv
  325. * @private
  326. * @default null
  327. * @type {jObject}
  328. */
  329.  
  330. _panelContentDiv: null,
  331. /**
  332. * Default duration of the SubPanel animation in milliseconds.
  333. *
  334. * @property _animatePanelDuration
  335. * @private
  336. * @default 0.5
  337. * @type {Number}
  338. */
  339.  
  340. _animatePanelDuration: 0.5, //0.4,
  341.  
  342. timeLine: null,
  343.  
  344. /**
  345. * Apply the shortening plugin to the panel data
  346. *
  347. * @method parseContent
  348. * @param {jObject} data Content to be shortened
  349. * @return {jObject} Content with after shortening long text nodes
  350. */
  351. parseContent: function (data) {
  352. //console.log(this._attr.showChars, jQuery.type(data), data);
  353. return (jQuery.type(data) === "object" ? data : $(data))
  354. .find(".shorten-candidate").shorten({
  355. showChars: this._attr.showChars
  356. })
  357. .removeClass("shorten-candidate").end();
  358. },
  359. /**
  360. * Returns this SubPanel's settings object.
  361. *
  362. * @method getAttributes
  363. * @return {SubPanelSettings} This SubPanel's settings
  364. */
  365. getAttributes: function () {
  366. return this._attr;
  367. },
  368. /**
  369. * Returns this SubPanel's container `div`.
  370. *
  371. * @method getContainer
  372. * @return {jobject} This SubPanel's `div`
  373. */
  374. getContainer: function () {
  375. return this.container;
  376. },
  377. /**
  378. * Returns the inner `div` of the SubPanel
  379. *
  380. * @method getPanel
  381. * @return {jObject} The inner `div` of the SubPanel
  382. */
  383. getPanel: function () {
  384. return this.panel;
  385. },
  386. /**
  387. * Returns the `origin` of this SubPanel.
  388. *
  389. * @method getOrigin
  390. * @return {String} The `origin` of this SubPanel
  391. */
  392. getOrigin: function () {
  393. return this._attr.origin;
  394. },
  395. /**
  396. * Returns the `guid` of this SubPanel.
  397. *
  398. * @method getOrigin
  399. * @return {String} The `guid` of this SubPanel
  400. */
  401. getGuid: function () {
  402. return this._attr.guid;
  403. },
  404. /**
  405. * Destroys this SubPanel.
  406. *
  407. * @method destroy
  408. * @param {Number} speed The duration of the animation in milliseconds
  409. * @param {Deferred} deferred The deferred to be resolved after the SubPanel is destroyed
  410. */
  411. destroy: function (speed, deferred) {
  412. if (this._attr.doOnHide) {
  413. this._attr.doOnHide();
  414. }
  415.  
  416. this._closing = true;
  417. this._destroyDeferred = deferred;
  418.  
  419. // remove CSS animation class to prevent flickering
  420. this._subPanelContentDiv.find(".fadeInDown").removeClass("fadeInDown");
  421.  
  422. //sidePanel.getContainer().after(this.container);
  423. layoutController.getPanelContainer().before(this.container);
  424. //adjutSubPanelDimensions(this);
  425.  
  426. layoutController.subPanelChange(false, this._attr.origin, this.container, false);
  427.  
  428. // do after closing animation completes
  429. this.timeLine.eventCallback("onReverseComplete",
  430. function () {
  431. if (this._attr.doAfterHide) {
  432. this._attr.doAfterHide();
  433. }
  434. if (this._attr.doOnDestroy) {
  435. this._attr.doOnDestroy();
  436. }
  437. this._visible = false;
  438. layoutController.subPanelChange(false, this._attr.origin, null, true);
  439.  
  440. this.container.remove();
  441.  
  442. if (deferred) {
  443. deferred.resolve(true);
  444. }
  445. }, [], this);
  446.  
  447. this.timeLine.reverse();
  448. },
  449.  
  450. /**
  451. * Reopens the SubPanel - stops the closing animation and initiates the opening animation.
  452. *
  453. * @method reopen
  454. */
  455. reopen: function () {
  456. //this.panel.stop();
  457. this.timeLine.pause();
  458. this._closing = false;
  459. if (this._destroyDeferred) {
  460. this._destroyDeferred.cancel();
  461. this._destroyDeferred = null;
  462. }
  463.  
  464. this.open();
  465. },
  466.  
  467. /**
  468. * Opens the SubPanel. Sends out `EventManager.GUI.SUBPANEL_CHANGE` event.
  469. *
  470. * @method open
  471. */
  472. open: function () {
  473. if (this._attr.doOnOpen) {
  474. this._attr.doOnOpen();
  475. }
  476. this._visible = true;
  477. layoutController.subPanelChange(true, this._attr.origin, this.container, false);
  478.  
  479. this.timeLine.play();
  480. },
  481.  
  482. /**
  483. * Assigns a new origin to the SubPanel.
  484. *
  485. * @method changeOrigin
  486. * @param {String} newOrigin The new origin of the SubPanel.
  487. */
  488. changeOrigin: function (newOrigin) {
  489. this._attr.origin = newOrigin;
  490. },
  491.  
  492. /**
  493. * Shifts the SubPanel to the new node in the DOM.
  494. *
  495. * @method shiftTarget
  496. * @param {jObject} newTarget A node in the DOM to shift the SubPanel to
  497. */
  498. shiftTarget: function (newTarget) {
  499. if (this._attr.target !== newTarget) {
  500. // remove animation class to prevent flickering of data
  501. this._subPanelContentDiv.find(".fadeInDown").removeClass("fadeInDown");
  502. newTarget.after(this.container);
  503. this._attr.target = newTarget;
  504. }
  505. },
  506.  
  507. /**
  508. * Creates a new instance of SubPanel.
  509. *
  510. * @method create
  511. * @param {SubPanelSettings} a Settings for the SubPanel
  512. */
  513. create: function (a) {
  514. var //subPanelContent,
  515. subPanelString,
  516. parsedContent;
  517.  
  518. console.log("create panel, ", a.origin);
  519.  
  520. a.guid = a.guid || UtilMisc.guid();
  521.  
  522. dojoLang.mixin(this._attr, a);
  523.  
  524. tmpl.cache = {};
  525. tmpl.templates = subPanelTemplate;
  526.  
  527. subPanelString = tmpl(this._attr.templateKey,
  528. dojoLang.mixin(
  529. this._attr,
  530. {
  531. closeTitle: i18n.t('gui.actions.close')
  532. }
  533. )
  534. );
  535.  
  536. this.container = $(subPanelString).insertAfter(this._attr.target);
  537. this.panel = this.container.find(".sub-panel");
  538.  
  539. this._subPanelContentDiv = this.panel.find(".sub-panel-content");
  540. this._panelTitle = this.panel.find(".panel-title");
  541. this._panelContentDiv = this.panel.find(".panel-content-div");
  542.  
  543. // set panel content
  544. parsedContent = this.parseContent(this._attr.content);
  545. this._panelContentDiv.empty().append(parsedContent);
  546.  
  547. this.timeLine = new TimelineLite(
  548. {
  549. paused: true,
  550. onComplete: function () {
  551. if (this._attr.doAfterOpen) {
  552. this._attr.doAfterOpen();
  553. }
  554.  
  555. layoutController.subPanelChange(true, this._attr.origin, this.container, true);
  556. },
  557. onCompleteScope: this
  558. })
  559. .to(this.panel, this._animatePanelDuration, { left: 0, ease: "easeOutCirc" })
  560. .to(loadIndicator, this._animatePanelDuration, { right: this.panel.width() + 6, ease: "easeOutCirc" }, 0); // 6 is double border width
  561.  
  562. Theme.tooltipster(this.container);
  563.  
  564. this.update(this._attr);
  565. },
  566.  
  567. /**
  568. * Performs an update of the content and title of the SubPanel, running appropriate animation and `doOn-` / `doAfter-` functions.
  569. *
  570. * @method update
  571. * @param {SubPanelSettings} a New settings for the SubPanel
  572. */
  573. update: function (a) {
  574. // helper functions
  575. var animateContentDuration = 300,
  576. sOut = '<ul class="loadingAnimation"><li></li><li></li><li></li><li></li><li></li><li></li></ul>',
  577.  
  578. updateDefered = [new Deferred(), new Deferred()],
  579.  
  580. animateContent = function (node, newData, d) {
  581. if (newData) {
  582. node.addClass('animated fadeOutDown');
  583. window.setTimeout(dojoLang.hitch(this,
  584. function () {
  585. node
  586. //.html(newData)
  587. .empty().append(newData)
  588. .removeClass("fadeOutDown")
  589. .addClass('animated fadeInDown'); //.find(".shorten-candidate").shorten();
  590.  
  591. d.resolve();
  592. }),
  593. animateContentDuration);
  594. }
  595. },
  596.  
  597. setContent = function (node, oldData, newData, parsedData, visible, d) {
  598. newData = (newData === null) ? parsedData = sOut : newData;
  599. if (newData) {
  600. if (newData !== oldData) {
  601. if (visible) {
  602. //ideally, need to wait until the animation completes before proceeding?
  603. animateContent(node, parsedData, d);
  604. } else {
  605. node.empty().append(parsedData);
  606. d.resolve();
  607. }
  608. } else {
  609. d.resolve();
  610. }
  611. } else {
  612. d.resolve();
  613. }
  614. },
  615.  
  616. updateContent = dojoLang.hitch(this,
  617. function (a) {
  618. // if the content in the subpanel is scrolled down, scroll back to the top
  619. TweenLite.to(this._subPanelContentDiv, animateContentDuration / 1000,
  620. { scrollTop: 0, ease: "easeOutCirc" });
  621.  
  622. setContent(this._panelTitle, this._attr.title, a.title, a.title, this._visible, updateDefered[0]);
  623. setContent(this._panelContentDiv, this._attr.content, a.content, this.parseContent(a.content), this._visible, updateDefered[1]);
  624.  
  625. dojoLang.mixin(this._attr, a);
  626. }
  627. );
  628.  
  629. // doAfterUpdate should be called AFTER update (animation) completes...
  630. UtilMisc.afterAll(updateDefered, function () {
  631. if (a.doAfterUpdate) {
  632. a.doAfterUpdate();
  633. }
  634. });
  635.  
  636. // panel is closing; new data is not an update
  637. if (this._closing && !a.update) {
  638. //
  639. if (this._attr.guid !== a.guid) {
  640. if (this._attr.doOnHide) {
  641. this._attr.doOnHide();
  642. }
  643. if (this._attr.doAfterHide) {
  644. this._attr.doAfterHide();
  645. }
  646.  
  647. // move panel to the new target
  648. a.target.after(this.container);
  649. updateContent(a);
  650. }
  651.  
  652. this.reopen();
  653.  
  654. // panel is not closing
  655. } else if (!this._closing) {
  656. // data is not an update
  657. if (!a.update && this._attr.guid !== a.guid) {
  658. if (this._attr.doOnHide) {
  659. this._attr.doOnHide();
  660. }
  661. if (this._attr.doAfterHide) {
  662. this._attr.doAfterHide();
  663. }
  664.  
  665. // move panel to the new target
  666. a.target.after(this.container);
  667. updateContent(a);
  668.  
  669. if (a.doOnOpen) {
  670. a.doOnOpen();
  671. }
  672. if (a.doAfterOpen) {
  673. a.doAfterOpen();
  674. }
  675. }
  676.  
  677. // guid is the same - data can update or not (should be an update)
  678. //if (a.update && attr.guid === a.guid) {
  679. if (this._attr.guid === a.guid) {
  680. updateContent(a);
  681. }
  682. }
  683. }
  684. },
  685.  
  686. helpToggle = $("#helpToggle"),
  687. helpSectionContainer = $("#help-section-container"),
  688. helpSection = $("#help-section"),
  689.  
  690. addLayerToggle = $("#addLayer-toggle"),
  691. addLayerSectionContainer = $("#addLayer-section-container"),
  692. //AddLayerSection = $("#addLayer-section"),
  693.  
  694. cssButtonPressedClass = "button-pressed",
  695. cssExpandedClass = "state-expanded",
  696.  
  697. helpPanelPopup,
  698. addLayerPanelPopup,
  699.  
  700. transitionDuration = 0.5,
  701.  
  702. layoutController;
  703.  
  704. /**
  705. * Controls layout transition such as full-data and full-screen modes, opening and closing of the side panel, adjusts layout when resizing the browser window.
  706. *
  707. * @class LayoutController
  708. * @static
  709. * @for GUI
  710. */
  711. layoutController = (function () {
  712. var viewport = $(".viewport"),
  713. mapDiv = $("#map-div"),
  714. mapContent = $("#mapContent"),
  715. fullScreenToggle = $("#fullScreenToggle"),
  716. fullScreenPopup,
  717.  
  718. mapToolbar = $("#map-toolbar"),
  719. basemapControls = $("#basemapControls"),
  720.  
  721. panelDiv = $("#panel-div"),
  722. panelToggle = $("#panel-toggle"),
  723. panelPopup,
  724. panelWidthDefault, // default width of the SidePanel.
  725. layoutWidthThreshold = 1200, // minimum width of the wide layout
  726.  
  727. windowWidth,
  728.  
  729. layoutChange,
  730.  
  731. _isFullData = false,
  732. fullDataTimeLine = new TimelineLite({
  733. paused: true,
  734. onComplete: function () {
  735. adjustHeight();
  736. layoutChange();
  737.  
  738. // set tooltips on the collapsed toolbar
  739. mapToolbar
  740. .find(".map-toolbar-item-button")
  741. .map(function (i, node) {
  742. node = $(node);
  743. node
  744. .addClass("_tooltip")
  745. .attr(
  746. "title",
  747. node.find("span").text()
  748. );
  749. });
  750.  
  751. Theme.tooltipster(mapToolbar);
  752.  
  753. //console.log("finished", EventManager.Datagrid.APPLY_EXTENT_FILTER);
  754. //topic.publish(EventManager.Datagrid.APPLY_EXTENT_FILTER);
  755. },
  756. onReverseComplete: function () {
  757. viewport.removeClass("full-data-mode");
  758.  
  759. adjustHeight();
  760. layoutChange();
  761.  
  762. // remove tooltips from the restored toolbar
  763. Theme.tooltipster(mapToolbar, null, "destroy");
  764.  
  765. mapToolbar
  766. .find(".map-toolbar-item-button")
  767. .removeClass("_tooltip")
  768. .removeAttr("title");
  769.  
  770. //console.log("reverse finished", EventManager.Datagrid.APPLY_EXTENT_FILTER);
  771. //topic.publish(EventManager.Datagrid.APPLY_EXTENT_FILTER);
  772. }
  773. }),
  774. panelToggleTimeLine = new TimelineLite({ paused: true }),
  775. fullDataSubpanelChangeTimeLine = new TimelineLite({ paused: true }),
  776.  
  777. // timeline generating functions
  778. createFullDataTL,
  779. createPanelToggleTL,
  780. createFullDataSubpanelChangeTL,
  781.  
  782. timeLines;
  783.  
  784. createFullDataTL = function () {
  785. fullDataTimeLine
  786. .fromTo(mapDiv, transitionDuration, { width: "auto" }, { width: 35, ease: "easeOutCirc" }, 0)
  787.  
  788. .fromTo(mapContent, transitionDuration, { opacity: 1 }, { opacity: 0, ease: "easeOutCirc" }, 0)
  789. .set(mapContent, { top: "500px" })
  790.  
  791. .to(panelToggle, transitionDuration, { right: -13, ease: "easeOutCirc" }, 0)
  792. .set(panelToggle, { display: "none" })
  793.  
  794. .to(basemapControls, transitionDuration / 2, { opacity: 0, ease: "easeOutCirc" }, 0)
  795. .to(basemapControls, 0, { display: "none" }, transitionDuration / 2)
  796. .fromTo(mapToolbar, transitionDuration / 2,
  797. { width: "100%", height: "32px" },
  798. { width: "32px", height: $("#map-div").height(), ease: "easeOutCirc" }, transitionDuration / 2)
  799.  
  800. .to(mapToolbar.find(".map-toolbar-item-button span"), transitionDuration / 2, { width: 0, ease: "easeOutCirc" }, 0)
  801. .set(mapToolbar.find(".map-toolbar-item-button span"), { display: "none" }, transitionDuration / 2)
  802.  
  803. .fromTo(panelDiv.find(".wb-tabs > ul li:first"), transitionDuration, { width: "50%" }, { width: "0%", display: "none", ease: "easeOutCirc" }, 0)
  804. .fromTo(panelDiv.find(".wb-tabs > ul li:last"), transitionDuration, { width: "50%" }, { width: "100%", className: "+=h5", ease: "easeOutCirc" }, 0)
  805.  
  806. .fromTo(panelDiv, transitionDuration,
  807. { width: panelWidthDefault, left: "auto" },
  808. { left: 35, width: "auto", ease: "easeOutCirc" }, 0);
  809. };
  810.  
  811. createPanelToggleTL = function () {
  812. panelToggleTimeLine
  813. .fromTo(panelDiv, transitionDuration, { right: 0 }, { right: -panelWidthDefault, ease: "easeOutCirc" }, 0)
  814. .set(panelDiv, { display: "none" }, transitionDuration)
  815.  
  816. .fromTo(mapDiv, transitionDuration, { right: panelWidthDefault }, { right: 0, ease: "easeOutCirc" }, 0);
  817. };
  818.  
  819. createFullDataSubpanelChangeTL = function () {
  820. fullDataSubpanelChangeTimeLine
  821. .fromTo(panelDiv, transitionDuration,
  822. { right: 0 },
  823. { right: panelWidthDefault, ease: "easeOutCirc" });
  824. };
  825.  
  826. timeLines = [
  827. {
  828. timeLine: fullDataTimeLine,
  829. generator: createFullDataTL
  830. },
  831. {
  832. timeLine: panelToggleTimeLine,
  833. generator: createPanelToggleTL
  834. },
  835. {
  836. timeLine: fullDataSubpanelChangeTimeLine,
  837. generator: createFullDataSubpanelChangeTL
  838. }
  839. ];
  840.  
  841. /**
  842. * Fires an event when the layout of the page changes.
  843. *
  844. * @method layoutChange
  845. * @private
  846. * @for LayoutController
  847. */
  848. layoutChange = function () {
  849. if (!_isFullData) {
  850. console.log("GUI --> EventManager.GUI.LAYOUT_CHANGE");
  851. topic.publish(EventManager.GUI.LAYOUT_CHANGE);
  852. }
  853. };
  854.  
  855. /**
  856. * Adjusts the height of the help section based on the height of the window.
  857. *
  858. * @method adjustHeight
  859. * @private
  860. */
  861. function adjustHeight() {
  862. helpSection.css({
  863. "max-height": jWindow.height() - (_isFullData ? jWindow.height() * 0.2 : mapToolbar.offset().top) - 90 // 90 is an arbitrary-wide gap between the help panel and the upper toolbar
  864. });
  865. }
  866.  
  867. /**
  868. * Executed after full-screen mode transition is complete.
  869. *
  870. * @method onFullScreenComplete
  871. * @private
  872. */
  873. function onFullScreenComplete() {
  874. adjustHeight();
  875. layoutChange();
  876.  
  877. topic.publish(EventManager.GUI.FULLSCREEN_CHANGE, {
  878. visible: Theme.isFullScreen()
  879. });
  880. }
  881.  
  882. /**
  883. * Publishes `PANEL_CHANGE` event when the visibility of the SidePanel changes.
  884. *
  885. * @method panelChange
  886. * @param {Boolean} visible Indicates whether the SidePanel is visible or not
  887. * @private
  888. */
  889. function panelChange(visible) {
  890. topic.publish(EventManager.GUI.PANEL_CHANGE, {
  891. visible: visible
  892. });
  893. }
  894.  
  895. /**
  896. * Slides the SidePanel open.
  897. *
  898. * @method openPanel
  899. * @private
  900. * @param {Deferred} d A deferred to be resolved upon completion of the animation
  901. */
  902. function openPanel(d) {
  903. /*jshint validthis: true */
  904. panelToggleTimeLine.eventCallback("onReverseComplete",
  905. function () {
  906. layoutChange();
  907. panelChange(true);
  908.  
  909. // update close button tooltips
  910. panelToggle
  911. .tooltipster("content", i18n.t("gui.actions.close"))
  912. .find("span.wb-invisible").text(i18n.t("gui.actions.close"));
  913.  
  914. d.resolve();
  915. }, [], this);
  916.  
  917. viewport.removeClass("no-sidepanel-mode");
  918. panelToggleTimeLine.reverse();
  919. }
  920.  
  921. /**
  922. * Slide the SidePanel close
  923. *
  924. * @method closePanel
  925. * @private
  926. * @param {Deferred} d A deferred to be resolved upon completion of the animation
  927. */
  928. function closePanel(d) {
  929. /*jshint validthis: true */
  930. panelToggleTimeLine.eventCallback("onComplete",
  931. function () {
  932. console.log("GUI <-- map/update-end from gui");
  933. layoutChange();
  934. panelChange(false);
  935.  
  936. // update open button tooltips
  937. panelToggle
  938. .tooltipster("content", i18n.t("gui.actions.open"))
  939. .find("span.wb-invisible").text(i18n.t("gui.actions.open"));
  940.  
  941. viewport.addClass("no-sidepanel-mode");
  942.  
  943. d.resolve();
  944. }, [], this);
  945.  
  946. panelToggleTimeLine.play();
  947. }
  948.  
  949. /**
  950. * Toggles the full-data mode on and off.
  951. *
  952. * @method _toggleFullDataMode
  953. * @param {Boolean} [fullData] if true, switches to full-data mode; if false, switches to summary mode; if undefined, toggle mode based on the current state
  954. * @private
  955. */
  956. function _toggleFullDataMode(fullData) {
  957. _isFullData = UtilMisc.isUndefined(fullData) ? !_isFullData : fullData;
  958.  
  959. if (_isFullData) {
  960. viewport.addClass("full-data-mode"); // set full-data-mode css class BEFORE animation; remove it after it finishes - on onReverseComplete callback
  961. fullDataTimeLine.play();
  962. } else {
  963. fullDataSubpanelChangeTimeLine.reverse(); // play this animation to readjust the sidepanel if the details panel was opened and not closed in full data mode
  964. fullDataTimeLine.reverse();
  965. }
  966.  
  967. // close subpanels
  968. utilDict.forEachEntry(subPanels, function (key) {
  969. hideSubPanel({
  970. origin: key
  971. });
  972. });
  973. }
  974.  
  975. /**
  976. * Changes internal panel width reference based on the window width.
  977. *
  978. * @method updatePanelWidth
  979. * @private
  980. */
  981. function updatePanelWidth() {
  982. panelWidthDefault = windowWidth < layoutWidthThreshold ? 340 : 430;
  983. }
  984.  
  985. /**
  986. * Optimizes layout based on the window width
  987. *
  988. * @method optimizeLayout
  989. * @private
  990. */
  991. function optimizeLayout() {
  992. if ((windowWidth < layoutWidthThreshold && jWindow.width() > layoutWidthThreshold) ||
  993. (windowWidth > layoutWidthThreshold && jWindow.width() < layoutWidthThreshold)) {
  994. windowWidth = jWindow.width();
  995. updatePanelWidth();
  996.  
  997. UtilMisc.resetTimelines(timeLines, true);
  998. }
  999. }
  1000.  
  1001. return {
  1002. /**
  1003. * Initializes layout controller.
  1004. *
  1005. * @method init
  1006. */
  1007. init: function () {
  1008. windowWidth = jWindow.width();
  1009. jWindow.on("resize", optimizeLayout);
  1010. updatePanelWidth();
  1011.  
  1012. UtilMisc.resetTimelines(timeLines);
  1013.  
  1014. Theme
  1015. .fullScreenCallback("onComplete", onFullScreenComplete)
  1016. .fullScreenCallback("onReverseComplete", onFullScreenComplete);
  1017.  
  1018. // initialize the panel popup
  1019. panelPopup = popupManager.registerPopup(panelToggle, "click",
  1020. openPanel, {
  1021. activeClass: cssExpandedClass,
  1022. closeHandler: closePanel
  1023. }
  1024. );
  1025.  
  1026. // set listener to the panel toggle
  1027. topic.subscribe(EventManager.GUI.PANEL_TOGGLE, function (event) {
  1028. panelPopup.toggle(null, event.visible);
  1029. });
  1030.  
  1031. // set listener to the full-screen toggle
  1032. fullScreenPopup = popupManager.registerPopup(fullScreenToggle, "click",
  1033. function (d) {
  1034. Theme.toggleFullScreenMode();
  1035. d.resolve();
  1036. }, {
  1037. activeClass: "button-pressed",
  1038. setClassBefore: true
  1039. }
  1040. );
  1041.  
  1042. // if the vertical space is too small, trigger the full-screen
  1043. if (mapContent.height() < jWindow.height() * 0.6) {
  1044. fullScreenPopup.open();
  1045. }
  1046.  
  1047. adjustHeight();
  1048. },
  1049.  
  1050. /**
  1051. * Toggles the FullScreen mode of the application
  1052. *
  1053. * @method toggleFullScreenMode
  1054. * @param {Boolean} fullscreen true - expand; false - collapse; undefined - toggle;
  1055. */
  1056. toggleFullScreenMode: function (fullscreen) {
  1057. fullScreenPopup.toggle(null, fullscreen);
  1058. },
  1059.  
  1060. /**
  1061. * Returns the state of the full-data mode.
  1062. *
  1063. * @method isFullData
  1064. * @return {Boolean} True is full-data mode on; false otherwise
  1065. */
  1066. isFullData: function () {
  1067. return _isFullData;
  1068. },
  1069.  
  1070. /**
  1071. * Toggles the full-data mode on and off.
  1072. *
  1073. * @method toggleFullDataMode
  1074. * @param {Boolean} [fullData] if true, switches to full-data mode; if false, switches to summary mode; if undefined, toggle mode based on the current state
  1075. */
  1076. toggleFullDataMode: function (fullData) {
  1077. _toggleFullDataMode(fullData);
  1078. },
  1079.  
  1080. /**
  1081. * Fires an event when the subpanel closes or opens.
  1082. *
  1083. * @method subPanelChange
  1084. * @param {Boolean} visible indicates whether the subpanel is visible or not (the panel is considered invisible when it's being destroyed, starts closing)
  1085. * @param {String} origin origin of the subpanel
  1086. * @param {JObject} container subpanel container
  1087. * @param {Boolean} isComplete indicates if subPanel transition has completed or just started
  1088. */
  1089. subPanelChange: function (visible, origin, container, isComplete) {
  1090. // check if the fullData transition is already underway
  1091. if (!fullDataTimeLine.isActive() && _isFullData && !isComplete) {
  1092. // adjust the sidePanel position shifting the right edge to the left, making space for the subpanel to open at
  1093. if (visible) {
  1094. fullDataSubpanelChangeTimeLine.play();
  1095. } else if (!visible) {
  1096. fullDataSubpanelChangeTimeLine.reverse();
  1097. }
  1098. }
  1099.  
  1100. // hide the sidepanel toggle when a subpanel is opened to prevent the user from closing sidepanel with subpanel still opened
  1101. if (!isComplete) {
  1102. if (visible) {
  1103. panelToggle.hide();
  1104. } else {
  1105. panelToggle.show();
  1106. }
  1107. }
  1108.  
  1109. topic.publish(EventManager.GUI.SUBPANEL_CHANGE, {
  1110. visible: visible,
  1111. origin: origin,
  1112. container: container,
  1113. offsetLeft: (container) ? container.width() + 25 + layoutController.getPanelWidth() : layoutController.getPanelWidth(),
  1114. isComplete: isComplete
  1115. });
  1116. },
  1117.  
  1118. /**
  1119. * Returns the outer most `div` of this SidePanel.
  1120. *
  1121. * @method getContainer
  1122. * @return {jObject} The outer most `div` of this SidePanel
  1123. */
  1124. getPanelContainer: function () {
  1125. return panelDiv;
  1126. },
  1127.  
  1128. /**
  1129. * Gets the width of this SidePanel.
  1130. *
  1131. * @method width
  1132. * @return {Number} The width of this SidePanel
  1133. * @for LayoutController
  1134. */
  1135. getPanelWidth: function () {
  1136. return panelDiv.filter(":visible").width();
  1137. }
  1138. };
  1139. }());
  1140.  
  1141. /**
  1142. * Create a new SubPanel with the settings provided.
  1143. *
  1144. * @private
  1145. * @method newSubPanel
  1146. * @param {SubPanelSettings} attr SubPanel settings
  1147. * @return {SubPanel} A newly created SubPanel
  1148. * @for GUI
  1149. */
  1150. function newSubPanel(attr) {
  1151. var subPanel = Object.create(subPanelPrototype);
  1152. subPanel._attr = Object.create(subPanelAttr);
  1153. subPanel.create(attr);
  1154. //adjutSubPanelDimensions(subPanel);
  1155.  
  1156. return subPanel;
  1157. }
  1158.  
  1159. /**
  1160. * Creates and opens a new SubPanel with given settings.
  1161. * If the SubPanel with the requested `origin` is already present, updates its content.
  1162. *
  1163. * @method showSubPanel
  1164. * @private
  1165. * @param {SubPanelSettings} attr Settings for the SubPanel instance
  1166. */
  1167. function showSubPanel(attr) {
  1168. var deferred = new Deferred(),
  1169. subPanel;
  1170.  
  1171. deferred.then(function () {
  1172. attr = subPanel.getAttributes();
  1173. subPanel = subPanels[attr.origin];
  1174.  
  1175. subPanel.open();
  1176. subPanel.getPanel().find(".sub-panel-toggle")
  1177. .on("click", dojoLang.hitch(this, function () {
  1178. hideSubPanel(attr);
  1179.  
  1180. // reset focus back to link where the subpanel was created from
  1181. if (attr.target.selector !== "#map-div") {
  1182. $(attr.target).find(":tabbable").first().focus();
  1183. }
  1184. }));
  1185. });
  1186.  
  1187. // take over the panel spawned by other components
  1188. if (attr.consumeOrigin && subPanels[attr.consumeOrigin]) {
  1189. subPanel = subPanels[attr.consumeOrigin];
  1190. subPanel.changeOrigin(attr.origin);
  1191. subPanel.shiftTarget(attr.target);
  1192.  
  1193. delete subPanels[attr.consumeOrigin];
  1194. subPanels[attr.origin] = subPanel;
  1195. }
  1196.  
  1197. if (subPanels[attr.origin]) {
  1198. // if the panel exists, just update it
  1199. subPanels[attr.origin].update(attr);
  1200. } else if (!attr.update) {
  1201. // create if doesn't
  1202. subPanel = newSubPanel(attr);
  1203. subPanels[attr.origin] = subPanel;
  1204.  
  1205. // close all other panels; and open the newly created one after all others are closed
  1206. UtilMisc.executeOnDone(subPanels,
  1207. function (p, d) {
  1208. if (p && p.getOrigin() !== attr.origin) {
  1209. hideSubPanel({
  1210. origin: p.getOrigin()
  1211. }, 200, d);
  1212. } else {
  1213. d.resolve(true);
  1214. }
  1215. },
  1216. deferred);
  1217. }
  1218. }
  1219.  
  1220. /**
  1221. * Closes the SubPanel whose `origin` is specified in the `attr` parameter.
  1222. *
  1223. * @method hideSubPanel
  1224. * @private
  1225. * @param {SubPanelSettings} attr only `origin` attribute is required here
  1226. * @param {Number} speed Duration of the closing animation
  1227. * @param {Deferred} d The deferred object to be resolved upon successful closing of the panel
  1228. */
  1229. function hideSubPanel(attr, speed, d) {
  1230. var deferred = new Deferred(function () {
  1231. if (d) {
  1232. d.cancel();
  1233. }
  1234. });
  1235.  
  1236. deferred.then(function () {
  1237. // remove the panel from the object after it closes
  1238. delete subPanels[attr.origin]; // more on delete: http://perfectionkills.com/understanding-delete/
  1239. if (d) {
  1240. d.resolve(true);
  1241. }
  1242. });
  1243.  
  1244. if (subPanels[attr.origin]) {
  1245. subPanels[attr.origin].destroy(speed, deferred);
  1246. }
  1247. }
  1248.  
  1249. /**
  1250. * Moves the SubPanel with the specified `origin` in the DOM hierarchy to the new specified `target`; if `target` is not specified, the SubPanel is attached to the SidePanel.
  1251. *
  1252. * @method dockSubPanel
  1253. * @private
  1254. * @param {SubPanelSettings} attr Settings for the SubPanel; only `target` and `origin` are required here
  1255. */
  1256. function dockSubPanel(attr) {
  1257. var target = attr.target || layoutController.getPanelContainer(),
  1258. subPanel = subPanels[attr.origin];
  1259.  
  1260. if (subPanel) {
  1261. //console.log("docking subpanel");
  1262. subPanel.shiftTarget(target);
  1263. }
  1264. }
  1265.  
  1266. /**
  1267. * Finds a SubPanel with `origin` equal to the supplied `consumeOrigin` and
  1268. * + changes its `origin` to the supplied `origin`
  1269. * + moves the SubPanel in the DOM hierarchy and attaches it to the specified target
  1270. *
  1271. * @method captureSubPanel
  1272. * @private
  1273. * @param {SubPanelSettings} attr Settings for the SubPanel; only `origin`, `consumeOrigin` and `target` are required here
  1274. */
  1275. function captureSubPanel(attr) {
  1276. var subPanel;
  1277.  
  1278. if (attr.consumeOrigin === attr.origin && subPanels[attr.consumeOrigin]) {
  1279. subPanel = subPanels[attr.origin];
  1280. subPanel.shiftTarget(attr.target);
  1281. } else if (attr.consumeOrigin && subPanels[attr.consumeOrigin]) {
  1282. subPanel = subPanels[attr.consumeOrigin];
  1283. subPanel.changeOrigin(attr.origin);
  1284. subPanel.shiftTarget(attr.target);
  1285.  
  1286. delete subPanels[attr.consumeOrigin];
  1287. subPanels[attr.origin] = subPanel;
  1288. }
  1289. }
  1290.  
  1291. return {
  1292. /**
  1293. * Call load to initialize the GUI module.
  1294. *
  1295. * @method load
  1296. * @param {Number} id ID of this module
  1297. * @param {Object} req dojo required, can be used to require additional modules, etc.
  1298. * @param {Function} load The callback function to be called as the very last thing in load
  1299. */
  1300. load: function (id, req, load) {
  1301. // measure available space on every page resize
  1302.  
  1303. subPanelTemplate = JSON.parse(tmplHelper.stringifyTemplate(subPanelTemplate));
  1304.  
  1305. layoutController.init();
  1306.  
  1307. // registering help popup
  1308. helpPanelPopup = popupManager.registerPopup(helpToggle, "click",
  1309. function (d) {
  1310. topic.publish(EventManager.GUI.HELP_PANEL_CHANGE, { visible: true });
  1311. topic.publish(EventManager.GUI.TOOLBAR_SECTION_OPEN, { id: "help-section" });
  1312. console.log(EventManager.GUI.HELP_PANEL_CHANGE + "; visible:", true);
  1313.  
  1314. // close this panel if any other panel is opened
  1315. UtilMisc.subscribeOnce(EventManager.GUI.TOOLBAR_SECTION_OPEN, dojoLang.hitch(this,
  1316. function () {
  1317. if (this.isOpen()) {
  1318. this.close();
  1319. }
  1320. })
  1321. );
  1322.  
  1323. helpSectionContainer.slideToggle("fast", function () {
  1324. d.resolve();
  1325. });
  1326. }, {
  1327. activeClass: cssButtonPressedClass,
  1328. target: helpSectionContainer,
  1329. closeHandler: function (d) {
  1330. topic.publish(EventManager.GUI.HELP_PANEL_CHANGE, { visible: false });
  1331. topic.publish(EventManager.GUI.TOOLBAR_SECTION_CLOSE, { id: "help-section" });
  1332. console.log(EventManager.GUI.HELP_PANEL_CHANGE + "; visible:", false);
  1333.  
  1334. helpSectionContainer.slideToggle("fast", function () {
  1335. d.resolve();
  1336. });
  1337. },
  1338. resetFocusOnClose: true
  1339. }
  1340. );
  1341.  
  1342. //Start AddLayer popup controller
  1343. addLayerPanelPopup = popupManager.registerPopup(addLayerToggle, "click",
  1344. function (d) {
  1345. topic.publish(EventManager.GUI.ADD_LAYER_PANEL_CHANGE, { visible: true });
  1346. topic.publish(EventManager.GUI.TOOLBAR_SECTION_OPEN, { id: "add-layer-section" });
  1347. console.log(EventManager.GUI.ADD_LAYER_PANEL_CHANGE + " visible:", true);
  1348.  
  1349. // close this panel if any other panel is opened
  1350. UtilMisc.subscribeOnce(EventManager.GUI.TOOLBAR_SECTION_OPEN, dojoLang.hitch(this,
  1351. function () {
  1352. if (this.isOpen()) {
  1353. this.close();
  1354. }
  1355. })
  1356. );
  1357.  
  1358. addLayerSectionContainer.slideToggle("fast", function () {
  1359. d.resolve();
  1360. });
  1361. }, {
  1362. activeClass: cssButtonPressedClass,
  1363. target: addLayerSectionContainer,
  1364. closeHandler: function (d) {
  1365. topic.publish(EventManager.GUI.ADD_LAYER_PANEL_CHANGE, { visible: false });
  1366. topic.publish(EventManager.GUI.TOOLBAR_SECTION_CLOSE, { id: "add-layer-section" });
  1367. console.log(EventManager.GUI.ADD_LAYER_PANEL_CHANGE + " visible:", false);
  1368.  
  1369. addLayerSectionContainer.slideToggle("fast", function () {
  1370. d.resolve();
  1371. });
  1372. },
  1373. resetFocusOnClose: true
  1374. }
  1375. );
  1376.  
  1377. $("#addLayer-add").on("click", function () {
  1378. topic.publish(EventManager.Map.ADD_LAYER, null);
  1379.  
  1380. addLayerPanelPopup.close();
  1381. });
  1382. //End Add Layer
  1383.  
  1384. //start extended grid
  1385. topic.subscribe(EventManager.GUI.DATAGRID_EXPAND, function () {
  1386. layoutController.toggleFullDataMode();
  1387. });
  1388. //end extended grid
  1389.  
  1390. topic.subscribe(EventManager.GUI.TOGGLE_FULLSCREEN, function (evt) {
  1391. layoutController.toggleFullScreenMode(evt.expand);
  1392. });
  1393.  
  1394. topic.subscribe(EventManager.GUI.SUBPANEL_OPEN, function (attr) {
  1395. showSubPanel(attr);
  1396. });
  1397.  
  1398. topic.subscribe(EventManager.GUI.SUBPANEL_CLOSE, function (attr) {
  1399. if (attr.origin === "all") {
  1400. utilDict.forEachEntry(subPanels, function (key) {
  1401. //attr.origin = key;
  1402. hideSubPanel({
  1403. origin: key
  1404. });
  1405. });
  1406. } else {
  1407. dojoArray.forEach(attr.origin.split(","), function (element) {
  1408. //attr.origin = element;
  1409. hideSubPanel({
  1410. origin: element
  1411. });
  1412. });
  1413. }
  1414. });
  1415.  
  1416. topic.subscribe(EventManager.GUI.SUBPANEL_DOCK, function (attr) {
  1417. var na;
  1418. if (attr.origin === "all") {
  1419. utilDict.forEachEntry(subPanels, function (key) {
  1420. na = Object.create(attr);
  1421. na.origin = key;
  1422. dockSubPanel(na);
  1423. });
  1424. } else {
  1425. dojoArray.forEach(attr.origin.split(","), function (element) {
  1426. na = Object.create(attr);
  1427. na.origin = element;
  1428. dockSubPanel(na);
  1429. });
  1430. }
  1431. console.log(EventManager.GUI.SUBPANEL_DOCK, attr);
  1432. });
  1433.  
  1434. topic.subscribe(EventManager.GUI.SUBPANEL_CAPTURE, function (attr) {
  1435. var na;
  1436. if (attr.consumeOrigin === "all") {
  1437. utilDict.forEachEntry(subPanels, function (key) {
  1438. na = Object.create(attr);
  1439. na.consumeOrigin = key;
  1440. captureSubPanel(na);
  1441. });
  1442. } else {
  1443. dojoArray.forEach(attr.consumeOrigin.split(","), function (element) {
  1444. na = Object.create(attr);
  1445. na.consumeOrigin = element;
  1446. captureSubPanel(na);
  1447. });
  1448. }
  1449. console.log(EventManager.GUI.SUBPANEL_CAPTURE, attr);
  1450. });
  1451.  
  1452. sidePanelTabList.find("li a").click(function () {
  1453. var selectedPanelId = $(this).attr("href").substr(1);
  1454.  
  1455. sidePanelTabPanels.find("details[id=" + selectedPanelId + "]").each(
  1456. function () {
  1457. topic.publish(EventManager.GUI.TAB_SELECTED, {
  1458. id: this.id,
  1459. tabName: $(this).data("panel-name")
  1460. });
  1461. });
  1462.  
  1463. // the panel currently open is being deselected
  1464. sidePanelTabPanels.find("details[aria-expanded=true]").each(
  1465. function () {
  1466. topic.publish(EventManager.GUI.TAB_DESELECTED, {
  1467. id: this.id,
  1468. tabName: $(this).data("panel-name")
  1469. });
  1470. });
  1471. });
  1472.  
  1473. // List of objects containing an event name and an event argument. The events should
  1474. // be anything to wait for before publishing a map extent change, it should include
  1475. // anything that will change the size of the map (e.g. fullscreen, closing the panel).
  1476. // If the map extent change occurs BEFORE something that changes the size of the map (e.g. fullscreen)
  1477. // then the map extent will change again.
  1478. var waitList = [];
  1479.  
  1480. if (!RAMP.state.ui.sidePanelOpened) {
  1481. // NOTE: panel change not triggered here (see map extent change below)
  1482. waitList.push({
  1483. publishName: EventManager.GUI.PANEL_TOGGLE,
  1484. eventArg: {
  1485. origin: "bootstrapper",
  1486. visible: RAMP.state.ui.sidePanelOpened
  1487. },
  1488. subscribeName: EventManager.GUI.PANEL_CHANGE
  1489. });
  1490. }
  1491.  
  1492. if (RAMP.state.ui.fullscreen) {
  1493. // NOTE: fullscreen not triggered here (see map extent change below)
  1494. waitList.push({
  1495. publishName: EventManager.GUI.TOGGLE_FULLSCREEN,
  1496. eventArg: {
  1497. expand: true
  1498. },
  1499. subscribeName: EventManager.GUI.FULLSCREEN_CHANGE
  1500. });
  1501. }
  1502.  
  1503. // return the callback
  1504. load();
  1505.  
  1506. // This should be the last thing that happens
  1507. if (waitList.isEmpty()) {
  1508. topic.publish(EventManager.GUI.UPDATE_COMPLETE);
  1509. } else {
  1510. // Wait for things such as fullscreen or panel collapse
  1511. // to finish before publishing the UPDATE_COMPLETE.
  1512.  
  1513. // Note it's important to subscribe to the events, then
  1514. // publish them, that's why it was done in such an obscure way
  1515. // using the waitList (otherwise if we just publish the
  1516. // event like above, then subscribe to it here, the event
  1517. // might have completed before reaching this point)
  1518. var eventNames = dojoArray.map(waitList, function (obj) {
  1519. return obj.subscribeName;
  1520. });
  1521.  
  1522. UtilMisc.subscribeAll(eventNames, function () {
  1523. topic.publish(EventManager.GUI.UPDATE_COMPLETE);
  1524. });
  1525.  
  1526. dojoArray.forEach(waitList, function (obj) {
  1527. topic.publish(obj.publishName, obj.eventArg);
  1528. });
  1529. }
  1530. }
  1531. };
  1532. }
  1533. );