Reusable Accessible Mapping Platform

API Docs for: 3.0.0
Show:

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

  1. /* global define, i18n, jQuery, console, $, document */
  2.  
  3. /**
  4. * BookmarkLink submodule
  5. *
  6. * Keeps track of the current state of the map and updates the GetLinkPanel's textbook accordingly. On page load, if any
  7. * parameters are detected in the URL, this module will attempt to recreate the map according to the parameters. Internally,
  8. * this module subscribes to all events that change the state of the map (e.g. extent-change, layers toggled on/off). It contains
  9. * a dictionary that map event names to an object that contains the minimum information needed to reconstruct the map for that particular
  10. * event. For example, if an extent change occurred, this module will add a key "map/extent-change" (or update if the entry already exists)
  11. * and put an object that contains the minimum information needed to reconstruct the map to that extent (in this case it would be
  12. * xmin, ymin, xmax, ymax. Spatial reference is not needed since the map always has the same spatial reference).
  13. *
  14. * @module RAMP
  15. * @submodule BookmarkLink
  16. * @main BookmarkLink
  17. */
  18.  
  19. /**
  20. * BookmarkLink class.
  21. *
  22. *
  23. * ### Steps for making the bookmark link update with an event
  24. *
  25. * 1. Subscribe to the event of interest (e.g. map extent-change)
  26. * 2. Create an object containing fields that will contain the necessary
  27. * information for reconstructing the map to the same state. (e.g. for
  28. * map extent-change, a useful object might be one that represents the
  29. * current extent of the map: `{ xmin : 123, xmax : 456, ymin : 789, ymax : 000}).`
  30. *
  31. * __IMPORTANT__: the object must be serializable since it will be added to the URL and
  32. * should serialize to a reasonable length String. If the fields contain
  33. * non-primitives, e.g. array, object, one must manually serialize the field first.
  34. * Also only use anonymous objects with custom fields, do not use class objects
  35. * (e.g. use an anonymous { } object to store map extent instead of ESRI's map
  36. * {{#crossLink "Esri/geometry/Extent"}}{{/crossLink}} object, since it will contain other fields and methods that will also
  37. * be serialized).
  38. *
  39. * 3. Call updateURL, passing it a name (e.g. "newExtent") and the object
  40. * (a name is required for efficiency, this way the URL will only need to
  41. * serialize and update the given object instead of all objects).
  42. *
  43. * @class BookmarkLink
  44. * @static
  45. * @uses dojo/_base/declare
  46. * @uses require
  47. * @uses dojo/dom-construct
  48. * @uses dojo/io-query
  49. * @uses dojo/_base/lang
  50. * @uses dojo/dom
  51. * @uses dojo/_base/array
  52. * @uses dojo/topic
  53. * @uses dijit/form/TextBox
  54. * @uses dijit/TitlePane
  55. * @uses esri/geometry/Extent
  56. * @uses GlobalStorage
  57. * @uses Map
  58. * @uses EventManager
  59. * @uses Url
  60. * @uses Util
  61. * @uses Dictionary
  62. * @uses PopupManager
  63. */
  64.  
  65. define([
  66. // Dojo
  67. "dojo/_base/declare", "require", "dojo/dom-construct", "dojo/io-query", "dojo/_base/lang",
  68. "dojo/dom", "dojo/_base/array", "dojo/topic", "dijit/form/TextBox", "dijit/TitlePane",
  69. // Esri
  70. "esri/geometry/Extent",
  71. // Ramp
  72. "ramp/globalStorage", "ramp/map", "ramp/eventManager", "ramp/ramp",
  73. // Util
  74. "utils/url", "utils/util", "utils/dictionary", "utils/array", "utils/popupManager"
  75. ],
  76.  
  77. function (
  78. // Dojo
  79. declare, dojoRequire, dojoDomConstruct, dojoQuery, dojoLang,
  80. dojoDom, dojoArray, topic, TextBox, TitlePane,
  81. // Esri
  82. Extent,
  83. // Ramp
  84. GlobalStorage, RampMap, EventManager, Ramp,
  85. // Util
  86. Url, UtilMisc, UtilDict, UtilArray, PopupManager) {
  87. "use strict";
  88.  
  89. // Using constants so we can have intellisense and not make silly typos
  90. var EVENT_EXTENT_CHANGE = "extentChange",
  91. EVENT_FULLSCREEN = "fullscreen",
  92. EVENT_PANEL_CHANGE = "panelChange",
  93. EVENT_TAB_CHANGE = "selectedTab",
  94. EVENT_BASEMAP_CHANGED = "basemapChange",
  95. PARAM = {
  96. FILTER: {
  97. VISIBLE_LAYERS: "visibleLayers",
  98. HIDDEN_LAYERS: "hiddenLayers",
  99. VISIBLE_BOXES: "visibleBoxes",
  100. HIDDEN_BOXES: "hiddenBoxes"
  101. }
  102. },
  103. HREF_MAILTO_TEMPLATE = "mailto:?subject={0}&body={1}%0D%0A%0D%0A{2}",
  104.  
  105. config,
  106.  
  107. queryObject,
  108.  
  109. linkPaneTextbox,
  110.  
  111. getlinkPopup,
  112.  
  113. getlinkToggle,
  114. getlinkSectionContainer,
  115. getlinkSection,
  116.  
  117. getlinkShortenButton,
  118. getlinkShortenButtonLabel,
  119. getlinkEmailButton,
  120.  
  121. getlinkloadinganimation,
  122.  
  123. cssButtonPressedClass = "button-pressed",
  124.  
  125. isShortLinkMode = false,
  126.  
  127. baseUrl,
  128.  
  129. /**
  130. * A dictionary mapping names (String) to query parameter (String) of the URL. The query parameter is
  131. * what ends up in the url. The key can be any arbitrary name (best to name them to describe the query
  132. * parameter).
  133. *
  134. * @private
  135. * @property parameters
  136. * @type {Object}
  137. */
  138. parameters = {},
  139.  
  140. /**
  141. * A dictionary mapping names (String) to anchors (String) used at the end of the URL.
  142. *
  143. * @private
  144. * @property anchors
  145. * @type {Object}
  146. */
  147. anchors = {},
  148.  
  149. /**
  150. * A dictionary containing layer id (String) as key and layer visibility (boolean) as value
  151. *
  152. * @private
  153. * @property layerVisibility
  154. * @type {Object}
  155. */
  156. layerVisibility = {},
  157.  
  158. /**
  159. * A dictionary containing layer id (String) as key and bounding box visibility (boolean) as value
  160. *
  161. * @private
  162. * @property boundingBoxVisibility
  163. * @type {Object}
  164. */
  165. boundingBoxVisibility = {},
  166.  
  167. /**
  168. * A dictionary with the layer id as key, and the transparency as value.
  169. *
  170. * @private
  171. * @property layerTransparency
  172. * @type {Object}
  173. */
  174. layerTransparency = {},
  175.  
  176. ui = {
  177. /**
  178. * Initiates additional UI components of the widget, setting listeners and other stuff.
  179. *
  180. * @method init
  181. * @private
  182. * @constructor
  183. */
  184. init: function () {
  185. getlinkloadinganimation = $("#getlink-section .loadingAnimation");
  186.  
  187. getlinkEmailButton = $(".getlink-email-button");
  188.  
  189. getlinkToggle = $("#getlink-toggle");
  190. getlinkSectionContainer = $("#getlink-section-container");
  191. getlinkSection = $("#getlink-section");
  192.  
  193. // select link when user focuses on the textbox http://stackoverflow.com/a/22102081
  194. linkPaneTextbox =
  195. $("#getlink-input")
  196. .on('focus', function () {
  197. var $this = $(this)
  198. .one('mouseup.mouseupSelect', function () {
  199. $this.select();
  200. return false;
  201. })
  202. .one('mousedown', function () {
  203. // compensate for untriggered 'mouseup' caused by focus via tab
  204. $this.off('mouseup.mouseupSelect');
  205. })
  206. .select();
  207. });
  208.  
  209. // toggle short/long link mode on click
  210. getlinkShortenButton =
  211. $(".getlink-shorten-button")
  212. .on("click", toggleShortLinkMode);
  213.  
  214. getlinkShortenButtonLabel = getlinkShortenButton.find("span.on-right");
  215.  
  216. getlinkPopup = PopupManager.registerPopup(getlinkToggle, "click",
  217. function (d) {
  218. topic.publish(EventManager.BookmarkLink.GETLINK_PANEL_CHANGED, { visible: true });
  219. topic.publish(EventManager.GUI.TOOLBAR_SECTION_OPEN, { id: "get-link-section" });
  220. console.log(EventManager.BookmarkLink.GETLINK_PANEL_CHANGED + " visible:", true);
  221.  
  222. // close this panel if any other panel is opened
  223. UtilMisc.subscribeOnce(EventManager.GUI.TOOLBAR_SECTION_OPEN, dojoLang.hitch(this,
  224. function () {
  225. if (this.isOpen()) {
  226. this.close();
  227. }
  228. })
  229. );
  230.  
  231. getlinkSectionContainer.slideDown("fast", function () {
  232. d.resolve();
  233. });
  234. },
  235. {
  236. activeClass: cssButtonPressedClass,
  237. target: getlinkSectionContainer,
  238. closeHandler: function (d) {
  239. topic.publish(EventManager.BookmarkLink.GETLINK_PANEL_CHANGED, { visible: false });
  240. topic.publish(EventManager.GUI.TOOLBAR_SECTION_CLOSE, { id: "get-link-section" });
  241. console.log(EventManager.BookmarkLink.GETLINK_PANEL_CHANGED + " visible:", false);
  242.  
  243. getlinkSectionContainer.slideUp("fast", function () {
  244. toggleShortLinkMode(false);
  245. d.resolve();
  246. });
  247. },
  248. resetFocusOnClose: true
  249. }
  250. );
  251.  
  252. /*topic.subscribe(EventManager.GUI.HELP_PANEL_CHANGE, function (attr) {
  253. if (getlinkPopup.isOpen() && attr.visible) {
  254. getlinkPopup.close();
  255. }
  256. });*/
  257. }
  258. };
  259.  
  260. /**
  261. * Update the parameter dictionary with the new values for the parameter. If paramObj is set to null,
  262. * essentially removes the given paramKey from the URL.
  263. *
  264. * @method addParameter
  265. * @private
  266. * @param {String} paramKey the parameter (e.g. extent) that was changed
  267. * @param {Object} paramObj an object representing data that can be serialized into the query parameter
  268. * of the URL (can be null, in which case the parameter will NOT be included in the URL)
  269. *
  270. */
  271. function addParameter(paramKey, paramObj) {
  272. if (paramObj === null) {
  273. parameters[paramKey] = null;
  274. } else {
  275. parameters[paramKey] = dojoQuery.objectToQuery(paramObj);
  276. }
  277. }
  278.  
  279. /*
  280. * Adds an anchor object to a tracking array
  281. * @method addAnchor
  282. * @param {String} anchorName a key value for the anchor
  283. * @param {Object} anchorObj an anchor object
  284. */
  285. function addAnchor(anchorName, anchorObj) {
  286. anchors[anchorName] = anchorObj;
  287. }
  288.  
  289. /*
  290. * Appends information to the current map's URL to create a mail-to link. Set the email buttons target URL.
  291. * @method setNewUrl
  292. * @param {String} url the base URL that defines the current state of the map
  293. */
  294. function setNewUrl(url) {
  295. var mailToHref = String.format(HREF_MAILTO_TEMPLATE,
  296. i18n.t("bookmarkLink.emailUrlSubject"),
  297. i18n.t("bookmarkLink.emailUrlBody"),
  298. encodeURIComponent(url));
  299. linkPaneTextbox.val(url);
  300. getlinkEmailButton.attr("href", mailToHref);
  301. }
  302.  
  303. /**
  304. * Updates the link displayed in the textbox. This function should be called whenever
  305. * one of the objects that are in the URL query is modified.
  306. *
  307. * @method updateURL
  308. * @private
  309. */
  310. function updateURL() {
  311. var link = baseUrl;
  312.  
  313. /* Appends all the query parameters to the link (a query parameter can be
  314. * excluded by setting it to null) */
  315. UtilDict.forEachEntry(parameters, function (key, value) {
  316. if (value) { // Value cannot be null or the empty String
  317. link += "&" + value;
  318. }
  319. });
  320.  
  321. // Need to add an extra "&" to the query if we have an anchor, otherwise
  322. // the last query will contain the anchors
  323. if (!UtilDict.isEmpty(anchors)) {
  324. link += "&";
  325. }
  326.  
  327. // Anchors have to be at the end
  328. UtilDict.forEachEntry(anchors, function (key, value) {
  329. link += "#" + value;
  330. });
  331.  
  332. if (isShortLinkMode) {
  333. if (linkPaneTextbox.is(":visible")) {
  334. getlinkloadinganimation.show();
  335.  
  336. jQuery.urlShortener({
  337. longUrl: link,
  338. success: function (shortUrl) {
  339. setNewUrl(shortUrl);
  340. getlinkloadinganimation.hide();
  341. },
  342. error: function (err) {
  343. console.error(JSON.stringify(err));
  344. getlinkloadinganimation.hide();
  345. }
  346. });
  347. }
  348. } else {
  349. setNewUrl(link);
  350. }
  351. }
  352.  
  353. /**
  354. * Toggle the short/long link mode and change the label accordingly
  355. *
  356. * @method toggleShortLinkMode
  357. * @param {Object} value true - shortLinkMode; false - !shortlinkMore; undefined - toggle;
  358. * @private
  359. */
  360. function toggleShortLinkMode(value) {
  361. var label;
  362.  
  363. isShortLinkMode = value === true ? true : (value === false ? false : !isShortLinkMode);
  364. label = isShortLinkMode ? i18n.t("bookmarkLink.longLink") : i18n.t("bookmarkLink.shortLink");
  365. getlinkShortenButtonLabel.text(label);
  366. updateURL();
  367. }
  368.  
  369. function updateConfig(homePage) {
  370. var event,
  371. urlObj = new Url(dojoRequire.toUrl(document.location));
  372.  
  373. config = GlobalStorage.config;
  374. baseUrl = urlObj.uri;
  375. queryObject = dojoQuery.queryToObject(urlObj.query);
  376.  
  377. //adds homePage (e.g. default.aspx or rampmap.aspx) if not present;
  378. //used in getlink or else the link would be invalid.
  379. if (baseUrl.indexOf(homePage) === -1) {
  380. baseUrl += homePage;
  381. }
  382. baseUrl += "?lang=" + config.lang;
  383.  
  384. // Move the API key to config.json??
  385. jQuery.urlShortener.settings.apiKey = 'AIzaSyB52ByjsXrOYlXxc2Q9GVpClLDwt0Lw6pc';
  386.  
  387. // Toggle the main panel
  388. if (queryObject.panelVisible) {
  389. event = {
  390. visible: UtilMisc.parseBool(queryObject.panelVisible)
  391. };
  392.  
  393. addParameter(EVENT_PANEL_CHANGE, event);
  394. config.ui.sidePanelOpened = event.visible;
  395. }
  396.  
  397. // Toggle fullscreen mode
  398. if (queryObject.fullscreen) {
  399. event = {
  400. fullscreen: UtilMisc.parseBool(queryObject.fullscreen)
  401. };
  402. addParameter(EVENT_FULLSCREEN, event);
  403. config.ui.fullscreen = event.fullscreen;
  404. }
  405.  
  406. // Check for map extent queries
  407. if (queryObject.xmin) {
  408. event = {
  409. xmin: parseFloat(queryObject.xmin.replace(/,/g, "")),
  410. ymin: parseFloat(queryObject.ymin.replace(/,/g, "")),
  411. xmax: parseFloat(queryObject.xmax.replace(/,/g, "")),
  412. ymax: parseFloat(queryObject.ymax.replace(/,/g, "")),
  413. sr: queryObject.sr
  414. };
  415.  
  416. addParameter(EVENT_EXTENT_CHANGE, event);
  417.  
  418. config.extents.defaultExtent = event;
  419. config.spatialReference = JSON.parse(queryObject.sr);
  420.  
  421. // Wait for things such as fullscreen or panel collapse
  422. // to finish before doing an extent change.
  423. }
  424.  
  425. // Select the correct basemap
  426. if (queryObject.baseMap) {
  427. event = {
  428. baseMap: queryObject.baseMap
  429. };
  430. addParameter(EVENT_BASEMAP_CHANGED, event);
  431.  
  432. dojoArray.forEach(config.basemaps, function (basemap) {
  433. basemap.showOnInit = (basemap.id === event.baseMap);
  434. });
  435. }
  436.  
  437. // Modify the layer transparency
  438. if (queryObject.layerTransparency) {
  439. addParameter(EventManager.FilterManager.LAYER_TRANSPARENCY_CHANGED, {
  440. layerTransparency: queryObject.layerTransparency
  441. });
  442.  
  443. UtilDict.forEachEntry(JSON.parse(queryObject.layerTransparency), function (key, value) {
  444. var layerConfig = UtilArray.find(config.featureLayers.concat(config.wmsLayers), function (layer) {
  445. return layer.id === key;
  446. });
  447. layerConfig.settings.opacity.default = value;
  448. });
  449. }
  450.  
  451. // check for selected tab queries
  452. if (queryObject.selectedTab) {
  453. // Just add the parameter, no need to do anything else
  454. // since we're using anchor tags
  455. addParameter(EVENT_TAB_CHANGE, {
  456. index: queryObject.selectedTab
  457. });
  458. }
  459.  
  460. var layerIds;
  461.  
  462. if (queryObject.visibleLayers) {
  463. layerIds = queryObject.visibleLayers.split("+");
  464.  
  465. layerIds.forEach(function (layerId) {
  466. var layerConfig = Ramp.getLayerConfigWithId(layerId);
  467. // make sure not null
  468. if (layerConfig !== null) {
  469. layerConfig.layerVisible = true;
  470.  
  471. layerVisibility[layerId] = true;
  472. }
  473. });
  474.  
  475. addParameter(PARAM.FILTER.VISIBLE_LAYERS, {
  476. visibleLayers: queryObject.visibleLayers
  477. });
  478. }
  479.  
  480. if (queryObject.hiddenLayers) {
  481. layerIds = queryObject.hiddenLayers.split("+");
  482.  
  483. layerIds.forEach(function (layerId) {
  484. var layerConfig = Ramp.getLayerConfigWithId(layerId);
  485.  
  486. if (layerConfig !== null) {
  487. layerConfig.layerVisible = false;
  488.  
  489. layerVisibility[layerId] = false;
  490. }
  491. });
  492.  
  493. addParameter(PARAM.FILTER.HIDDEN_LAYERS, {
  494. hiddenLayers: queryObject.hiddenLayers
  495. });
  496. }
  497.  
  498. if (queryObject.visibleBoxes) {
  499. layerIds = queryObject.visibleBoxes.split("+");
  500.  
  501. layerIds.forEach(function (layerId) {
  502. var layerConfig = Ramp.getLayerConfigWithId(layerId);
  503. if (layerConfig !== null) {
  504. layerConfig.boundingBoxVisible = true;
  505. boundingBoxVisibility[layerId] = true;
  506. }
  507. });
  508.  
  509. addParameter(PARAM.FILTER.VISIBLE_BOXES, {
  510. visibleBoxes: queryObject.visibleBoxes
  511. });
  512. }
  513.  
  514. if (queryObject.hiddenBoxes) {
  515. layerIds = queryObject.hiddenBoxes.split("+");
  516.  
  517. layerIds.forEach(function (layerId) {
  518. var layerConfig = Ramp.getLayerConfigWithId(layerId);
  519.  
  520. if (layerConfig !== null) {
  521. layerConfig.boundingBoxVisible = false;
  522. boundingBoxVisibility[layerId] = false;
  523. }
  524. });
  525.  
  526. addParameter(PARAM.FILTER.HIDDEN_BOXES, {
  527. hiddenBoxes: queryObject.hiddenBoxes
  528. });
  529. }
  530. }
  531.  
  532. return {
  533. /**
  534. * Instantiates a BookmarkLink. Subscribes to all the events that causes
  535. * the bookmark link to update (e.g. map extent change or feature layer visibility
  536. * change).
  537. *
  538. * @method init
  539. * {String} homePage a string denoting the name of the homePage (e.g. usually "Default.aspx" or "index.html")
  540. */
  541. createUI: function () {
  542. ui.init();
  543. },
  544.  
  545. updateConfig: updateConfig,
  546.  
  547. /**
  548. * Subscribe to map state changes so the URL displayed can be changed accordingly.
  549. * SUBSCRIBES TO:
  550. * map "extent-change"
  551. * Updates URL when map extent changes
  552. *
  553. * EventManager.GUI.FULLSCREEN_CHANGE
  554. * Updates URL when map goes into fullscreen mode
  555. *
  556. * EventManager.GUI.TAB_SELECTED
  557. * Updates URL when tabs are selected
  558. *
  559. * EventManager.GUI.PANEL_CHANGE
  560. * Updates URL when panel opens/closes
  561. *
  562. * EventManager.BasemapSelector.BASEMAP_CHANGED
  563. * Updates URL when basemap changed
  564. *
  565. * * ================================================================
  566. * Subscribe to updates
  567. * ================================================================
  568. * To include more information into the query string, do not get the information
  569. * directly from the object/module of interest, but rather make it publish an
  570. * event with data to include and subscribe to this event here.
  571. * @method subscribeAndUpdate
  572. * @private
  573. */
  574. subscribeAndUpdate: function () {
  575. topic.subscribe(EventManager.Map.EXTENT_CHANGE, function (event) {
  576. // Event fields: extent, delta, levelChange, lod;
  577. addParameter(EVENT_EXTENT_CHANGE, {
  578. xmin: event.extent.xmin,
  579. ymin: event.extent.ymin,
  580. xmax: event.extent.xmax,
  581. ymax: event.extent.ymax,
  582. sr: JSON.stringify(event.extent.spatialReference)
  583. });
  584. updateURL();
  585. });
  586.  
  587. topic.subscribe(EventManager.GUI.FULLSCREEN_CHANGE, function (event) {
  588. addParameter(EVENT_FULLSCREEN, {
  589. fullscreen: event.visible
  590. });
  591. updateURL();
  592. });
  593.  
  594. topic.subscribe(EventManager.GUI.TAB_SELECTED, function (event) {
  595. // Need to remove the "-link" part for the id to work
  596. // since when the page first loads, if the tab is deselected,
  597. // it's id will be missing the "-link" at the end.
  598. addAnchor(EVENT_TAB_CHANGE, event.id.replace("-link", ""));
  599. updateURL();
  600. });
  601.  
  602. topic.subscribe(EventManager.GUI.PANEL_CHANGE, function (event) {
  603. addParameter(EVENT_PANEL_CHANGE, {
  604. panelVisible: event.visible
  605. });
  606. updateURL();
  607. });
  608.  
  609. topic.subscribe(EventManager.BasemapSelector.BASEMAP_CHANGED, function (event) {
  610. addParameter(EVENT_BASEMAP_CHANGED, {
  611. baseMap: event.id
  612. });
  613. updateURL();
  614. });
  615.  
  616. topic.subscribe(EventManager.FilterManager.LAYER_VISIBILITY_TOGGLED, function (event) {
  617. var layerName = event.id;
  618. layerVisibility[layerName] = event.state;
  619.  
  620. // Only keep attributes that are different from the default config
  621. var visibleLayers = UtilDict.filter(layerVisibility, function (key, layerVisible) {
  622. return layerVisible && !Ramp.getLayerConfigWithId(key).layerVisible;
  623. }),
  624. hiddenLayers = UtilDict.filter(layerVisibility, function (key, boxVisible) {
  625. return !boxVisible && Ramp.getLayerConfigWithId(key).layerVisible;
  626. });
  627.  
  628. addParameter(PARAM.FILTER.HIDDEN_LAYERS, UtilDict.isEmpty(hiddenLayers) ? null : {
  629. // Convert an array of string into a "+" delimited string
  630. hiddenLayers: Object.keys(hiddenLayers).join("+")
  631. });
  632.  
  633. addParameter(PARAM.FILTER.VISIBLE_LAYERS, UtilDict.isEmpty(visibleLayers) ? null : {
  634. // Convert an array of string into a "+" delimited string
  635. visibleLayers: Object.keys(visibleLayers).join("+")
  636. });
  637.  
  638. updateURL();
  639. });
  640.  
  641. topic.subscribe(EventManager.FilterManager.BOX_VISIBILITY_TOGGLED, function (event) {
  642. var layerName = event.id;
  643. boundingBoxVisibility[layerName] = event.state;
  644.  
  645. // Only keep attributes that are different from the default config
  646. var visibleBoxes = UtilDict.filter(boundingBoxVisibility, function (key, boxVisible) {
  647. return boxVisible && !Ramp.getLayerConfigWithId(key).boundingBoxVisible;
  648. }),
  649. hiddenBoxes = UtilDict.filter(boundingBoxVisibility, function (key, boxVisible) {
  650. return !boxVisible && Ramp.getLayerConfigWithId(key).boundingBoxVisible;
  651. });
  652.  
  653. addParameter(PARAM.FILTER.HIDDEN_BOXES, UtilDict.isEmpty(hiddenBoxes) ? null : {
  654. // Convert an array of string into a "+" delimited string
  655. hiddenBoxes: Object.keys(hiddenBoxes).join("+")
  656. });
  657.  
  658. addParameter(PARAM.FILTER.VISIBLE_BOXES, UtilDict.isEmpty(visibleBoxes) ? null : {
  659. // Convert an array of string into a "+" delimited string
  660. visibleBoxes: Object.keys(visibleBoxes).join("+")
  661. });
  662.  
  663. updateURL();
  664. });
  665.  
  666. topic.subscribe(EventManager.FilterManager.LAYER_TRANSPARENCY_CHANGED, function (event) {
  667. addParameter(EventManager.FilterManager.LAYER_TRANSPARENCY_CHANGED, event);
  668. layerTransparency[event.layerId] = event.value;
  669.  
  670. addParameter(EventManager.FilterManager.LAYER_TRANSPARENCY_CHANGED, {
  671. layerTransparency: JSON.stringify(layerTransparency)
  672. });
  673.  
  674. updateURL();
  675. });
  676.  
  677. // This call is necessary to fill in the URL in the bookmark link
  678. // if this call is removed, there will be no URL in the bookmark link
  679. // until one of the above events fires
  680. updateURL();
  681. }
  682. };
  683. });