widgets/AttributesTable/_QueryTaskMixin.js
define([
'dojo/_base/declare',
'dojo/_base/lang',
'dojo/_base/array',
'dojo/topic',
'dojo/Deferred',
'esri/request',
'esri/config',
'esri/units',
'dojo/number',
'esri/tasks/query',
'esri/tasks/RelationshipQuery',
'esri/tasks/QueryTask',
'esri/geometry/Point',
'esri/geometry/Multipoint',
'esri/geometry/Polyline',
'esri/geometry/Polygon',
'esri/geometry/Extent',
'esri/geometry/geometryEngine',
'esri/tasks/BufferParameters'
], function (
declare,
lang,
array,
topic,
Deferred,
esriRequest,
esriConfig,
units,
num,
Query,
RelationshipQuery,
QueryTask,
Point,
Multipoint,
Polyline,
Polygon,
Extent,
geometryEngine,
BufferParameters
) {
return declare(null, {
results: null,
queryOptions: {},
// for all the standard parameters, see for https://developers.arcgis.com/javascript/jsapi/query-amd.html
defaultQueryOptions: {
queryParameters: {
type: 'spatial',
outputSpatialReference: 4326,
url: null,
layerID: null,
sublayerID: null,
objectIDs: null,
outFields: ['*'],
where: '1=1',
addToExisting: false,
geometry: null,
distance: null,
start: null,
num: null,
text: null,
timeExtent: null,
units: 'feet',
geometryPrecision: null,
groupByFieldsForStatistics: null,
orderByFields: null,
outStatistics: null,
pixelSize: null,
relationParam: null,
spatialRelationship: Query.SPATIAL_REL_INTERSECTS,
includeLayerDefinitions: false
},
// provide the parameters if there is a spatial query linked from a table/database query
linkedQuery: {
url: null,
layerID: null,
sublayerID: null,
linkField: null,
idProperty: null,
linkIDs: [] // if linkedQuery, then store the linkedIDs for use in linked query
},
// allow a buffer to be performed before a spatial
// query and then use the buffer geometry in the query
// if showOnly = true, the buffer is displayed but
// the query is not run
bufferParameters: {
distance: null,
unit: null,
geodesic: true,
showOnly: false
},
// executing a linked query?
isLinkedQuery: false,
// used for linkedQueries
linkField: null,
// default Unique ID
// if null, will attempt to get a field of
// type 'esriFieldTypeOID' from the results
idProperty: null
},
isLinkedQuery: false,
relatedTableURL: null,
layerJSON: {},
getQueryConfiguration: function (options) {
options = this.mixinDeep(lang.clone(this.defaultQueryOptions), options);
this.queryParameters = options.queryParameters;
this.bufferParameters = options.bufferParameters;
this.idProperty = options.idProperty;
this.linkField = options.linkField;
this.linkedQuery = options.linkedQuery;
this.isLinkedQuery = false;
this.relatedTableURL = null;
},
executeQueryTask: function (options) {
this.getConfiguration(options);
if (this.executingQuery === true) {
return;
}
var qp = this.getQueryParameters(options);
if (this.featureOptions.buffer && this.bufferParameters && this.bufferParameters.distance) {
this.executeBuffer();
return;
}
this.featureOptions.buffer = false;
var url = this.getQueryTaskURL();
if (!url) {
return;
}
// get feature layer for retrieving coded domain values later
// method continues even if there is an error loading the layer
var deferred = this.getLayerJSON(url);
deferred.then(lang.hitch(this, function () {
var qt = new QueryTask(url);
var q = this.buildQueryFromParameters(qp);
if (qp.type === 'relationship') {
var relatedDeferred = this.getRelationshipLayerJSON(url, qp);
relatedDeferred.then(lang.hitch(this, function () {
this.executingQuery = true;
this.growlQueryIsExecuting();
qt.executeRelationshipQuery(q, lang.hitch(this, 'processQueryResults'), lang.hitch(this, 'processQueryError'));
}));
} else {
this.executingQuery = true;
this.growlQueryIsExecuting();
qt.execute(q, lang.hitch(this, 'processQueryResults'), lang.hitch(this, 'processQueryError'));
}
}));
},
getQueryParameters: function (options) {
// grab the query parameters
if (options && options.queryOptions && options.queryOptions.queryParameters) {
this.queryParameters = options.queryOptions.queryParameters;
}
var qp = this.queryParameters;
// only clear everything when we want to preserve the previous results
if (qp.addToExisting !== true && !qp.bufferGeometry) {
this.clearAll();
}
this.getConfiguration(options);
qp = this.queryParameters;
if (qp.addToExisting !== true) {
this.clearFeatures();
}
this.clearSelectedFeatures();
if ((qp.addToExisting !== true) && (this.isLinkedQuery !== true || this.type === 'table') && (this.bufferParameters && !this.bufferParameters.showOnly)) {
this.clearGrid();
}
if (qp.geometry) {
qp.geometry = this.createGeometry(qp.geometry);
}
return qp;
},
growlQueryIsExecuting: function () {
if (this.growlOptions.loading && !this.isLinkedQuery) {
this.growlID = this.topicID + 'Growl-StartSearch';
var msg = lang.mixin(this.i18n.messages.searching, {
id: this.growlID,
timeout: (esriConfig.defaults.io.timeout + 5000),
level: 'info',
showProgressBar: true
});
topic.publish('growler/growl', msg);
}
},
buildQueryFromParameters: function (qp) {
var q = null;
if (qp.type === 'relationship') {
q = new RelationshipQuery();
} else {
q = new Query();
}
q.geometryPrecision = qp.geometryPrecision;
q.maxAllowableOffset = qp.maxAllowableOffset;
q.objectIds = qp.objectIDs;
q.outFields = qp.outFields;
q.outSpatialReference = qp.outSpatialReference || this.map.spatialReference;
q.returnGeometry = this.featureOptions.features;
q.where = qp.where;
if (qp.type === 'spatial') {
q.distance = qp.distance;
q.geometry = qp.bufferGeometry || qp.geometry;
q.groupByFieldsForStatistics = qp.groupByFieldsForStatistics;
q.num = qp.num;
q.orderByFields = qp.orderByFields;
q.outStatistics = qp.outStatistics;
q.pixelSize = qp.pixelSize;
q.relationParam = qp.relationParam;
q.spatialRelationship = qp.spatialRelationship;
q.start = qp.start;
q.text = qp.text;
q.timeExtent = qp.timeExtent;
q.units = qp.units;
} else if (qp.type === 'relationship') {
q.definitionExpression = qp.definitionExpression;
q.relationshipId = qp.relationshipID;
}
return q;
},
executeLinkedQuery: function (lq) {
var qp = this.queryParameters;
var linkField = lq.linkField || this.linkField;
var type = lq.type || 'spatial';
lq.type = type;
if (!lq.linkIDs) {
return;
}
lq.where = linkField + ' IN (' + lq.linkIDs.splice(',') + ')';
delete lq.linkedIDs;
if (!lq.outFields) {
lq.outFields = [];
}
if (lq.returnGeometry !== false && (type === 'spatial')) {
lq.returnGeometry = true;
}
if (!lq.distance && qp.distance) {
lq.distance = qp.distance;
}
if (!lq.units && qp.units) {
lq.units = qp.units;
}
if (!lq.geometry && qp.geometry) {
lq.geometry = qp.geometry;
}
if (!lq.spatialRelationship && qp.spatialRelationship) {
lq.spatialRelationship = qp.spatialRelationship;
}
this.executeQueryTask({
queryOptions: {
queryParameters: lq,
isLinkedQuery: true
}
});
},
executeBuffer: function () {
this.clearBufferGraphics();
// experimental use of geometryEngine
if (this.map.spatialReference.wkid === 4326 || this.map.spatialReference.wkid === 102100) {
var geometries = geometryEngine.geodesicBuffer(this.queryParameters.geometry, this.bufferParameters.distance, (this.bufferParameters.unit || units.FEET));
this.processBufferQueryResults([geometries]);
} else {
var buffParams = new BufferParameters();
buffParams.geometries = [this.queryParameters.geometry];
buffParams.distances = [this.bufferParameters.distance];
buffParams.unit = this.bufferParameters.unit || units.FEET;
buffParams.geodesic = this.bufferParameters.geodesic || true;
buffParams.bufferSpatialReference = this.map.spatialReference;
buffParams.outSpatialReference = this.map.spatialReference;
esriConfig.defaults.geometryService.buffer(buffParams, lang.hitch(this, 'processBufferQueryResults'));
}
},
refreshQueryTask: function () {
this.executeQueryTask(this.queryOptions);
},
processQueryError: function (error) {
this.clearGrowl();
this.executingQuery = false;
var msg = lang.mixin(this.i18n.messages.searchError, {
level: 'error',
timeout: 5000
});
topic.publish('growler/growl', msg);
topic.publish('viewer/handleError', {
error: error
});
},
processQueryResults: function (results) {
this.clearGrowl();
this.executingQuery = false;
if (!results) {
return;
}
this.results = results;
this.getFeaturesFromResults();
if (!this.idProperty) {
this.getIdProperty(results);
}
var originalRecs = this.getFeatureCount();
var recCount = this.getFeatureCount();
if (recCount > 0) {
if (this.featureOptions.source && this.queryParameters.geometry) {
this.addSourceGraphic(this.queryParameters.geometry);
}
this.populateGrid(results);
}
if (this.growlOptions.results && !this.isLinkedQuery) {
this.growlQueryResults(recCount, originalRecs);
}
topic.publish(this.topicID + '/queryResults', this.results);
topic.publish(this.attributesContainerID + '/openPane');
topic.publish(this.attributesContainerID + '/tableUpdated', this);
if (this.linkedQuery && (this.linkedQuery.url || this.linkedQuery.layerID)) {
var lq = lang.clone(this.linkedQuery);
this.executeLinkedQuery(lq);
} else {
this.isLinkedQuery = false;
}
this.queryParametersType = 'spatial';
this.linkedQuery = {
url: null,
linkIDs: []
};
},
growlQueryResults: function (recCount, originalRecs) {
var newRecs = (originalRecs === 0) ? 0 : recCount - originalRecs;
var msgNls = this.i18n.messages.searchResults;
var msg = msgNls.message;
if (!msg) {
if (recCount > 0) {
msg = '';
if (newRecs > 0) {
msg += num.format(newRecs) + ' ' + msgNls.newFeatures + ' ';
msg += (newRecs > 1) ? msgNls.features : msgNls.feature;
msg += ' ' + msgNls.found + '.<br/>';
}
msg += num.format(recCount) + ' ';
msg += (recCount > 1) ? msgNls.features : msgNls.feature;
msg += ' ' + msgNls.found;
msg += (newRecs > 0) ? ' ' + msgNls.total + '.' : '.';
} else {
msg = msgNls.noFeatures;
}
}
topic.publish('growler/growl', {
title: this.title + ' ' + msgNls.title,
message: msg,
level: 'info',
timeout: 5000
});
},
processBufferQueryResults: function (geometries) {
var showOnly = this.bufferParameters.showOnly;
// reset the buffer
this.bufferParameters = lang.clone(this.defaultQueryOptions.bufferParameters);
if (geometries && geometries.length > 0) {
this.addBufferGraphic(geometries[0]);
if (showOnly !== true) {
if (this.queryParameters) {
this.queryParameters.geometry = this.geometryToJson(this.queryParameters.geometry);
}
var qParams = lang.clone(this.queryParameters);
qParams.bufferGeometry = geometries[0];
this.executeQueryTask({
queryOptions: {
queryParameters: qParams,
bufferParameters: this.bufferParameters,
linkedQuery: this.linkedQuery,
linkField: this.linkField,
isLinkedQuery: this.isLinkedQuery
}
});
} else if (this.featureOptions.source) {
this.addSourceGraphic(this.queryParameters.geometry);
this.zoomToBufferGraphics();
}
}
},
// convert geometry to Json to avoid issues when cloning
geometryToJson: function (geom) {
if (geom && geom.type && geom.toJson) {
var type = geom.type;
geom = geom.toJson();
geom.type = type;
}
return geom;
},
// allow geometry to come as Json or a geometry type
createGeometry: function (geom) {
var type = geom.type;
if (geom.toJson) {
geom = geom.toJson();
}
switch (type) {
case 'point':
return new Point(geom);
case 'multipoint':
return new Multipoint(geom);
case 'polyline':
return new Polyline(geom);
case 'polygon':
return new Polygon(geom);
case 'extent':
return new Extent(geom);
default:
geom.type = type;
return geom;
}
},
getQueryResults: function () {
return this.results;
},
clearQueryResults: function () {
this.results = null;
},
hasLinkedQuery: function () {
var lq = this.linkedQuery;
if (this.linkField && lq && lq.linkField) {
if (!lq.linkIDs) {
lq.linkIDs = [];
}
return true;
}
return false;
},
// get the idProperty from an 'esriFieldTypeOID'
// type of field (if available) in the results
getIdProperty: function (results) {
var fields = results.fields;
if (fields && fields.length > 0) {
array.forEach(fields, lang.hitch(this, function (field) {
if (field.type === 'esriFieldTypeOID') {
this.idProperty = field.name;
}
}));
}
},
getQueryTaskLayerJSON: function () {
if (this.queryParameters.type === 'relationship' && this.relatedTableURL) {
return this.layerJSON[this.relatedTableURL];
}
var url = this.getQueryTaskURL();
return this.layerJSON[url];
},
getQueryTaskURL: function () {
var qp = this.queryParameters;
var url = qp.url;
if (url && qp.sublayerID) {
var len = url.length;
if (url.substring(len - 1, len) === '/') {
url = url.substring(0, len - 1);
}
url += '/' + qp.sublayerID;
}
if (!url && qp.layerID) {
var layer = this.map.getLayer(qp.layerID);
if (layer) {
var whereByLayerDef = null;
if (layer.declaredClass === 'esri.layers.FeatureLayer') { // Feature Layer
whereByLayerDef = layer.getDefinitionExpression();
url = layer.url;
} else if (layer.declaredClass === 'esri.layers.ArcGISDynamicMapServiceLayer') { // Dynamic Layer
whereByLayerDef = layer.layerDefinitions ? layer.layerDefinitions[qp.sublayerID] : null;
if (qp.sublayerID !== null) {
url = layer.url + '/' + qp.sublayerID;
} else if (layer.visibleLayers && layer.visibleLayers.length === 1) {
url = layer.url + '/' + layer.visibleLayers[0];
}
}
if (whereByLayerDef && qp.includeLayerDefinitions) {
if (qp.where) {
qp.where = '(' + whereByLayerDef + ') AND (' + qp.where + ')';
} else {
qp.where = whereByLayerDef;
}
}
}
}
return url;
},
getLayerJSON: function (url) {
var deferred = new Deferred();
var layerJSON = this.layerJSON;
if (layerJSON[url]) {
if (layerJSON[url].promise) {
return layerJSON[url].promise;
}
deferred.resolve(layerJSON[url]);
} else {
layerJSON[url] = deferred;
esriRequest({
url: url,
parameters: {
f: 'json'
},
content: {
f: 'json'
},
handleAs: 'json',
callbackParamName: 'callback'
}, {
disableIdentityLookup: false,
usePost: false,
useProxy: false
}).then(
lang.hitch(this, function (data) {
layerJSON[url] = data;
deferred.resolve(layerJSON[url]);
}),
lang.hitch(this, function () {
layerJSON[url] = {};
deferred.resolve(layerJSON[url]);
})
);
}
return deferred.promise;
},
getRelationshipLayerJSON: function (url, qp) {
var deferred = new Deferred(),
relatedDeferred = null,
layer = this.getQueryTaskLayerJSON();
if (layer && layer.relationships) {
var related = layer.relationships[qp.relationshipID];
if (related) {
var lastSlash = url.lastIndexOf('/');
this.relatedTableURL = url.substring(0, lastSlash + 1) + related.relatedTableId;
relatedDeferred = this.getLayerJSON(this.relatedTableURL);
relatedDeferred.then(lang.hitch(this, function () {
deferred.resolve();
}));
}
}
if (!relatedDeferred) {
deferred.resolve();
}
return deferred.promise;
}
});
});