Reusable Accessible Mapping Platform

API Docs for: 5.3.1
Show:

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

/*global define, $, esri, tmpl, RAMP, i18n, TimelineLite, window, console */
/*jslint white: true */

/**
*
* @module RAMP
*/

/**
* Populates the BasemapGallery widget located in the maps toolbar with items found in the application configuration.
* This module also handles all the event  needed to change the map basemap and update the UI
*
* ####Uses RAMP Templates:
* {{#crossLink "templates/basemap_selector_template.json"}}{{/crossLink}}
* 
* ####Imports RAMP Modules:
* {{#crossLink "GlobalStorage"}}{{/crossLink}}  
* {{#crossLink "Map"}}{{/crossLink}}  
* {{#crossLink "EventManager"}}{{/crossLink}}  
* {{#crossLink "Dictionary"}}{{/crossLink}}  
* {{#crossLink "PopupManager"}}{{/crossLink}}  
* {{#crossLink "Util"}}{{/crossLink}}  
* {{#crossLink "TmplHelper"}}{{/crossLink}}  
*
* @class BaseMapSelector
* @static
* @uses dojo/_base/array
* @uses dojo/_base/lang
* @uses dojo/dom-attr
* @uses dojo/query
* @uses dojo/topic
* @uses esri/dijit/BasemapGallery
*/

