cartodb-4.42/lib/assets/javascripts/builder/editor/layers/layer-content-view.js
2024-04-06 05:25:13 +00:00

656 lines
23 KiB
JavaScript
Executable File

var _ = require('underscore');
var $ = require('jquery');
var Backbone = require('backbone');
var CoreView = require('backbone/core-view');
var createTextLabelsTabPaneRouted = require('builder/components/tab-pane/create-text-labels-tab-pane-routed');
var TabPaneTemplate = require('./layer-tab-pane.tpl');
var LayerHeaderView = require('./layer-header-view');
var TablesCollection = require('builder/data/tables-collection');
var AnalysisSourceOptionsModel = require('./layer-content-views/analyses/analysis-source-options-model');
var AnalysisFormsCollection = require('./layer-content-views/analyses/analysis-forms-collection');
var TableManager = require('builder/components/table/table-manager');
var changeViewButtons = require('./change-view-buttons.tpl');
var editOverlay = require('./edit-overlay.tpl');
var DataView = require('./layer-content-views/data/data-view');
var LayerContentAnalysesView = require('./layer-content-views/analyses/analyses-view');
var StyleView = require('builder/editor/style/style-view');
var InfowindowsView = require('./layer-content-views/infowindow/infowindows-view');
var LegendsView = require('./layer-content-views/legend/legends-view');
var FeatureDefinitionModel = require('builder/data/feature-definition-model');
var AnalysesService = require('./layer-content-views/analyses/analyses-service');
var checkAndBuildOpts = require('builder/helpers/required-opts');
var TipsyTooltipView = require('builder/components/tipsy-tooltip-view');
var Toggler = require('builder/components/toggler/toggler-view');
var Router = require('builder/routes/router');
var fetchAllQueryObjects = require('builder/helpers/fetch-all-query-objects');
var LayerContentModel = require('builder/data/layer-content-model');
var TABS = {
data: 'data',
analyses: 'analyses',
style: 'style',
popups: 'popups',
legends: 'legends'
};
var DISABLED_TABS = [
TABS.style,
TABS.popups,
TABS.legends
];
var ROUTER_ADD_GEOMETRY = {
point: 'addPoint',
line: 'addLine',
polygon: 'addPolygon'
};
var CONTEXTS = LayerContentModel.CONTEXTS;
var REQUIRED_OPTS = [
'userActions',
'analysisDefinitionNodesCollection',
'layerDefinitionsCollection',
'layerDefinitionModel',
'widgetDefinitionsCollection',
'legendDefinitionsCollection',
'stackLayoutModel',
'modals',
'onboardings',
'configModel',
'editorModel',
'userModel',
'mapDefinitionModel',
'mapModeModel',
'visDefinitionModel',
'stateDefinitionModel',
'onboardingNotification'
];
module.exports = CoreView.extend({
module: 'editor:layers:layer-content-view',
events: {
'click .js-back': '_onClickBack',
'click .js-fix-sql': '_onClickFixSQL'
},
options: {
selectedTabItem: null,
analysisPayload: null // id or a new analysis node attrs (may not be complete)
},
initialize: function (opts) {
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
this.model = new LayerContentModel({}, {
querySchemaModel: this._getQuerySchemaModel(),
queryGeometryModel: this._getQueryGeometryModel(),
queryRowsCollection: this._getQueryRowsCollection()
});
this._initViewState();
this._onClickNewGeometry = this._onClickNewGeometry.bind(this);
this._togglerModel = new Backbone.Model({
labels: [_t('editor.context-switcher.map'), _t('editor.context-switcher.data')],
active: this._isContextTable()
});
AnalysesService.setLayerId(this._layerDefinitionModel.get('id'));
this._updateViewState();
this._initBinds();
},
render: function () {
this._unbindEvents();
this.clearSubViews();
this._destroyTable();
this.$el.empty();
this._renderHeaderView();
this._renderLayerTabPaneView();
if (this._isContextTable()) {
this._initTable();
}
this._renderContextButtons();
return this;
},
_initViewState: function () {
this._viewState = new Backbone.Model({
isLayerEmpty: false,
canBeGeoreferenced: false,
isLayerDone: false
});
this._setViewState();
},
_initBinds: function () {
var querySchemaModel = this._getQuerySchemaModel();
var queryRowsCollection = this._getQueryRowsCollection();
var queryGeometryModel = this._getQueryGeometryModel();
var layerDefinitionModel = this._layerDefinitionModel;
this.listenTo(this.model, 'change:context', this._renderContextButtons);
this.listenTo(this.model, 'change:state', this._updateViewState);
this.listenTo(this._getAnalysisDefinitionNodeModel(), 'change:error', this.render);
this.listenTo(layerDefinitionModel, 'remove', this._onClickBack);
this.listenTo(layerDefinitionModel, 'change:source', this._updateViewState);
this.listenTo(layerDefinitionModel, 'change:visible', this._renderContextButtons);
this.listenTo(this._viewState, 'change', this.render);
this.listenTo(this._editorModel, 'change:edition', this._changeStyle);
this.listenTo(this._togglerModel, 'change:active', this._onTogglerChanged);
this.listenTo(this._widgetDefinitionsCollection, 'add remove', this._renderContextButtons);
this.listenTo(this._onboardings, 'style', function () {
this._layerTabPaneView.collection.select('name', 'style');
});
this.listenTo(querySchemaModel, 'change:query', function () {
this._renderContextButtons();
if (this._editorModel.isEditing()) {
this.listenToOnce(querySchemaModel, 'sync', function (e) {
queryRowsCollection.fetch({
success: function () {
Router.goToDataTab(layerDefinitionModel.get('id'));
}
});
});
}
});
this.listenToOnce(queryGeometryModel, 'change:simple_geom', function (model, simpleGeom) {
this._renderContextButtons();
}, this);
this.listenTo(Router.getRouteModel(), 'change:currentRoute', this._handleRoute);
},
_renderLayerTabPaneView: function () {
var self = this;
var analysisPayload = this.options.analysisPayload;
var layerId = this._layerDefinitionModel.get('id');
var canBeGeoreferenced = this._viewState.get('canBeGeoreferenced');
var isDone = this._viewState.get('isLayerDone');
var isEmpty = this._viewState.get('isLayerEmpty') && isDone;
var selectedTabItem = this.options.selectedTabItem;
var analysisFailed = this._getAnalysisDefinitionNodeModel().hasFailed();
var isDisabled = canBeGeoreferenced || isEmpty || analysisFailed;
if (isDisabled && (!selectedTabItem || _.contains(DISABLED_TABS, selectedTabItem))) {
if ((!isEmpty && canBeGeoreferenced) || analysisFailed) {
selectedTabItem = TABS.analyses;
} else {
selectedTabItem = TABS.data;
}
}
var tabPaneTabs = [{
label: _t('editor.layers.menu-tab-pane-labels.data'),
name: TABS.data,
selected: selectedTabItem === TABS.data,
onClick: function () { Router.goToDataTab(layerId); },
disabled: false,
createContentView: function () {
return new DataView({
className: 'Editor-content',
widgetDefinitionsCollection: self._widgetDefinitionsCollection,
layerDefinitionModel: self._layerDefinitionModel,
stackLayoutModel: self._stackLayoutModel,
userActions: self._userActions,
configModel: self._configModel,
editorModel: self._editorModel,
userModel: self._userModel,
onboardings: self._onboardings,
onboardingNotification: self._onboardingNotification,
layerContentModel: self.model
});
}
}, {
label: _t('editor.layers.menu-tab-pane-labels.analyses'),
name: TABS.analyses,
selected: selectedTabItem === TABS.analyses,
disabled: isEmpty && !analysisFailed,
onClick: function () { Router.goToAnalysisTab(layerId); },
createContentView: function () {
var layerDefinitionModel = self._layerDefinitionModel;
var analysisSourceOptionsModel = new AnalysisSourceOptionsModel(null, {
analysisDefinitionNodesCollection: self._analysisDefinitionNodesCollection,
layerDefinitionsCollection: self._layerDefinitionsCollection,
tablesCollection: new TablesCollection(null, {
configModel: self._configModel
})
});
analysisSourceOptionsModel.fetch();
var analysisFormsCollection = new AnalysisFormsCollection(null, {
userActions: self._userActions,
configModel: self._configModel,
layerDefinitionModel: self._layerDefinitionModel,
analysisSourceOptionsModel: analysisSourceOptionsModel
});
analysisFormsCollection.resetByLayerDefinition();
if (!self._layerDefinitionModel.hasAnalyses() && canBeGeoreferenced && !analysisPayload) {
analysisPayload = AnalysesService.generateGeoreferenceAnalysis();
}
// e.g. when selected from layers view
var selectedNodeId;
if (_.isString(analysisPayload) && analysisFormsCollection.get(analysisPayload)) {
selectedNodeId = analysisPayload;
} else if (_.isObject(analysisPayload)) {
// payload passed after continue when an option was selected in add-analysis-view
selectedNodeId = analysisPayload.id;
// Detect when an analysis finishes so we can redirect and / or re-enable the tabs
self.listenTo(self._analysisDefinitionNodesCollection, 'add', function (model) {
var nodeSource = model.getPrimarySource();
// This logic only applies to a geocoding analysis that's the first, on a layer that lacks geocoding
if (nodeSource && nodeSource.get('type') === 'source' &&
model.get('type').indexOf('georeference') === 0 &&
canBeGeoreferenced) {
var callback = function (model) {
if (model.isDone() || model.hasFailed()) {
this.stopListening(model, 'change:status', callback);
}
if (model.isDone()) {
Router.goToStyleTab(layerId);
}
if (model.hasFailed()) {
self.render();
}
};
self.listenTo(model, 'change:status', callback);
}
});
analysisFormsCollection.addHead(analysisPayload);
} else {
var lastAnalysisNode = analysisFormsCollection.at(0);
if (lastAnalysisNode) {
selectedNodeId = lastAnalysisNode.get('id');
Router.goToAnalysisNode(layerDefinitionModel.get('id'), selectedNodeId, { trigger: false, replace: true });
} else {
Router.goToAnalysisTab(layerDefinitionModel.get('id'), { trigger: false, replace: true });
}
}
// remove payload once we have used it, to not have it being invoked again when switching tabs
selectedTabItem = null;
analysisPayload = null;
return new LayerContentAnalysesView({
className: 'Editor-content',
userActions: self._userActions,
analysisDefinitionNodesCollection: self._analysisDefinitionNodesCollection,
editorModel: self._editorModel,
userModel: self._userModel,
analysisFormsCollection: analysisFormsCollection,
configModel: self._configModel,
layerDefinitionModel: self._layerDefinitionModel,
stackLayoutModel: self._stackLayoutModel,
selectedNodeId: selectedNodeId,
onboardings: self._onboardings,
onboardingNotification: self._onboardingNotification,
layerContentModel: self.model
});
}
}, {
label: _t('editor.layers.menu-tab-pane-labels.style'),
name: TABS.style,
selected: selectedTabItem === TABS.style,
layerId: layerId,
onClick: function () { Router.goToStyleTab(layerId); },
disabled: isDisabled,
createContentView: function () {
// remove payload once we have used it, to not have it being invoked again when switching tabs
selectedTabItem = null;
var styleView = new StyleView({
className: 'Editor-content',
configModel: self._configModel,
userModel: self._userModel,
userActions: self._userActions,
analysisDefinitionsCollection: self.analysisDefinitionsCollection,
queryGeometryModel: self._getQueryGeometryModel(),
querySchemaModel: self._getQuerySchemaModel(),
queryRowsCollection: self._getQueryRowsCollection(),
layerDefinitionsCollection: self._layerDefinitionsCollection,
layerDefinitionModel: self._layerDefinitionModel,
editorModel: self._editorModel,
modals: self._modals,
onboardings: self._onboardings,
onboardingNotification: self._onboardingNotification,
layerContentModel: self.model
});
return styleView;
}
}, {
label: _t('editor.layers.menu-tab-pane-labels.popups'),
name: TABS.popups,
selected: selectedTabItem === TABS.popups,
onClick: function () { Router.goToPopupsTab(layerId); },
disabled: isDisabled,
createContentView: function () {
var nodeDefModel = self._layerDefinitionModel.getAnalysisDefinitionNodeModel();
return new InfowindowsView({
className: 'Editor-content',
userActions: self._userActions,
layerDefinitionModel: self._layerDefinitionModel,
querySchemaModel: nodeDefModel.querySchemaModel,
editorModel: self._editorModel,
layerContentModel: self.model
});
}
}, {
label: _t('editor.layers.menu-tab-pane-labels.legends'),
name: TABS.legends,
selected: selectedTabItem === TABS.legends,
onClick: function () { Router.goToLegendsTab(layerId); },
disabled: isDisabled,
createContentView: function () {
var nodeDefModel = self._layerDefinitionModel.getAnalysisDefinitionNodeModel();
return new LegendsView({
className: 'Editor-content',
mapDefinitionModel: self._mapDefinitionModel,
userActions: self._userActions,
layerDefinitionModel: self._layerDefinitionModel,
querySchemaModel: nodeDefModel.querySchemaModel,
queryRowsCollection: nodeDefModel.queryRowsCollection,
legendDefinitionsCollection: self._legendDefinitionsCollection,
editorModel: self._editorModel,
userModel: self._userModel,
configModel: self._configModel,
modals: self._modals,
layerContentModel: self.model
});
}
}];
var tabPaneOptions = {
tabPaneOptions: {
className: 'Tab-pane js-editorPanelContent',
template: TabPaneTemplate,
tabPaneItemOptions: {
tagName: 'li',
klassName: 'CDB-NavMenu-item'
}
},
tabPaneItemLabelOptions: {
tagName: 'button',
className: 'CDB-NavMenu-link u-upperCase'
}
};
this._layerTabPaneView = createTextLabelsTabPaneRouted(tabPaneTabs, tabPaneOptions);
this.$el.append(this._layerTabPaneView.render().el);
this.addView(this._layerTabPaneView);
this.listenTo(this._layerTabPaneView.collection, 'change:selected', this._quitEditing, this);
this._changeStyle();
},
_renderHeaderView: function () {
var analysisDefinitionNodeModel = this._getAnalysisDefinitionNodeModel();
var tableNodeModel = analysisDefinitionNodeModel.isSourceType() && analysisDefinitionNodeModel.getTableModel();
var layerHeaderView = new LayerHeaderView({
layerDefinitionsCollection: this._layerDefinitionsCollection,
layerDefinitionModel: this._layerDefinitionModel,
userActions: this._userActions,
configModel: this._configModel,
modals: this._modals,
tableNodeModel: tableNodeModel,
editorModel: this._editorModel,
stateDefinitionModel: this._stateDefinitionModel,
visDefinitionModel: this._visDefinitionModel,
userModel: this._userModel,
widgetDefinitionsCollection: this._widgetDefinitionsCollection
});
this.addView(layerHeaderView);
this.$el.append(layerHeaderView.render().$el);
},
_updateViewState: function () {
var setViewStateFn = this._setViewState.bind(this);
if (this.model.isDone()) {
setViewStateFn();
return false;
}
this._fetchAllQueryObjects({
queryRowsCollection: this._getQueryRowsCollection(),
queryGeometryModel: this._getQueryGeometryModel(),
querySchemaModel: this._getQuerySchemaModel()
}).then(function () {
setViewStateFn();
});
},
_fetchAllQueryObjects: fetchAllQueryObjects.bind(this),
_destroyTable: function () {
if (this._tableView) {
TableManager.destroy(this._tableView);
delete this._tableView;
}
},
_initTable: function () {
if (this._tableView) {
this._destroyTable();
}
this._tableView = TableManager.create({
analysisDefinitionNodeModel: this._getAnalysisDefinitionNodeModel(),
configModel: this._configModel,
modals: this._modals,
userModel: this._userModel
});
$('.js-editor .CDB-Dashboard-canvas').append(this._tableView.render().el);
},
_isContextTable: function () {
return this.model.get('context') === CONTEXTS.table;
},
_renderContextButtons: function () {
this._destroyContextButtons();
var analysisDefinitionNodeModel = this._getAnalysisDefinitionNodeModel();
var isReadOnly = analysisDefinitionNodeModel.isReadOnly();
var isVisible = this._layerDefinitionModel.get('visible');
var queryGeometryModel = this._getQueryGeometryModel();
$('.CDB-Dashboard-canvas .CDB-Map-canvas').append(
changeViewButtons({
context: this.model.get('context'),
isThereOtherWidgets: this._widgetDefinitionsCollection.isThereOtherWidgets(),
isThereTimeSeries: this._widgetDefinitionsCollection.isThereTimeSeries(),
isThereAnimatedTimeSeries: this._widgetDefinitionsCollection.isThereTimeSeries({ animated: true }),
queryGeometryModel: queryGeometryModel.get('simple_geom'),
isSourceType: analysisDefinitionNodeModel.isSourceType(),
isVisible: isVisible,
isReadOnly: isReadOnly
})
);
var togglerView = new Toggler({
model: this._togglerModel
});
$('.js-mapTableView').append(togglerView.render().el);
$('.js-newGeometry').on('click', this._onClickNewGeometry);
this._addContextButtonsTooltips(isReadOnly, isVisible);
},
_addContextButtonsTooltips: function (isReadOnly, isVisible) {
if (isReadOnly || !isVisible) {
var disabledButtonTooltip = new TipsyTooltipView({
el: $('.js-newGeometryView'),
title: function () {
return _t('editor.edit-feature.geometry-disabled');
}
});
this.addView(disabledButtonTooltip);
return;
}
var tooltips = $('.js-newGeometry').map(function () {
return new TipsyTooltipView({
el: $(this),
title: function () {
return $(this).data('tooltip');
}
});
});
_.forEach(tooltips, this.addView.bind(this));
},
_destroyContextButtons: function () {
$('.js-newGeometry').off('click', this._onClickNewGeometry);
$('.js-switchers').remove();
},
_onTogglerChanged: function () {
var context = this._togglerModel.get('active') ? CONTEXTS.table : CONTEXTS.map;
context === CONTEXTS.table
? this._initTable()
: this._destroyTable();
this.model.set('context', context);
},
_unbindEvents: function () {
if (this._layerTabPaneView && this._layerTabPaneView.collection) {
this._layerTabPaneView.collection.off('change:selected', this._quitEditing, this);
}
},
_onClickBack: function () {
this._editorModel.set({ edition: false });
Router.goToLayerList();
},
_onClickNewGeometry: function (ev) {
var $target = $(ev.target).closest('.js-newGeometry');
var featureType = $target.data('feature-type');
var addGeometry = ROUTER_ADD_GEOMETRY[featureType];
if ($target.closest('.js-newGeometryItem').hasClass('is-disabled')) return false;
if (Router[addGeometry]) {
Router[addGeometry](this._layerDefinitionModel.get('id'));
}
},
_handleRoute: function (routeModel) {
var currentRoute = routeModel.get('currentRoute');
var routeName = currentRoute[0];
var OVERLAY_FADE_MS = 200;
if (routeName.indexOf('add_feature_') === 0) {
var featureType = routeName.split('add_feature_')[1];
var editOverlayText = _t('editor.edit-feature.overlay-text', { featureType: _t('editor.edit-feature.features.' + featureType) });
$('.CDB-Dashboard-canvas .CDB-Map-canvas').append(editOverlay({
text: editOverlayText
}));
$('.js-editOverlay').fadeIn(OVERLAY_FADE_MS, function () {
$('.js-editOverlay').removeClass('is-hidden');
});
var feature = this._newFeatureDefinitionModel({ featureType: featureType });
this._mapModeModel.enterDrawingFeatureMode(feature);
this._stackLayoutModel.goToStep(2, feature.getLayerDefinition(), !feature.isNew());
}
},
_newFeatureDefinitionModel: function (opts) {
return new FeatureDefinitionModel({}, {
configModel: this._configModel,
layerDefinitionModel: this._layerDefinitionModel,
userModel: this._userModel,
featureType: opts.featureType
});
},
_onClickFixSQL: function () {
this._editorModel.set({ edition: true });
this._layerTabPaneView.getTabPane('data').set({ selected: true });
},
_changeStyle: function () {
this._layerTabPaneView && this._layerTabPaneView.changeStyleMenu(this._editorModel);
},
_quitEditing: function () {
if (this._layerTabPaneView.getSelectedTabPaneName() !== TABS.style &&
this._layerTabPaneView.getSelectedTabPaneName() !== TABS.popups &&
this._layerTabPaneView.getSelectedTabPaneName() !== TABS.legends) {
this._editorModel.set({ edition: false });
}
},
_getAnalysisDefinitionNodeModel: function () {
return this._layerDefinitionModel.getAnalysisDefinitionNodeModel();
},
_getQueryGeometryModel: function () {
return this._getAnalysisDefinitionNodeModel().queryGeometryModel;
},
_getQuerySchemaModel: function () {
return this._getAnalysisDefinitionNodeModel().querySchemaModel;
},
_getQueryRowsCollection: function () {
return this._getAnalysisDefinitionNodeModel().queryRowsCollection;
},
_setViewState: function () {
var self = this;
var emptyPromise = this._layerDefinitionModel.isEmptyAsync();
var geoReferencePromise = this._layerDefinitionModel.canBeGeoreferenced();
this._viewState.set('isLayerDone', self._layerDefinitionModel.isDone());
Promise.all([emptyPromise, geoReferencePromise])
.then(function (values) {
self._viewState.set({
isLayerEmpty: values[0],
canBeGeoreferenced: values[1],
isLayerDone: self._layerDefinitionModel.isDone()
});
});
},
clean: function () {
this._destroyContextButtons();
this._destroyTable();
CoreView.prototype.clean.apply(this);
}
});