Reusable Accessible Mapping Platform

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