diff --git a/viewer/js/config/app.js b/viewer/js/config/app.js index b2b71c050..1ac4434c8 100644 --- a/viewer/js/config/app.js +++ b/viewer/js/config/app.js @@ -39,8 +39,9 @@ 'viewer/_ConfigMixin', // manage the Configuration 'viewer/_LayoutMixin', // build and manage the Page Layout and User Interface 'viewer/_MapMixin', // build and manage the Map - 'viewer/_WidgetsMixin' // build and manage the Widgets + 'viewer/_WidgetsMixin', // build and manage the Widgets + 'viewer/_WebMapMixin' // for WebMaps //'config/_customMixin' ], function ( @@ -50,8 +51,9 @@ _ConfigMixin, _LayoutMixin, _MapMixin, - _WidgetsMixin + _WidgetsMixin, + _WebMapMixin //_MyCustomMixin ) { @@ -60,7 +62,9 @@ _ConfigMixin, _LayoutMixin, _MapMixin, - _WidgetsMixin + _WidgetsMixin, + + _WebMapMixin ]))(); controller.startup(); }); diff --git a/viewer/js/config/viewer.js b/viewer/js/config/viewer.js index b37015a41..1c3a12f08 100644 --- a/viewer/js/config/viewer.js +++ b/viewer/js/config/viewer.js @@ -78,6 +78,9 @@ define([ sliderStyle: 'small' }, + //webMapId: 'ef9c7fbda731474d98647bebb4b33c20', // High Cost Mortgage + // webMapOptions: {}, + // panes: { // left: { // splitter: true @@ -278,7 +281,7 @@ define([ srcNodeRef: 'growlerDijit', options: {} }, - geocoder: { + search: { include: true, type: 'domNode', path: 'esri/dijit/Search', @@ -301,14 +304,6 @@ define([ position: 3, options: 'config/identify' }, - basemaps: { - include: true, - id: 'basemaps', - type: 'domNode', - path: 'gis/dijit/Basemaps', - srcNodeRef: 'basemapsDijit', - options: 'config/basemaps' - }, mapInfo: { include: false, id: 'mapInfo', diff --git a/viewer/js/viewer/_ControllerBase.js b/viewer/js/viewer/_ControllerBase.js index 08ae67ed6..3863c9ff2 100644 --- a/viewer/js/viewer/_ControllerBase.js +++ b/viewer/js/viewer/_ControllerBase.js @@ -32,6 +32,26 @@ define([ // add growler here? return; } + }, + + mixinDeep: function (dest, source) { + //Recursively mix the properties of two objects + var empty = {}; + for (var name in source) { + if (!(name in dest) || (dest[name] !== source[name] && (!(name in empty) || empty[name] !== source[name]))) { + try { + if (source[name].constructor === Object) { + dest[name] = this.mixinDeep(dest[name], source[name]); + } else { + dest[name] = source[name]; + } + } catch (e) { + // Property in destination object not set. Create it and set its value. + dest[name] = source[name]; + } + } + } + return dest; } }); }); \ No newline at end of file diff --git a/viewer/js/viewer/_MapMixin.js b/viewer/js/viewer/_MapMixin.js index 4806d7e75..924777ecc 100644 --- a/viewer/js/viewer/_MapMixin.js +++ b/viewer/js/viewer/_MapMixin.js @@ -27,27 +27,49 @@ define([ var returnDeferred = new Deferred(); var returnWarnings = []; - this._createMap(); + this._createMap(returnWarnings).then( + lang.hitch(this, '_createMapResult', returnDeferred, returnWarnings) + ); + return returnDeferred; + }, + + _createMap: function (returnWarnings) { + var mapDeferred = new Deferred(), + container = dom.byId(this.config.layout.map) || 'mapCenter'; - if (this.config.mapOptions.basemap) { - this.map.on('load', lang.hitch(this, '_initLayers', returnWarnings)); + if (this.config.webMapId) { + if (this._initWebMap) { + mapDeferred = this._initWebMap(this.config.webMapId, container, this.config.webMapOptions); + } else { + returnWarnings.push('The "_WebMapMixin" Controller Mixin is required to use a webmap'); + mapDeferred.resolve(returnWarnings); + } } else { - this._initLayers(returnWarnings); + this.map = new Map(container, this.config.mapOptions); + mapDeferred.resolve(returnWarnings); } + return mapDeferred; + }, + + _createMapResult: function (returnDeferred, returnWarnings) { + if (this.map) { + if (!this.config.webMapId && this.config.mapOptions && this.config.mapOptions.basemap) { + this.map.on('load', lang.hitch(this, '_initLayers', returnWarnings)); + } else { + this._initLayers(returnWarnings); + } - if (this.config.operationalLayers && this.config.operationalLayers.length > 0) { - on.once(this.map, 'layers-add-result', lang.hitch(this, '_onLayersAddResult', returnDeferred, returnWarnings)); + if (this.config.operationalLayers && this.config.operationalLayers.length > 0) { + on.once(this.map, 'layers-add-result', lang.hitch(this, '_onLayersAddResult', returnDeferred, returnWarnings)); + } else { + returnDeferred.resolve(returnWarnings); + } } else { returnDeferred.resolve(returnWarnings); } return returnDeferred; }, - _createMap: function () { - var container = dom.byId(this.config.layout.map) || 'mapCenter'; - this.map = new Map(container, this.config.mapOptions); - }, - _onLayersAddResult: function (returnDeferred, returnWarnings, lyrsResult) { array.forEach(lyrsResult.layers, function (addedLayer) { if (addedLayer.success !== true) { @@ -171,18 +193,21 @@ define([ }); } - this.map.on('resize', function (evt) { - var pnt = evt.target.extent.getCenter(); - setTimeout(function () { - evt.target.centerAt(pnt); - }, 100); - }); + if (this.map) { + this.map.on('resize', function (evt) { + var pnt = evt.target.extent.getCenter(); + setTimeout(function () { + evt.target.centerAt(pnt); + }, 100); + }); - // in _LayoutsMixin - this.createPanes(); + // in _LayoutsMixin + this.createPanes(); + + // in _WidgetsMixin + this.initWidgets(); + } - // in _WidgetsMixin - this.initWidgets(); }, initMapError: function (err) { diff --git a/viewer/js/viewer/_WebMapMixin.js b/viewer/js/viewer/_WebMapMixin.js new file mode 100644 index 000000000..b24e4d0d4 --- /dev/null +++ b/viewer/js/viewer/_WebMapMixin.js @@ -0,0 +1,230 @@ +define([ + 'dojo/_base/declare', + 'dojo/_base/lang', + 'dojo/_base/array', + + 'esri/arcgis/utils', + 'esri/units', + + 'dojo/i18n!config/nls/main' + +], function ( + declare, + lang, + array, + + arcgisUtils, + units, + + i18n +) { + return declare(null, { + + _initWebMap: function (webMapId, container, webMapOptions) { + webMapOptions = webMapOptions || {}; + if (!webMapOptions.mapOptions && this.config.mapOptions) { + webMapOptions.mapOptions = this.config.mapOptions; + } + + var mapDeferred = arcgisUtils.createMap(webMapId, container, webMapOptions); + mapDeferred.then(lang.hitch(this, function (response) { + this.webMap = { + clickEventHandle: response.clickEventHandle, + clickEventListener: response.clickEventListener, + itemInfo: response.itemInfo + }; + this.map = response.map; + + // get the layerInfos from the webmap + this._initWebMapLayerInfos(response); + + // add any widgets included in the webmap + this._initWebMapWidgets(response); + })); + return mapDeferred; + + }, + + _initWebMapLayerInfos: function (response) { + if (this.config.layerControlLayerInfos) { + // get the layerInfos for the layerControl widget from the config + this.layerControlLayerInfos = this.config.layerControlLayerInfos; + + } else if (response.itemInfo && response.itemInfo.itemData) { + // get the layerInfos for the layerControl widget from the webmap + + // https://developers.arcgis.com/web-map-specification/objects/operationalLayers/ + var layerTypes = { + 'CSV': 'csv', + 'ArcGISMapServiceLayer': 'dynamic', + 'ArcGISFeatureLayer': 'feature', + 'GeoRSSLayer': 'georss', + 'ArcGISImageServiceLayer': 'image', + 'esri/layers/ArcGISImageServiceVectorLayer': 'imagevector', + 'KML': 'kml', + 'ArcGISStreamLayer': 'stream', + 'ArcGISTiledMapServiceLayer': 'tiled', + 'VectorTileLayer': 'vectortile', + 'WebTiledLayer': 'webtiled', + 'WMS': 'wms' + + /* + Are these supported in Webmaps? + + 'dataadapter': 'esri/layer/DataAdapterFeatureLayer', //untested + label: 'esri/layers/LabelLayer', //untested + mapimage: 'esri/layers/MapImageLayer', //untested + osm: 'esri/layers/OpenStreetMapLayer', + raster: 'esri/layers/RasterLayer', + 'wfs': 'esri/layers/WFSLayer', + 'esri/layers/WMTS': 'wmts' + */ + }; + + var operationalLayers = response.itemInfo.itemData.operationalLayers; + array.forEach(operationalLayers, lang.hitch(this, function (layer) { + var layerType = layerTypes[layer.layerType]; + if (layerType) { + this.layerControlLayerInfos.push({ + layer: layer.layerObject, + type: layerType, + title: layer.title + }); + } + })); + } + + if (this.config.legendLayerInfos) { + // get the layerInfos for the legend widget from the config + this.legendLayerInfos = this.config.legendLayerInfos; + } else { + // get the layerInfos for the legend widget from the webmap + this.legendLayerInfos = arcgisUtils.getLegendLayers(response); + } + }, + + _initWebMapWidgets: function (response) { + if (!response.itemInfo || !response.itemInfo.itemData) { + return; + } + + // existing widgets if any + var widgets = this.config.widgets; + + var bookmarks = response.itemInfo.itemData.bookmarks; + if (bookmarks && bookmarks.length > 0) { + widgets.bookmarks = this.mixinDeep({ + include: true, + id: 'bookmarks', + type: 'titlePane', + path: 'gis/dijit/Bookmarks', + title: 'Bookmarks', + open: false, + position: 999, + options: { + map: true, + editable: false, + bookmarks: bookmarks + } + }, widgets.bookmarks || {}); + } + + if (response.itemInfo.itemData.applicationProperties) { + // https://developers.arcgis.com/web-map-specification/objects/viewing/ + var viewing = response.itemInfo.itemData.applicationProperties.viewing; + // possible widgets: basemapGallery, measure, routing, search + + if (viewing.basemapGallery && viewing.basemapGallery.enabled) { + if (!widgets.basemaps || !widgets.basemaps.include) { // basemap gallery widget and basemaps widget cannot co-exist + widgets.basemapGallery = this.mixinDeep({ + include: true, + id: 'basemapGallery', + type: 'domNode', + path: 'gis/dijit/BasemapGallery', + srcNodeRef: 'basemapsDijit', + options: { + map: true + } + }, widgets.basemapGallery || {}); + } + } + + if (viewing.routing && viewing.routing.enabled) { + widgets.directions = this.mixinDeep({ + include: true, + id: 'directions', + type: 'titlePane', + path: 'gis/dijit/Directions', + title: i18n.viewer.widgets.directions, + open: false, + position: 999, + options: { + map: true, + mapRightClickMenu: true, + options: { + routeTaskUrl: 'https://sampleserver3.arcgisonline.com/ArcGIS/rest/services/Network/USA/NAServer/Route', + routeParams: { + directionsLanguage: 'en-US', + directionsLengthUnits: units.MILES + }, + active: false //for 3.12, starts active by default, which we dont want as it interfears with mapClickMode + } + } + }, widgets.directions || {}); + } + + if (viewing.measure && viewing.measure.enabled) { + widgets.measure = this.mixinDeep({ + include: true, + id: 'measurement', + type: 'titlePane', + path: 'gis/dijit/Measurement', + title: i18n.viewer.widgets.measure, + open: false, + position: 999, + options: { + map: true, + mapClickMode: true, + defaultAreaUnit: units.SQUARE_MILES, + defaultLengthUnit: units.MILES + } + }, widgets.measure || {}); + } + + if (viewing.search && viewing.search.enabled) { + widgets.search = this.mixinDeep({ + include: true, + type: 'domNode', + path: 'esri/dijit/Search', + srcNodeRef: 'geocoderButton', + options: { + map: true, + visible: true, + enableButtonMode: true, + expanded: true, + disablePlaceFinder: viewing.search.disablePlaceFinder, + hintText: viewing.search.hintText, + layers: viewing.search.layers + } + }, widgets.search || {}); + } + } + + // https://developers.arcgis.com/web-map-specification/objects/widgets/ + if (response.itemInfo.itemData.widgets) { + var timeSlider = response.itemInfo.itemData.widgets.timeSlider; + if (timeSlider) { + widgets.timeSlider = this.mixinDeep({ + include: true, + type: 'domNode', + path: 'esri/dijit/TimeSlider', + srcNodeRef: 'geocoderButton', + options: lang.mixin({ + map: true + }, timeSlider) + }, widgets.slider || {}); + } + } + } + }); +}); \ No newline at end of file