Reusable Accessible Mapping Platform

API Docs for: 3.0.0
Show:

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

  1. /*global define, $, window, TweenLite, TimelineLite, tmpl, i18n, console, jQuery */
  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. this.timeLine.eventCallback("onReverseComplete",
  429. function () {
  430. if (this._attr.doAfterHide) {
  431. this._attr.doAfterHide();
  432. }
  433. if (this._attr.doOnDestroy) {
  434. this._attr.doOnDestroy();
  435. }
  436. this._visible = false;
  437. layoutController.subPanelChange(false, this._attr.origin, null, true);
  438.  
  439. this.container.remove();
  440.  
  441. if (deferred) {
  442. deferred.resolve(true);
  443. }
  444. }, [], this);
  445.  
  446. this.timeLine.reverse();
  447. },
  448.  
  449. /**
  450. * Reopens the SubPanel - stops the closing animation and initiates the opening animation.
  451. *
  452. * @method reopen
  453. */
  454. reopen: function () {
  455. //this.panel.stop();
  456. this.timeLine.pause();
  457. this._closing = false;
  458. if (this._destroyDeferred) {
  459. this._destroyDeferred.cancel();
  460. this._destroyDeferred = null;
  461. }
  462.  
  463. this.open();
  464. },
  465.  
  466. /**
  467. * Opens the SubPanel. Sends out `EventManager.GUI.SUBPANEL_CHANGE` event.
  468. *
  469. * @method open
  470. */
  471. open: function () {
  472. if (this._attr.doOnOpen) {
  473. this._attr.doOnOpen();
  474. }
  475. this._visible = true;
  476. layoutController.subPanelChange(true, this._attr.origin, this.container, false);
  477.  
  478. this.timeLine.play();
  479. },
  480.  
  481. /**
  482. * Assigns a new origin to the SubPanel.
  483. *
  484. * @method changeOrigin
  485. * @param {String} newOrigin The new origin of the SubPanel.
  486. */
  487. changeOrigin: function (newOrigin) {
  488. this._attr.origin = newOrigin;
  489. },
  490.  
  491. /**
  492. * Shifts the SubPanel to the new node in the DOM.
  493. *
  494. * @method shiftTarget
  495. * @param {jObject} newTarget A node in the DOM to shift the SubPanel to
  496. */
  497. shiftTarget: function (newTarget) {
  498. if (this._attr.target !== newTarget) {
  499. // remove animation class to prevent flickering of data
  500. this._subPanelContentDiv.find(".fadeInDown").removeClass("fadeInDown");
  501. newTarget.after(this.container);
  502. this._attr.target = newTarget;
  503. }
  504. },
  505.  
  506. /**
  507. * Creates a new instance of SubPanel.
  508. *
  509. * @method create
  510. * @param {SubPanelSettings} a Settings for the SubPanel
  511. */
  512. create: function (a) {
  513. var //subPanelContent,
  514. subPanelString,
  515. parsedContent;
  516.  
  517. console.log("create panel, ", a.origin);
  518.  
  519. a.guid = a.guid || UtilMisc.guid();
  520.  
  521. dojoLang.mixin(this._attr, a);
  522.  
  523. tmpl.cache = {};
  524. tmpl.templates = subPanelTemplate;
  525.  
  526. subPanelString = tmpl(this._attr.templateKey,
  527. dojoLang.mixin(
  528. this._attr,
  529. {
  530. closeTitle: i18n.t('gui.actions.close')
  531. }
  532. )
  533. );
  534.  
  535. //subPanelContent = String.format(subPanelContentTemplate, this._attr.panelName, this._attr.title);
  536. //subPanelString = String.format(subPanelTemplate2, this._attr.containerClass, subPanelContent);
  537.  
  538. this.container = $(subPanelString).insertAfter(this._attr.target);
  539. this.panel = this.container.find(".sub-panel");
  540.  
  541. this._subPanelContentDiv = this.panel.find(".sub-panel-content");
  542. this._panelTitle = this.panel.find(".panel-title");
  543. this._panelContentDiv = this.panel.find(".panel-content-div");
  544.  
  545. // set content
  546. parsedContent = this.parseContent(this._attr.content);
  547. this._panelContentDiv.empty().append(parsedContent);
  548.  
  549. this.timeLine = new TimelineLite(
  550. {
  551. paused: true,
  552. onComplete: function () {
  553. if (this._attr.doAfterOpen) {
  554. this._attr.doAfterOpen();
  555. }
  556.  
  557. layoutController.subPanelChange(true, this._attr.origin, this.container, true);
  558. },
  559. onCompleteScope: this
  560. })
  561. .to(this.panel, this._animatePanelDuration,
  562. {
  563. left: 0,
  564. ease: "easeOutCirc"
  565. });
  566.  
  567. Theme.tooltipster(this.container);
  568.  
  569. this.update(this._attr);
  570. },
  571.  
  572. /**
  573. * Performs an update of the content and title of the SubPanel, running appropriate animation and `doOn-` / `doAfter-` functions.
  574. *
  575. * @method update
  576. * @param {SubPanelSettings} a New settings for the SubPanel
  577. */
  578. update: function (a) {
  579. // helper functions
  580. var animateContentDuration = 300,
  581. sOut = '<ul class="loadingAnimation"><li></li><li></li><li></li><li></li><li></li><li></li></ul>',
  582.  
  583. updateDefered = [new Deferred(), new Deferred()],
  584.  
  585. animateContent = function (node, newData, d) {
  586. if (newData) {
  587. node.addClass('animated fadeOutDown');
  588. window.setTimeout(dojoLang.hitch(this,
  589. function () {
  590. node
  591. //.html(newData)
  592. .empty().append(newData)
  593. .removeClass("fadeOutDown")
  594. .addClass('animated fadeInDown'); //.find(".shorten-candidate").shorten();
  595.  
  596. d.resolve();
  597. }),
  598. animateContentDuration);
  599. }
  600. },
  601.  
  602. setContent = function (node, oldData, newData, parsedData, visible, d) {
  603. newData = (newData === null) ? parsedData = sOut : newData;
  604. if (newData) {
  605. if (newData !== oldData) {
  606. if (visible) {
  607. //ideally, need to wait until the animation completes before proceeding?
  608. animateContent(node, parsedData, d);
  609. } else {
  610. node.empty().append(parsedData);
  611. d.resolve();
  612. }
  613. } else {
  614. d.resolve();
  615. }
  616. } else {
  617. d.resolve();
  618. }
  619. },
  620.  
  621. updateContent = dojoLang.hitch(this,
  622. function (a) {
  623. /*this._subPanelContentDiv.animate({
  624. scrollTop: 0
  625. }, animateContentDuration, "easeOutCirc");*/
  626.  
  627. TweenLite.to(this._subPanelContentDiv, animateContentDuration / 1000,
  628. {scrollTop: 0, ease: "easeOutCirc"});
  629.  
  630. setContent(this._panelTitle, this._attr.title, a.title, a.title, this._visible, updateDefered[0]);
  631. setContent(this._panelContentDiv, this._attr.content, a.content, this.parseContent(a.content), this._visible, updateDefered[1]);
  632.  
  633. dojoLang.mixin(this._attr, a);
  634. }
  635. );
  636.  
  637. // doAfterUpdate should be called AFTER update (animation) completes...
  638. UtilMisc.afterAll(updateDefered, function () {
  639. if (a.doAfterUpdate) {
  640. a.doAfterUpdate();
  641. }
  642. });
  643.  
  644. // panel is closing; new data is not an update
  645. if (this._closing && !a.update) {
  646. //
  647. if (this._attr.guid !== a.guid) {
  648. if (this._attr.doOnHide) {
  649. this._attr.doOnHide();
  650. }
  651. if (this._attr.doAfterHide) {
  652. this._attr.doAfterHide();
  653. }
  654.  
  655. // move panel to the new target
  656. a.target.after(this.container);
  657. updateContent(a);
  658. }
  659.  
  660. this.reopen();
  661.  
  662. // panel is not closing
  663. } else if (!this._closing) {
  664. // data is not an update
  665. if (!a.update && this._attr.guid !== a.guid) {
  666. if (this._attr.doOnHide) {
  667. this._attr.doOnHide();
  668. }
  669. if (this._attr.doAfterHide) {
  670. this._attr.doAfterHide();
  671. }
  672.  
  673. // move panel to the new target
  674. a.target.after(this.container);
  675. updateContent(a);
  676.  
  677. if (a.doOnOpen) {
  678. a.doOnOpen();
  679. }
  680. if (a.doAfterOpen) {
  681. a.doAfterOpen();
  682. }
  683. }
  684.  
  685. // guid is the same - data can update or not (should be an update)
  686. //if (a.update && attr.guid === a.guid) {
  687. if (this._attr.guid === a.guid) {
  688. updateContent(a);
  689. }
  690. }
  691. }
  692. },
  693.  
  694. helpToggle = $("#helpToggle"),
  695. helpSectionContainer = $("#help-section-container"),
  696. helpSection = $("#help-section"),
  697.  
  698. addLayerToggle = $("#addLayer-toggle"),
  699. addLayerSectionContainer = $("#addLayer-section-container"),
  700. //AddLayerSection = $("#addLayer-section"),
  701.  
  702. //viewPortHeight,
  703.  
  704. cssButtonPressedClass = "button-pressed",
  705. cssExpandedClass = "state-expanded",
  706.  
  707. helpPanelPopup,
  708. addLayerPanelPopup,
  709.  
  710. transitionDuration = 0.5,
  711.  
  712. layoutController;
  713.  
  714. layoutController = (function () {
  715. var viewport = $(".viewport"),
  716. mapDiv = $("#map-div"),
  717. mapContent = $("#mapContent"),
  718. fullScreenToggle = $("#fullScreenToggle"),
  719. fullScreenPopup,
  720.  
  721. mapToolbar = $("#map-toolbar"),
  722. basemapControls = $("#basemapControls"),
  723.  
  724. panelDiv = $("#panel-div"),
  725. panelToggle = $("#panel-toggle"),
  726. panelPopup,
  727. panelWidthDefault,
  728.  
  729. windowWidth,
  730.  
  731. duration = 0.5,
  732.  
  733. layoutChange,
  734.  
  735. _isFullData = false,
  736. fullDataTimeLine = new TimelineLite({
  737. paused: true,
  738. onComplete: function () {
  739. adjustHeight();
  740. layoutChange();
  741.  
  742. mapToolbar
  743. .find(".map-toolbar-item-button")
  744. .map(function (i, node) {
  745. node = $(node);
  746. node
  747. .addClass("_tooltip")
  748. .attr(
  749. "title",
  750. node.find("span").text()
  751. );
  752. });
  753. Theme.tooltipster(mapToolbar);
  754.  
  755. console.log("finished", EventManager.Datagrid.APPLY_EXTENT_FILTER);
  756. //topic.publish(EventManager.Datagrid.APPLY_EXTENT_FILTER);
  757. },
  758. onReverseComplete: function () {
  759. adjustHeight();
  760. layoutChange();
  761.  
  762. Theme.tooltipster(mapToolbar, null, "destroy");
  763.  
  764. mapToolbar
  765. .find(".map-toolbar-item-button")
  766. .removeClass("_tooltip")
  767. .removeAttr("title");
  768.  
  769. console.log("reverse finished", EventManager.Datagrid.APPLY_EXTENT_FILTER);
  770. //topic.publish(EventManager.Datagrid.APPLY_EXTENT_FILTER);
  771. }
  772. }),
  773.  
  774. panelToggleTimeLine = new TimelineLite({ paused: true }),
  775. fullDataSubpanelChangeTimeLine = new TimelineLite({ paused: true });
  776.  
  777. /**
  778. * Fires an event when the layout of the page changes.
  779. *
  780. * @method layoutChange
  781. * @private
  782. */
  783. layoutChange = function () {
  784. if (!_isFullData) {
  785. console.log("GUI --> EventManager.GUI.LAYOUT_CHANGE");
  786. topic.publish(EventManager.GUI.LAYOUT_CHANGE);
  787. }
  788. };
  789.  
  790. function adjustHeight() {
  791. helpSection.css({
  792. "max-height": jWindow.height() - (_isFullData ? jWindow.height() * 0.2 : mapToolbar.offset().top) - 90 // 56 is an arbitrary-wide gap between the help panel and the upper toolbar
  793. });
  794. }
  795.  
  796. function onFullScreenComplete() {
  797. adjustHeight();
  798. layoutChange();
  799. topic.publish(EventManager.GUI.FULLSCREEN_CHANGE, {
  800. visible: Theme.isFullScreen()
  801. });
  802. }
  803.  
  804. /**
  805. * Return the default width of the SidePanel.
  806. *
  807. * @method getPanelWidthDefault
  808. * @private
  809. * @return {Number} The default width of the SidePanel
  810. */
  811. function getPanelWidthDefault() {
  812. if (!panelWidthDefault) {
  813. panelWidthDefault = panelDiv.width();
  814. }
  815.  
  816. return panelWidthDefault;
  817. }
  818.  
  819. function createTimelines() {
  820. // fullDataTransition
  821. fullDataTimeLine
  822. .set(viewport, { className: "+=full-data-mode" })
  823. .fromTo(mapDiv, transitionDuration, { width: "auto" }, { right: "auto", width: 35, ease: "easeOutCirc" }, 0)
  824.  
  825. .fromTo(mapContent, transitionDuration, { opacity: 1 }, { opacity: 0, ease: "easeOutCirc" }, 0)
  826. .set(mapContent, { top: "500px" })
  827.  
  828. .to(panelToggle, transitionDuration, { right: -13, ease: "easeOutCirc" }, 0)
  829. .set(panelToggle, { display: "none" })
  830.  
  831. .to(basemapControls, transitionDuration / 2, { opacity: 0, ease: "easeOutCirc" }, 0)
  832. .to(basemapControls, 0, { display: "none" }, transitionDuration / 2)
  833. .fromTo(mapToolbar, transitionDuration / 2,
  834. { width: "100%", height: "32px" },
  835. { width: "32px", height: $("#map-div").height(), ease: "easeOutCirc" }, duration / 2)
  836.  
  837. .to(mapToolbar.find(".map-toolbar-item-button span"), transitionDuration / 2, { width: 0, ease: "easeOutCirc" }, 0)
  838. .set(mapToolbar.find(".map-toolbar-item-button span"), { display: "none" }, transitionDuration / 2)
  839.  
  840. .fromTo(panelDiv.find(".wb-tabs > ul li:first"), transitionDuration, { width: "50%" }, { width: "0%", display: "none", ease: "easeOutCirc" }, 0)
  841. .fromTo(panelDiv.find(".wb-tabs > ul li:last"), transitionDuration, { width: "50%" }, { width: "100%", className: "+=h5", ease: "easeOutCirc" }, 0);
  842.  
  843. // panelToggleTransition
  844. panelToggleTimeLine
  845. .set(viewport, { className: "+=no-sidepanel-mode" })
  846. .fromTo(panelDiv, transitionDuration, { right: 0 }, { right: -getPanelWidthDefault(), ease: "easeOutCirc" }, 0)
  847. .set(panelDiv, { display: "none" }, transitionDuration)
  848. .fromTo(mapDiv, transitionDuration, { right: getPanelWidthDefault() }, { right: 0, ease: "easeOutCirc" }, 0);
  849.  
  850. fullDataSubpanelChangeTimeLine
  851. .fromTo(panelDiv, transitionDuration,
  852. { right: "0px" },
  853. { right: getPanelWidthDefault(), ease: "easeOutCirc", immediateRender: false });
  854. }
  855.  
  856. function killTimelines() {
  857. fullDataSubpanelChangeTimeLine.kill();
  858. panelToggleTimeLine.kill();
  859. fullDataTimeLine.kill();
  860. }
  861.  
  862. function recreateTimelines() {
  863. panelWidthDefault = null;
  864. killTimelines();
  865. createTimelines();
  866. }
  867. /**
  868. * Publishes `PANEL_CHANGE` event when the visibility of the SidePanel changes.
  869. *
  870. * @method panelChange
  871. * @param {Boolean} visible Indicates whether the SidePanel is visible or not
  872. * @for SidePanel
  873. * @private
  874. */
  875. function panelChange(visible) {
  876. topic.publish(EventManager.GUI.PANEL_CHANGE, {
  877. visible: visible
  878. });
  879. }
  880.  
  881. /**
  882. * Slides the SidePanel open.
  883. *
  884. * @method openPanel
  885. * @private
  886. * @param {Deferred} d A deferred to be resolved upon completion of the animation
  887. */
  888. function openPanel(d) {
  889. /*jshint validthis: true */
  890. panelToggleTimeLine.eventCallback("onReverseComplete",
  891. function () {
  892. layoutChange();
  893. panelChange(true);
  894.  
  895. panelToggle
  896. .tooltipster("content", i18n.t("gui.actions.close"))
  897. .find("span.wb-invisible").text(i18n.t("gui.actions.close"));
  898.  
  899. d.resolve();
  900. }, [], this);
  901.  
  902. panelToggleTimeLine.reverse();
  903. }
  904.  
  905. /**
  906. * Slide the SidePanel close
  907. *
  908. * @method closePanel
  909. * @private
  910. * @param {Deferred} d A deferred to be resolved upon completion of the animation
  911. */
  912. function closePanel(d) {
  913. /*jshint validthis: true */
  914. panelToggleTimeLine.eventCallback("onComplete",
  915. function () {
  916. console.log("GUI <-- map/update-end from gui");
  917. layoutChange();
  918. panelChange(false);
  919.  
  920. panelToggle
  921. .tooltipster("content", i18n.t("gui.actions.open"))
  922. .find("span.wb-invisible").text(i18n.t("gui.actions.open"));
  923.  
  924. d.resolve();
  925. }, [], this);
  926.  
  927. panelToggleTimeLine.play();
  928. }
  929.  
  930. function _toggleFullDataMode(fullData) {
  931. _isFullData = UtilMisc.isUndefined(fullData) ? !_isFullData : fullData;
  932.  
  933. if (_isFullData) {
  934. TweenLite
  935. .fromTo(panelDiv, transitionDuration,
  936. { width: getPanelWidthDefault(), right: 0, left: "auto" },
  937. { left: 35, right: 0, width: "auto", ease: "easeOutCirc" });
  938.  
  939. fullDataTimeLine.play();
  940. } else {
  941. TweenLite
  942. //.set(panelDiv, { }, 0)
  943. .fromTo(panelDiv, transitionDuration,
  944. { width: panelDiv.css("width"), clearProps: "left", right: panelDiv.css("right") },
  945. { right: 0, width: getPanelWidthDefault(), ease: "easeInCirc" });
  946.  
  947. fullDataTimeLine.reverse();
  948. }
  949.  
  950. utilDict.forEachEntry(subPanels, function (key) {
  951. hideSubPanel({
  952. origin: key
  953. });
  954. });
  955. }
  956.  
  957. function optimizeLayout() {
  958. if ((windowWidth < 1200 && jWindow.width() > 1200) ||
  959. (windowWidth > 1200 && jWindow.width() < 1200)) {
  960. recreateTimelines();
  961.  
  962. windowWidth = jWindow.width();
  963. }
  964. }
  965.  
  966. return {
  967. init: function () {
  968. createTimelines();
  969.  
  970. windowWidth = jWindow.width();
  971. jWindow.on("resize", optimizeLayout);
  972.  
  973. Theme
  974. .fullScreenCallback("onComplete", onFullScreenComplete)
  975. .fullScreenCallback("onReverseComplete", onFullScreenComplete);
  976.  
  977. // initialize the panel popup
  978. panelPopup = popupManager.registerPopup(panelToggle, "click",
  979. openPanel, {
  980. activeClass: cssExpandedClass,
  981. closeHandler: closePanel
  982. }
  983. );
  984.  
  985. // set listener to the panel toggle
  986. topic.subscribe(EventManager.GUI.PANEL_TOGGLE, function (event) {
  987. panelPopup.toggle(null, event.visible);
  988. });
  989. // set listener to the full-screen toggle
  990. fullScreenPopup = popupManager.registerPopup(fullScreenToggle, "click",
  991. function (d) {
  992. Theme.toggleFullScreenMode();
  993. d.resolve();
  994. }, {
  995. activeClass: "button-pressed",
  996. setClassBefore: true
  997. }
  998. );
  999.  
  1000. // if the vertical space is too small, trigger the full-screen
  1001. if (mapContent.height() < jWindow.height() * 0.6) {
  1002. fullScreenPopup.open();
  1003. }
  1004.  
  1005. adjustHeight();
  1006. },
  1007.  
  1008. /**
  1009. * Toggles the FullScreen mode of the application
  1010. *
  1011. * @method toggleFullScreenMode
  1012. * @private
  1013. * @param {boolean} fullscreen true - expand; false - collapse; undefined - toggle;
  1014. */
  1015. toggleFullScreenMode: function (fullscreen) {
  1016. fullScreenPopup.toggle(null, fullscreen);
  1017. },
  1018.  
  1019. isFullData: function () {
  1020. return _isFullData;
  1021. },
  1022.  
  1023. toggleFullDataMode: function (fullData) {
  1024. _toggleFullDataMode(fullData);
  1025. },
  1026.  
  1027. /**
  1028. * Fires an event when the subpanel closes or opens.
  1029. *
  1030. * @method subPanelChange
  1031. * @private
  1032. * @param {Boolean} visible indicates whether the panel is visible or not
  1033. * @param {String} origin origin of the subpanel
  1034. * @param {JObject} container subpanel container
  1035. * @param {Boolean} isComplete indicates if subPanel transition has completed or just started
  1036. */
  1037. subPanelChange: function (visible, origin, container, isComplete) {
  1038. // check if the fullData transition is already underway
  1039. if (!fullDataTimeLine.isActive() && _isFullData && !isComplete) {
  1040. // adjust the sidePanel position shifting the right edge to the left, making space for the subpanel to open at
  1041. if (visible) {
  1042. fullDataSubpanelChangeTimeLine.play(0);
  1043. } else if (!visible) {
  1044. fullDataSubpanelChangeTimeLine.reverse();
  1045. }
  1046. }
  1047.  
  1048. // hide the sidepanel toggle when a subpanel is opened to prevent the user from closing sidepanel with subpanel still opened
  1049. if (!isComplete) {
  1050. if (visible) {
  1051. panelToggle.hide();
  1052. } else {
  1053. panelToggle.show();
  1054. }
  1055. }
  1056.  
  1057. topic.publish(EventManager.GUI.SUBPANEL_CHANGE, {
  1058. visible: visible,
  1059. origin: origin,
  1060. container: container,
  1061. offsetLeft: (container) ? container.width() + 25 + layoutController.getPanelWidth() : layoutController.getPanelWidth(),
  1062. isComplete: isComplete
  1063. });
  1064. },
  1065.  
  1066. /**
  1067. * Returns the outer most `div` of this SidePanel.
  1068. *
  1069. * @method getContainer
  1070. * @return {jObject} The outer most `div` of this SidePanel
  1071. */
  1072. getPanelContainer: function () {
  1073. return panelDiv;
  1074. },
  1075.  
  1076. /**
  1077. * Gets the width of this SidePanel.
  1078. *
  1079. * @method width
  1080. * @return {Number} The width of this SidePanel
  1081. */
  1082. getPanelWidth: function () {
  1083. return panelDiv.filter(":visible").width();
  1084. }
  1085. };
  1086. }());
  1087.  
  1088. /**
  1089. * Create a new SubPanel with the settings provided.
  1090. *
  1091. * @private
  1092. * @method newSubPanel
  1093. * @param {SubPanelSettings} attr SubPanel settings
  1094. * @return {SubPanel} A newly created SubPanel
  1095. * @for GUI
  1096. */
  1097. function newSubPanel(attr) {
  1098. var subPanel = Object.create(subPanelPrototype);
  1099. subPanel._attr = Object.create(subPanelAttr);
  1100. subPanel.create(attr);
  1101. //adjutSubPanelDimensions(subPanel);
  1102.  
  1103. return subPanel;
  1104. }
  1105.  
  1106. /**
  1107. * Adjusts dimensions of the help panel relative to the mapContent `div`.
  1108. *
  1109. * @method adjustHelpDimensions
  1110. * @private
  1111. */
  1112. /*function adjustHelpDimensions() {
  1113. helpSection.css({
  1114. "max-height": mapContent.height() - 56 // 56 is an arbitrary-wide gap between the help panel and the upper toolbar
  1115. });
  1116. }*/
  1117.  
  1118. /**
  1119. * Adjusts the dimensions and position of the SubPanel when layout of the page is changing.
  1120. *
  1121. * @method adjutSubPanelDimensions
  1122. * @private
  1123. * @param {SubPanel} subPanel SubPanel whose dimensions and position need to be adjusted
  1124. */
  1125. /*function adjutSubPanelDimensions(subPanel) {
  1126. function adjust(p, d) {
  1127. if (p) {
  1128. p.getContainer()
  1129. .height(viewPortHeight - $("#map-toolbar").height()); //mapToolbar.height());
  1130.  
  1131. //.position({
  1132. //my: "right top",
  1133. //at: "right top+32",
  1134. //of: "#map-div" // mapContent
  1135. //});
  1136. if (d) {
  1137. d.resolve(true);
  1138. }
  1139. }
  1140. }
  1141.  
  1142. if (subPanel) {
  1143. adjust(subPanel);
  1144. } else {
  1145. UtilMisc.executeOnDone(subPanels, adjust);
  1146. }
  1147. }*/
  1148.  
  1149. /**
  1150. * Creates and opens a new SubPanel with given settings.
  1151. * If the SubPanel with the requested `origin` is already present, updates its content.
  1152. *
  1153. * @method showSubPanel
  1154. * @private
  1155. * @param {SubPanelSettings} attr Settings for the SubPanel instance
  1156. */
  1157. function showSubPanel(attr) {
  1158. var deferred = new Deferred(),
  1159. subPanel;
  1160.  
  1161. deferred.then(function () {
  1162. attr = subPanel.getAttributes();
  1163. subPanel = subPanels[attr.origin];
  1164.  
  1165. subPanel.open();
  1166. subPanel.getPanel().find(".sub-panel-toggle")
  1167. .on("click", dojoLang.hitch(this, function () {
  1168. hideSubPanel(attr);
  1169.  
  1170. // reset focus back to link where the subpanel was created from
  1171. if (attr.target.selector !== "#map-div") {
  1172. $(attr.target).find(":tabbable").first().focus();
  1173. }
  1174. }));
  1175.  
  1176. loadIndicator.animate({
  1177. right: subPanel.getPanel().width() + 6
  1178. }, "easeOutCirc");
  1179. });
  1180.  
  1181. // take over the panel spawned by other components
  1182. if (attr.consumeOrigin && subPanels[attr.consumeOrigin]) {
  1183. subPanel = subPanels[attr.consumeOrigin];
  1184. subPanel.changeOrigin(attr.origin);
  1185. subPanel.shiftTarget(attr.target);
  1186.  
  1187. delete subPanels[attr.consumeOrigin];
  1188. subPanels[attr.origin] = subPanel;
  1189. }
  1190.  
  1191. if (subPanels[attr.origin]) {
  1192. // if the panel exists, just update it
  1193. subPanels[attr.origin].update(attr);
  1194. } else if (!attr.update) {
  1195. // create if doesn't
  1196. subPanel = newSubPanel(attr);
  1197. subPanels[attr.origin] = subPanel;
  1198.  
  1199. // close all other panels; and open the newly created one after all others are closed
  1200. UtilMisc.executeOnDone(subPanels,
  1201. function (p, d) {
  1202. if (p && p.getOrigin() !== attr.origin) {
  1203. hideSubPanel({
  1204. origin: p.getOrigin()
  1205. }, 200, d);
  1206. } else {
  1207. d.resolve(true);
  1208. }
  1209. },
  1210. deferred);
  1211. }
  1212. }
  1213.  
  1214. /**
  1215. * Closes the SubPanel whose `origin` is specified in the `attr` parameter.
  1216. *
  1217. * @method hideSubPanel
  1218. * @private
  1219. * @param {SubPanelSettings} attr only `origin` attribute is required here
  1220. * @param {Number} speed Duration of the closing animation
  1221. * @param {Deferred} d The deferred object to be resolved upon successful closing of the panel
  1222. */
  1223. function hideSubPanel(attr, speed, d) {
  1224. var deferred = new Deferred(function () {
  1225. if (d) {
  1226. d.cancel();
  1227. }
  1228. });
  1229.  
  1230. deferred.then(function () {
  1231. // remove the panel from the object after it closes
  1232. delete subPanels[attr.origin]; // more on delete: http://perfectionkills.com/understanding-delete/
  1233. if (d) {
  1234. d.resolve(true);
  1235. }
  1236. });
  1237.  
  1238. if (subPanels[attr.origin]) {
  1239. subPanels[attr.origin].destroy(speed, deferred);
  1240.  
  1241. loadIndicator.animate({
  1242. right: 3
  1243. }, "easeOutCirc");
  1244. }
  1245. }
  1246.  
  1247. /**
  1248. * 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.
  1249. *
  1250. * @method dockSubPanel
  1251. * @private
  1252. * @param {SubPanelSettings} attr Settings for the SubPanel; only `target` and `origin` are required here
  1253. */
  1254. function dockSubPanel(attr) {
  1255. var target = attr.target || layoutController.getPanelContainer(),
  1256. subPanel = subPanels[attr.origin];
  1257.  
  1258. if (subPanel) {
  1259. //console.log("docking subpanel");
  1260. subPanel.shiftTarget(target);
  1261. }
  1262. }
  1263.  
  1264. /**
  1265. * Finds a SubPanel with `origin` equal to the supplied `consumeOrigin` and
  1266. * + changes its `origin` to the supplied `origin`
  1267. * + moves the SubPanel in the DOM hierarchy and attaches it to the specified target
  1268. *
  1269. * @method captureSubPanel
  1270. * @private
  1271. * @param {SubPanelSettings} attr Settings for the SubPanel; only `origin`, `consumeOrigin` and `target` are required here
  1272. */
  1273. function captureSubPanel(attr) {
  1274. var subPanel;
  1275.  
  1276. if (attr.consumeOrigin === attr.origin && subPanels[attr.consumeOrigin]) {
  1277. subPanel = subPanels[attr.origin];
  1278. subPanel.shiftTarget(attr.target);
  1279. } else if (attr.consumeOrigin && subPanels[attr.consumeOrigin]) {
  1280. subPanel = subPanels[attr.consumeOrigin];
  1281. subPanel.changeOrigin(attr.origin);
  1282. subPanel.shiftTarget(attr.target);
  1283.  
  1284. delete subPanels[attr.consumeOrigin];
  1285. subPanels[attr.origin] = subPanel;
  1286. }
  1287. }
  1288.  
  1289. return {
  1290. /**
  1291. * Call load to initialize the GUI module.
  1292. *
  1293. * @method load
  1294. * @param {Number} id ID of this module
  1295. * @param {?} req ???
  1296. * @param {Function} load The callback function
  1297. */
  1298. load: function (id, req, load) {
  1299. // measure available space on every page resize
  1300.  
  1301. subPanelTemplate = JSON.parse(tmplHelper.stringifyTemplate(subPanelTemplate));
  1302.  
  1303. layoutController.init();
  1304.  
  1305. // registering help popup
  1306. helpPanelPopup = popupManager.registerPopup(helpToggle, "click",
  1307. function (d) {
  1308. topic.publish(EventManager.GUI.HELP_PANEL_CHANGE, { visible: true });
  1309. topic.publish(EventManager.GUI.TOOLBAR_SECTION_OPEN, { id: "help-section" });
  1310. console.log(EventManager.GUI.HELP_PANEL_CHANGE + "; visible:", true);
  1311.  
  1312. // close this panel if any other panel is opened
  1313. UtilMisc.subscribeOnce(EventManager.GUI.TOOLBAR_SECTION_OPEN, dojoLang.hitch(this,
  1314. function () {
  1315. if (this.isOpen()) {
  1316. this.close();
  1317. }
  1318. })
  1319. );
  1320.  
  1321. helpSectionContainer.slideToggle("fast", function () {
  1322. d.resolve();
  1323. });
  1324. }, {
  1325. activeClass: cssButtonPressedClass,
  1326. target: helpSectionContainer,
  1327. closeHandler: function (d) {
  1328. topic.publish(EventManager.GUI.HELP_PANEL_CHANGE, { visible: false });
  1329. topic.publish(EventManager.GUI.TOOLBAR_SECTION_CLOSE, { id: "help-section" });
  1330. console.log(EventManager.GUI.HELP_PANEL_CHANGE + "; visible:", false);
  1331.  
  1332. helpSectionContainer.slideToggle("fast", function () {
  1333. d.resolve();
  1334. });
  1335. },
  1336. resetFocusOnClose: true
  1337. }
  1338. );
  1339.  
  1340. //Start AddLayer popup controller
  1341. addLayerPanelPopup = popupManager.registerPopup(addLayerToggle, "click",
  1342. function (d) {
  1343. topic.publish(EventManager.GUI.ADD_LAYER_PANEL_CHANGE, { visible: true });
  1344. topic.publish(EventManager.GUI.TOOLBAR_SECTION_OPEN, { id: "add-layer-section" });
  1345. console.log(EventManager.GUI.ADD_LAYER_PANEL_CHANGE + " visible:", true);
  1346.  
  1347. // close this panel if any other panel is opened
  1348. UtilMisc.subscribeOnce(EventManager.GUI.TOOLBAR_SECTION_OPEN, dojoLang.hitch(this,
  1349. function () {
  1350. if (this.isOpen()) {
  1351. this.close();
  1352. }
  1353. })
  1354. );
  1355.  
  1356. addLayerSectionContainer.slideToggle("fast", function () {
  1357. d.resolve();
  1358. });
  1359. }, {
  1360. activeClass: cssButtonPressedClass,
  1361. target: addLayerSectionContainer,
  1362. closeHandler: function (d) {
  1363. topic.publish(EventManager.GUI.ADD_LAYER_PANEL_CHANGE, { visible: false });
  1364. topic.publish(EventManager.GUI.TOOLBAR_SECTION_CLOSE, { id: "add-layer-section" });
  1365. console.log(EventManager.GUI.ADD_LAYER_PANEL_CHANGE + " visible:", false);
  1366.  
  1367. addLayerSectionContainer.slideToggle("fast", function () {
  1368. d.resolve();
  1369. });
  1370. },
  1371. resetFocusOnClose: true
  1372. }
  1373. );
  1374.  
  1375. $("#addLayer-add").on("click", function () {
  1376. topic.publish(EventManager.Map.ADD_LAYER, null);
  1377.  
  1378. addLayerPanelPopup.close();
  1379. });
  1380. //End Add Layer
  1381.  
  1382. //start extended grid
  1383. topic.subscribe(EventManager.GUI.DATAGRID_EXPAND, function () {
  1384. layoutController.toggleFullDataMode();
  1385. });
  1386. //end extended grid
  1387.  
  1388. topic.subscribe(EventManager.GUI.TOGGLE_FULLSCREEN, function (evt) {
  1389. layoutController.toggleFullScreenMode(evt.expand);
  1390. });
  1391.  
  1392. topic.subscribe(EventManager.GUI.SUBPANEL_OPEN, function (attr) {
  1393. showSubPanel(attr);
  1394. });
  1395.  
  1396. topic.subscribe(EventManager.GUI.SUBPANEL_CLOSE, function (attr) {
  1397. if (attr.origin === "all") {
  1398. utilDict.forEachEntry(subPanels, function (key) {
  1399. //attr.origin = key;
  1400. hideSubPanel({
  1401. origin: key
  1402. });
  1403. });
  1404. } else {
  1405. dojoArray.forEach(attr.origin.split(","), function (element) {
  1406. //attr.origin = element;
  1407. hideSubPanel({
  1408. origin: element
  1409. });
  1410. });
  1411. }
  1412. });
  1413.  
  1414. topic.subscribe(EventManager.GUI.SUBPANEL_DOCK, function (attr) {
  1415. var na;
  1416. if (attr.origin === "all") {
  1417. utilDict.forEachEntry(subPanels, function (key) {
  1418. na = Object.create(attr);
  1419. na.origin = key;
  1420. dockSubPanel(na);
  1421. });
  1422. } else {
  1423. dojoArray.forEach(attr.origin.split(","), function (element) {
  1424. na = Object.create(attr);
  1425. na.origin = element;
  1426. dockSubPanel(na);
  1427. });
  1428. }
  1429. console.log(EventManager.GUI.SUBPANEL_DOCK, attr);
  1430. });
  1431.  
  1432. topic.subscribe(EventManager.GUI.SUBPANEL_CAPTURE, function (attr) {
  1433. var na;
  1434. if (attr.consumeOrigin === "all") {
  1435. utilDict.forEachEntry(subPanels, function (key) {
  1436. na = Object.create(attr);
  1437. na.consumeOrigin = key;
  1438. captureSubPanel(na);
  1439. });
  1440. } else {
  1441. dojoArray.forEach(attr.consumeOrigin.split(","), function (element) {
  1442. na = Object.create(attr);
  1443. na.consumeOrigin = element;
  1444. captureSubPanel(na);
  1445. });
  1446. }
  1447. console.log(EventManager.GUI.SUBPANEL_CAPTURE, attr);
  1448. });
  1449.  
  1450. sidePanelTabList.find("li a").click(function () {
  1451. var selectedPanelId = $(this).attr("href").substr(1);
  1452.  
  1453. sidePanelTabPanels.find("details[id=" + selectedPanelId + "]").each(
  1454. function () {
  1455. topic.publish(EventManager.GUI.TAB_SELECTED, {
  1456. id: this.id,
  1457. tabName: $(this).data("panel-name")
  1458. });
  1459. });
  1460.  
  1461. // the panel currently open is being deselected
  1462. sidePanelTabPanels.find("details[aria-expanded=true]").each(
  1463. function () {
  1464. topic.publish(EventManager.GUI.TAB_DESELECTED, {
  1465. id: this.id,
  1466. tabName: $(this).data("panel-name")
  1467. });
  1468. });
  1469.  
  1470. });
  1471.  
  1472. // List of objects containing an event name and an event argument. The events should
  1473. // be anything to wait for before publishing a map extent change, it should include
  1474. // anything that will change the size of the map (e.g. fullscreen, closing the panel).
  1475. // If the map extent change occurs BEFORE something that changes the size of the map (e.g. fullscreen)
  1476. // then the map extent will change again.
  1477. var waitList = [];
  1478.  
  1479. if (!GlobalStorage.config.ui.sidePanelOpened) {
  1480. // NOTE: panel change not triggered here (see map extent change below)
  1481. waitList.push({
  1482. publishName: EventManager.GUI.PANEL_TOGGLE,
  1483. eventArg: {
  1484. origin: "bootstrapper",
  1485. visible: GlobalStorage.config.ui.sidePanelOpened
  1486. },
  1487. subscribeName: EventManager.GUI.PANEL_CHANGE
  1488. });
  1489. }
  1490.  
  1491. if (GlobalStorage.config.ui.fullscreen) {
  1492. // NOTE: fullscreen not triggered here (see map extent change below)
  1493. waitList.push({
  1494. publishName: EventManager.GUI.TOGGLE_FULLSCREEN,
  1495. eventArg: {
  1496. expand: true
  1497. },
  1498. subscribeName: EventManager.GUI.FULLSCREEN_CHANGE
  1499. });
  1500. }
  1501.  
  1502. // return the callback
  1503. load();
  1504.  
  1505. // This should be the last thing that happens
  1506. if (waitList.isEmpty()) {
  1507. topic.publish(EventManager.GUI.UPDATE_COMPLETE);
  1508. } else {
  1509. // Wait for things such as fullscreen or panel collapse
  1510. // to finish before publishing the UPDATE_COMPLETE.
  1511.  
  1512. // Note it's important to subscribe to the events, then
  1513. // publish them, that's why it was done in such an obscure way
  1514. // using the waitList (otherwise if we just publish the
  1515. // event like above, then subscribe to it here, the event
  1516. // might have completed before reaching this point)
  1517. var eventNames = dojoArray.map(waitList, function (obj) {
  1518. return obj.subscribeName;
  1519. });
  1520.  
  1521. UtilMisc.subscribeAll(eventNames, function () {
  1522. topic.publish(EventManager.GUI.UPDATE_COMPLETE);
  1523. });
  1524.  
  1525. dojoArray.forEach(waitList, function (obj) {
  1526. topic.publish(obj.publishName, obj.eventArg);
  1527. });
  1528. }
  1529. }
  1530. };
  1531. }
  1532. );