define([
'dojo/_base/array',
'dojo/_base/lang',
'dojo/Deferred',
'dojo/promise/all',
'dojo/string',
'esri/arcade/arcade',
'esri/arcade/Feature',
'esri/tasks/RelationshipQuery',
'jimu/utils',
'jimu/LayerStructure'
],
function(
array, lang, Deferred, all, string,
Arcade, ArcadeFeature, RelationshipQuery, jimuUtils, LayerStructure) {
var mo = {};
/*------------------------------------------------------------------------------------------------------------------*/
/********read arcade expr info*********/
mo.readExprInfo = {};
//public functions
/** Get arcade profiles by type.
* @param {string} type, arcade profile types, it could be three types:render, label, infoTemplate.
it's optional, if no type or wrong type, it retruns all the profiles.
* @return {object} when type is one of the three strings above.
format: {render:[],label:[],infoTemplate:[]}
{array} when type is none or others, every item in array is a object.
item's format depends on the type of arcade profile's type.
eg: infoTemplate's format is {layer:'layerId',expressionInfos:[]}
//https://developers.arcgis.com/javascript/3/jshelp/arcade.html#popups
*/
mo.readExprInfo.getArcadeProfilesByType = function(map, operlayer, /* optional */ type){
var args = {
map: map,
layer: operlayer
};
if(type === 'render'){
return mo.readExprInfo._getArcadeRender(args);
}else if(type === 'label'){
return mo.readExprInfo._getArcadeLabel(args);
}else if(type === 'infoTemplate'){
return mo.readExprInfo._getArcadeInfoTemplate(args);
}else{
return mo.readExprInfo._getAllArcade(args);
}
};
//private functions
mo.readExprInfo._getArcadeRender = function(args) {
return mo.readExprInfo._lookupArcadeRender({layers: mo.readExprInfo._checkPassInLayer(args)});
};
mo.readExprInfo._getArcadeInfoTemplate = function(args) {
return mo.readExprInfo._lookupArcadeInfoTemplate({layers: mo.readExprInfo._checkPassInLayer(args)});
};
mo.readExprInfo._getArcadeLabel = function(args) {
return mo.readExprInfo._lookupArcadeLabel({layers: mo.readExprInfo._checkPassInLayer(args)});
};
mo.readExprInfo._getAllArcade = function(args) {
var render = mo.readExprInfo._getArcadeRender(args);
var infoTemplate = mo.readExprInfo._getArcadeInfoTemplate(args);
var label = mo.readExprInfo._getArcadeLabel(args);
// var arcadeObj = Object.assign(infoTemplate, render, label);
// return arcadeObj;
return {
render: render,
infoTemplate: infoTemplate,
label: label
};
};
mo.readExprInfo._checkPassInLayer = function(args) {
var layerList = [];
if(typeof(args) !== 'undefined') {
if(typeof(args.layer) !== 'undefined') {
if((args.layer !== null) && (args.layer !== '')) {
//layerList.push(args.layer);
layerList = mo.readExprInfo._getAllMapLayers(args);
} else {
layerList = mo.readExprInfo._getAllMapLayers();
}
} else {
layerList = mo.readExprInfo._getAllMapLayers();
}
} else {
layerList = mo.readExprInfo._getAllMapLayers();
}
return layerList;
};
//This gets all the operational layers and gets the info and places it in a custom data object.
mo.readExprInfo._getAllMapLayers = function(args) {
var lookupId = "";
var layerList = [];
var layerStructure = LayerStructure.getInstance();
// if(typeof(args) !== 'undefined') {
if(args && args.layer) {
lookupId = args.layer.id;
}
if(lookupId !== "") {
var foundLayer = layerStructure.getNodeById(lookupId);
// if(foundLayer !== 'undefined') {
if(foundLayer) {
layerList.push(foundLayer._layerInfo.layerObject);
}
} else {
//No layer ID passed in, just get all layers.
layerStructure.traversal(function(layerNode) {
//check to see if type exist and if it's not any tiles
if(typeof(layerNode._layerInfo.layerObject.type) !== 'undefined') {
if((layerNode._layerInfo.layerObject.type).indexOf("tile") === -1) {
layerList.push(layerNode._layerInfo.layerObject);
}
} else {
//It is a grouped Map service
layerList.push(layerNode._layerInfo.layerObject);
}
});
}
return layerList;
};
mo.readExprInfo._lookupArcadeRender = function(args) {
var lyrArcadeExpr = [];
if(args.layers.length > 0) {
array.forEach(args.layers, lang.hitch(this, function(layer) {
var expObj = {};
//Only add if there is a renderer object, maps services do not have this.
if(typeof(layer.renderer) !== 'undefined') {
var lyrObjRender = layer.renderer;
if(typeof(lyrObjRender.valueExpression) !== 'undefined') {
expObj.layer = layer.id;
expObj.valueExpression = lyrObjRender.valueExpression;
expObj.valueExpressionTitle = lyrObjRender.valueExpressionTitle;
if(typeof(lyrObjRender.values) !== 'undefined') {
expObj.values = lyrObjRender.values;
}
if(typeof(lyrObjRender.visualVariables) !== 'undefined') {
expObj.visualVariables = lyrObjRender.visualVariables;
}
}
if(Object.keys(expObj).length > 1 && expObj.constructor === Object) {
lyrArcadeExpr.push(expObj);
}else{ //if layer has no arcade expressionInfos
lyrArcadeExpr.push({
layer: layer.id,
valueExpression: null,
valueExpressionTitle: null,
values: null,
visualVariables: null
});
}
}
}));
}
return lyrArcadeExpr;
};
mo.readExprInfo._lookupArcadeInfoTemplate = function(args) {
var lyrArcadeExpr = [];
if(args.layers.length > 0) {
array.forEach(args.layers, lang.hitch(this, function(layer) {
var expObj = {};
//InfoTemplate handling for feature layers
if(typeof(layer.infoTemplate) !== 'undefined') {
if(typeof(layer.infoTemplate.info.expressionInfos) !== 'undefined') {
if(layer.infoTemplate.info.expressionInfos.length > 0) {
expObj.layer = layer.id;
expObj.expressionInfos = layer.infoTemplate.info.expressionInfos;
}
}
} else {
//feature handling for map service
if(typeof(layer.infoTemplates) !== 'undefined') {
//check if it template is arcade and only store it if it is.
//Arcade is stored in the group level, not sublayers
for (var key in layer.infoTemplates) {
if((layer.infoTemplates).hasOwnProperty(key)) {
if(typeof(layer.infoTemplates[key].infoTemplate.info.expressionInfos) !== 'undefined') {
var arcadeNode = layer.infoTemplates[key].infoTemplate;
if(arcadeNode.info.expressionInfos.length > 0) {
expObj.layer = layer.id;
expObj.expressionInfos = arcadeNode.info.expressionInfos;
}
}
}
}
} else {
//no infoTemplates defined
}
}
if(Object.keys(expObj).length > 1 && expObj.constructor === Object) {
lyrArcadeExpr.push(expObj);
}else{ //if layer has no arcade expressionInfos
lyrArcadeExpr.push({
layer: layer.id,
expressionInfos: null
});
}
}));
}
return lyrArcadeExpr;
};
mo.readExprInfo._lookupArcadeLabel = function(args) {
var lyrArcadeExpr = [];
if(args.layers.length > 0) {
array.forEach(args.layers, lang.hitch(this, function(layer) {
var expObj = {};
var lyrObj = layer;
if(typeof(lyrObj.labelingInfo) !== 'undefined') {
array.forEach(lyrObj.labelingInfo, lang.hitch(this, function(labelInfo) {
//check if 'name' exists, it is present when Arcade is used
if(typeof(labelInfo.name) !== 'undefined') {
expObj.layer = layer.id;
expObj.name = labelInfo.name;
expObj.labelingInfo = labelInfo;
expObj.expression = labelInfo.labelExpressionInfo.expression;
}
}));
}
if(Object.keys(expObj).length > 1 && expObj.constructor === Object) {
lyrArcadeExpr.push(expObj);
}else{ //if layer has no arcade expressionInfos
lyrArcadeExpr.push({
layer: layer.id,
name: null,
labelingInfo: null,
expression: null
});
}
}));
}
return lyrArcadeExpr;
};
/********************custom arcade expression***********************/
mo.customExpr = {};
/**
* Get one graphic's all attributes by custom expressionInfos.
* @param {array} expressionInfos, every item is an object, likes: {name,title,expression}
* @param {object} graphic, it contains geometry, a symbol, attributes, or an infoTemplate
* @return {object} attributes object, including orginal attributes and custom arcade attributes
*/
mo.customExpr.getAttributesFromCustomArcadeExpr = function (expressionInfos, graphic, layerDefinition) {
var parsedExprs = mo.InfoTemplate._parseArcadeExpressions(expressionInfos);
var graphicAttrs = mo.InfoTemplate._combineFeatureAttributesAndExpressionResolutions(graphic, parsedExprs,
layerDefinition);
return graphicAttrs;
};
/**
* Get Graphics's attributes value list by custom expressionInfos.
* @param {array} expressionInfos, every item is an object, likes: {name,title,expression}
* @param {array} graphicList, graphic object array.
* @return {array} every item in array is an object, it contains the attributes's values by custom expressionInfos.
*/
mo.customExpr.getAttributesValueFromCustomArcadeExprBatch = function (expressionInfos, graphicList, layerDefinition) {
var features = [];
for(var key in graphicList){
features.push(graphicList[key]);
}
var parsedExprs = mo.InfoTemplate._parseArcadeExpressions(expressionInfos);
var customExprTemplates = mo.InfoTemplate._getExprNamesArrayFromPopupExprInfos(expressionInfos);
customExprTemplates.relationships = null;
customExprTemplates.parsedExpressions = parsedExprs;
var def = new Deferred();
var labelDef = mo.InfoTemplate._getPopupFieldsValueFromArcadeFeatures(features, customExprTemplates,
layerDefinition);
labelDef.then(function(customFieldsValueArray){
def.resolve(customFieldsValueArray);
});
return def;
};
/********************Render***********************/
/********************Label***********************/
/********************InfoTemplate***********************/
mo.InfoTemplate = {};
/**
* Get one graphic's all attributes.
* @param {operlayer} layer that need to have id, popupInfo, layerObject(has fields).
* @param {object} graphic, it contains geometry, a symbol, attributes, or an infoTemplate
* @return {object} attributes object, including orginal attributes and arcade attributes
format:{'OBJECTID':1, 'expression/expr1':"394-Waterous", 'relationships/0/CHAINS':123}
*/
mo.InfoTemplate.getAttributesFromInfoTemplate = function (map, operlayer, graphic) {
var layerDefinition = {fields: []};
if(operlayer.layerObject && operlayer.layerObject.fields){
layerDefinition.fields = operlayer.layerObject.fields;
}
var arcadeTemplate = mo.readExprInfo.getArcadeProfilesByType(map, operlayer, 'infoTemplate');
var expressionInfos = arcadeTemplate[0].expressionInfos;
if(!expressionInfos){
return graphic.attributes;
}
var parsedExprs = mo.InfoTemplate._parseArcadeExpressions(expressionInfos);
var graphicAttrs = mo.InfoTemplate._combineFeatureAttributesAndExpressionResolutions(graphic, parsedExprs,
layerDefinition);
return graphicAttrs;
};
/**
* Get Graphics's attributes value list which configurate in InfoTemplate.
* @param {operlayer} layer that need to have id, popupInfo, layerObject(has fields).
* @param {array} graphicList, graphic object array.
* @return {array} every item in array is an object
it contains the attributes's values which configurate in InfoTemplate.
format:[["278-Waterous","Waterous---str"],["279-Clow Corporation","Clow Corporation---str"]]
*/
mo.InfoTemplate.getAttributesValueFromInfoTemplateBatch = function (map, operlayer, graphicList) {
var layerDefinition = {fields: []};
if(operlayer.layerObject && operlayer.layerObject.fields){
layerDefinition.fields = operlayer.layerObject.fields;
}
var features = [];
for(var key in graphicList){
features.push(graphicList[key]);
}
var arcadeTemplate = mo.readExprInfo.getArcadeProfilesByType(map, operlayer, 'infoTemplate');
var expressionInfos = arcadeTemplate[0].expressionInfos;
var def = new Deferred();
if(!expressionInfos){//layer has no arcade infotemplate
def.resolve([]);
return def;
}
var parsedExprs = mo.InfoTemplate._parseArcadeExpressions(expressionInfos);
var popupExprTemplates = null;
var popupDesc = operlayer.popupInfo.description;
//from popupDesc
if(popupDesc !== null){
popupExprTemplates = mo.InfoTemplate._convertPopupDescToFieldNamesArray(popupDesc);
popupExprTemplates.relationships = mo.InfoTemplate._getRelationshipQueries(operlayer);
}else{ //from expressionInfos
popupExprTemplates = mo.InfoTemplate._getExprNamesArrayFromPopupExprInfos(expressionInfos);
popupExprTemplates.relationships = null; //doesn't support relationships in this case.
}
//add parsedExpr
popupExprTemplates.parsedExpressions = parsedExprs;
var labelDef = mo.InfoTemplate._getPopupFieldsValueFromArcadeFeatures(features, popupExprTemplates,
layerDefinition);
labelDef.then(function(popupFieldsValueArray){
def.resolve(popupFieldsValueArray);
});
return def;
};
// Get relationshipQueries from a layer.
mo.InfoTemplate._getRelationshipQueries = function (operLayer) {
var hasRelationships = false, relationships = {}, relationshipFieldPattern = /\{relationships\/\d+\//gm,
relationshipIdPattern = /\d+/, matches;
matches = operLayer.popupInfo.description.match(relationshipFieldPattern);
if (matches) {
hasRelationships = true;
array.forEach(matches, function (match) {
var relatedQuery, id = match.match(relationshipIdPattern)[0];
if (!relationships.hasOwnProperty(id)) {
relatedQuery = new RelationshipQuery();
relatedQuery.outFields = ['*'];
relatedQuery.relationshipId = id;
relatedQuery.returnGeometry = false;
relationships[id] = {
operLayer: operLayer,
relatedQuery: relatedQuery
};
}
});
}
return hasRelationships? relationships : null;
};
/**
* Converts the arcade exprs obj of a custom popup into a multiline label specification;
* @param {array} popupExprInfos, an arry of objects by customing popup arcade exprs in configuration.
every object, likes: {name,title,expression}
* @return {array} an array of exprs's name, format: ["${expression/expr1}"]
*/
mo.InfoTemplate._getExprNamesArrayFromPopupExprInfos = function (popupExprInfos) {
var exprNames = [];
for(var key in popupExprInfos){
var exprName = popupExprInfos[key].name;
exprNames.push('${expression/' + exprName + '}');
}
return exprNames;
};
/**
* Converts the text of a custom popup into an array of fields's name.
* conversion splits text into lines on
s,
* removes HTML tags, and changes field tags from popup style to string.substitute form.
* @param {string} popupDesc, popup's descriptions.
* format: "{OBJECTID}
{relationships/0/Creator}
{expression/expr0}"
* @return {array} an array of fields' name
* format: ["${OBJECTID}","${relationships/0/CreationDate}","${expression/expr0}"]
*/
mo.InfoTemplate._convertPopupDescToFieldNamesArray = function (popupDesc) {
// e.g., Occupant
{FULLADDR}
{MUNICIPALITY}, IL {PSTLZIP5}
// Sanitize the description after converting
s and