layer/layerRec/attribRecord.js

  1. 'use strict';
  2. const layerRecord = require('./layerRecord.js')();
  3. const attribFC = require('./attribFC.js')();
  4. /**
  5. * @class AttribRecord
  6. */
  7. class AttribRecord extends layerRecord.LayerRecord {
  8. // this class has functions common to layers that have attributes
  9. get clickTolerance () { return this._tolerance; }
  10. /**
  11. * Create a layer record with the appropriate geoApi layer type. Layer config
  12. * should be fully merged with all layer options defined (i.e. this constructor
  13. * will not apply any defaults).
  14. * @param {Object} layerClass the ESRI api object for the layer
  15. * @param {Object} esriRequest the ESRI api object for making web requests with proxy support
  16. * @param {Object} apiRef object pointing to the geoApi. allows us to call other geoApi functions.
  17. * @param {Object} config layer config values
  18. * @param {Object} esriLayer an optional pre-constructed layer
  19. * @param {Function} epsgLookup an optional lookup function for EPSG codes (see geoService for signature)
  20. */
  21. constructor (layerClass, esriRequest, apiRef, config, esriLayer, epsgLookup) {
  22. super(layerClass, apiRef, config, esriLayer, epsgLookup);
  23. this._esriRequest = esriRequest;
  24. this._tolerance = this.config.tolerance;
  25. }
  26. /**
  27. * Get the best user-friendly name of a field. Uses alias if alias is defined, else uses the system attribute name.
  28. *
  29. * @param {String} attribName the attribute name we want a nice name for
  30. * @return {Promise} resolves to the best available user friendly attribute name
  31. */
  32. aliasedFieldName (attribName) {
  33. return this._featClasses[this._defaultFC].aliasedFieldName(attribName);
  34. }
  35. /**
  36. * Retrieves attributes from a layer for a specified feature index
  37. * @return {Promise} promise resolving with formatted attributes to be consumed by the datagrid and esri feature identify
  38. */
  39. getFormattedAttributes () {
  40. return this._featClasses[this._defaultFC].getFormattedAttributes();
  41. }
  42. checkDateType (attribName) {
  43. return this._featClasses[this._defaultFC].checkDateType(attribName);
  44. }
  45. /**
  46. * Returns attribute data for this layer.
  47. *
  48. * @function getAttribs
  49. * @returns {Promise} resolves with a layer attribute data object
  50. */
  51. getAttribs () {
  52. return this._featClasses[this._defaultFC].getAttribs();
  53. }
  54. /**
  55. * Returns layer-specific data for this Record
  56. *
  57. * @function getLayerData
  58. * @returns {Promise} resolves with a layer data object
  59. */
  60. getLayerData () {
  61. return this._featClasses[this._defaultFC].getLayerData();
  62. }
  63. getFeatureName (objId, attribs) {
  64. return this._featClasses[this._defaultFC].getFeatureName(objId, attribs);
  65. }
  66. fetchGraphic (objId, ignoreLocal = false) {
  67. return this._featClasses[this._defaultFC].fetchGraphic(objId, ignoreLocal);
  68. }
  69. getFeatureCount (url) {
  70. if (url) {
  71. // wrapping server call in a function, as we regularly encounter sillyness
  72. // where we need to execute the count request twice.
  73. // having a function (with finalTry flag) lets us handle the double-request
  74. const esriServerCount = (layerUrl, finalTry = false) => {
  75. // extract info for this service
  76. const defService = this._esriRequest({
  77. url: `${layerUrl}/query`,
  78. content: {
  79. f: 'json',
  80. where: '1=1',
  81. returnCountOnly: true,
  82. returnGeometry: false
  83. },
  84. callbackParamName: 'callback',
  85. handleAs: 'json',
  86. });
  87. return new Promise(resolve => {
  88. defService.then(serviceResult => {
  89. if (serviceResult && (typeof serviceResult.error === 'undefined') &&
  90. (typeof serviceResult.count !== 'undefined')) {
  91. // we got a row count
  92. resolve(serviceResult.count);
  93. } else if (!finalTry) {
  94. // do a second attempt
  95. resolve(esriServerCount(layerUrl, true));
  96. } else {
  97. // tells the app it failed
  98. resolve(-1);
  99. }
  100. }, error => {
  101. // failed to load service info.
  102. // TODO any tricks to avoid duplicating the error case in both blocks?
  103. if (!finalTry) {
  104. // do a second attempt
  105. resolve(esriServerCount(layerUrl, true));
  106. } else {
  107. // tells the app it failed
  108. console.warn(error);
  109. resolve(-1);
  110. }
  111. });
  112. });
  113. };
  114. return esriServerCount(url);
  115. } else {
  116. // file based layer. count local features
  117. return Promise.resolve(this._layer.graphics.length);
  118. }
  119. }
  120. /**
  121. * Transforms esri key-value attribute object into key value array with format suitable
  122. * for consumption by the details pane.
  123. *
  124. * @param {Object} attribs attribute key-value mapping, potentially with aliases as keys
  125. * @param {Array} fields optional. fields definition array for layer. no aliasing done if not provided
  126. * @return {Array} attribute data transformed into a list, with potential field aliasing applied
  127. */
  128. attributesToDetails (attribs, fields) {
  129. // TODO make this extensible / modifiable / configurable to allow different details looks for different data
  130. // simple array of text mapping for demonstration purposes. fancy grid formatting later?
  131. return Object.keys(attribs)
  132. .map(key => {
  133. const fieldType = fields ? fields.find(f => f.name === key) : null;
  134. return {
  135. key: attribFC.AttribFC.aliasedFieldNameDirect(key, fields), // need synchronous variant of alias lookup
  136. value: attribs[key],
  137. type: fieldType ? fieldType.type : fieldType
  138. };
  139. });
  140. }
  141. }
  142. module.exports = () => ({
  143. AttribRecord
  144. });