diff --git a/src/feature.js b/src/feature.js index a0f48ef41a..9f9941cadc 100644 --- a/src/feature.js +++ b/src/feature.js @@ -53,7 +53,7 @@ var feature = function (arg) { // Don't bind handlers for improved performance on features that don't // require it. - if (!m_selectionAPI) { + if (!this.selectionAPI()) { return; } @@ -413,24 +413,37 @@ var feature = function (arg) { //////////////////////////////////////////////////////////////////////////// /** * Get/Set visibility of the feature + * + * @param {boolean|undefined} val: undefined to return the visibility, a + * boolean to change the visibility. + * @param {boolean} direct: if true, when getting the visibility, disregard + * the visibility of the parent layer, and when setting, refresh the state + * regardless of whether it has changed or not. + * @return {boolean|object} either the visibility (if getting) or the feature + * (if setting). */ //////////////////////////////////////////////////////////////////////////// - this.visible = function (val) { + this.visible = function (val, direct) { if (val === undefined) { + if (!direct && m_layer && m_layer.visible && !m_layer.visible()) { + return false; + } return m_visible; } - if (m_visible !== val) { + if (m_visible !== val || direct) { m_visible = val; m_this.modified(); - + if (m_layer && m_layer.visible && !m_layer.visible()) { + val = false; + } // bind or unbind mouse handlers on visibility change - if (m_visible) { + if (val) { m_this._bindMouseHandlers(); } else { m_this._unbindMouseHandlers(); } for (var i = 0; i < m_dependentFeatures.length; i += 1) { - m_dependentFeatures[i].visible(val); + m_dependentFeatures[i].visible(m_visible, direct); } } return m_this; @@ -532,16 +545,26 @@ var feature = function (arg) { //////////////////////////////////////////////////////////////////////////// /** - * Query or set if the selection API is enabled for this feature. - * @returns {bool} + * Get/Set if the selection API is enabled for this feature. + * + * @param {boolean|undefined} val: undefined to return the selectionAPI + * state, or a boolean to change the state. + * @param {boolean} direct: if true, when getting the selectionAPI state, + * disregard the state of the parent layer, and when setting, refresh the + * state regardless of whether it has changed or not. + * @return {boolean|object} either the selectionAPI state (if getting) or the + * feature (if setting). */ //////////////////////////////////////////////////////////////////////////// - this.selectionAPI = function (arg) { + this.selectionAPI = function (arg, direct) { if (arg === undefined) { + if (!direct && m_layer && m_layer.selectionAPI && !m_layer.selectionAPI()) { + return false; + } return m_selectionAPI; } arg = !!arg; - if (arg !== m_selectionAPI) { + if (arg !== m_selectionAPI || direct) { m_selectionAPI = arg; this._unbindMouseHandlers(); this._bindMouseHandlers(); diff --git a/src/featureLayer.js b/src/featureLayer.js index 1f2fa62dc9..7ca8d0ab95 100644 --- a/src/featureLayer.js +++ b/src/featureLayer.js @@ -30,6 +30,8 @@ var featureLayer = function (arg) { s_init = this._init, s_exit = this._exit, s_update = this._update, + s_visible = this.visible, + s_selectionAPI = this.selectionAPI, s_draw = this.draw; //////////////////////////////////////////////////////////////////////////// @@ -231,13 +233,72 @@ var featureLayer = function (arg) { */ //////////////////////////////////////////////////////////////////////////// this.draw = function () { - // Call sceneObject.draw, which calls draw on all child objects. - s_draw(); + if (m_this.visible()) { + // Call sceneObject.draw, which calls draw on all child objects. + s_draw(); - // Now call render on the renderer. In certain cases it may not do - // anything if the if the child objects are drawn on the screen already. - if (m_this.renderer()) { - m_this.renderer()._render(); + // Now call render on the renderer. In certain cases it may not do + // anything if the child objects are drawn on the screen already. + if (m_this.renderer()) { + m_this.renderer()._render(); + } + } + return m_this; + }; + + //////////////////////////////////////////////////////////////////////////// + /** + * Get/Set visibility of the layer + * + * @param {boolean|undefined} val: undefined to return the visibility, a + * boolean to change the visibility. + * @return {boolean|object} either the visibility (if getting) or the layer + * (if setting). + */ + //////////////////////////////////////////////////////////////////////////// + this.visible = function (val) { + if (val === undefined) { + return s_visible(); + } + if (m_this.visible() !== val) { + s_visible(val); + + // take a copy of the features; changing visible could mutate them. + var features = m_features.slice(), i; + + for (i = 0; i < features.length; i += 1) { + features[i].visible(features[i].visible(undefined, true), true); + } + if (val) { + m_this.draw(); + } + } + return m_this; + }; + + //////////////////////////////////////////////////////////////////////////// + /** + * Get/Set selectionAPI of the layer + * + * @param {boolean|undefined} val: undefined to return the selectionAPI + * state, or a boolean to change it. + * @return {boolean|object} either the selectionAPI state (if getting) or the + * layer (if setting). + */ + //////////////////////////////////////////////////////////////////////////// + this.selectionAPI = function (val) { + if (val === undefined) { + return s_selectionAPI(); + } + if (m_this.selectionAPI() !== val) { + s_selectionAPI(val); + + // take a copy of the features; changing selectionAPI could mutate them. + var features = m_features.slice(), i; + + for (i = 0; i < features.length; i += 1) { + features[i].selectionAPI(features[i].selectionAPI(undefined, true), true); + } } return m_this; }; diff --git a/src/gl/quadFeature.js b/src/gl/quadFeature.js index 6be4e59c40..55fe0a8f52 100644 --- a/src/gl/quadFeature.js +++ b/src/gl/quadFeature.js @@ -373,11 +373,11 @@ var gl_quadFeature = function (arg) { m_this._build(); } if (m_actor_color) { - m_actor_color.setVisible(m_this.visible()); + m_actor_color.setVisible(m_this.visible(undefined, true)); m_actor_color.material().setBinNumber(m_this.bin()); } if (m_actor_image) { - m_actor_image.setVisible(m_this.visible()); + m_actor_image.setVisible(m_this.visible(undefined, true)); m_actor_image.material().setBinNumber(m_this.bin()); } m_this.updateTime().modified(); diff --git a/src/layer.js b/src/layer.js index 74cf53a6ae..da5676b7d2 100644 --- a/src/layer.js +++ b/src/layer.js @@ -55,6 +55,8 @@ var layer = function (arg) { m_active = arg.active === undefined ? true : arg.active, m_opacity = arg.opacity === undefined ? 1 : arg.opacity, m_attribution = arg.attribution || null, + m_visible = arg.visible === undefined ? true : arg.visible, + m_selectionAPI = arg.selectionAPI === undefined ? true : arg.selectionAPI, m_zIndex; m_rendererName = checkRenderer(m_rendererName); @@ -192,20 +194,27 @@ var layer = function (arg) { //////////////////////////////////////////////////////////////////////////// /** - * Get whether or not the layer is active. An active layer will receive + * Get/Set whether or not the layer is active. An active layer will receive * native mouse when the layer is on top. Non-active layers will never * receive native mouse events. * - * @returns {Boolean} + * @returns {Boolean|object} */ //////////////////////////////////////////////////////////////////////////// - this.active = function () { - return m_active; + this.active = function (arg) { + if (arg === undefined) { + return m_active; + } + if (m_active !== arg) { + m_active = arg; + m_node.toggleClass('active', m_active); + } + return this; }; //////////////////////////////////////////////////////////////////////////// /** - * Get/Set root node of the layer + * Get root node of the layer * * @returns {div} */ @@ -352,6 +361,48 @@ var layer = function (arg) { return m_attribution; }; + //////////////////////////////////////////////////////////////////////////// + /** + * Get/Set visibility of the layer + * + * @param {boolean|undefined} val: undefined to return the visibility, a + * boolean to change the visibility. + * @return {boolean|object} either the visibility (if getting) or the layer + * (if setting). + */ + //////////////////////////////////////////////////////////////////////////// + this.visible = function (val) { + if (val === undefined) { + return m_visible; + } + if (m_visible !== val) { + m_visible = val; + m_node.css('display', m_visible ? '' : 'none'); + m_this.modified(); + } + return m_this; + }; + + //////////////////////////////////////////////////////////////////////////// + /** + * Get/Set selectionAPI of the layer + * + * @param {boolean|undefined} val: undefined to return the selectionAPI + * state, or a boolean to change it. + * @return {boolean|object} either the selectionAPI state (if getting) or the + * layer (if setting). + */ + //////////////////////////////////////////////////////////////////////////// + this.selectionAPI = function (val) { + if (val === undefined) { + return m_selectionAPI; + } + if (m_selectionAPI !== val) { + m_selectionAPI = val; + } + return m_this; + }; + //////////////////////////////////////////////////////////////////////////// /** * Init layer diff --git a/src/pixelmapFeature.js b/src/pixelmapFeature.js index 43c5edb237..facb46bc5d 100644 --- a/src/pixelmapFeature.js +++ b/src/pixelmapFeature.js @@ -339,13 +339,14 @@ var pixelmapFeature = function (arg) { m_quadFeature = m_this.layer().createFeature('quad', { selectionAPI: false, gcs: m_this.gcs(), - visible: m_this.visible() + visible: m_this.visible(undefined, true) }); m_this.dependentFeatures([m_quadFeature]); - m_quadFeature.style({image: m_info.canvas, - position: m_this.style.get('position')}) - .data([{}]) - .draw(); + m_quadFeature.style({ + image: m_info.canvas, + position: m_this.style.get('position')}) + .data([{}]) + .draw(); } /* If we prepared the pixelmap and rendered it, send a prepared event */ if (prepared) { diff --git a/src/polygonFeature.js b/src/polygonFeature.js index f745177bf4..3b5bc753af 100644 --- a/src/polygonFeature.js +++ b/src/polygonFeature.js @@ -271,7 +271,7 @@ var polygonFeature = function (arg) { m_lineFeature = m_this.layer().createFeature('line', { selectionAPI: false, gcs: m_this.gcs(), - visible: m_this.visible() + visible: m_this.visible(undefined, true) }); m_this.dependentFeatures([m_lineFeature]); } diff --git a/src/tileLayer.js b/src/tileLayer.js index ac39d2a963..efab5ab1c8 100644 --- a/src/tileLayer.js +++ b/src/tileLayer.js @@ -138,6 +138,11 @@ module.exports = (function () { */ ////////////////////////////////////////////////////////////////////////////// var tileLayer = function (options) { + 'use strict'; + if (!(this instanceof tileLayer)) { + return new tileLayer(options); + } + featureLayer.call(this, options); var $ = require('jquery'); var geo_event = require('./event'); @@ -147,11 +152,6 @@ module.exports = (function () { var adjustLayerForRenderer = require('./registry').adjustLayerForRenderer; var Tile = require('./tile'); - if (!(this instanceof tileLayer)) { - return new tileLayer(options); - } - featureLayer.call(this, options); - options = $.extend(true, {}, this.constructor.defaults, options || {}); if (!options.cacheSize) { // this size should be sufficient for a 4k display @@ -177,6 +177,7 @@ module.exports = (function () { var s_init = this._init, s_exit = this._exit, + s_visible = this.visible, m_lastTileSet = [], m_maxBounds = [], m_exited; @@ -1063,6 +1064,9 @@ module.exports = (function () { evt.event.event === geo_event.rotate)) { return; } + if (!this.visible()) { + return; + } var map = this.map(), bounds = map.bounds(undefined, null), mapZoom = map.zoom(), @@ -1430,6 +1434,30 @@ module.exports = (function () { return m_tileOffsetValues[level]; }; + //////////////////////////////////////////////////////////////////////////// + /** + * Get/Set visibility of the layer + * + * @param {boolean|undefined} val: undefined to return the visibility, a + * boolean to change the visibility. + * @return {boolean|object} either the visibility (if getting) or the layer + * (if setting). + */ + //////////////////////////////////////////////////////////////////////////// + this.visible = function (val) { + if (val === undefined) { + return s_visible(); + } + if (this.visible() !== val) { + s_visible(val); + + if (val) { + this._update(); + } + } + return this; + }; + /** * Initialize after the layer is added to the map. */ diff --git a/tests/cases/feature.js b/tests/cases/feature.js index 1360d89205..304f9974bc 100644 --- a/tests/cases/feature.js +++ b/tests/cases/feature.js @@ -198,6 +198,20 @@ describe('geo.feature', function () { feat.dependentFeatures([]); expect(feat.visible(true)).toBe(feat); expect(depFeat.visible()).toBe(false); + + // the layer can control the visibility + expect(feat.visible()).toBe(true); + expect(feat.visible(undefined, true)).toBe(true); + layer.visible(false); + expect(feat.visible()).toBe(false); + expect(feat.visible(undefined, true)).toBe(true); + expect(feat.visible(false, true)).toBe(feat); + expect(feat.visible()).toBe(false); + expect(feat.visible(undefined, true)).toBe(false); + layer.visible(true); + expect(feat.visible()).toBe(false); + expect(feat.visible(true, true)).toBe(feat); + expect(feat.visible()).toBe(true); }); }); describe('Check class accessors', function () { @@ -281,6 +295,21 @@ describe('geo.feature', function () { expect(feat.selectionAPI()).toBe(true); expect(feat.selectionAPI(0)).toBe(feat); expect(feat.selectionAPI()).toBe(false); + + // the layer can control the visibility + feat.selectionAPI(true); + expect(feat.selectionAPI()).toBe(true); + expect(feat.selectionAPI(undefined, true)).toBe(true); + layer.selectionAPI(false); + expect(feat.selectionAPI()).toBe(false); + expect(feat.selectionAPI(undefined, true)).toBe(true); + expect(feat.selectionAPI(false, true)).toBe(feat); + expect(feat.selectionAPI()).toBe(false); + expect(feat.selectionAPI(undefined, true)).toBe(false); + layer.selectionAPI(true); + expect(feat.selectionAPI()).toBe(false); + expect(feat.selectionAPI(true, true)).toBe(feat); + expect(feat.selectionAPI()).toBe(true); }); }); }); diff --git a/tests/cases/featureLayer.js b/tests/cases/featureLayer.js index ec4ba6648c..9dcfe3f239 100644 --- a/tests/cases/featureLayer.js +++ b/tests/cases/featureLayer.js @@ -127,6 +127,43 @@ describe('geo.featureLayer', function () { expect(layer.features().length).toBe(2); expect(layer.features()).toEqual([feat2, feat1]); }); + it('visible', function () { + expect(layer.visible()).toBe(true); + expect(feat1.visible()).toBe(true); + expect(feat1.visible(undefined, true)).toBe(true); + expect(layer.visible(false)).toBe(layer); + expect(layer.visible()).toBe(false); + expect(feat1.visible()).toBe(false); + expect(feat1.visible(undefined, true)).toBe(true); + expect(layer.visible(true)).toBe(layer); + expect(layer.visible()).toBe(true); + expect(feat1.visible()).toBe(true); + expect(feat1.visible(undefined, true)).toBe(true); + }); + it('selectionAPI', function () { + feat1.selectionAPI(true); + expect(layer.selectionAPI()).toBe(true); + expect(feat1.selectionAPI()).toBe(true); + expect(feat1.selectionAPI(undefined, true)).toBe(true); + expect(layer.selectionAPI(false)).toBe(layer); + expect(layer.selectionAPI()).toBe(false); + expect(feat1.selectionAPI()).toBe(false); + expect(feat1.selectionAPI(undefined, true)).toBe(true); + expect(layer.selectionAPI(true)).toBe(layer); + expect(layer.selectionAPI()).toBe(true); + expect(feat1.selectionAPI()).toBe(true); + expect(feat1.selectionAPI(undefined, true)).toBe(true); + }); + it('active', function () { + expect(layer.active()).toBe(true); + expect(layer.node().hasClass('active')).toBe(true); + expect(layer.active(false)).toBe(layer); + expect(layer.active()).toBe(false); + expect(layer.node().hasClass('active')).toBe(false); + expect(layer.active(true)).toBe(layer); + expect(layer.active()).toBe(true); + expect(layer.node().hasClass('active')).toBe(true); + }); it('draw', function () { sinon.stub(feat1, 'draw', function () {}); expect(layer.draw()).toBe(layer); diff --git a/tests/cases/tileLayer.js b/tests/cases/tileLayer.js index 6a81bbe8e2..428374b290 100644 --- a/tests/cases/tileLayer.js +++ b/tests/cases/tileLayer.js @@ -96,6 +96,8 @@ describe('geo.tileLayer', function () { }, updateAttribution: function () { }, + bounds: function () { + }, node: get_set('node'), children: function () { return []; @@ -415,6 +417,29 @@ describe('geo.tileLayer', function () { expect(l.tilesAtZoom(2)).toEqual({x: 4, y: 3}); expect(l.tilesAtZoom(3)).toEqual({x: 8, y: 6}); }); + it('visible', function () { + var m = map(), layer, count = 0; + opts.map = m; + layer = geo.tileLayer(opts); + // check if we are updating by doing the least possible and tracking it + layer._getTiles = function () { + count += 1; + }; + layer._updateSubLayers = undefined; + + expect(layer.visible()).toBe(true); + layer._update(); + expect(count).toBe(1); + expect(layer.visible(false)).toBe(layer); + expect(layer.visible()).toBe(false); + layer._update(); + expect(count).toBe(1); + expect(layer.visible(true)).toBe(layer); + expect(layer.visible()).toBe(true); + expect(count).toBe(2); + layer._update(); + expect(count).toBe(3); + }); }); describe('Public utility methods', function () { describe('isValid', function () {