layer/layerRec/layerInterface.js

  1. 'use strict';
  2. const shared = require('./shared.js')();
  3. // Controls Interface class is used to provide something to the UI that it can bind to.
  4. // It helps the UI keep in line with the layer state.
  5. // Due to bindings, we cannot destroy & recreate an interface when a legend item
  6. // goes from 'Unknown Placeholder' to 'Specific Layer Type'. This means we cannot
  7. // do object heirarchies, as to go from PlaceholderInterface to FeatureLayerInterface
  8. // would require a new object. Instead, we have a class that exposes all possible
  9. // methods and properties as error throwing stubs. Then we replace those functions
  10. // with real ones once we know the flavour of interface we want.
  11. class LayerInterface {
  12. /**
  13. * @param {Object} source object that provides info to the interface. usually a LayerRecord or FeatureClass
  14. * @param {Array} availableControls [optional=[]] an array or controls names that are displayed inside the legendEntry
  15. * @param {Array} disabledControls [optional=[]] an array or controls names that are disabled and cannot be interacted wiht by a user
  16. */
  17. constructor (source, availableControls = [], disabledControls = []) {
  18. // TEST STATUS basic
  19. this._source = source;
  20. this._availableControls = availableControls;
  21. this._disabledControls = disabledControls;
  22. this._isPlaceholder = true;
  23. }
  24. // shortcut function for throwing errors on unimplemented functions.
  25. _iAmError () {
  26. throw new Error('Call not supported.');
  27. }
  28. get isPlaceholder () { return this._isPlaceholder; } // returns Boolean
  29. // these expose ui controls available on the interface and indicate which ones are disabled
  30. get availableControls () { return this._availableControls; } // returns Array
  31. get disabledControls () { return this._disabledControls; } // returns Array
  32. get symbology () { this._iAmError(); } // returns Object
  33. // can be group or node name
  34. get name () { this._iAmError(); } // returns String
  35. // these are needed for the type flag
  36. get layerType () { this._iAmError(); } // returns Promise of String
  37. get geometryType () { this._iAmError(); } // returns Promise of String
  38. get featureCount () { this._iAmError(); } // returns Promise of Integer
  39. // layer states
  40. get state () { this._iAmError(); } // returns String
  41. get isRefreshing () { this._iAmError(); } // returns Boolean
  42. // these return the current values of the corresponding controls
  43. get visibility () { this._iAmError(); } // returns Boolean
  44. get opacity () { this._iAmError(); } // returns Decimal
  45. get boundingBox () { this._iAmError(); } // returns Boolean
  46. get query () { this._iAmError(); } // returns Boolean
  47. get snapshot () { this._iAmError(); } // returns Boolean
  48. // fetches attributes for use in the datatable
  49. get formattedAttributes () { this._iAmError(); } // returns Promise of Object
  50. // content for static legend entires (non-layer/non-group)
  51. get infoType () { this._iAmError(); } // returns ?
  52. get infoContent () { this._iAmError(); } // returns ?
  53. // these set values to the corresponding controls
  54. setVisibility () { this._iAmError(); }
  55. setOpacity () { this._iAmError(); }
  56. setBoundingBox () { this._iAmError(); }
  57. setQuery () { this._iAmError(); }
  58. setSnapshot () { this._iAmError(); }
  59. // updates what this interface is pointing to, in terms of layer data source.
  60. // often, the interface starts with a placeholder to avoid errors and return
  61. // defaults. This update happens after a layer has loaded, and new now want
  62. // the interface reading off the real FC.
  63. // TODO docs
  64. updateSource (newSource) {
  65. // TEST STATUS basic
  66. this._source = newSource;
  67. }
  68. convertToSingleLayer (layerRecord) {
  69. // TEST STATUS basic
  70. this._source = layerRecord;
  71. this._isPlaceholder = false;
  72. newProp(this, 'symbology', standardGetSymbology);
  73. newProp(this, 'state', standardGetState);
  74. newProp(this, 'isRefreshing', standardGetIsRefreshing);
  75. newProp(this, 'visibility', standardGetVisibility);
  76. newProp(this, 'opacity', standardGetOpacity);
  77. newProp(this, 'boundingBox', standardGetBoundingBox);
  78. newProp(this, 'query', standardGetQuery);
  79. newProp(this, 'name', standardGetName);
  80. newProp(this, 'geometryType', standardGetGeometryType);
  81. newProp(this, 'layerType', standardGetLayerType);
  82. newProp(this, 'featureCount', standardGetFeatureCount);
  83. this.setVisibility = standardSetVisibility;
  84. this.setOpacity = standardSetOpacity;
  85. this.setBoundingBox = standardSetBoundingBox;
  86. this.setQuery = standardSetQuery;
  87. }
  88. convertToFeatureLayer (layerRecord) {
  89. // TEST STATUS basic
  90. this.convertToSingleLayer(layerRecord);
  91. newProp(this, 'snapshot', featureGetSnapshot);
  92. newProp(this, 'formattedAttributes', standardGetFormattedAttributes);
  93. this.setSnapshot = featureSetSnapshot;
  94. }
  95. convertToDynamicLeaf (dynamicFC) {
  96. // TEST STATUS basic
  97. this._source = dynamicFC;
  98. this._isPlaceholder = false;
  99. // TODO name property
  100. newProp(this, 'symbology', dynamicLeafGetSymbology);
  101. newProp(this, 'state', dynamicLeafGetState);
  102. newProp(this, 'isRefreshing', dynamicLeafGetIsRefreshing);
  103. newProp(this, 'name', dynamicLeafGetName);
  104. newProp(this, 'visibility', dynamicLeafGetVisibility);
  105. newProp(this, 'opacity', dynamicLeafGetOpacity);
  106. newProp(this, 'query', dynamicLeafGetQuery);
  107. newProp(this, 'formattedAttributes', dynamicLeafGetFormattedAttributes);
  108. newProp(this, 'geometryType', dynamicLeafGetGeometryType);
  109. newProp(this, 'layerType', dynamicLeafGetLayerType);
  110. newProp(this, 'featureCount', dynamicLeafGetFeatureCount);
  111. this.setVisibility = dynamicLeafSetVisibility;
  112. this.setOpacity = dynamicLeafSetOpacity;
  113. this.setQuery = dynamicLeafSetQuery;
  114. }
  115. convertToDynamicGroup (layerRecord, groupId, name = '') {
  116. // TEST STATUS basic
  117. // Note: we do not support opacity on dynamic groups
  118. this._source = layerRecord;
  119. this._groupId = groupId;
  120. this._isPlaceholder = false;
  121. this._name = name;
  122. // contains a list of all child leaves for fast access
  123. this._childLeafs = [];
  124. newProp(this, 'visibility', dynamicGroupGetVisibility);
  125. newProp(this, 'layerType', dynamicGroupGetLayerType);
  126. newProp(this, 'state', dynamicGroupGetState);
  127. newProp(this, 'isRefreshing', dynamicGroupGetIsRefreshing);
  128. newProp(this, 'name', dynamicGroupGetName);
  129. this.setVisibility = dynamicGroupSetVisibility;
  130. }
  131. convertToStatic () {
  132. // TEST STATUS none
  133. // TODO figure out what is involved here.
  134. this._isPlaceholder = false;
  135. }
  136. convertToFakeGroup (fakeGroupRecord) {
  137. // TODO name?
  138. this._source = fakeGroupRecord;
  139. this._isPlaceholder = false; // TODO is fake considered placeholder?
  140. newProp(this, 'visibility', standardGetVisibility);
  141. this.setVisibility = standardSetVisibility;
  142. }
  143. convertToPlaceholder (placeholderFC) {
  144. this._source = placeholderFC;
  145. this._isPlaceholder = true;
  146. newProp(this, 'symbology', standardGetSymbology);
  147. newProp(this, 'name', standardGetName);
  148. newProp(this, 'state', standardGetState);
  149. newProp(this, 'isRefreshing', standardGetIsRefreshing);
  150. }
  151. }
  152. /**
  153. * Worker function to add or override a get property on an object
  154. *
  155. * @function newProp
  156. * @private
  157. * @param {Object} target the object that will receive the new property
  158. * @param {String} propName name of the get property
  159. * @param {Function} getter the function defining the guts of the get property.
  160. */
  161. function newProp(target, propName, getter) {
  162. // TEST STATUS none
  163. Object.defineProperty(target, propName, {
  164. get: getter,
  165. enumerable: true,
  166. configurable: true
  167. });
  168. }
  169. // these functions are upgrades to the duds above.
  170. // we don't use arrow notation, as we want the `this` to point at the object
  171. // that these functions get smashed into.
  172. function stateCalculator(inputState) {
  173. // returns one of Loading, Loaded, Error
  174. // TODO verify what DEFAULT actually is
  175. const states = shared.states;
  176. switch (inputState) {
  177. case states.NEW:
  178. case states.LOADING:
  179. return states.LOADING;
  180. case states.LOADED:
  181. case states.REFRESH:
  182. case states.DEFAULT:
  183. return states.LOADED;
  184. case states.ERROR:
  185. return states.ERROR;
  186. }
  187. }
  188. function standardGetState() {
  189. /* jshint validthis: true */
  190. // TEST STATUS none
  191. return stateCalculator(this._source.state);
  192. }
  193. function dynamicLeafGetState() {
  194. /* jshint validthis: true */
  195. // TEST STATUS none
  196. return stateCalculator(this._source.state);
  197. }
  198. function dynamicGroupGetState() {
  199. /* jshint validthis: true */
  200. // TEST STATUS none
  201. return stateCalculator(this._source.state);
  202. }
  203. function standardGetIsRefreshing() {
  204. /* jshint validthis: true */
  205. // TEST STATUS none
  206. return this._source.state === shared.states.REFRESH;
  207. }
  208. function dynamicLeafGetIsRefreshing() {
  209. /* jshint validthis: true */
  210. // TEST STATUS none
  211. return this._source.state === shared.states.REFRESH;
  212. }
  213. function dynamicGroupGetIsRefreshing() {
  214. /* jshint validthis: true */
  215. // TEST STATUS none
  216. return this._source.state === shared.states.REFRESH;
  217. }
  218. function standardGetVisibility() {
  219. /* jshint validthis: true */
  220. // TEST STATUS none
  221. return this._source.visibility;
  222. }
  223. function dynamicLeafGetVisibility() {
  224. /* jshint validthis: true */
  225. // TEST STATUS basic
  226. return this._source.getVisibility();
  227. }
  228. function dynamicGroupGetVisibility() {
  229. /* jshint validthis: true */
  230. // TEST STATUS basic
  231. // check visibility of all children.
  232. // only return false if all children are invisible
  233. return this._childLeafs.some(leaf => { return leaf.visibility; });
  234. }
  235. function standardGetName() {
  236. /* jshint validthis: true */
  237. // TEST STATUS none
  238. return this._source.layerName;
  239. }
  240. function dynamicLeafGetName() {
  241. /* jshint validthis: true */
  242. // TEST STATUS none
  243. return this._source.name;
  244. }
  245. function dynamicGroupGetName() {
  246. /* jshint validthis: true */
  247. // TEST STATUS none
  248. // funny case here. dynamic groups source the parent record.
  249. // so we just hold the name within the proxy.
  250. return this._name;
  251. }
  252. function standardGetOpacity() {
  253. /* jshint validthis: true */
  254. // TEST STATUS none
  255. return this._source.opacity;
  256. }
  257. function dynamicLeafGetOpacity() {
  258. /* jshint validthis: true */
  259. // TEST STATUS none
  260. return this._source.opacity;
  261. }
  262. function standardGetLayerType() {
  263. /* jshint validthis: true */
  264. // TEST STATUS none
  265. // it's a promise
  266. return this._source.layerType;
  267. }
  268. function dynamicGroupGetLayerType() {
  269. /* jshint validthis: true */
  270. // TEST STATUS none
  271. return Promise.resolve(shared.clientLayerType.ESRI_GROUP);
  272. }
  273. function dynamicLeafGetLayerType() {
  274. /* jshint validthis: true */
  275. // TEST STATUS none
  276. return this._source.layerType.then(lt => {
  277. switch (lt) {
  278. case 'Feature Layer':
  279. return shared.clientLayerType.ESRI_FEATURE;
  280. case 'Raster Layer':
  281. return shared.clientLayerType.ESRI_RASTER;
  282. default:
  283. throw new Error('Unexpected layer type in dynamicLeafGetLayerType', lt);
  284. }
  285. });
  286. }
  287. function standardGetBoundingBox() {
  288. /* jshint validthis: true */
  289. // TEST STATUS none
  290. // dont be fooled by function/prop name, we are returning bbox visibility,
  291. // not the box itself
  292. return this._source.isBBoxVisible();
  293. }
  294. function standardGetQuery() {
  295. /* jshint validthis: true */
  296. // TEST STATUS none
  297. return this._source.isQueryable();
  298. }
  299. // TODO do we have group-level queryable settings?
  300. // e.g. click a control on dynamic root, all childs get setting?
  301. function dynamicLeafGetQuery() {
  302. /* jshint validthis: true */
  303. // TEST STATUS none
  304. return this._source.queryable();
  305. }
  306. function standardGetFormattedAttributes() {
  307. /* jshint validthis: true */
  308. // TEST STATUS none
  309. return this._source.getFormattedAttributes();
  310. }
  311. function dynamicLeafGetFormattedAttributes() {
  312. /* jshint validthis: true */
  313. // TEST STATUS none
  314. // TODO code-wise this looks identical to standardGetFormattedAttributes.
  315. // however in this case, ._source is a DynamicFC, not a LayerRecord.
  316. // This is safer. Deleting this would avoid the duplication. Decide.
  317. return this._source.getFormattedAttributes();
  318. }
  319. function standardGetSymbology() {
  320. /* jshint validthis: true */
  321. // TEST STATUS none
  322. return this._source.symbology;
  323. }
  324. function dynamicLeafGetSymbology() {
  325. /* jshint validthis: true */
  326. // TEST STATUS none
  327. // TODO code-wise this looks identical to standardGetSymbology.
  328. // however in this case, ._source is a DynamicFC, not a LayerRecord.
  329. // This is safer. Deleting this would avoid the duplication. Decide.
  330. return this._source.symbology;
  331. }
  332. function standardGetGeometryType() {
  333. /* jshint validthis: true */
  334. // TEST STATUS none
  335. return this._source.getGeomType();
  336. }
  337. function dynamicLeafGetGeometryType() {
  338. /* jshint validthis: true */
  339. // TEST STATUS none
  340. return this._source.geomType;
  341. }
  342. function standardGetFeatureCount() {
  343. /* jshint validthis: true */
  344. // TEST STATUS none
  345. return this._source.getFeatureCount();
  346. }
  347. function dynamicLeafGetFeatureCount() {
  348. /* jshint validthis: true */
  349. // TEST STATUS none
  350. return this._source._parent.getFeatureCount(this._source._idx);
  351. }
  352. function standardSetVisibility(value) {
  353. /* jshint validthis: true */
  354. // TEST STATUS none
  355. this._source.visibility = value;
  356. }
  357. function dynamicLeafSetVisibility(value) {
  358. /* jshint validthis: true */
  359. // TEST STATUS none
  360. this._source.setVisibility(value);
  361. // TODO see if we need to trigger any refresh of parents.
  362. // it may be that the bindings automatically work.
  363. }
  364. function dynamicGroupSetVisibility(value) {
  365. /* jshint validthis: true */
  366. // TEST STATUS none
  367. // TODO be aware of cycles of updates. may need a force / dont broadcast flag.
  368. // since we are only hitting leaves and skipping child-groups, should be ok.
  369. this._childLeafs.forEach(leaf => {
  370. leaf.setVisibility(value);
  371. });
  372. }
  373. function standardSetOpacity(value) {
  374. /* jshint validthis: true */
  375. // TEST STATUS none
  376. this._source._layer.opacity = value;
  377. }
  378. function dynamicLeafSetOpacity(value) {
  379. /* jshint validthis: true */
  380. // TEST STATUS none
  381. this._source.opacity = value;
  382. }
  383. function standardSetBoundingBox(value) {
  384. /* jshint validthis: true */
  385. // TEST STATUS none
  386. // TODO test if object exists? Is it possible to have control without bbox layer?
  387. this._source.bbox.visible = value;
  388. }
  389. function standardSetQuery(value) {
  390. /* jshint validthis: true */
  391. // TEST STATUS none
  392. this._source.setQueryable(value);
  393. }
  394. function dynamicLeafSetQuery(value) {
  395. /* jshint validthis: true */
  396. // TEST STATUS none
  397. this._source.queryable = value;
  398. }
  399. function featureGetSnapshot() {
  400. /* jshint validthis: true */
  401. // TEST STATUS none
  402. return this._source.isSnapshot;
  403. }
  404. function featureSetSnapshot() {
  405. // TEST STATUS none
  406. // TODO trigger the snapshot process. need the big picture on how this orchestrates.
  407. // it involves a layer reload so possible this function is irrelevant, as the record
  408. // will likely get nuked
  409. console.log('MOCKING THE SNAPSHOT PROCESS');
  410. }
  411. // TODO implement function to get .name
  412. // where does it come from in single-layer? config? verify new schema
  413. // group node? a config entry? a layer property in auto-gen?
  414. // deal with unbound information-only case (static entry)?
  415. // TODO implement infoType / infoContent for static entry.
  416. // who supplies this? how does it get passed in.
  417. module.exports = () => ({
  418. LayerInterface
  419. });