layer/layerRec/attribFC.js

  1. 'use strict';
  2. const shared = require('./shared.js')();
  3. const basicFC = require('./basicFC.js')();
  4. /**
  5. * @class AttribFC
  6. */
  7. class AttribFC extends basicFC.BasicFC {
  8. // attribute-specific variant for feature class object.
  9. // deals with stuff specific to a feature class that has attributes
  10. /**
  11. * Create an attribute specific feature class object
  12. * @param {Object} parent the Record object that this Feature Class belongs to
  13. * @param {String} idx the service index of this Feature Class. an integer in string format. use '0' for non-indexed sources.
  14. * @param {Object} layerPackage a layer package object from the attribute module for this feature class
  15. * @param {Object} config the config object for this sublayer
  16. */
  17. constructor (parent, idx, layerPackage, config) {
  18. // TEST STATUS basic
  19. super(parent, idx, config);
  20. this._layerPackage = layerPackage;
  21. this._geometryType = undefined; // this indicates unknown to the ui.
  22. this._fcount = undefined;
  23. // moar?
  24. }
  25. /**
  26. * Returns attribute data for this FC.
  27. *
  28. * @function getAttribs
  29. * @returns {Promise} resolves with a layer attribute data object
  30. */
  31. getAttribs () {
  32. // TEST STATUS none
  33. return this._layerPackage.getAttribs();
  34. }
  35. /**
  36. * Returns layer-specific data for this FC.
  37. *
  38. * @function getLayerData
  39. * @returns {Promise} resolves with a layer data object
  40. */
  41. getLayerData () {
  42. // TEST STATUS basic
  43. return this._layerPackage.layerData;
  44. }
  45. // this will actively download / refresh the internal symbology
  46. loadSymbology () {
  47. this.getLayerData().then(lData => {
  48. if (lData.layerType === 'Feature Layer') {
  49. // feature always has a single item, so index 0
  50. this.symbology = shared.makeSymbologyArray(lData.legend.layers[0].legend);
  51. } else {
  52. // non-feature source. use legend server
  53. super.loadSymbology();
  54. }
  55. });
  56. }
  57. /**
  58. * Extract the feature name from a feature as best we can.
  59. * Support for dynamic layers is limited at the moment. // TODO explain this comment
  60. *
  61. * @function getFeatureName
  62. * @param {String} objId the object id of the attribute
  63. * @param {Object} attribs optional. the dictionary of attributes for the feature. uses internal attributes if not provided.
  64. * @returns {Promise} resolves with the name of the feature
  65. */
  66. getFeatureName (objId, attribs) {
  67. // TEST STATUS none
  68. let nameField = '';
  69. if (this.nameField) {
  70. nameField = this.nameField;
  71. } else if (this.parent._layer && this.parent._layer.displayField) {
  72. nameField = this.parent._layer.displayField;
  73. }
  74. if (nameField) {
  75. // determine if we have been given a set of attributes, or need to use our own
  76. let attribPromise;
  77. if (attribs) {
  78. attribPromise = Promise.resolve(attribs);
  79. } else {
  80. attribPromise = this.getAttribs().then(layerAttribs => {
  81. return layerAttribs.features[layerAttribs.oidIndex[objId]].attributes;
  82. });
  83. }
  84. // after attributes are loaded, extract name
  85. return attribPromise.then(finalAttribs => {
  86. return finalAttribs[nameField];
  87. });
  88. } else {
  89. // FIXME wire in "feature" to translation service
  90. return Promise.resolve('Feature ' + objId);
  91. }
  92. }
  93. /**
  94. * Retrieves attributes from a layer for a specified feature index
  95. * @return {Promise} promise resolving with formatted attributes to be consumed by the datagrid and esri feature identify
  96. */
  97. getFormattedAttributes () {
  98. // TEST STATUS basic
  99. if (this._formattedAttributes) {
  100. return this._formattedAttributes;
  101. }
  102. this._formattedAttributes = Promise.all([this.getAttribs(), this.getLayerData()])
  103. .then(([aData, lData]) => {
  104. // create columns array consumable by datables
  105. const columns = lData.fields
  106. .filter(field =>
  107. // assuming there is at least one attribute - empty attribute budnle promises should be rejected, so it never even gets this far
  108. // filter out fields where there is no corresponding attribute data
  109. aData.features[0].attributes.hasOwnProperty(field.name))
  110. .map(field => ({
  111. data: field.name,
  112. title: field.alias || field.name
  113. }));
  114. return {
  115. columns,
  116. rows: aData.features.map(feature => feature.attributes),
  117. fields: lData.fields, // keep fields for reference ...
  118. oidField: lData.oidField, // ... keep a reference to id field ...
  119. oidIndex: aData.oidIndex, // ... and keep id mapping array
  120. renderer: lData.renderer
  121. };
  122. })
  123. .catch(() => {
  124. delete this._formattedAttributes; // delete cached promise when the geoApi `getAttribs` call fails, so it will be requested again next time `getAttributes` is called;
  125. throw new Error('Attrib loading failed');
  126. });
  127. return this._formattedAttributes;
  128. }
  129. /**
  130. * Check to see if the attribute in question is an esriFieldTypeDate type.
  131. *
  132. * @param {String} attribName the attribute name we want to check if it's a date or not
  133. * @return {Promise} resolves to true or false based on the attribName type being esriFieldTypeDate
  134. */
  135. checkDateType (attribName) {
  136. // TEST STATUS none
  137. // grab attribute info (waiting for it it finish loading)
  138. return this.getLayerData().then(lData => {
  139. // inspect attribute fields
  140. if (lData.fields) {
  141. const attribField = lData.fields.find(field => {
  142. return field.name === attribName;
  143. });
  144. if (attribField && attribField.type) {
  145. return attribField.type === 'esriFieldTypeDate';
  146. }
  147. }
  148. return false;
  149. });
  150. }
  151. /**
  152. * Get the best user-friendly name of a field. Uses alias if alias is defined, else uses the system attribute name.
  153. *
  154. * @param {String} attribName the attribute name we want a nice name for
  155. * @return {Promise} resolves to the best available user friendly attribute name
  156. */
  157. aliasedFieldName (attribName) {
  158. // TEST STATUS none
  159. // grab attribute info (waiting for it it finish loading)
  160. return this.getLayerData().then(lData => {
  161. return AttribFC.aliasedFieldNameDirect(attribName, lData.fields);
  162. });
  163. }
  164. static aliasedFieldNameDirect (attribName, fields) {
  165. // TEST STATUS none
  166. let fName = attribName;
  167. // search for aliases
  168. if (fields) {
  169. const attribField = fields.find(field => {
  170. return field.name === attribName;
  171. });
  172. if (attribField && attribField.alias && attribField.alias.length > 0) {
  173. fName = attribField.alias;
  174. }
  175. }
  176. return fName;
  177. }
  178. /**
  179. * Convert an attribute set so that any keys using aliases are converted to proper fields
  180. *
  181. * @param {Object} attribs attribute key-value mapping, potentially with aliases as keys
  182. * @param {Array} fields fields definition array for layer
  183. * @return {Object} attribute key-value mapping with fields as keys
  184. */
  185. static unAliasAttribs (attribs, fields) {
  186. // TEST STATUS none
  187. const newA = {};
  188. fields.forEach(field => {
  189. // attempt to extract on name. if not found, attempt to extract on alias
  190. // dump value into the result
  191. newA[field.name] = attribs.hasOwnProperty(field.name) ? attribs[field.name] : attribs[field.alias];
  192. });
  193. return newA;
  194. }
  195. // TODO perhaps a splitting of server url and layer index to make things consistent between feature and dynamic?
  196. // could be on constructor, then parent can easily feed in the treats.
  197. }
  198. module.exports = () => ({
  199. AttribFC
  200. });