var _ = require('underscore'); var checkAndBuildOpts = require('builder/helpers/required-opts'); var WidgetsService = require('builder/editor/widgets/widgets-service'); var WidgetDefinitionModel = require('builder/data/widget-definition-model'); var WidgetsNotifications = require('builder/widgets-notifications'); var getStylesWithoutAutostyles = require('builder/helpers/styles-without-autostyle'); var WIDGET_STYLE_PARAMS = [ 'widget_style_definition', 'auto_style_definition', 'auto_style_allowed' ]; var REQUIRED_OPTS = [ 'diDashboardHelpers', 'layerDefinitionsCollection', 'widgetDefinitionsCollection', 'analysisDefinitionNodesCollection' ]; /** * Only manage **WIDGET** actions between Deep-Insights (CARTO.js) and Builder * */ module.exports = { track: function (options) { checkAndBuildOpts(options, REQUIRED_OPTS, this); this._getStylesWithoutAutostyles = getStylesWithoutAutostyles(); this._widgetDefinitionsCollection.on('add', this._onWidgetDefinitionAdded, this); this._widgetDefinitionsCollection.on('sync', this._onWidgetDefinitionSynced, this); this._widgetDefinitionsCollection.on('change', this._onWidgetDefinitionChanged, this); this._widgetDefinitionsCollection.on('destroy', this._onWidgetDefinitionDestroyed, this); this._widgetDefinitionsCollection.on('add remove reset', this._invalidateSize, this); this._widgetDefinitionsCollection.on('setSelected', this._setSelectedWidget, this); this._widgetDefinitionsCollection.each(this._onWidgetDefinitionAdded, this); WidgetsNotifications.track(this._widgetDefinitionsCollection); return this; }, _invalidateSize: function () { var vis = this._diDashboardHelpers.getMap(); vis.invalidateSize(); }, _onWidgetDefinitionAdded: function (model) { var widgetModel = this._diDashboardHelpers.getWidget(model.id); if (widgetModel) { var layerDefModel = this._layerDefinitionsCollection.findWhere({ id: model.get('layer_id') }); widgetModel.set({ show_source: true, show_stats: true, show_options: true, table_name: layerDefModel.get('table_name') }); this._bindWidgetChanges(widgetModel); } }, _onWidgetDefinitionSynced: function (model) { var widgetModel = this._diDashboardHelpers.getWidget(model.id); if (!widgetModel) { this._createWidgetModel(model); } }, _onWidgetAutoStyleColorChanged: function (model) { var isAutoStyleApplied = model.isAutoStyle(); var autoStyleInfo = model.getAutoStyle(); var layerId = model.layerModel.id; var layerDefModel = this._layerDefinitionsCollection.findWhere({ id: layerId }); var nodeDefModel = layerDefModel && layerDefModel.getAnalysisDefinitionNodeModel(); var styleModel = layerDefModel && layerDefModel.styleModel; var geometryType = nodeDefModel && nodeDefModel.get('simple_geom'); if (layerDefModel) { layerDefModel.set({ autoStyle: isAutoStyleApplied ? model.id : false, cartocss: autoStyleInfo.cartocss }); } if (isAutoStyleApplied && styleModel && geometryType) { styleModel.setPropertiesFromAutoStyle({ definition: autoStyleInfo.definition, geometryType: geometryType, widgetId: model.id }); } }, _onWidgetAutoStyleChanged: function (model) { var isAutoStyleApplied = model.isAutoStyle(); var autoStyleInfo = model.getAutoStyle(); var layerId = model.layerModel.id; var layerDefModel = this._layerDefinitionsCollection.findWhere({ id: layerId }); var nodeDefModel = layerDefModel && layerDefModel.getAnalysisDefinitionNodeModel(); var styleModel = layerDefModel && layerDefModel.styleModel; var onLayerChange = _.debounce(function () { var dontResetStyles = true; // In order to make it more visible model.cancelAutoStyle(dontResetStyles); }, 10); var stylesWithoutAutostyles = this._getStylesWithoutAutostyles(layerDefModel); if (layerDefModel && nodeDefModel) { if (isAutoStyleApplied) { layerDefModel.set({ autoStyle: model.id }); } } else { return; } if (isAutoStyleApplied) { var geometryType = nodeDefModel.get('simple_geom'); styleModel.setPropertiesFromAutoStyle({ definition: autoStyleInfo.definition, geometryType: geometryType, widgetId: model.id }); layerDefModel.set(_.extend( { cartocss: autoStyleInfo.cartocss, cartocss_custom: false }, stylesWithoutAutostyles )); layerDefModel.once('change:autoStyle change:cartocss', onLayerChange, this); } else { layerDefModel.unbind('change:autoStyle change:cartocss', onLayerChange, this); var autoStyleId = layerDefModel.get('autoStyle'); if (autoStyleId && autoStyleId === model.id) { styleModel.resetPropertiesFromAutoStyle(); layerDefModel.set({ autoStyle: false, cartocss_custom: layerDefModel.get('previousCartoCSSCustom'), cartocss: layerDefModel.get('previousCartoCSS') }); // Because we are messing with the autoStyle property on saving, // whenever we disable autoStyle, we save the layer to force // the sync on the cartocss layerDefModel.save(); } } }, _onWidgetCustomAutoStyleColorChanged: function (model) { var isAutoStyleApplied = model.isAutoStyle(); var autoStyleInfo = model.getAutoStyle(); var layerId = model.layerModel.id; var layerDefModel = this._layerDefinitionsCollection.findWhere({ id: layerId }); var nodeDefModel = layerDefModel && layerDefModel.getAnalysisDefinitionNodeModel(); var styleModel = layerDefModel && layerDefModel.styleModel; if (isAutoStyleApplied) { var geometryType = nodeDefModel.get('simple_geom'); styleModel.setPropertiesFromAutoStyle({ definition: autoStyleInfo.definition, geometryType: geometryType, widgetId: model.id }); layerDefModel.set({ cartocss: autoStyleInfo.cartocss, cartocss_custom: false }, {silent: true}); // In order to make legends aware styleModel.trigger('style:update'); } }, _onWidgetDefinitionChanged: function (model) { var widgetModel = this._diDashboardHelpers.getWidget(model.id); // Only try to update if there's a corresponding widget model // E.g. the change of type will remove the model and provoke change events, which are not of interest (here), // since the widget model should be re-created for the new type anyway. if (widgetModel) { if (model.hasChanged('type')) { widgetModel.remove(); this._createWidgetModel(model); } else { var attrs = this._formatWidgetAttrs(model.changedAttributes(), model); widgetModel.update(attrs); } } var colorChanged = !model.get('widget_color_changed') && model.changedAttributes() && model.changedAttributes().widget_style_definition && model.changedAttributes().widget_style_definition.color; if (colorChanged) { model.set({ widget_color_changed: true }); } }, _onWidgetDefinitionDestroyed: function (model) { var widgetModel = this._diDashboardHelpers.getWidget(model.id); if (widgetModel) { if (widgetModel.isAutoStyle()) { widgetModel.cancelAutoStyle(); } this._unbindWidgetChanges(widgetModel); widgetModel.remove(); } }, _setSelectedWidget: function (selectedWidgetId) { var collection = this._diDashboardHelpers.getWidgets(); collection.forEach(function (widget) { widget.trigger('setDisabled', widget, selectedWidgetId); }); }, _onEditWidget: function (model) { var widgetDefinitionModel = this._widgetDefinitionsCollection.get(model.id); if (widgetDefinitionModel) { WidgetsService.editWidget(widgetDefinitionModel); } }, _onRemoveWidget: function (model) { var widgetDefinitionModel = this._widgetDefinitionsCollection.get(model.id); if (widgetDefinitionModel) { WidgetsService.removeWidget(widgetDefinitionModel); } }, _bindWidgetChanges: function (model) { model.bind('editWidget', this._onEditWidget, this); model.bind('removeWidget', this._onRemoveWidget, this); model.bind('customAutoStyle', this._onWidgetCustomAutoStyleColorChanged, this); model.bind('change:autoStyle', this._onWidgetAutoStyleChanged, this); model.bind('change:color', this._onWidgetAutoStyleColorChanged, this); }, _unbindWidgetChanges: function (model) { model.unbind('editWidget', this._onEditWidget, this); model.unbind('removeWidget', this._onRemoveWidget, this); model.unbind('customAutoStyle', this._onWidgetCustomAutoStyleColorChanged, this); model.unbind('change:autoStyle', this._onWidgetAutoStyleChanged, this); model.unbind('change:color', this._onWidgetAutoStyleColorChanged, this); }, _createWidgetModel: function (model) { // e.g. 'time-series' => createTimeSeriesWidget var infix = model.get('type').replace(/(^\w|-\w)/g, function (match) { return match.toUpperCase().replace('-', ''); }); var methodName = 'create' + infix + 'Widget'; var layerId = model.get('layer_id'); var layerModel = this._diDashboardHelpers.getLayer(layerId); var layerDefModel = this._layerDefinitionsCollection.findWhere({ id: layerId }); var attrs = this._formatWidgetAttrs(model.attributes, model); var widgetModel = this._diDashboardHelpers.getDashboard()[methodName](attrs, layerModel); if (widgetModel) { widgetModel.set({ show_source: true, show_stats: true, show_options: true, table_name: layerDefModel.get('table_name') }); this._bindWidgetChanges(widgetModel); } }, /** * Massage some data points to the expected format of deep-insights API */ _formatWidgetAttrs: function (changedAttrs, widgetDefinitionModel) { var formattedAttrs = changedAttrs; // Source formatting if (_.isString(formattedAttrs.source)) { formattedAttrs = _.omit(formattedAttrs, 'source'); formattedAttrs.source = this._diDashboardHelpers.getAnalysisByNodeId(changedAttrs.source); } // Widget style or auto style changes var thereIsWidgetStyleChange = _.find(formattedAttrs, function (value, key) { return _.contains(WIDGET_STYLE_PARAMS, key); }); if (!_.isUndefined(thereIsWidgetStyleChange)) { formattedAttrs = _.omit(formattedAttrs, WIDGET_STYLE_PARAMS); formattedAttrs.style = widgetDefinitionModel.toJSON().style; } return formattedAttrs; }, manageTimeSeriesForTorque: function (model) { function recreateWidget (currentTimeseries, newLayer, animated) { var persistName = currentTimeseries && currentTimeseries.get('title'); // This prevents a bug if user has a range selected and switches column newLayer.unset('customDuration'); this._createTimeseries(newLayer, animated, persistName); } // not a cartodb layer if (!model.styleModel) return; var animatedChanged = model.styleModel.changedAttributes().animated; var previousAnimated = model.styleModel.previous('animated'); var attributeChanged; if (animatedChanged && previousAnimated && animatedChanged.attribute !== previousAnimated.attribute) attributeChanged = animatedChanged.attribute; var typeChanged = model.styleModel.changedAttributes().type; var animatedAttribute = model.styleModel.get('animated') && model.styleModel.get('animated').attribute; var previousType = model.styleModel.previous('type'); if (!typeChanged && !attributeChanged) return; var type = model.styleModel.get('type'); var widgetModel = this._diDashboardHelpers.getWidgets().filter(function (model) { return model.get('type') === 'time-series'; })[0]; var currentTimeseries = this._getTimeseriesDefinition(); var newLayer = this._diDashboardHelpers.getLayer(model.id); if (type !== 'animation' && previousType === 'animation' && this._lastType !== type) { if (widgetModel) { this._removeTimeseries(); } this._lastType = type; this._lastTSAnimateChange = ''; } if (type === 'animation' && (this._lastTSAnimateChange !== attributeChanged || this._lastType !== 'animation')) { if (widgetModel) { this._removeTimeseries(); } if (newLayer.get('type') === 'torque' || model.get('type') === 'torque') { recreateWidget.call(this, currentTimeseries, newLayer, _.extend({ animated: true }, animatedChanged, { attribute: animatedAttribute })); } this._lastType = type; this._lastTSAnimateChange = attributeChanged; } }, _removeTimeseries: function () { this._widgetDefinitionsCollection.models.forEach(function (def) { if (def.get('type') === 'time-series') { def.set({avoidNotification: true}, {silent: true}); def.destroy(); } }); }, _getTimeseriesDefinition: function () { return this._widgetDefinitionsCollection.findWhere({type: 'time-series'}); }, _createTimeseries: function (newLayer, animatedChanged, persist) { function getColumnType (sourceId, columnName) { var node = this._analysisDefinitionNodesCollection.get(sourceId); return node && node.querySchemaModel.getColumnType(columnName); } this._removeTimeseries(); var attribute = animatedChanged && animatedChanged.attribute || ''; var animated = animatedChanged && animatedChanged.animated; var sourceId = newLayer.get('source'); var source = this._diDashboardHelpers.getAnalysisByNodeId(sourceId); if (attribute) { var baseAttrs = { type: 'time-series', layer_id: newLayer.get('id'), source: source, options: { column: attribute, title: persist || 'time_date__t', animated: animated }, style: { widget_style: WidgetDefinitionModel.getDefaultWidgetStyle('time-series') } }; var columnType = getColumnType.call(this, sourceId, attribute); if (columnType !== 'date') { baseAttrs.options.bins = 256; } this._widgetDefinitionsCollection.create(baseAttrs, { wait: true }); } } };