/** * Layer panel view added in the right menu * * - It needs at least layer, visualization and user models. * Globalerror to show connection or fetching errors. * * var layer_view = new cdb.admin.LayerPanelView({ * model: layer_model, * vis: vis_model, * user: user_model, * globalError: globalError_obj * }); */ cdb.admin.LayerPanelView = cdb.admin.RightMenu.extend({ _TEXTS: { error: { default: _t('Something went wrong, try again later') }, visible: _t('This layer is hidden, changes won\'t be shown \ until you make it visible'), dblclick: _t('Double click will allow you to rename it') }, MODULES: ['infowindow', 'legends'], className: "layer_panel", events: { 'dblclick span.name': '_editAlias', 'click .name_info a': '_goToLayer', 'click .info': '_switchTo', 'click a.visibility': '_switchVisibility', 'click a.remove': '_removeLayer', 'keyup input.alias': '_changeAlias', 'click input.alias': 'killEvent', 'click': 'setPanelStatus' }, initialize: function() { cdb.admin.RightMenu.prototype.initialize.call(this); _.bindAll(this, '_editAlias'); // Set internal vars this.table = this.model.table; this.sqlView = new cdb.admin.SQLViewData(); this.map = this.options.vis.map; this.globalError = this.options.globalError; this.user = this.options.user; this.vis = this.options.vis; // Set status view model this.view_model = new cdb.core.Model({ state:'idle' }) this.render(); // Set current panel view and data layer this.activeWorkView = 'table'; this.setDataLayer(this.model); // New added layers need to wait to get model id this.model.bind('change:id', function(m) { this.vis.save('active_layer_id', this.dataLayer.id); this.$el.attr('model-id', this.model.id); }, this); this.model.bind('change:type', this._setLayerType, this); // Show message when layer is not visible this.model.bind('change:visible', this.setVisibleMsg, this); this.model.bind('destroy', function() { this.trigger('destroy', this.dataLayer.cid); }, this); // When status change binding this.view_model.bind('change:state', this._onChangeStatus, this); this.add_related_model(this.view_model); // Bind when panel is closed cdb.god.bind("panel_action", this.setPanelStatus, this); this.add_related_model(cdb.god); this.$el.attr('model-id', this.model.id); this._setLayerType(); }, /* Layer events functions */ // Set active this tab _switchTo: function(e) { if (e) e.preventDefault(); var isActive = this.vis.get('active_layer_id') == this.dataLayer.id; // Preventing problem with double click // over span.name if (isActive && $(e.target).prop("tagName").toLowerCase() == "span" && $(e.target).prop("className") == "name") { // Preventing display the tooltip when user is // double clicking! :( var self = this; setTimeout(function() { if (!self._dblClick) self.$('.layer-info .info .name').tipsy("show"); delete self._dblClick; }, 150); return false; } this.trigger('switchTo', this); this.view_model.set('state', 'idle'); this.filters._addFilters(); return false; }, _goToLayer: function(ev) { if (ev) { ev.stopPropagation(); } }, // Change view model to show table // name alias input _editAlias: function(e) { e.preventDefault(); e.stopPropagation(); if (this.vis.isVisualization()) { this._dblClick = true; this.view_model.set('state', this.view_model.get('state') == 'idle' ? 'editing' : 'idle' ); } else { this.trigger('switchTo', this); } return false; }, // Change layer state when input is ready _changeAlias: function(e) { var value = $(e.target).val(); // If form is submitted, go out! if (e && e.keyCode == 13) { this.view_model.set({ state: 'idle' }); return false; } }, setPanelStatus: function() { this.view_model.set('state', 'idle'); }, _onChangeStatus: function() { var $el = this.$('.layer-info div.left'); if (this.view_model.get('state') == "idle") { var alias = $el.find('input').val(); $el.find('input').hide(); $el.find('span.name').show(); $el.find('.name_info').show(); if (alias != this.view_model.get('table_name_alias')) { // Set new changes in model if (alias == "" || alias == " ") { this.dataLayer.unset('table_name_alias') } else { this.dataLayer.set({ table_name_alias: alias }) } this.dataLayer.save(); this.setLayerName(this.dataLayer); } } else { $el.find('span.name').hide(); $el.find('.name_info').hide(); $el.find('input') .val(this.dataLayer.get('table_name_alias') || this.dataLayer.get('table_name').replace(/_/g,' ')) .show() .focus(); } }, _setLayerType: function() { this.$el.attr('layer-type', this.model.get('type').toLowerCase()); }, activated: function() { // remove layer-info this.deactivated(); this.$el.html(''); this.render(); // Set data layer this.setDataLayer(this.model); // Set previous active layer this.setActivePanelView(); this.panels.bind('tabEnabled', this.saveActivePanelView, this); }, deactivated: function() { this.clearSubViews(); this._removeButtons(); this.view_model.set('state', 'idle'); }, // Set visibility of the map layer _switchVisibility: function(e) { e.preventDefault(); if (!this.vis.isVisualization()) { cdb.log.info("You can't toggle the layer visibility in a table view"); return false; } // Hide infowindow if it is open this.model.infowindow && this.model.infowindow.set('visibility', false); this.model.save({ 'visible': !this.model.get('visible') }); }, // Remove this view and the map layer _removeLayer: function(e) { e.preventDefault(); // Check if the visualization is devired type and not table if (!this.vis.isVisualization()) { cdb.log.info("You can't remove a layer in a table view"); return false; } this.trigger('delete', this); }, /* Layer info functions (order, options, name, ...) */ _setLayerInfo: function(layer) { this.setLayerName(layer); this.setLayerOptions(layer); this.setLayerOrder(layer); this.setVisibility(layer); this.setVisibleMsg(layer); this.setView(layer); }, // Set view options setView: function(layer) { this.$el[ this.vis.isVisualization() ? 'addClass' : 'removeClass' ]('vis') }, setVisibleMsg: function() { var editors = ['sql_mod', 'cartocss_mod']; // Remove message this.$('.layer-views div.info.warning').remove() // Add message if it is necessary if (!this.model.get('visible')) { var $div = $('
') .addClass('info warning') .append('

' + this._TEXTS.visible + '

'); var isEditor = _.contains(editors, this.currentPanelView); $div[ isEditor ? 'addClass' : 'removeClass' ]('editor'); this.$('.layer-views').append($div); } }, // Layer name setLayerName: function(layer) { if (this.vis.isVisualization()) { // table name this.$('.layer-info .info .name') .text(layer.get('table_name_alias') || layer.get('table_name').replace(/_/g,' ')); // table name alias var layerUrl = layer.table && layer.table.viewUrl(); this.$('.layer-info .info .name_info') .html('view of ') .append($('').attr('href', layerUrl).text(layer.get('table_name'))); // table synced? if (this.table.isSync()) { this.$('.layer-info .info .name') .append($('').addClass('synced')); } else { this.$('.layer-info i.synced').remove(); } // Set tipsy bind this._setLayerTooltip(); } else { // Unset tipsy bind this._unsetLayerTooltip(); this.$('.layer-info .info .name').text(layer.get('table_name')); } }, _setLayerTooltip: function() { var self = this; this.$('.layer-info .info .name').tipsy({ trigger: 'manual', fade: true, gravity: 's', title: function() { return self._TEXTS.dblclick } }) .bind('mouseleave', function() { $(this).tipsy('hide'); }); }, _unsetLayerTooltip: function() { var $name = this.$('.layer-info .info .name'); // Remove tipsy if ($name.data('tipsy')) { $name.unbind('mouseenter mouseleave'); $name.data('tipsy').remove(); } }, // Layer options setLayerOptions: function(layer) { // Layer options buttons if (this.vis && !this.vis.isVisualization()) { this.$('.layer-info div.right').hide(); } else { this.$('.layer-info div.right').show(); } }, setVisibility: function(layer) { this._maybeHideVisibilitySwitch(); var isVisible = layer.get('visible'); this.$(".layer-info div.right a.switch") .toggleClass('enabled', !!isVisible) .toggleClass('disabled', !isVisible); }, _maybeHideVisibilitySwitch: function() { var sw = this.$(".layer-info div.right a.switch"); var close = this.$(".layer-info div.right a.remove"); var dataLayers = this.vis.map.layers.getDataLayers(); if (dataLayers.length === 1) { sw.hide(); close.hide(); } else { sw.css('display', 'inline-block'); close.css('display', 'inline-block'); } }, // Layer order setLayerOrder: function(layer) { var order = '1'; if(this.vis.isVisualization()) { order = layer.collection.indexOf(layer); } this.$('.layer-info .info .order').text(order); }, /* Set data of the layer (bindings, errors, modules, ...) */ setDataLayer: function(dataLayer) { var self = this; this.add_related_model(dataLayer); var enabledModulesInit = self.MODULES; if (!self.dataLayer) { self.dataLayer = dataLayer; this._initDataLayer(dataLayer); } // Set layer info this._setLayerInfo(dataLayer); /* SQL */ var sql = new cdb.admin.mod.SQL({ model: self.dataLayer, user: self.user, className: "sql_panel editor" }); /* Filters */ this.filters = new cdb.admin.mod.Filters({ table: self.table, sqlView: self.sqlView, dataLayer: self.dataLayer }); // load the scroll when the panel is open cdb.god.bind("end_narrow", function() { self.filters.loadScroll() }, this); /* Wizards */ var activeWizards = { polygon: "SimpleWizard", cluster: "ClusterWizard", intensity: "IntensityWizard", bubble: "BubbleWizard", choropleth: "ChoroplethWizard", color: "CategoryWizard", category: "CategoryWizard", density: "DensityWizard", torque: "TorqueWizard", torque_cat: "TorqueCategoryWizard", torque_heat: "TorqueHeatWizard" }; var wizards = new cdb.admin.mod.CartoCSSWizard({ user: this.user, model: this.dataLayer, table: this.table, map: this.map, className: "wizards_panel", wizards: activeWizards }).bind('modules', function(enabledModules) { enabledModulesInit = enabledModules; this.enableModules(enabledModules); }, this).bind('activeWizard', function(type) { // Close infowindow if it exists. this.dataLayer.infowindow && this.dataLayer.infowindow.set('visibility', false); }, this); /* Infowindow */ var infowindow = this.infowindow = new cdb.admin.mod.InfoWindow({ table: this.table, user: this.user, dataLayer: dataLayer }); infowindow.bind('tabChanged', this._onModuleTabChanged, this); /* CartoCSS editor */ var editorPanel = new cdb.admin.mod.CartoCSSEditor({ model: this.dataLayer, table: this.table, user: this.user, className: "csseditor_panel editor" }).bind('hasErrors', function() { self.addClassToButton('cartocss_mod', 'has_errors'); }).bind('clearError', function() { self.removeClassFromButton('cartocss_mod', 'has_errors'); }); /* Legends */ var legends = new cdb.admin.mod.LegendEditor({ model: dataLayer.legend, dataLayer: dataLayer, className: "legends_panel", availableLegends: [ { name: "none", enabled: true }, { name: "custom", enabled: true }, { name: "color", enabled: false }, { name: "category", enabled: false }, { name: "bubble", enabled: false }, { name: "choropleth", enabled: false }, { name: "intensity", enabled: false }, { name: "density", enabled: false }, { name: "torque_cat", enabled: false } ], }).bind('modules', function(enabledModules) { enabledModulesInit = enabledModules; self.enableModules(enabledModules); }).bind('tabChanged', this._onModuleTabChanged, this); if (!this.user.featureEnabled('disabled_ui_sql')) { self.addModule(sql.render(), ['table', 'tableLite', 'map', 'mapLite']); } self.addModule(wizards.render(), ['map', 'mapLite']); self.addModule(infowindow.render(), ['map', 'mapLite']); if (!this.user.featureEnabled('disabled_ui_cartocss')) { self.addModule(editorPanel.render(), ['map', 'mapLite']); } self.addModule(legends.render(), ['map', 'mapLite']); self.addModule(this.filters.render(), ['table', 'tableLite', 'map', 'mapLite']); /* Lateral menu modules */ var mergeTables = self.addToolButton("merge_datasets", 'table'); var addRow = self.addToolButton('add_row', 'table'); var addColumn = self.addToolButton('add_column', 'table'); var addGeom = self.addToolButton('add_feature', 'map'); addRow.bind('click', this._addRow, this); addColumn.bind('click', this.trigger.bind(this, 'addColumn', this)); mergeTables.bind('click', this._mergeTables, this); addGeom.bind('click', this._addFeature, this); this.enableModules(enabledModulesInit); this._bindDataLayer(); }, // set initial parameters to the layer _initDataLayer: function(layer) { layer.bind('change:table_name', this.setLayerName, this); layer.bind('change:order', this.setLayerOrder, this); layer.bind('change:visible', this.setVisibility, this); layer.collection.bind('add remove', this._maybeHideVisibilitySwitch, this); this.add_related_model(layer.collection); layer.set({ stat_tag: this.vis.get('id'), user_name: this.user.get("username"), maps_api_template: cdb.config.get('maps_api_template'), cartodb_logo: false, no_cdn: false, force_cors: true // use CORS to control error management in a better way }); // set api key var e = layer.get('extra_params') || {}; e.api_key = e.map_key = this.user.get('api_key'); layer.set('extra_params', e); layer.invalidate(); }, // bind related ui changed to datalayer _bindDataLayer: function() { var self = this; this.dataLayer.bindSQLView(this.sqlView); this.dataLayer .bind('parseError', function() { if(self.activeWorkView === 'map') { self.globalError.showError('There is a problem with the map tiles. Please, check your CartoCSS style.', 'error', 0, 'tiles'); } }, this) .bind('sqlNoMercator', function() { if(self.activeWorkView === 'map') { // don't show this error, the warning is shown in the sql bar //self.globalError.showError(_t('the_geom_webmercator column should be selected'), 'warn', 0, 'tiles'); } }, this) .bind('error', function(model, resp) { var aborted = resp && resp.statusText === 'abort'; if(self.activeWorkView === 'map' && !aborted) { self.globalError.showError('There is a problem with your connection', 'error', 0, 'tiles'); } }, this) .bind('tileOk', function() { self.globalError.hide('tiles'); }, this); this.table.bind('columnRename columnDelete columnAdded geolocated', function() { self.dataLayer.invalidate(); }, this); this.table.bind('change:geometry_types', function() { if(this.table.get('geometry_types').length) { this._enableGeometryRelatedWizards(); } else { this._disableGeometryRelatedWizards(); } }, this); // Need to check buttons when permission changes this.table.bind('change:permission', this._checkButtons, this); this.table.bind('change:readOnly', this._checkButtons, this); this.table.bind('change:synchronization', this._checkButtons, this); this.table.bind('change:isSync', this._checkButtons, this); this.vis.bind('change:type', function() { this.setLayerOptions() this.setLayerName(this.model) }, this); this.model.bind('applySQLView errorSQLView clearSQLView', this._checkButtons, this); // this.model.bind('clearSQLView', this._onResetSQL, this); // this.model.bind('applySQLView', this._onApplySQL, this); // this.model.bind('errorSQLView', this._onErrorSQL, this); this.model.unbind('applyFilter', this._applyFilter, this); this.model.bind('applyFilter', this._applyFilter, this); // Add related models to be cleaned when view is destroyed this.add_related_model(this.vis); this.add_related_model(this.table); this._checkButtons(); }, enableModules: function(enabledModules) { var self = this; _(self.MODULES).each(function(m) { if (m === "infowindow" && !self.model.wizard_properties.supportsInteractivity()) { self.disableModule("infowindow_mod"); } else { if (_.contains(enabledModules, m)) { self.enableModule(m + "_mod"); } else { self.disableModule(m + "_mod"); } } }); }, SQL_WIZARDS: ['cartocss_mod', 'wizards_mod', 'infowindow_mod', 'legends_mod'], _disableGeometryRelatedWizards: function() { var self = this; _(this.SQL_WIZARDS).each(function(m) { self.disableModule(m); }); }, _enableGeometryRelatedWizards: function() { var self = this; _(this.SQL_WIZARDS).each(function(m) { if (m == 'infowindow_mod' && !self.model.wizard_properties.supportsInteractivity()) { self.disableModule(m); } else { self.enableModule(m); } }); }, _addRow: function() { this.table.data().addRow({ at: 0}); this.trigger('createRow'); cdb.god.trigger("closeDialogs"); }, _mergeTables: function() { var user = this.user; var view = new cdb.editor.MergeDatasetsView({ table: this.table, user: user }); view.appendToBody(); cdb.god.trigger("closeDialogs"); }, _addFeature: function(mod) { if (this.map.get('provider') === 'leaflet') { this.map.clamp(); } if (this.table.isGeoreferenced()) { this._addGeometry(); } else { this._showScratchDialog(); } }, _showScratchDialog: function() { var view = new cdb.editor.ScratchView({ clean_on_hide: true, enter_to_confirm: true, table: this.table, skipDisabled: true }); view.bind("newGeometry", this._addGeometry, this); view.appendToBody(); }, _addGeometry: function(type) { // row is saved by geometry editor if it is needed type = type || this.table.geomColumnTypes()[0]; this.dataLayer.trigger("startEdition", type); }, /* Module functions */ // When a tab is activated within a sub-module. // It could be the indowindow view, filters view, etc. _onModuleTabChanged: function(action) { this.trigger('tabChanged', action); }, // check buttons if they should be enabled or not _checkButtons: function() { var self = this; var gt = this.table.get('geometry_types'); // Changes over the SQL button var sql_button_changes = { applied: 'remove', has_errors: 'remove' }; // *Table with read permissions* // if (this.table.isReadOnly()) { // Data layer has a query applied? if (this.table.isInSQLView()) { if(this.model.getCurrentState() === 'error') { sql_button_changes = { applied: 'add', has_errors: 'add' } } else { sql_button_changes.applied = 'add'; } } // Check if there is any geometry if (gt && gt.length === 0) { this._disableGeometryRelatedWizards(); } else { this._enableGeometryRelatedWizards(); } this._readOnlyTableButtons(); } else { // *Table with write permissions* // // Check if there is any geometry if(gt && gt.length === 0) { this._disableGeometryRelatedWizards(); } else { this._enableGeometryRelatedWizards(); } // Enable writable buttons this._writableTableButtons(); } // Set title changes (as in name, sync info,...) this.setLayerName(this.dataLayer); // Set sql button changes _.each(sql_button_changes, function(value, key) { self[ value === "remove" ? 'removeClassFromButton' : 'addClassToButton' ]('sql_mod', key); }); }, _removeButtons: function() { this.buttons = []; this.panels.unbind('tabEnabled', this.saveActivePanelView, this); this.panels.clean(); this.tabs.clean(); }, // Enable the correct buttons depending on // if the layer is in query mode or not setActiveWorkView: function(workView) { this.activeWorkView = workView; this._checkButtons(); this.setActivePanelView(true); }, saveActivePanelView: function(name) { this.currentPanelView = name; this.setVisibleMsg(); }, setActivePanelView: function(work_view) { if (work_view || !this.currentPanelView) { if (this.activeWorkView === 'map') { var gt = this.table.get('geometry_types'); if(this.model.getCurrentState() !== 'error' && (gt && gt.length > 0)) { this.active('wizards_mod'); } } else { this.active('sql_mod'); } } else { this.active(this.currentPanelView); } }, _readOnlyTableButtons: function() { if(this.activeWorkView === 'map') { this.showTools('mapLite', true); } else { this.showTools('tableLite', true); } }, _writableTableButtons: function() { if(this.activeWorkView === 'map') { this.showTools('map', true); } else { this.showTools('table', true); } }, _applyFilter: function(column_name) { var col = { column: column_name }; var exists = this.filters.filters.find(function(a) { return a.get("column") == col.column }); if (!exists) this.filters.filters.add(col); }, /* View visibility functions */ hide: function() { this.$('.layer-sidebar').hide(); this.$('.layer-views').hide(); }, show: function() { this.$('.layer-sidebar').show(); this.$('.layer-views').show(); }, showModule: function(modName, modTab) { // Set tab in the module if (modTab && this[modName]) this[modName].setActiveTab(modTab); // Show module this.trigger('show', modName + "_mod", this); }, clean: function() { this._unsetLayerTooltip(); this._removeButtons(); this.elder('clean'); } });