const $ = require('jquery'); const CoreView = require('backbone/core-view'); const UserModel = require('dashboard/data/user-model'); const AuthenticatedUserModel = require('dashboard/data/authenticated-user-model'); const SQLViewData = require('dashboard/data/table/sqlviewdata-model'); const PublicCartoTableMetadata = require('./public-carto-table-metadata'); const PublicHeader = require('./public-header/public-header'); const PublicTableTab = require('dashboard/views/public-dataset/table-tab/public-table-tab'); const TabPane = require('dashboard/components/tabpane/tabpane'); const MapView = require('dashboard/views/public-dataset/map-view/map-view'); const Tabs = require('dashboard/components/tabs/tabs'); const checkAndBuildOpts = require('builder/helpers/required-opts'); const ModalsServiceModel = require('builder/components/modals/modals-service-model'); const ViewFactory = require('builder/components/view-factory'); const templateApiDialog = require('dashboard/views/public-dataset/dialogs/api-call.tpl'); const PublicExportView = require('dashboard/views/public-dataset/dialogs/export/public-export-view'); const REQUIRED_OPTS = [ 'configModel' ]; /** * Table public view * */ module.exports = CoreView.extend({ events: { 'click .js-Navmenu-link--download': '_exportTable', 'click .js-Navmenu-link--api': '_apiCallTable' }, initialize: function (options) { checkAndBuildOpts(options, REQUIRED_OPTS, this); this._initModels(); this._initViews(); this._initBinds(); }, _initModels: function () { this._modals = new ModalsServiceModel(); // Table this.table = new PublicCartoTableMetadata({ id: this.options.table_name, name: this.options.table_name, description: this.options.vizjson.description || '' }, { configModel: this._configModel }); this.table.set({ user_name: this.options.user_name, vizjson: this.options.vizjson, schema: this.options.schema }); this.columns = this.table.data(); this.sqlView = new SQLViewData(null, { configModel: this._configModel }); this.sqlView.syncMethod = 'read'; var query = this.query = this.table.data().getSQL(); this.table.useSQLView(this.sqlView); this.sqlView.options.set('rows_per_page', 20, { silent: true }); this._fetchData(query); // User this.user = new UserModel({ username: this.options.user_name }); // Authenticated user this.authenticated_user = new AuthenticatedUserModel(); }, _initViews: function () { // Public header if (this.$('.cartodb-public-header').length > 0) { var header = new PublicHeader({ el: this.$('.cartodb-public-header'), model: this.authenticated_user, vis: this.table, current_view: this._getCurrentView(), owner_username: this.options.owner_username, isMobileDevice: this.options.isMobileDevice, isCartoDBHosted: this._configModel.get('cartodb_com_hosted') }); this.addView(header); // Fetch authenticated user model this.authenticated_user.fetch(); } // Navigation - This is what switches the view on mobile // TODO: this is insanely complex for just two buttons // this.header = new cdb.open.PublicHeader({ // el: this.$('.navigation'), // model: this.table, // user: this.user, // belong_organization: belong_organization, // config: this.options.config // }); // this.addView(this.header); // Tabpanes this.workViewTable = new TabPane({ el: this.$('.pane_table') }); this.addView(this.workViewTable); this.workViewMap = new TabPane({ el: this.$('.pane_map') }); this.addView(this.workViewMap); // Public app tabs this.tabs = new Tabs({ el: this.$('.navigation ul'), slash: true }); this.addView(this.tabs); // Help tooltip - I can't find any span with help class, I think this is unnecessary // var tooltip = new cdb.common.TipsyTooltip({ // el: this.$('span.help'), // gravity: $.fn.tipsy.autoBounds(250, 's') // }); // this.addView(tooltip); // Table tab this.tableTab = new PublicTableTab({ configModel: this._configModel, model: this.table, vizjson: this.options.vizjson, user_name: this.options.user_name }); // Map tab this.mapTab = new MapView({ username: this.options.user_name, serverUrl: this._configModel.get('maps_api_template').replace('{user}', this.options.user_name), sqlUrl: this._configModel.getSqlApiUrl(), dataset: this.table.get('name'), vizjson: this.table.get('vizjson'), geometry: this.table.get('geometry_types') }); this.listenTo(this.mapTab, 'mapBoundsChanged', function (options) { const { bounds, center, zoom } = options; this.model.set('map', { bounds: [ bounds.getNorthEast().lng, bounds.getNorthEast().lat, bounds.getSouthWest().lng, bounds.getSouthWest().lat ], center, zoom }); }); this.listenTo(this.mapTab, 'boundsChanged', function (options) { this.model.set('bounds', options.bounds); }); this.workViewTable.addTab('table', this.tableTab.render()); this.workViewMap.addTab('map', this.mapTab.render()); this.workViewTable.active('table'); this.workViewMap.active('map'); this.mapTab.enableMap(); $('.pane_table').addClass('is-active'); }, _updateTable: function () { var sql = (this.model.get('bounds') && this.model.get('map')) ? (this.query + ' WHERE the_geom && ST_MakeEnvelope(' + this.model.get('map')['bounds'][0] + ', ' + this.model.get('map')['bounds'][1] + ', ' + this.model.get('map')['bounds'][2] + ', ' + this.model.get('map')['bounds'][3] + ', 4326)') : this.query; this._fetchData(sql); }, _fetchData: function (sql) { if (sql) { this.sqlView.setSQL(sql); } this.sqlView.fetch({ success: () => { this.$('.js-spinner').remove(); this.tableTab.deactivated(); this.tableTab.activated(); } }); }, _exportTable: function (e) { e.preventDefault(); // If a sql is applied but it is not valid, don't let the user export it if (!this.sqlView.getSQL()) return false; this._modals.create((modalModel) => new PublicExportView({ modalModel, configModel: this._configModel, model: this.table, user_data: this.user.toJSON(), bounds: this.sqlView.getSQL() !== this.query }) ); }, _apiCallTable: function (e) { e.preventDefault(); // If a sql is applied but it is not valid, don't show the dialog if (!this.sqlView.getSQL()) return false; this._modals.create(() => ViewFactory.createByTemplate( templateApiDialog, { url: this._configModel.getSqlApiUrl(), sql: this.sqlView.getSQL(), schema: this.table.attributes.original_schema.slice(0, 5), rows: this.table.dataModel.models } ) ); }, _initBinds: function () { this.listenTo(this.model, 'change:bounds', function (model) { this._updateTable(); }); this.listenTo(this.model, 'change:map', function (model) { if (model.get('bounds')) { this._updateTable(); } }); this.authenticated_user.bind('change', this._onUserLogged, this); this.add_related_model(this.authenticated_user); this.listenToOnce(this.table, 'change:geometry_types', function (model) { this.mapTab.setGeometry(model.get('geometry_types')); }); }, // Get type of current view // - It could be, dashboard, table or visualization _getCurrentView: function () { var pathname = location.pathname; if (pathname.indexOf('/tables/') !== -1) { return 'table'; } if (pathname.indexOf('/viz/') !== -1) { return 'visualization'; } // Other case -> dashboard (datasets, visualizations,...) return 'dashboard'; }, keyUp: function (e) {}, _onUserLogged: function () { // Check if edit button should be visible if (this.options.owner_username === this.authenticated_user.get('username')) { this.$('.extra_options .edit').css('display', 'inline-block'); this.$('.extra_options .oneclick').css('display', 'none'); } }, invalidateMap: function () { this.mapTab.invalidateMap(); }, showTable: function () { $('.pane_table').addClass('is-active'); $('.pane_map').removeClass('is-active'); this.tabs.activate('table'); }, showMap: function () { $('.pane_table').removeClass('is-active'); $('.pane_map').addClass('is-active'); this.tabs.activate('map'); } });