Reusable Accessible Mapping Platform

API Docs for: 5.3.1
Show:

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

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