Initial commit
This commit is contained in:
@@ -0,0 +1,395 @@
|
||||
var _ = require('underscore');
|
||||
var Backbone = require('backbone');
|
||||
var LegendManager = require('./legend-manager');
|
||||
var linkLayerInfowindow = require('./link-layer-infowindow');
|
||||
var linkLayerTooltip = require('./link-layer-tooltip');
|
||||
var layerTypesAndKinds = require('builder/data/layer-types-and-kinds');
|
||||
var Notifier = require('builder/components/notifier/notifier');
|
||||
var checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
var NotificationErrorMessageHandler = require('builder/editor/layers/notification-error-message-handler');
|
||||
var basemapProvidersAndCategories = require('builder/data/basemap-providers-and-categories');
|
||||
|
||||
var REQUIRED_OPTS = [
|
||||
'analysisDefinitionNodesCollection',
|
||||
'editFeatureOverlay',
|
||||
'layerDefinitionsCollection',
|
||||
'legendDefinitionsCollection',
|
||||
'diDashboardHelpers'
|
||||
];
|
||||
|
||||
var LAYER_TYPE_TO_LAYER_CREATE_METHOD = {
|
||||
'cartodb': 'createCartoDBLayer',
|
||||
'gmapsbase': 'createGMapsBaseLayer',
|
||||
'plain': 'createPlainLayer',
|
||||
'tiled': 'createTileLayer',
|
||||
'torque': 'createTorqueLayer',
|
||||
'wms': 'createWMSLayer'
|
||||
};
|
||||
|
||||
var CARTODBJS_TO_CARTODB_ATTRIBUTE_MAPPINGS = {
|
||||
'layer_name': ['table_name_alias', 'table_name']
|
||||
};
|
||||
|
||||
var BLACKLISTED_LAYER_DEFINITION_ATTRS = {
|
||||
'all': [ 'letter', 'kind' ],
|
||||
'Tiled': [ 'category', 'selected', 'highlighted' ],
|
||||
'CartoDB': [ 'color', 'letter' ],
|
||||
'torque': [ 'color', 'letter' ]
|
||||
};
|
||||
|
||||
/**
|
||||
* Only manage **LAYER** actions between Deep-Insights (CARTO.js) and Builder
|
||||
*
|
||||
*/
|
||||
|
||||
var LayersIntegration = {
|
||||
|
||||
track: function (options) {
|
||||
checkAndBuildOpts(options, REQUIRED_OPTS, this);
|
||||
|
||||
this._visMap = this._diDashboardHelpers.visMap();
|
||||
|
||||
this._layerDefinitionsCollection.each(this._linkLayerErrors, this);
|
||||
this._layerDefinitionsCollection.on('add', this._onLayerDefinitionAdded, this);
|
||||
this._layerDefinitionsCollection.on('sync', this._onLayerDefinitionSynced, this);
|
||||
this._layerDefinitionsCollection.on('change', this._onLayerDefinitionChanged, this);
|
||||
this._layerDefinitionsCollection.on('remove', this._onLayerDefinitionRemoved, this);
|
||||
this._layerDefinitionsCollection.on('layerMoved', this._onLayerDefinitionMoved, this);
|
||||
this._layerDefinitionsCollection.on('baseLayerChanged', this._onBaseLayerChanged, this);
|
||||
|
||||
this._layerDefinitionsCollection.each(function (layerDefModel) {
|
||||
LegendManager.track(layerDefModel);
|
||||
|
||||
linkLayerInfowindow(layerDefModel, this._visMap);
|
||||
linkLayerTooltip(layerDefModel, this._visMap);
|
||||
|
||||
if (layerDefModel.has('source')) {
|
||||
this._resetStylesIfNoneApplied(layerDefModel);
|
||||
}
|
||||
}, this);
|
||||
|
||||
// In order to sync layer selector and layer visibility
|
||||
this._diDashboardHelpers.getLayers().on('change:visible', function (layer, visible) {
|
||||
var layerDefModel = this._layerDefinitionsCollection.findWhere({id: layer.id});
|
||||
if (layerDefModel) {
|
||||
if (layerDefModel.get('visible') !== visible) {
|
||||
layerDefModel.save({visible: visible});
|
||||
}
|
||||
}
|
||||
}, this);
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_onLayerDefinitionRemoved: function (m) {
|
||||
if (!m.isNew()) {
|
||||
var layer = this._diDashboardHelpers.getLayer(m.id);
|
||||
layer && layer.remove();
|
||||
}
|
||||
},
|
||||
|
||||
_onLayerDefinitionMoved: function (m, from, to) {
|
||||
this._diDashboardHelpers.moveCartoDBLayer(from, to);
|
||||
},
|
||||
|
||||
_createLayer: function (layerDefModel, options) {
|
||||
options = options || {};
|
||||
var attrs = JSON.parse(JSON.stringify(layerDefModel.attributes)); // deep clone
|
||||
attrs = this._adaptAttrsToCDBjs(layerDefModel.get('type'), attrs);
|
||||
|
||||
// create the legends for the new layer
|
||||
var legends = this._legendDefinitionsCollection.findByLayerDefModel(layerDefModel);
|
||||
if (legends.length > 0) {
|
||||
attrs.legends = _.map(legends, function (legend) {
|
||||
return legend.toJSON();
|
||||
});
|
||||
}
|
||||
|
||||
var createMethodName = LAYER_TYPE_TO_LAYER_CREATE_METHOD[attrs.type.toLowerCase()];
|
||||
if (!createMethodName) throw new Error('no create method name found for type ' + attrs.type);
|
||||
|
||||
if (attrs.source) {
|
||||
// Make sure the analysis is created first
|
||||
var nodeDefModel = this._analysisDefinitionNodesCollection.get(attrs.source);
|
||||
// Dependency with another integration
|
||||
this.trigger('onLayerCreation', nodeDefModel);
|
||||
// Set analysis model instead of the string ID source
|
||||
attrs.source = this._diDashboardHelpers.getAnalysisByNodeId(attrs.source);
|
||||
}
|
||||
|
||||
var layerPosition = this._layerDefinitionsCollection.indexOf(layerDefModel);
|
||||
|
||||
this._visMap[createMethodName](attrs, _.extend({
|
||||
at: layerPosition
|
||||
}, options));
|
||||
|
||||
linkLayerInfowindow(layerDefModel, this._visMap);
|
||||
linkLayerTooltip(layerDefModel, this._visMap);
|
||||
LegendManager.track(layerDefModel);
|
||||
|
||||
this._linkLayerErrors(layerDefModel);
|
||||
},
|
||||
|
||||
_linkLayerErrors: function (m) {
|
||||
var layer = this._diDashboardHelpers.getLayer(m.id);
|
||||
if (layer) {
|
||||
if (layer.get('error')) {
|
||||
this._setLayerError(m, layer.get('error'));
|
||||
}
|
||||
layer.on('change:error', function (model, cdbError) {
|
||||
this._setLayerError(m, cdbError);
|
||||
}, this);
|
||||
}
|
||||
},
|
||||
|
||||
_setLayerError: function (layerDefinitionModel, cdbError) {
|
||||
var notification = Notifier.getNotification(layerDefinitionModel.id);
|
||||
var mainErrorMessage = layerDefinitionModel.getName() + ': ' + (cdbError && cdbError.message);
|
||||
|
||||
if (!cdbError) {
|
||||
layerDefinitionModel.unset('error');
|
||||
notification && Notifier.removeNotification(notification);
|
||||
return;
|
||||
}
|
||||
|
||||
var errorMessage = NotificationErrorMessageHandler.extractError(mainErrorMessage);
|
||||
|
||||
if (notification) {
|
||||
notification.update({
|
||||
status: errorMessage.type,
|
||||
info: errorMessage.message
|
||||
});
|
||||
} else {
|
||||
Notifier.addNotification({
|
||||
id: layerDefinitionModel.id,
|
||||
status: errorMessage.type,
|
||||
closable: true,
|
||||
button: false,
|
||||
info: errorMessage.message
|
||||
});
|
||||
}
|
||||
|
||||
if (cdbError.subtype === 'turbo-carto') {
|
||||
var line;
|
||||
try {
|
||||
line = cdbError.context.source.start.line;
|
||||
} catch (error) {}
|
||||
|
||||
layerDefinitionModel.set('error', {
|
||||
type: cdbError.type,
|
||||
subtype: cdbError.subtype,
|
||||
line: line,
|
||||
message: cdbError.message
|
||||
});
|
||||
} else if (errorMessage) {
|
||||
layerDefinitionModel.set('error', {
|
||||
type: errorMessage.type,
|
||||
subtype: cdbError.subtype,
|
||||
message: errorMessage.message
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_onLayerDefinitionAdded: function (m, c, opts) {
|
||||
// Base and labels layers are synced in a separate method
|
||||
if (!layerTypesAndKinds.isTypeDataLayer(m.get('type'))) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If added but not yet saved, postpone the creation until persisted (see sync listener)
|
||||
if (!m.isNew()) {
|
||||
if (!this._diDashboardHelpers.getLayer(m.id)) {
|
||||
this._createLayer(m);
|
||||
} else {
|
||||
// we need to sync model positions
|
||||
this._tryUpdateLayerPosition(m);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_tryUpdateLayerPosition: function (m) {
|
||||
var builderPosition = this._layerDefinitionsCollection.indexOf(m);
|
||||
var cdbLayer = this._diDashboardHelpers.getLayer(m.id);
|
||||
var cdbPosition;
|
||||
|
||||
if (cdbLayer) {
|
||||
cdbPosition = this._diDashboardHelpers.getLayers().indexOf(cdbLayer);
|
||||
}
|
||||
|
||||
var indexChanges = m.isDataLayer() && cdbPosition > 0 && builderPosition > 0 && builderPosition !== cdbPosition;
|
||||
|
||||
if (indexChanges) {
|
||||
this._diDashboardHelpers.moveCartoDBLayer(cdbPosition, builderPosition);
|
||||
}
|
||||
},
|
||||
|
||||
_onLayerDefinitionSynced: function (m) {
|
||||
// Base and labels layers are synced in a separate method
|
||||
if (!layerTypesAndKinds.isTypeDataLayer(m.get('type'))) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._diDashboardHelpers.getLayer(m.id)) {
|
||||
this._createLayer(m);
|
||||
}
|
||||
},
|
||||
|
||||
_onLayerDefinitionChanged: function (layerDefinitionModel, changedAttributes) {
|
||||
var attrs = layerDefinitionModel.changedAttributes();
|
||||
var attrsNames = _.keys(attrs);
|
||||
|
||||
// Base and labels layers are synced in a separate method
|
||||
if (!layerTypesAndKinds.isTypeDataLayer(layerDefinitionModel.get('type'))) {
|
||||
return;
|
||||
}
|
||||
|
||||
// return if only the 'error' attribute has changed (no need to sync anything)
|
||||
if (attrsNames.length === 1 && attrsNames[0] === 'error') {
|
||||
return;
|
||||
}
|
||||
|
||||
var layer = this._diDashboardHelpers.getLayer(layerDefinitionModel.id);
|
||||
if (!layerDefinitionModel.isNew()) {
|
||||
if (!layer) {
|
||||
this._createLayer(layerDefinitionModel);
|
||||
return;
|
||||
}
|
||||
|
||||
if (attrs.type) {
|
||||
layer.remove();
|
||||
this._createLayer(layerDefinitionModel);
|
||||
} else {
|
||||
if (layerDefinitionModel.get('source') && !layer.get('source')) {
|
||||
attrs.source = layerDefinitionModel.get('source');
|
||||
}
|
||||
var onlySourceChanged = attrs.source && _.keys(attrs).length === 1;
|
||||
if (attrs.source) {
|
||||
// Set analysis model instead of the string ID source
|
||||
attrs.source = this._diDashboardHelpers.getAnalysisByNodeId(attrs.source);
|
||||
// Set source with setSource method
|
||||
layer.setSource(attrs.source, { silent: !onlySourceChanged });
|
||||
// Remove source form attrs to avoid updating source
|
||||
delete attrs.source;
|
||||
}
|
||||
attrs = this._adaptAttrsToCDBjs(layerDefinitionModel.get('type'), attrs);
|
||||
layer.update(attrs, { silent: onlySourceChanged });
|
||||
}
|
||||
// Find an animated layer if exists
|
||||
var animatedLayerDefinitionModel = this._layerDefinitionsCollection.find(function (model) {
|
||||
return model && model.styleModel && model.styleModel.get('type') === 'animation';
|
||||
});
|
||||
// Dependency with widgets-integration
|
||||
// If there is an animated layer, use that layer, else use the provided layer
|
||||
this.trigger('onLayerChanged', animatedLayerDefinitionModel || layerDefinitionModel);
|
||||
}
|
||||
},
|
||||
|
||||
_onBaseLayerChanged: function () {
|
||||
var baseLayerDefinition = this._layerDefinitionsCollection.getBaseLayer();
|
||||
var newBaseLayerAttrs = baseLayerDefinition.changedAttributes();
|
||||
|
||||
var newBaseLayerType = baseLayerDefinition.get('type');
|
||||
var newMapProvider = basemapProvidersAndCategories.getProvider(newBaseLayerType);
|
||||
var mapProviderChanged = false;
|
||||
if (baseLayerDefinition.hasChanged('type')) {
|
||||
var previousBaseLayerType = baseLayerDefinition.previous('type');
|
||||
var previousMapProvider = basemapProvidersAndCategories.getProvider(previousBaseLayerType);
|
||||
mapProviderChanged = previousMapProvider !== newMapProvider;
|
||||
}
|
||||
|
||||
// If the map provider has changed (eg: Leaflet -> Google Maps), we add/update/remove base and
|
||||
// labels layers silently so that CartoDB.js doesn't pick up those changes and tries to add/update/remove
|
||||
// layers until the new map provider has been set
|
||||
var handleLayersSilently = mapProviderChanged;
|
||||
|
||||
// Base layer
|
||||
var cdbjsLayer = this._diDashboardHelpers.getLayer(baseLayerDefinition.id);
|
||||
|
||||
// If the type of base layer has changed. eg: Tiled -> Plain
|
||||
if (newBaseLayerAttrs.type) {
|
||||
cdbjsLayer.remove({ silent: handleLayersSilently });
|
||||
this._createLayer(baseLayerDefinition, { silent: handleLayersSilently });
|
||||
} else {
|
||||
cdbjsLayer.update(this._adaptAttrsToCDBjs(baseLayerDefinition.get('type'), newBaseLayerAttrs), {
|
||||
silent: handleLayersSilently
|
||||
});
|
||||
}
|
||||
|
||||
// Labels layer
|
||||
var labelsLayerDefinition = this._layerDefinitionsCollection.getLabelsLayer();
|
||||
var cdbjsTopLayer = this._diDashboardHelpers.getLayers().last();
|
||||
var cdbjsHasLabelsLayer = cdbjsTopLayer.get('type') === 'Tiled';
|
||||
|
||||
if (labelsLayerDefinition) {
|
||||
if (cdbjsHasLabelsLayer) {
|
||||
var changedAttrs = labelsLayerDefinition.changedAttributes();
|
||||
if (changedAttrs) {
|
||||
cdbjsTopLayer.update(this._adaptAttrsToCDBjs(labelsLayerDefinition.get('type'), changedAttrs), {
|
||||
silent: handleLayersSilently
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this._createLayer(labelsLayerDefinition, { silent: handleLayersSilently });
|
||||
}
|
||||
} else if (cdbjsHasLabelsLayer) {
|
||||
cdbjsTopLayer.remove({ silent: handleLayersSilently });
|
||||
}
|
||||
|
||||
// Map provider
|
||||
this._visMap.set('provider', newMapProvider);
|
||||
|
||||
if (handleLayersSilently) {
|
||||
// Reload map if everything (previously) was done silently
|
||||
this._diDashboardHelpers.reloadMap();
|
||||
}
|
||||
|
||||
// Render again the edit-feature-overlay, in order to
|
||||
// decide if delegate or not events
|
||||
this._editFeatureOverlay.render();
|
||||
|
||||
// Dependency with map-integration class
|
||||
this.trigger('onBaseLayerChanged');
|
||||
},
|
||||
|
||||
_resetStylesIfNoneApplied: function (layerDefModel) {
|
||||
var nodeDefModel = layerDefModel.getAnalysisDefinitionNodeModel();
|
||||
var nodeModel = this._diDashboardHelpers.getAnalysisByNodeId(layerDefModel.get('source'));
|
||||
var isAnalysisNode = nodeModel && nodeModel.get('type') !== 'source';
|
||||
var isDone = nodeModel && nodeModel.isDone();
|
||||
var queryGeometryModel = nodeDefModel && nodeDefModel.queryGeometryModel;
|
||||
var styleModel = layerDefModel.styleModel;
|
||||
|
||||
if (isAnalysisNode && styleModel.hasNoneStyles() && isDone) {
|
||||
var simpleGeom = queryGeometryModel.get('simple_geom');
|
||||
|
||||
var applyDefaultStyles = function () {
|
||||
simpleGeom = queryGeometryModel.get('simple_geom');
|
||||
styleModel.setDefaultPropertiesByType('simple', simpleGeom);
|
||||
};
|
||||
|
||||
if (!simpleGeom) {
|
||||
queryGeometryModel.once('change:simple_geom', applyDefaultStyles, this);
|
||||
queryGeometryModel.fetch();
|
||||
} else {
|
||||
applyDefaultStyles();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_adaptAttrsToCDBjs: function (layerType, attrs) {
|
||||
attrs = _.omit(attrs, BLACKLISTED_LAYER_DEFINITION_ATTRS['all'], BLACKLISTED_LAYER_DEFINITION_ATTRS[layerType]);
|
||||
_.each(CARTODBJS_TO_CARTODB_ATTRIBUTE_MAPPINGS, function (cdbAttrs, cdbjsAttr) {
|
||||
_.each(cdbAttrs, function (cdbAttr) {
|
||||
if (attrs[cdbAttr] && !attrs[cdbjsAttr]) {
|
||||
attrs[cdbjsAttr] = attrs[cdbAttr];
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return attrs;
|
||||
}
|
||||
};
|
||||
|
||||
_.extend(LayersIntegration, Backbone.Events);
|
||||
|
||||
module.exports = LayersIntegration;
|
||||
Reference in New Issue
Block a user