diff --git a/src/js/templates/search/catalogSearch.html b/src/js/templates/search/catalogSearch.html index ea4de5f67..367890c8b 100644 --- a/src/js/templates/search/catalogSearch.html +++ b/src/js/templates/search/catalogSearch.html @@ -16,7 +16,7 @@ id="toggle-spatial-filter" type="checkbox" name="map" - checked="true" + <% if (mapFilterOn) { %> checked="true" <% } %> class="map-filter-toggle__checkbox" /> diff --git a/src/js/views/maps/CesiumWidgetView.js b/src/js/views/maps/CesiumWidgetView.js index 938036820..e9fc02087 100644 --- a/src/js/views/maps/CesiumWidgetView.js +++ b/src/js/views/maps/CesiumWidgetView.js @@ -194,10 +194,6 @@ define( // raised. view.camera.percentChanged = 0.1 - // Zoom functions executed after each scene render - view.scene.postRender.addEventListener(function () { - view.postRender(); - }); // Disable HDR lighting for better performance and to avoid changing imagery colors. view.scene.highDynamicRange = false; @@ -224,81 +220,148 @@ define( view.updateDataSourceDisplay.call(view) }) + view.setListeners(); + view.addLayers(); + // Go to the home position, if one is set. view.flyHome(0) - // If users are allowed to click on features for more details, initialize - // picking behavior on the map. + // If users are allowed to click on features for more details, + // initialize picking behavior on the map. if (view.model.get('showFeatureInfo')) { view.initializePicking() } - // Set listeners for when the Cesium camera changes a significant amount. - view.camera.changed.addEventListener(function () { - view.trigger('moved') - view.model.trigger('moved') - // Update the bounding box for the visible area in the Map model - view.updateViewExtent() - // If the scale bar is showing, update the pixel to meter scale on the map - // model when the camera angle/zoom level changes - if (view.model.get('showScaleBar')) { - view.updateCurrentScale() - } - }) + return this - view.camera.moveEnd.addEventListener(function () { - view.trigger('moveEnd') - view.model.trigger('moveEnd') - }) - view.camera.moveStart.addEventListener(function () { - view.trigger('moveStart') - view.model.trigger('moveStart') - }) + } + catch (error) { + console.log( + 'Failed to render a CesiumWidgetView. Error details: ' + error + ); + } + }, - // Sets listeners for when the mouse moves, depending on the value of the map - // model's showScaleBar and showFeatureInfo attributes - view.setMouseMoveListeners() - - // When the appearance of a layer has been updated, then tell Cesium to - // re-render the scene. Each layer model triggers the 'appearanceChanged' - // function whenever the color, opacity, etc. has been updated in the - // associated Cesium model. - view.stopListening(view.model.get('layers'), 'appearanceChanged') - view.listenTo(view.model.get('layers'), 'appearanceChanged', view.requestRender) - - // Other views may trigger an event on the layer/asset model that indicates - // that the map should navigate to the extent of the data, or on the Map model - // to navigate to the home position. - view.stopListening(view.model.get('layers'), 'flyToExtent') - view.listenTo(view.model.get('layers'), 'flyToExtent', view.flyTo) - view.stopListening(view.model, 'flyHome') - view.listenTo(view.model, 'flyHome', view.flyHome) - - // Add each layer from the Map model to the Cesium widget. Render using the - // function configured in the View's mapAssetRenderFunctions property. Add in - // reverse order for layers to appear in the correct order on the map. - const layers = view.model.get('layers') - _.each(layers.last(layers.length).reverse(), function (mapAsset) { - view.addAsset(mapAsset) - }); + /** + * Set all of the listeners for the CesiumWidgetView. This function is + * called during the render function. + * @since x.x.x + */ + setListeners: function () { + + const view = this; - // The Cesium Widget will support just one terrain option to start. Later, - // we'll allow users to switch between terrains if there is more than one. - var terrains = view.model.get('terrains') - var terrainModel = terrains ? terrains.first() : false; - if (terrainModel) { - view.addAsset(terrainModel) + // Zoom functions executed after each scene render + view.scene.postRender.addEventListener(function () { + view.postRender(); + }); + + // When the user first interacts with the map, update the model. + // Ignore the event where the user just moves the mouse over the map. + view.listenOnceForInteraction(function () { + view.model.set('firstInteraction', true); + }, ["MOUSE_MOVE"]); + + // Set listeners for when the Cesium camera changes a significant + // amount. + view.camera.changed.addEventListener(function () { + view.trigger('moved') + view.model.trigger('moved') + // Update the bounding box for the visible area in the Map model + view.updateViewExtent() + // If the scale bar is showing, update the pixel to meter scale on + // the map model when the camera angle/zoom level changes + if (view.model.get('showScaleBar')) { + view.updateCurrentScale() } + }) + + view.camera.moveEnd.addEventListener(function () { + view.trigger('moveEnd') + view.model.trigger('moveEnd') + }) + view.camera.moveStart.addEventListener(function () { + view.trigger('moveStart') + view.model.trigger('moveStart') + }) + + // Sets listeners for when the mouse moves, depending on the value + // of the map model's showScaleBar and showFeatureInfo attributes + view.setMouseMoveListeners() + + // When the appearance of a layer has been updated, then tell Cesium + // to re-render the scene. Each layer model triggers the + // 'appearanceChanged' function whenever the color, opacity, etc. + // has been updated in the associated Cesium model. + view.stopListening(view.model.get('layers'), 'appearanceChanged') + view.listenTo(view.model.get('layers'), 'appearanceChanged', view.requestRender) + + // Other views may trigger an event on the layer/asset model that + // indicates that the map should navigate to the extent of the data, + // or on the Map model to navigate to the home position. + view.stopListening(view.model.get('layers'), 'flyToExtent') + view.listenTo(view.model.get('layers'), 'flyToExtent', view.flyTo) + view.stopListening(view.model, 'flyHome') + view.listenTo(view.model, 'flyHome', view.flyHome) + }, + /** + * Listen for any user interaction with the map. Once an interaction has + * occurred, run the callback function and stop listening for + * interactions. Useful for detecting the first user interaction with the + * map. + * @param {function} callback - The function to run once the interaction + * has occurred. + * @param {string[]} ignore - An array of Cesium.ScreenSpaceEventType + * labels to ignore. See + * {@link https://cesium.com/learn/cesiumjs/ref-doc/ScreenSpaceEventType.html} + * @since x.x.x + */ + listenOnceForInteraction: function ( + callback, + ignore = [] + ) { + const view = this; + const events = Cesium.ScreenSpaceEventType; + const inputHandler = new Cesium.ScreenSpaceEventHandler( + view.scene.canvas + ); + if (!ignore || !Array.isArray(ignore)) ignore = []; + + Object.entries(events).forEach(function ([label, value]) { + if (ignore.includes(label)) return; + inputHandler.setInputAction(function () { + callback(); + inputHandler.destroy(); + }, value); + }); + }, + /** + * Add all of the model's layers to the map. This function is called + * during the render function. + * @since x.x.x + */ + addLayers: function () { - return this + const view = this; - } - catch (error) { - console.log( - 'Failed to render a CesiumWidgetView. Error details: ' + error - ); + // Add each layer from the Map model to the Cesium widget. Render + // using the function configured in the View's mapAssetRenderFunctions + // property. Add in reverse order for layers to appear in the correct + // order on the map. + const layers = view.model.get('layers') + _.each(layers.last(layers.length).reverse(), function (mapAsset) { + view.addAsset(mapAsset) + }); + + // The Cesium Widget will support just one terrain option to start. + // Later, we'll allow users to switch between terrains if there is + // more than one. + var terrains = view.model.get('terrains') + var terrainModel = terrains ? terrains.first() : false; + if (terrainModel) { + view.addAsset(terrainModel) } }, diff --git a/src/js/views/search/CatalogSearchView.js b/src/js/views/search/CatalogSearchView.js index 2dccc5559..b4066dd21 100644 --- a/src/js/views/search/CatalogSearchView.js +++ b/src/js/views/search/CatalogSearchView.js @@ -93,13 +93,25 @@ define([ filtersVisible: true, /** - * Whether to limit the search to the extent of the map. If true, the - * search will update when the user pans or zooms the map. + * Whether to limit the search to the extent of the map. When true, the + * search will update when the user pans or zooms the map. This property + * will be updated when the user clicks the map filter toggle. Whatever is + * set during the initial render will be the default. * @type {boolean} * @since 2.25.0 + * @default false + */ + limitSearchToMapArea: false, + + /** + * Whether to limit the search to the extent the first time the user + * interacts with the map. This only applies if limitSearchToMapArea is + * initially set to false. + * @type {boolean} + * @since x.x.x * @default true */ - limitSearchToMapArea: true, + limitSearchToMapOnInteraction: true, /** * The View that displays the search results. The render method will be @@ -300,8 +312,9 @@ define([ addSpatialFilter: options.addSpatialFilter !== false, }); } - model.connect(); + this.model = model; + this.model.connect(); }, /** @@ -317,6 +330,9 @@ define([ // Render the search components this.renderComponents(); + + // Set up the initial map toggle state + this.setMapToggleState(); }, /** @@ -354,6 +370,27 @@ define([ this.toggleMapVisibility(this.mapVisible); }, + /** + * Sets the initial state of the map filter toggle. Optionally listens + * for the first user interaction with the map before turning on the + * spatial filter. + * @since x.x.x + */ + setMapToggleState: function () { + // Set the initial state of the spatial filter + this.toggleMapFilter(this.limitSearchToMapArea); + + if (this.limitSearchToMapOnInteraction && !this.limitSearchToMapArea) { + this.listenToOnce( + this.model.get("map"), + "change:firstInteraction", + function () { + this.toggleMapFilter(true); + } + ); + } + }, + /** * Sets up the basic components of this view * @since 2.22.0 @@ -373,7 +410,11 @@ define([ this.addLinkedData(); // Render the template - this.$el.html(this.template({})); + this.$el.html( + this.template({ + mapFilterOn: this.limitSearchToMapArea === true, + }) + ); } catch (e) { console.log( "There was an error setting up the CatalogSearchView:" + e @@ -817,12 +858,25 @@ define([ ? !this.limitSearchToMapArea // the opposite of the current mode : newSetting; // the provided new mode if it is a boolean + // Select the map filter toggle checkbox so that we can keep it in sync + // with the new setting + let mapFilterToggle = this.el.querySelector(this.mapFilterToggle); + // If it's not a checkbox input, find the child checkbox input + if (mapFilterToggle && mapFilterToggle.tagName != "INPUT") { + mapFilterToggle = mapFilterToggle.querySelector("input"); + } if (newSetting) { // If true, then the filter should be ON this.model.connectFiltersMap(); + if (mapFilterToggle) { + mapFilterToggle.checked = true; + } } else { // If false, then the filter should be OFF this.model.disconnectFiltersMap(true); + if (mapFilterToggle) { + mapFilterToggle.checked = false; + } } this.limitSearchToMapArea = newSetting; },