define([
// Dojo
    "dojo/_base/array", "dojo/_base/lang", "dojo/dom-attr", "dojo/query", "dojo/topic",
// Templates
    "dojo/text!./templates/basemap_selector_template.json",
// Ramp
    "ramp/globalStorage", "ramp/map", "ramp/eventManager",
// Esri
    "esri/dijit/BasemapGallery",
// Util
    "utils/dictionary", "utils/popupManager", "utils/util", "utils/tmplHelper"],

function (
        // Dojo
        dojoArray, dojoLang, domAttr, query, topic,
        // Templates
        basemapselectorTemplate,
        // Ramp
        GlobalStorage, RampMap, EventManager,
        // Esri
        BasemapGallery,
        // Util
        Dictionary, PopupManager, UtilMisc, TmplHelper) {
    "use strict";

    var basemapGallery,

        currentBasemapId,
        currentTileSchema,
        basemaps,

        placementAnchorId = "basemapGallery",

        ui = (function () {
            var baseMapControls,
                baseMapToggle,

                selectorSectionContainer,
                selectorSection,

                selectorPopup,
                projectionPopup,
                basemapPopup,

                selectorOpenTimeline = new TimelineLite({ paused: true }),

                transitionDuration = 0.4,

                cssButtonPressedClass = "button-pressed";

            function createSelectorOpenTL() {
                var time = selectorOpenTimeline.time();

                selectorOpenTimeline
                    .clear()
                    //.set(selectorSectionContainer, { display: "block" }, 0)
                    .fromTo(selectorSection, transitionDuration,
                        { top: -selectorSectionContainer.find(".basemapselector-section").outerHeight() - 20 },
                        { top: 0, ease: "easeOutCirc" }, 0)
                    .seek(time);
            }

            function setTooltips() {
                // set tooltips on the overflowing spans with basemap/projection names

                if (selectorPopup.isOpen()) {
                    selectorSection
                        .find("span[title]:visible")
                        .each(function () {
                            var node = $(this);
                            if (node.attr("title")) {
                                if (node.isOverflowed()) {
                                    node.tooltipster({ theme: '.tooltipster-shadow' });
                                } else {
                                    node.removeAttr("title");
                                }
                            }
                        });
                }
            }

            return {
                /**
                * Initiates additional UI components of the widget, setting listeners and registering the popup functionality
                *
                * @method init
                * @private
                * @return {object} itself
                *
                */
                init: function (basemapId, tileSchema) {
                    var data = [],
                        pj = {},
                        basemapControl,
                        projectionControl;

                    baseMapControls = $("#basemapControls");
                    baseMapToggle = $("#baseMapToggle");

                    // group basemaps by projection
                    basemaps.forEach(function (m) {
                        if (!pj[m.tileSchema]) {
                            pj[m.tileSchema] = [];
                        }
                        pj[m.tileSchema].push(m);
                    });
                    Dictionary.forEachEntry(pj, function (k, p) {
                        data.push(
                            {
                                isActive: k === tileSchema,
                                id: k,
                                tileShema: tileSchema,
                                name: k,
                                maps: p
                            }
                        );
                    });

                    // load JSON templates for basemap and skin every node under the basemap selector
                    tmpl.templates = JSON.parse(TmplHelper.stringifyTemplate(basemapselectorTemplate));

                    baseMapControls.append(tmpl("basemapselector", data));
                    selectorSectionContainer = baseMapControls.find("#basemapselector-section-container");
                    selectorSection = selectorSectionContainer.find(".basemapselector-section");

                    // turn on the opening and closing of the basemap selector section
                    selectorPopup = PopupManager.registerPopup(baseMapControls, "hoverIntent",
                        function (d) {
                            baseMapToggle.addClass("button-pressed");
                            createSelectorOpenTL();

                            selectorOpenTimeline.eventCallback("onComplete", function () {
                                d.resolve();
                                setTooltips();
                            });

                            selectorSectionContainer.show();
                            selectorOpenTimeline.play();
                        },
                        {
                            activeClass: cssButtonPressedClass,
                            target: selectorSectionContainer,
                            closeHandler: function (d) {
                                createSelectorOpenTL();

                                selectorOpenTimeline.eventCallback("onReverseComplete", function () {
                                    baseMapToggle.removeClass("button-pressed");
                                    selectorSectionContainer.hide();
                                    d.resolve();
                                });

                                selectorOpenTimeline.reverse();
                            },
                            timeout: 500
                        }
                    );

                    // show/hide basemap lists based on what projection group is active
                    projectionPopup = PopupManager.registerPopup(selectorSectionContainer, "click",
                        function (d) {
                            var fromHeight,
                                toHeight,
                                heightTimeline;

                            if (!this.isOpen()) {
                                fromHeight = selectorSection.height();
                                toHeight = this.target.height();
                                heightTimeline = new TimelineLite({
                                    onComplete: setTooltips
                                });

                                projectionPopup.close();

                                // animate resizing of the selector when switching between projection groups
                                heightTimeline
                                    .set(this.target, { display: "block" }, 0)
                                    .fromTo(this.target, transitionDuration, { height: fromHeight }, { height: toHeight }, 0)
                                    .to(selectorSection, transitionDuration, { height: toHeight, ease: "easeOutCirc" }, 0);
                            }

                            d.resolve();
                        },
                        {
                            closeHandler: function (d) {
                                this.target.hide();
                                d.resolve();
                            },
                            openOnly: true,
                            activeClass: cssButtonPressedClass,
                            handleSelector: ".projection-button",
                            containerSelector: ".projection-list-item",
                            targetSelector: ".basemap-list-pane"
                        }
                    );

                    // listen to clicks on basemap list and switch basemap accordingly
                    basemapPopup = PopupManager.registerPopup(selectorSectionContainer, "click",
                        function (d) {
                            if (!this.isOpen()) {
                                basemapPopup.close();
                                selectBasemap(this.target.data("basemap-id"), this.target.data("tileshema"));
                            }

                            d.resolve();
                        },
                        {
                            closeHandler: function (d) {
                                d.resolve();
                            },
                            openOnly: true,
                            handleSelector: ".basemap-button",
                            activeClass: cssButtonPressedClass
                        }
                    );

                    basemapControl = selectorSectionContainer.find("button[data-basemap-id='" + basemapId + "']");
                    projectionControl = selectorSectionContainer.find("button[data-projection-id='" + tileSchema + "']");

                    basemapPopup.open(basemapControl);
                    projectionPopup.open(projectionControl);

                    selectorSectionContainer.hide(); // hide baseselector after it's initiated

                    this.updateToggleLabel();

                    topic.publish(EventManager.BasemapSelector.UI_COMPLETE);

                    return this;
                },
                /*
                 * Changes the text shown on the toolbar to match the currently selected basemap's title
                 * @method updateToggleLabel
                 * @private
                 *
                 */
                updateToggleLabel: function () {
                    baseMapToggle.find("span:first").text(basemapGallery.getSelected().title);
                }
            };
        }());

    /**
    * Initializes functions that publish events.
    *
    * @method initTopics
    * @private
    */
    function initTopics() {
        /* PUBLISH */
        basemapGallery.on("selection-change", function () {
            var basemap = basemapGallery.getSelected();

            ui.updateToggleLabel();
            topic.publish(EventManager.BasemapSelector.BASEMAP_CHANGED, {
                id: basemap.id,
                cssStyle: basemap.scaleCssClass
            });
        });
    }

    /**
    * Initializes class listeners.
    *
    * @method initListeners
    * @private
    */
    function initListeners() {
        /* SUBSCRIBE */
        topic.subscribe(EventManager.BasemapSelector.TOGGLE, function (eventArg) {
            selectBasemap(eventArg.id);
        });
    }

    /**
    * Selects a basemap in the basemapgallery based on the supplied basemap id. If the tileShema is different from the current one, reload the page.
    *
    * @method selectBasemap
    * @param {String} basemapId a basemap id used to select a basemap in the basemapgallery
    * @param {String} tileSchema a tileShema of the selected basemap
    * @private
    */
    function selectBasemap(basemapId, tileSchema) {
        if (currentBasemapId !== basemapId) {
            if (currentTileSchema === tileSchema) {
                currentBasemapId = basemapId;
                basemapGallery.select(currentBasemapId);
            } else {
                //we need to generate a bookmark and reload the page

                //set listner for bookmark complete
                topic.subscribe(EventManager.BookmarkLink.BOOKMARK_GENERATED, function (eventArg) {
                    //run away, load new page
                    window.location.href = eventArg.link;
                });

                // trigger bookmark generation.  don't care about cssStyle as we are going to reload the site anyways
                topic.publish(EventManager.BasemapSelector.BASEMAP_CHANGED, {
                    id: basemapId,
                    cssStyle: ""
                });
            }
        }
    }

    return {
        /*
         * Adds all of the basemaps specified in the application configuration to the basemap selector widget and then calls function to initializes event handling
         * @method init
         * @constructor
         *
         */
        init: function () {
            var initialBasemap,
                esriBasemaps = [],
                basemapId;

            basemaps = RAMP.config.basemaps;

            RAMP.basemapIndex = {};

            dojoArray.forEach(basemaps, function (basemap, i) {
                var basemapDijit,
                    basemapLayers = [];

                // iterate over basemap layers and create layer objects for each;
                // these objects can have any of the properties of the Basemap param constructor object here: https://developers.arcgis.com/javascript/jsapi/basemaplayer-amd.html#basemaplayer1
                basemap.layers.forEach(function (layer) {
                    //console.log(layer);
                    basemapLayers.push(
                        new esri.dijit.BasemapLayer(layer)
                    );
                });

                basemapDijit = new esri.dijit.Basemap({
                    id: basemap.id,
                    layers: basemapLayers, // shovel all the layers into the basemap
                    title: String.format("{0}, {1}", basemap.name, i18n.t('config.tileSchema.' + basemap.tileSchema)),
                    thumbnailUrl: basemap.thumbnail
                });
                basemapDijit.scaleCssClass = basemap.scaleCssClass;

                esriBasemaps.push(basemapDijit);

                //store index in lookup for bookmark module
                RAMP.basemapIndex[basemap.id] = i;
            });

            //Create and start the selector
            basemapGallery = new BasemapGallery({
                showArcGISBasemaps: false,
                basemaps: esriBasemaps,
                map: RampMap.getMap()
            }, placementAnchorId);

            basemapGallery.on('error', function (evt) {
                //TODO handle this in a nicer way
                console.log('Error occurred when loading basemap: ' + evt.message);
            });

            basemapGallery.startup();

            initialBasemap = basemaps[RAMP.config.initialBasemapIndex];
            // currentBasemapId is not specified from the start because the basemap hasn't been selected yet through the basemapgallery
            basemapId = initialBasemap.id;
            currentTileSchema = initialBasemap.tileSchema;

            initTopics();
            initListeners();

            ui
                .init(basemapId, currentTileSchema)
            ;
        }
    };
});