cartodb/lib/assets/javascripts/builder/data/layer-definitions-collection.js
2020-06-15 10:58:47 +08:00

412 lines
12 KiB
JavaScript
Executable File

var $ = require('jquery');
var _ = require('underscore');
var Backbone = require('backbone');
var LayerDefinitionModel = require('./layer-definition-model');
var layerLetters = require('./layer-letters');
var layerColors = require('./layer-colors');
var nodeIds = require('builder/value-objects/analysis-node-ids');
var layerTypesAndKinds = require('./layer-types-and-kinds');
var SQLUtils = require('builder/helpers/sql-utils');
var TableNameUtils = require('builder/helpers/table-name-utils');
var checkAndBuildOpts = require('builder/helpers/required-opts');
var REQUIRED_OPTS = [
'analysisDefinitionNodesCollection',
'configModel',
'mapId',
'stateDefinitionModel',
'userModel'
];
var generateLabelsLayerName = function (layerName) {
return layerName + ' Labels';
};
/**
* Collection of layer definitions
*/
module.exports = Backbone.Collection.extend({
initialize: function (models, opts) {
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
},
url: function () {
var baseUrl = this._configModel.get('base_url');
return baseUrl + '/api/v1/maps/' + this._mapId + '/layers';
},
parse: function (r) {
return r.layers;
},
save: function (options) {
this.each(function (layerDefModel, index) {
layerDefModel.set('order', index, { silent: true });
});
Backbone.sync('update', this, options);
},
toJSON: function () {
return {
layers: Backbone.Collection.prototype.toJSON.apply(this, arguments)
};
},
/**
* Intended to be called from entry point, to make sure initial layers are taken into account
*/
resetByLayersData: function (layersData) {
this.reset(layersData, {
silent: true,
initialLetters: _.chain(layersData)
.pluck('options')
.pluck('letter')
.compact()
.value()
});
this._sanitizeLabels();
},
comparator: function (model) {
return model.get('order');
},
model: function (d, opts) {
var self = opts.collection;
// Add data required for new editor if not set (e.g. a vis created on old editor doesn't contain letter and source)
var o = _.clone(d.options) || {};
var attrs = _.defaults(
{ options: o },
_.omit(d, ['options']
));
if (!isNaN(opts.at)) {
attrs.options.order = opts.at;
}
if (layerTypesAndKinds.isKindDataLayer(attrs.kind)) {
o.letter = o.letter || layerLetters.next(self._takenLetters(opts.initialLetters));
o.color = o.color || layerColors.getColorForLetter(o.letter);
// Create source attr if it does not already exist
var sourceId = nodeIds.next(o.letter);
if (o.table_name && (!o.source || o.source === sourceId)) {
var tableName = TableNameUtils.getUnqualifiedName(o.table_name);
var userName = o.user_name || TableNameUtils.getUsername(o.table_name);
var qualifyTableName = (userName && self._configModel.get('user_name') !== userName) || self._userModel.isInsideOrg();
tableName = TableNameUtils.getQualifiedTableName(o.table_name, userName, qualifyTableName);
o.source = o.source || sourceId;
o.query = o.query || SQLUtils.getDefaultSQLFromTableName(tableName);
// Add analysis definition unless already existing
self._analysisDefinitionNodesCollection.createSourceNode({
id: sourceId,
tableName: tableName,
query: o.query
});
}
}
var parseAttrs = typeof opts.parse === 'boolean' ? opts.parse : true;
var m = new LayerDefinitionModel(attrs, {
parse: parseAttrs,
collection: self,
configModel: self._configModel
});
return m;
},
nextLetter: function () {
return layerLetters.next(this._takenLetters());
},
findAnalysisDefinitionNodeModel: function (id) {
return this._analysisDefinitionNodesCollection.get(id);
},
findOwnerOfAnalysisNode: function (nodeDefModel) {
return this.find(function (layerDefModel) {
return layerDefModel.isOwnerOfAnalysisNode(nodeDefModel);
});
},
findPrimaryParentLayerToAnalysisNode: function (nodeDefModel, opts) {
if (!nodeDefModel) return;
opts = opts || {};
if (!_.isArray(opts.exclude)) {
opts.exclude = [opts.exclude];
}
var owner = this.findOwnerOfAnalysisNode(nodeDefModel);
return this.find(function (layerDefModel) {
if (layerDefModel === owner) return;
if (_.contains(opts.exclude, layerDefModel)) return;
var layerHeadNode = layerDefModel.getAnalysisDefinitionNodeModel();
if (nodeDefModel === layerHeadNode) return true;
if (layerHeadNode) {
var primarySourceOfLastOwnNode = _.last(layerHeadNode.linkedListBySameLetter()).getPrimarySource();
return nodeDefModel === primarySourceOfLastOwnNode;
}
});
},
isThereAnyGeometryData: function () {
var dataLayers = this._getDataLayers();
var allQueryGeometryModels = this._getAllQueryGeometryModels(dataLayers);
var queryGeometryModelsValuePromises = _.map(allQueryGeometryModels, function (queryGeometryModel) {
return queryGeometryModel.hasValueAsync();
});
return Promise.all(queryGeometryModelsValuePromises)
.then(function (hasGeomValues) {
return _.some(hasGeomValues);
});
},
loadAllQueryGeometryModels: function (callback) {
var promises = this.filter(function (layerDefModel) {
return layerDefModel.isDataLayer();
}).map(function (layerDefModel) {
var queryGeometryModel = layerDefModel.getAnalysisDefinitionNodeModel().queryGeometryModel;
var status = queryGeometryModel.get('status');
var deferred = new $.Deferred();
if (queryGeometryModel.isFetched()) {
deferred.resolve();
} else if (queryGeometryModel.canFetch()) {
if (status !== 'fetching') {
queryGeometryModel.fetch({
success: function () {
deferred.resolve();
},
error: function () {
deferred.reject();
}
});
} else {
deferred.resolve();
}
} else {
deferred.reject();
}
return deferred.promise();
}, this);
$.when.apply($, promises).done(callback);
},
isThereAnyTorqueLayer: function () {
return this.any(function (model) {
return layerTypesAndKinds.isTorqueType(model.get('type'));
});
},
isThereAnyCartoDBLayer: function () {
return this.any(function (model) {
return layerTypesAndKinds.isCartoDBType(model.get('type'));
});
},
anyContainsNode: function (nodeDefModel) {
return this.any(function (layerDefModel) {
return layerDefModel.containsNode(nodeDefModel);
});
},
isDataLayerOnTop: function (lyrModel) {
var currentPos = this.indexOf(lyrModel);
return currentPos === this.getTopDataLayerIndex();
},
getTopDataLayerIndex: function () {
var layerOnTop = this.getLayerOnTop();
var at = this.indexOf(layerOnTop);
var hasLabels = layerOnTop &&
!layerTypesAndKinds.isCartoDBType(layerOnTop.get('type')) &&
!layerTypesAndKinds.isTorqueType(layerOnTop.get('type'));
if (hasLabels) { at--; }
return at;
},
getNumberOfDataLayers: function () {
return this.select(function (model) {
return layerTypesAndKinds.isTypeDataLayer(model.get('type'));
}).length;
},
_takenLetters: function (otherLetters) {
var valuesFromAddedModels = _.compact(this.pluck('letter'));
// When adding multiple items the models created so far are stored in the internal object this._byId,
// need to make sure to take them into account when returning already taken letter.
var valuesFromModelsNotYetAdded = _.chain(this._byId).values().invoke('get', 'letter').value();
return _.union(valuesFromAddedModels, valuesFromModelsNotYetAdded, otherLetters);
},
// This ensures that we don't end up with more than one labels layer
_sanitizeLabels: function () {
var troubled = this.models.filter(function (layer) {
return layerTypesAndKinds.isTiledType(layer.get('type'));
}).filter(function (layer) {
var index = this.models.indexOf(layer);
return (index > 0 && index < this.length - 1);
}.bind(this));
troubled.length > 0 && _.each(troubled, function (layer) {
layer.destroy();
});
},
setBaseLayer: function (newBaseLayerAttrs) {
this.trigger('changingBaseLayer');
newBaseLayerAttrs = _.clone(newBaseLayerAttrs);
if (this.isBaseLayerAdded(newBaseLayerAttrs)) {
this.trigger('baseLayerChanged');
return false;
}
var newBaseLayerHasLabels = !!(newBaseLayerAttrs.labels && newBaseLayerAttrs.labels.urlTemplate);
var labelsLayerOptions = {
silent: true,
wait: true, // do not add/remove the layer unless it's created/removed/updated successfully
success: function () {
this._sanitizeLabels();
this.trigger('baseLayerChanged');
}.bind(this),
error: function () {
this.trigger('baseLayerFailed');
}.bind(this)
};
var baseLayerOptions = {
silent: true,
wait: true, // do not update the layer unless it's created/updated successfully
success: function () {
var labelsLayer = this.getLabelsLayer();
if (newBaseLayerHasLabels) {
if (labelsLayer) {
this._updateLabelsLayer(newBaseLayerAttrs, labelsLayerOptions);
} else {
this._addLabelsLayer(newBaseLayerAttrs, labelsLayerOptions);
}
} else {
if (labelsLayer) {
this._destroyLabelsLayer(labelsLayerOptions);
} else {
this._sanitizeLabels();
this.trigger('baseLayerChanged');
}
}
}.bind(this),
error: function () {
this.trigger('baseLayerFailed');
}.bind(this)
};
if (this.getBaseLayer()) {
this._updateBaseLayer(newBaseLayerAttrs, baseLayerOptions);
} else {
this._createBaseLayer(newBaseLayerAttrs, baseLayerOptions);
}
},
isBaseLayerAdded: function (basemapAttrs) {
var baseLayerDefinitionModel = this.getBaseLayer();
return baseLayerDefinitionModel.matchesAttrs(basemapAttrs);
},
_updateBaseLayer: function (basemapAttrs, opts) {
var currentBaseLayer = this.getBaseLayer();
var newBaseLayerAttrs = _.omit(basemapAttrs, [ 'id', 'order', 'labels', 'template' ]);
currentBaseLayer.attributes = _.extend({
order: 0
}, _.pick(currentBaseLayer.attributes, [
'id',
'type',
'category'
]));
currentBaseLayer.save(newBaseLayerAttrs, opts);
},
_createBaseLayer: function (newBaseLayerAttrs, opts) {
newBaseLayerAttrs = _.extend(newBaseLayerAttrs, {
order: 0
});
return this.create(newBaseLayerAttrs, _.extend({ at: 0 }, opts));
},
_updateLabelsLayer: function (newBaseLayerAttrs, opts) {
var labelsLayer = this.getLabelsLayer();
var newAttrs = _.omit(newBaseLayerAttrs, 'labels');
newAttrs = _.pick(newAttrs, _.identity);
newAttrs = _.extend(newAttrs, newBaseLayerAttrs.labels, {
name: generateLabelsLayerName(newBaseLayerAttrs.name)
});
labelsLayer.save(newAttrs, opts);
},
_addLabelsLayer: function (newBaseLayerAttrs, opts) {
var optionsAttribute = _.pick(newBaseLayerAttrs, ['type', 'subdomains', 'minZoom', 'maxZoom', 'name', 'className', 'attribution', 'category']);
optionsAttribute = _.extend(optionsAttribute, newBaseLayerAttrs.labels, {
name: generateLabelsLayerName(newBaseLayerAttrs.name)
});
var labelsLayerAttrs = {
order: this.getLayerOnTop().get('order') + 1,
options: optionsAttribute
};
this.create(labelsLayerAttrs, opts);
},
_destroyLabelsLayer: function (opts) {
this.getLayerOnTop().destroy(opts);
},
_getAllQueryGeometryModels: function (dataLayers) {
return _.compact(_.map(dataLayers, function (dataLayer) {
return dataLayer.getAnalysisDefinitionNodeModel().queryGeometryModel;
}));
},
_getDataLayers: function () {
return this.filter(function (layerDefinitionModel) {
return layerDefinitionModel.isDataLayer();
});
},
getBaseLayer: function () {
return this.first();
},
getLabelsLayer: function () {
var layerOnTop = this.getLayerOnTop();
if (this.size() > 1 && layerTypesAndKinds.isTiledType(layerOnTop.get('type'))) {
return layerOnTop;
}
},
getLayerOnTop: function () {
return this.last();
}
});