'use strict';
const layerInterface = require('./layerInterface.js')();
const root = require('./root.js')();
const shared = require('./shared.js')();
const geometryTypes = {
POINT: 'Point',
MULTIPOINT: 'MultiPoint',
LINESTRING: 'LineString',
MULTILINESTRING: 'MultiLineString'
}
const defaultSymbols = {
POINT: {
width: 16.5,
height: 16.5,
type: 'esriSMS',
color: [255, 0, 0],
outline: {
color: [0,0,0],
width: 1,
type: 'esriSLS',
style: 'esriSLSSolid'
}
}
}
/**
* @class GraphicsRecord
*/
class GraphicsRecord extends root.Root {
/**
* Create a graphics layer record with the appropriate geoApi layer type.
* TODO: possibly have an intermediate class between root and layerRecord to have all the duplicated items, such as hovertips.
* TODO: add identify functionality and fix up hover listeners / other features that might need to be modified
* @param {Object} esriBundle bundle of API classes
* @param {Object} apiRef object pointing to the geoApi. allows us to call other geoApi functions.
* @param {String} name name and id of the layer.
*/
constructor (esriBundle, apiRef, name) {
super();
this._bundle = esriBundle;
this._apiRef = apiRef;
this._layerClass = esriBundle.GraphicsLayer;
this._name = name;
this._id = name;
this._layer = this._layerClass({ id: this._name });
this.bindEvents(this._layer);
this._hoverListeners = [];
}
get layerId () { return this._id; }
get name () { return this._name; }
set name (value) {
this._name = value;
this._id = value;
}
get layerType () { return shared.clientLayerType.ESRI_GRAPHICS; }
get visibility () {
if (this._layer) {
return this._layer.visible;
} else {
return true; // TODO what should a proper default be? example of this situation??
}
}
set visibility (value) {
if (this._layer) {
this._layer.setVisibility(value);
}
// TODO do we need an ELSE case here?
}
get opacity () {
if (this._layer) {
return this._layer.opacity;
} else {
return 1; // TODO what should a proper default be? example of this situation??
}
}
set opacity (value) {
if (this._layer) {
this._layer.setOpacity(value);
}
// TODO do we need an ELSE case here?
}
/**
* Attach record event handlers to common layer events
*
* @function bindEvents
* @param {Object} layer the api layer object
*/
bindEvents (layer) {
// TODO optional refactor. Rather than making the events object in the parameter,
// do it as a variable, and only add mouse-over, mouse-out events if we are
// in an app configuration that will use it. May save a bit of processing
// by not having unused events being handled and ignored.
// Second optional thing. Call a separate wrapEvents in FeatuerRecord class
// TODO apply johann update here
this._apiRef.events.wrapEvents(layer, {
// wrapping the function calls to keep `this` bound correctly
'mouse-over': e => this.onMouseOver(e),
'mouse-out': e => this.onMouseOut(e)
});
}
/**
* Wire up mouse hover listener.
*
* @function addHoverListener
* @param {Function} listenerCallback function to call when a hover event happens
*/
addHoverListener (listenerCallback) {
this._hoverListeners.push(listenerCallback);
return listenerCallback;
}
/**
* Remove a mouse hover listener.
*
* @function removeHoverListener
* @param {Function} listenerCallback function to not call when a hover event happens
*/
removeHoverListener (listenerCallback) {
const idx = this._hoverListeners.indexOf(listenerCallback);
if (idx < 0) {
throw new Error('Attempting to remove a listener which is not registered.');
}
this._hoverListeners.splice(idx, 1);
}
/**
* Provides the proxy interface object to the layer.
*
* @function getProxy
* @returns {Object} the proxy interface for the layer
*/
getProxy () {
if (!this._rootProxy) {
this._rootProxy = new layerInterface.LayerInterface(this);
this._rootProxy.convertToGraphicsLayer(this);
}
return this._rootProxy;
}
/**
* Triggers when the mouse enters a feature of the layer.
*
* @function onMouseOver
* @param {Object} standard mouse event object
*/
onMouseOver (e) {
if (this._hoverListeners.length > 0) {
const showBundle = {
type: 'mouseOver',
point: e.screenPoint,
target: e.target
};
// tell anyone listening we moused into something
this._fireEvent(this._hoverListeners, showBundle);
}
}
/**
* Triggers when the mouse leaves a feature of the layer.
*
* @function onMouseOut
* @param {Object} standard mouse event object
*/
onMouseOut (e) {
// tell anyone listening we moused out
const outBundle = {
type: 'mouseOut',
target: e.target
};
this._fireEvent(this._hoverListeners, outBundle);
}
/**
* Identify the type of geometry being added and add it to the map.
*
* @function addGeometry
* @param {Object|Array} geo api geometry class to be added
* @param {Object} spatialReference the projection the graphics should be in
*/
addGeometry(geo, spatialReference) {
const geometries = Array.isArray(geo) ? geo : [ geo ];
geometries.forEach(geometry => {
if (geometry.type === geometryTypes.POINT) {
const coords = geometry.xy.projectToPoint(spatialReference);
const icon = geometry.icon;
this._addPoint(coords, spatialReference, icon);
}
// TODO: add 'private' functions and conditions for other geometry types as well
});
}
/**
* Add a point where specified using longitute and latitute.
*
* @function _addPoint
* @private
* @param {Object} coords the long and lat to use as the graphic location
* @param {Object} spatialReference the projection the graphics should be in
* @param {String} icon data url to for layer icon. defaults to a red point
*/
_addPoint(coords, spatialReference, icon) {
const point = new this._bundle.Point({
x: coords.x,
y: coords.y,
spatialReference: spatialReference
});
let symbol;
if (icon) {
// TODO: discuss how to handle the width / height issue when passing in an icon
symbol = {
width: 16.5,
height: 16.5,
type: 'esriPMS',
contentType: 'image/png',
url: icon
};
}
const marker = new this._bundle.Graphic({ symbol: symbol || defaultSymbols.POINT });
marker.setGeometry(point);
this._layer.add(marker);
}
/**
* Remove the specified graphic.
*
* @function removeGeometry
* @param {Number} index index of the graphic to remove from the layer
*/
removeGeometry(index) {
const graphic = this._layer.graphics[index];
this._layer.remove(graphic);
};
}
module.exports = () => ({
GraphicsRecord
});