Initial commit
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
<div class="u-flex u-justifyCenter">
|
||||
<div class="Modal-inner Modal-inner--grid u-flex u-justifyCenter">
|
||||
<div class="Modal-icon">
|
||||
<svg width="24px" height="25px" viewbox="521 436 24 25" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<path d="M524.5,440 L540.5,440 L540.5,460 L524.5,460 L524.5,440 Z M528.5,437 L536.5,437 L536.5,440 L528.5,440 L528.5,437 Z M522,440 L544,440 L522,440 Z M528.5,443.5 L528.5,455.5 L528.5,443.5 Z M532.5,443.5 L532.5,455.5 L532.5,443.5 Z M536.5,443.5 L536.5,455.5 L536.5,443.5 Z" id="Shape" stroke="#F19243" stroke-width="1" fill="none"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<h2 class=" CDB-Text CDB-Size-huge is-light u-bSpace--xl">
|
||||
<%- _t('components.table.rows.destroy.title', { cartodb_id: cartodb_id }) %>
|
||||
</h2>
|
||||
<p class="CDB-Text CDB-Size-large u-altTextColor"><%- _t('components.table.rows.destroy.desc') %></p>
|
||||
<ul class="Modal-listActions u-flex u-alignCenter">
|
||||
<li class="Modal-listActionsitem">
|
||||
<button class="CDB-Button CDB-Button--secondary CDB-Button--big js-cancel">
|
||||
<span class="CDB-Button-Text CDB-Text is-semibold CDB-Size-medium u-upperCase">
|
||||
<%- _t('components.table.rows.destroy.cancel') %>
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
<li class="Modal-listActionsitem">
|
||||
<button class="CDB-Button CDB-Button--primary CDB-Button--big js-confirm">
|
||||
<span class="CDB-Button-Text CDB-Text is-semibold CDB-Size-medium u-upperCase">
|
||||
<%- _t('components.table.rows.destroy.confirm') %>
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
34
lib/assets/javascripts/builder/components/table/body/table-body-cell.tpl
Executable file
34
lib/assets/javascripts/builder/components/table/body/table-body-cell.tpl
Executable file
@@ -0,0 +1,34 @@
|
||||
<td class="Table-cellItem" data-attribute="<%- columnName %>" title="<%- value %>" data-clipboard-text='<%- value %>'>
|
||||
<div class="
|
||||
Table-cell u-flex u-justifySpace
|
||||
<%- columnName === 'cartodb_id' || (type === 'geometry' && geometry !== 'point') ? 'Table-cell--short' : '' %>
|
||||
">
|
||||
<!--
|
||||
WARNING: .fs-hide class excludes this element, which might
|
||||
contain sensitive data, from FullStory recordings.
|
||||
Do not remove it unless we are no longer using FullStory!
|
||||
|
||||
More info:
|
||||
https://help.fullstory.com/technical-questions/exclude-elements
|
||||
-->
|
||||
<p class="
|
||||
fs-hide
|
||||
CDB-Text CDB-Size-medium
|
||||
u-ellipsis u-rSpace--xl
|
||||
<%- type === 'number' && columnName !== 'cartodb_id' ? 'is-number' : '' %>
|
||||
<%- value === null ? 'is-null' : '' %>
|
||||
<%- columnName === 'cartodb_id' ? 'is-cartodbId' : '' %>
|
||||
js-value
|
||||
">
|
||||
<%- value === null ? 'null' : formattedValue %>
|
||||
</p>
|
||||
|
||||
<% if (columnName !== 'cartodb_id') { %>
|
||||
<button class="CDB-Shape-threePoints is-blue is-small Table-cellItemOptions js-cellOptions">
|
||||
<div class="CDB-Shape-threePointsItem"></div>
|
||||
<div class="CDB-Shape-threePointsItem"></div>
|
||||
<div class="CDB-Shape-threePointsItem"></div>
|
||||
</button>
|
||||
<% } %>
|
||||
</div>
|
||||
</td>
|
||||
95
lib/assets/javascripts/builder/components/table/body/table-body-row-view.js
Executable file
95
lib/assets/javascripts/builder/components/table/body/table-body-row-view.js
Executable file
@@ -0,0 +1,95 @@
|
||||
var CoreView = require('backbone/core-view');
|
||||
var _ = require('underscore');
|
||||
var cellTemplate = require('./table-body-cell.tpl');
|
||||
var pointGeojsonParser = require('builder/helpers/point-geojson-parser');
|
||||
var checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
|
||||
var FORMATTED_TYPE_VALUES = {
|
||||
'geometry': pointGeojsonParser
|
||||
};
|
||||
|
||||
var REQUIRED_OPTS = [
|
||||
'columnsCollection',
|
||||
'tableViewModel',
|
||||
'canHideColumns'
|
||||
];
|
||||
|
||||
/*
|
||||
* Table body row view
|
||||
*/
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
className: 'Table-row',
|
||||
tagName: 'tr',
|
||||
|
||||
options: {
|
||||
simpleGeometry: ''
|
||||
},
|
||||
|
||||
initialize: function (opts) {
|
||||
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
|
||||
|
||||
this.el.setAttribute('data-model', this.model.cid);
|
||||
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.$el.empty();
|
||||
this._generateRowsHTML();
|
||||
return this;
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.model.bind('change', this.render, this);
|
||||
this.model.bind('destroy', this.remove, this);
|
||||
},
|
||||
|
||||
_generateRowsHTML: function () {
|
||||
var r = [];
|
||||
|
||||
for (var i = 0, l = this._columnsCollection.size(); i < l; i++) {
|
||||
var model = this._columnsCollection.at(i);
|
||||
var columnName = model.get('name');
|
||||
|
||||
if (!this._tableViewModel.isCustomQueryApplied() && columnName === 'the_geom_webmercator') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (this._canHideColumns && columnName === 'center' && model.isGeometryColumn()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
r[i] = this._generateCellHTML(columnName);
|
||||
}
|
||||
|
||||
this.el.innerHTML = r.join('');
|
||||
},
|
||||
|
||||
_generateCellHTML: function (columnName) {
|
||||
var simpleGeometry = this.options.simpleGeometry;
|
||||
var columnModel = _.first(this._columnsCollection.where({ name: columnName }));
|
||||
var columnType = columnModel.get('type');
|
||||
var value = this.model.get(columnName);
|
||||
var formattedValue = value;
|
||||
|
||||
if (columnType === 'geometry') {
|
||||
if (simpleGeometry === 'point') {
|
||||
formattedValue = FORMATTED_TYPE_VALUES[columnType](value);
|
||||
} else {
|
||||
formattedValue = simpleGeometry && (simpleGeometry.charAt(0).toUpperCase() + simpleGeometry.slice(1));
|
||||
}
|
||||
} else if (FORMATTED_TYPE_VALUES[columnType]) {
|
||||
formattedValue = FORMATTED_TYPE_VALUES[columnType](value);
|
||||
}
|
||||
|
||||
return cellTemplate({
|
||||
value: value,
|
||||
formattedValue: formattedValue,
|
||||
type: columnType,
|
||||
columnName: columnName,
|
||||
geometry: simpleGeometry
|
||||
});
|
||||
}
|
||||
});
|
||||
490
lib/assets/javascripts/builder/components/table/body/table-body-view.js
Executable file
490
lib/assets/javascripts/builder/components/table/body/table-body-view.js
Executable file
@@ -0,0 +1,490 @@
|
||||
var CoreView = require('backbone/core-view');
|
||||
var $ = require('jquery');
|
||||
var _ = require('underscore');
|
||||
var Clipboard = require('clipboard');
|
||||
var TableBodyRowView = require('./table-body-row-view');
|
||||
var ContextMenuView = require('builder/components/context-menu/context-menu-view');
|
||||
var CustomListCollection = require('builder/components/custom-list/custom-list-collection');
|
||||
var addTableRowOperation = require('builder/components/table/operations/table-add-row');
|
||||
var removeTableRowOperation = require('builder/components/table/operations/table-remove-row');
|
||||
var editCellOperation = require('builder/components/table/operations/table-edit-cell');
|
||||
var ConfirmationModalView = require('builder/components/modals/confirmation/modal-confirmation-view');
|
||||
var TablePaginatorView = require('builder/components/table/paginator/table-paginator-view');
|
||||
var tableBodyTemplate = require('./table-body.tpl');
|
||||
var renderLoading = require('builder/components/loading/render-loading');
|
||||
var ErrorView = require('builder/components/error/error-view');
|
||||
var tableNoRowsTemplate = require('./table-no-rows.tpl');
|
||||
var EditorsServiceModel = require('builder/components/table/editors/editors-service-model');
|
||||
var EditorsModel = require('builder/components/table/editors/types/editor-model');
|
||||
var errorParser = require('builder/helpers/error-parser');
|
||||
var magicPositioner = require('builder/helpers/magic-positioner');
|
||||
var checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
|
||||
var EDITORS_MAP = {
|
||||
'string': require('builder/components/table/editors/types/editor-string-view'),
|
||||
'number': require('builder/components/table/editors/types/editor-base-view'),
|
||||
'boolean': require('builder/components/table/editors/types/editor-boolean-view'),
|
||||
'date': require('builder/components/table/editors/types/editor-date-view'),
|
||||
'default': require('builder/components/table/editors/types/editor-string-view')
|
||||
};
|
||||
|
||||
var REQUIRED_OPTS = [
|
||||
'columnsCollection',
|
||||
'modals',
|
||||
'queryGeometryModel',
|
||||
'querySchemaModel',
|
||||
'rowsCollection',
|
||||
'canHideColumns',
|
||||
'tableViewModel'
|
||||
];
|
||||
|
||||
/*
|
||||
* Table body view
|
||||
*/
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
className: 'Table-body',
|
||||
tagName: 'div',
|
||||
|
||||
events: {
|
||||
'click': '_onClick',
|
||||
'dblclick': '_onDblClick'
|
||||
},
|
||||
|
||||
initialize: function (opts) {
|
||||
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
|
||||
|
||||
this._editors = new EditorsServiceModel();
|
||||
|
||||
this._closeEditor = this._closeEditor.bind(this);
|
||||
this._hideContextMenu = this._hideContextMenu.bind(this);
|
||||
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
this._destroyScrollBinding();
|
||||
this.$el.empty();
|
||||
|
||||
// Render results when we have the schema and goemetry is not being fetched
|
||||
if (this._querySchemaModel.isFetched() && !this._queryGeometryModel.isFetching() && !this._rowsCollection.isFetching()) {
|
||||
if (!this._rowsCollection.size()) {
|
||||
this._renderNoRows();
|
||||
} else {
|
||||
this.$el.html(tableBodyTemplate());
|
||||
this._rowsCollection.each(this._renderBodyRow, this);
|
||||
this._initPaginator();
|
||||
}
|
||||
} else {
|
||||
this._renderQueryState();
|
||||
}
|
||||
|
||||
this.$el.toggleClass('Table-body--relative', !!this._tableViewModel.get('relativePositionated'));
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this._queryGeometryModel.bind('change:status', this.render, this);
|
||||
this._querySchemaModel.bind('change:status', this.render, this);
|
||||
this._rowsCollection.bind('reset', _.debounce(this.render.bind(this), 20), this);
|
||||
this._rowsCollection.bind('add', function (model) {
|
||||
if (this._rowsCollection.size() === 1) {
|
||||
this.render();
|
||||
} else {
|
||||
this._renderBodyRow(model);
|
||||
}
|
||||
}, this);
|
||||
this._rowsCollection.bind('remove', this._onRemoveRow, this);
|
||||
this._rowsCollection.bind('fail', function (mdl, response) {
|
||||
if (!response || (response && response.statusText !== 'abort')) {
|
||||
this._renderError(errorParser(response));
|
||||
}
|
||||
}, this);
|
||||
this.add_related_model(this._queryGeometryModel);
|
||||
this.add_related_model(this._querySchemaModel);
|
||||
this.add_related_model(this._rowsCollection);
|
||||
},
|
||||
|
||||
_renderQueryState: function () {
|
||||
var querySchemaStatus = this._querySchemaModel.get('status');
|
||||
var nodeReady = this._querySchemaModel.get('ready');
|
||||
var geometryStatus = this._queryGeometryModel.get('status');
|
||||
var rowsCollectionStatus = this._rowsCollection.getStatusValue();
|
||||
|
||||
if (nodeReady) {
|
||||
if (querySchemaStatus === 'unavailable' && geometryStatus === 'unavailable' ||
|
||||
rowsCollectionStatus === 'unavailable') {
|
||||
this._renderError(this._querySchemaModel.get('query_errors'));
|
||||
} else {
|
||||
this._renderLoading();
|
||||
}
|
||||
} else {
|
||||
this._renderLoading();
|
||||
}
|
||||
},
|
||||
|
||||
_renderLoading: function () {
|
||||
this.$el.html(
|
||||
renderLoading({
|
||||
title: _t('components.table.rows.loading.title')
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
_renderError: function (desc) {
|
||||
var view = new ErrorView({
|
||||
title: _t('components.table.rows.error.title'),
|
||||
desc: desc || _t('components.table.rows.error.desc')
|
||||
});
|
||||
this.addView(view);
|
||||
this.$el.html(view.render().el);
|
||||
},
|
||||
|
||||
_renderNoRows: function () {
|
||||
this.$el.html(
|
||||
tableNoRowsTemplate({
|
||||
page: this._tableViewModel.get('page'),
|
||||
customQuery: this._tableViewModel.isCustomQueryApplied()
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
_initPaginator: function () {
|
||||
var paginatorView = new TablePaginatorView({
|
||||
rowsCollection: this._rowsCollection,
|
||||
tableViewModel: this._tableViewModel,
|
||||
scrollToBottom: this._scrollToBottom.bind(this)
|
||||
});
|
||||
|
||||
// Bug in Chrome with position:fixed :(, so we have to choose body as
|
||||
// parent
|
||||
var $el = $('body');
|
||||
// But if we have chosen relativePositionated, we should add close
|
||||
// to the table view
|
||||
if (this._tableViewModel.get('relativePositionated')) {
|
||||
$el = this.$el.closest('.Table').parent();
|
||||
}
|
||||
|
||||
$el.append(paginatorView.render().el);
|
||||
this.addView(paginatorView);
|
||||
},
|
||||
|
||||
_initScrollBinding: function () {
|
||||
$('.Table').scroll(this._hideContextMenu);
|
||||
this.$('.js-tbody').scroll(this._hideContextMenu);
|
||||
},
|
||||
|
||||
_destroyScrollBinding: function () {
|
||||
$('.Table').off('scroll', this._hideContextMenu);
|
||||
this.$('.js-tbody').off('scroll', this._hideContextMenu);
|
||||
},
|
||||
|
||||
_renderBodyRow: function (mdl) {
|
||||
var view = new TableBodyRowView({
|
||||
model: mdl,
|
||||
columnsCollection: this._columnsCollection,
|
||||
simpleGeometry: this._queryGeometryModel.get('simple_geom'),
|
||||
canHideColumns: this._canHideColumns,
|
||||
tableViewModel: this._tableViewModel
|
||||
});
|
||||
this.addView(view);
|
||||
this.$('.js-tbody').append(view.render().el);
|
||||
},
|
||||
|
||||
_onRemoveRow: function () {
|
||||
if (!this._rowsCollection.size()) {
|
||||
this._rowsCollection.resetFetch();
|
||||
this._queryGeometryModel.resetFetch();
|
||||
|
||||
var page = this._tableViewModel.get('page');
|
||||
if (page > 0) {
|
||||
this._tableViewModel.set('page', page - 1);
|
||||
} else {
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_hasContextMenu: function () {
|
||||
return this._menuView;
|
||||
},
|
||||
|
||||
_hideContextMenu: function () {
|
||||
this._unhighlightCell();
|
||||
this._destroyScrollBinding();
|
||||
this._menuView.collection.unbind(null, null, this);
|
||||
this.removeView(this._menuView);
|
||||
this._menuView.clean();
|
||||
delete this._menuView;
|
||||
},
|
||||
|
||||
_highlightCell: function ($tableCellItem, $tableRow) {
|
||||
$tableCellItem.addClass('is-highlighted');
|
||||
$tableRow.addClass('is-highlighted');
|
||||
},
|
||||
|
||||
_unhighlightCell: function () {
|
||||
this.$('.Table-cellItem.is-highlighted, .Table-row.is-highlighted').removeClass('is-highlighted');
|
||||
},
|
||||
|
||||
_showContextMenu: function (ev) {
|
||||
var self = this;
|
||||
var position = { x: ev.clientX, y: ev.clientY };
|
||||
var $tableRow = $(ev.target).closest('.Table-row');
|
||||
var $tableCellItem = $(ev.target).closest('.Table-cellItem');
|
||||
var modelCID = $tableRow.data('model');
|
||||
var attribute = $tableCellItem.data('attribute');
|
||||
var rowModel = self._rowsCollection.get({ cid: modelCID });
|
||||
var menuItems = [];
|
||||
|
||||
menuItems.push({
|
||||
label: _t('components.table.rows.options.copy'),
|
||||
val: 'copy',
|
||||
action: function () {
|
||||
self._copyValue($tableCellItem);
|
||||
}
|
||||
});
|
||||
|
||||
if (!this._tableViewModel.isDisabled()) {
|
||||
menuItems = [
|
||||
{
|
||||
label: _t('components.table.rows.options.edit'),
|
||||
val: 'edit',
|
||||
action: function () {
|
||||
self._editCell(rowModel, attribute);
|
||||
}
|
||||
}, {
|
||||
label: _t('components.table.rows.options.create'),
|
||||
val: 'create',
|
||||
action: function () {
|
||||
self._addRow();
|
||||
}
|
||||
}
|
||||
].concat(menuItems);
|
||||
|
||||
menuItems.push({
|
||||
label: _t('components.table.rows.options.delete'),
|
||||
val: 'delete',
|
||||
destructive: true,
|
||||
action: function () {
|
||||
self._removeRow(rowModel);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// No options?, don't open anything
|
||||
if (!menuItems.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var collection = new CustomListCollection(menuItems);
|
||||
|
||||
this._menuView = new ContextMenuView({
|
||||
className: 'Table-rowMenu ' + ContextMenuView.prototype.className,
|
||||
collection: collection,
|
||||
triggerElementID: modelCID,
|
||||
position: position
|
||||
});
|
||||
|
||||
this._menuView.$el.css(
|
||||
magicPositioner({
|
||||
parentView: $('body'),
|
||||
posX: position.x,
|
||||
posY: position.y
|
||||
})
|
||||
);
|
||||
|
||||
collection.bind('change:selected', function (menuItem) {
|
||||
var action = menuItem.get('action');
|
||||
action && action();
|
||||
}, this);
|
||||
|
||||
this._menuView.model.bind('change:visible', function (model, isContextMenuVisible) {
|
||||
if (this._hasContextMenu() && !isContextMenuVisible) {
|
||||
this._hideContextMenu();
|
||||
}
|
||||
}, this);
|
||||
|
||||
this._menuView.show();
|
||||
this.addView(this._menuView);
|
||||
|
||||
this._highlightCell($tableCellItem, $tableRow);
|
||||
this._initScrollBinding();
|
||||
},
|
||||
|
||||
_onClick: function (ev) {
|
||||
var isCellOptions = $(ev.target).hasClass('js-cellOptions');
|
||||
if (isCellOptions) {
|
||||
if (this._hasContextMenu()) {
|
||||
this._hideContextMenu();
|
||||
} else {
|
||||
this._showContextMenu(ev);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_onDblClick: function (ev) {
|
||||
var $tableCellItem = $(ev.target).closest('.Table-cellItem');
|
||||
var isCellOptions = $(ev.target).hasClass('js-cellOptions');
|
||||
|
||||
if ($tableCellItem && !isCellOptions) {
|
||||
var $tableRow = $tableCellItem.closest('.Table-row');
|
||||
var modelCID = $tableRow.data('model');
|
||||
var attribute = $tableCellItem.data('attribute');
|
||||
var rowModel = this._rowsCollection.get({ cid: modelCID });
|
||||
|
||||
if (!this._tableViewModel.isDisabled() && rowModel && attribute && attribute !== 'cartodb_id') {
|
||||
this._editCell(rowModel, attribute);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_copyValue: function ($el) {
|
||||
// Work-around for Clipboard \o/
|
||||
this._clipboard = new Clipboard($el.get(0));
|
||||
$el.click();
|
||||
this._clipboard.destroy();
|
||||
},
|
||||
|
||||
_addRow: function () {
|
||||
addTableRowOperation({
|
||||
rowsCollection: this._rowsCollection
|
||||
});
|
||||
},
|
||||
|
||||
_initEditorScrollBinding: function () {
|
||||
$('.Table').scroll(this._closeEditor);
|
||||
this.$('.js-tbody').scroll(this._closeEditor);
|
||||
},
|
||||
|
||||
_destroyEditorScrollBinding: function () {
|
||||
$('.Table').unbind('scroll', this._closeEditor);
|
||||
this.$('.js-tbody').unbind('scroll', this._closeEditor);
|
||||
},
|
||||
|
||||
_closeEditor: function () {
|
||||
this._unhighlightCell();
|
||||
this._destroyEditorScrollBinding();
|
||||
this._editors.unbind(null, null, this);
|
||||
this._editors.destroy();
|
||||
},
|
||||
|
||||
_saveValue: function (rowModel, attribute, newValue) {
|
||||
if (rowModel.get(attribute) !== newValue) {
|
||||
editCellOperation({
|
||||
rowModel: rowModel,
|
||||
attribute: attribute,
|
||||
newValue: newValue
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_doCellEdition: function (rowModel, attribute) {
|
||||
var $tableRow = this.$('[data-model="' + rowModel.cid + '"]');
|
||||
var $tableCell = $tableRow.find('[data-attribute="' + attribute + '"]');
|
||||
var $options = $tableCell.find('.js-cellOptions');
|
||||
var columnModel = _.first(this._columnsCollection.where({ name: attribute }));
|
||||
var type = columnModel.get('type');
|
||||
|
||||
this._highlightCell($tableCell, $tableRow);
|
||||
this._initEditorScrollBinding();
|
||||
|
||||
var model = new EditorsModel({
|
||||
type: type,
|
||||
value: rowModel.get(attribute)
|
||||
});
|
||||
|
||||
this._editors.bind('destroyedEditor', this._closeEditor, this);
|
||||
this._editors.bind('confirmedEditor', function () {
|
||||
if (model.isValid()) {
|
||||
this._saveValue(
|
||||
rowModel,
|
||||
attribute,
|
||||
model.get('value')
|
||||
);
|
||||
}
|
||||
}, this);
|
||||
|
||||
var position = $options.offset();
|
||||
|
||||
if ($tableCell.index() > 1) {
|
||||
position.right = window.innerWidth - position.left;
|
||||
delete position.left;
|
||||
}
|
||||
|
||||
if (this._rowsCollection.size() > 4 && ($tableRow.index() + 2) >= (this._rowsCollection.size() - 1)) {
|
||||
position.bottom = window.innerHeight - position.top;
|
||||
delete position.top;
|
||||
} else {
|
||||
position.top = position.top + 20;
|
||||
}
|
||||
|
||||
var View = EDITORS_MAP[type];
|
||||
|
||||
if (!View) {
|
||||
View = EDITORS_MAP['default'];
|
||||
}
|
||||
|
||||
this._editors.create(
|
||||
function (editorModel) {
|
||||
return new View({
|
||||
editorModel: editorModel,
|
||||
model: model
|
||||
});
|
||||
},
|
||||
position
|
||||
);
|
||||
},
|
||||
|
||||
_editCell: function (rowModel, attribute) {
|
||||
var callback = this._doCellEdition.bind(this, rowModel, attribute);
|
||||
rowModel.fetchRowIfGeomIsNotLoaded(callback);
|
||||
},
|
||||
|
||||
_removeRow: function (rowModel) {
|
||||
var self = this;
|
||||
|
||||
this._modals.create(
|
||||
function (modalModel) {
|
||||
return new ConfirmationModalView({
|
||||
modalModel: modalModel,
|
||||
template: require('./modals-templates/remove-table-row.tpl'),
|
||||
renderOpts: {
|
||||
cartodb_id: rowModel.get('cartodb_id')
|
||||
},
|
||||
loadingTitle: _t('components.table.rows.destroy.loading', {
|
||||
cartodb_id: rowModel.get('cartodb_id')
|
||||
}),
|
||||
runAction: function () {
|
||||
removeTableRowOperation({
|
||||
tableViewModel: self._tableViewModel,
|
||||
rowModel: rowModel,
|
||||
onSuccess: function () {
|
||||
modalModel.destroy();
|
||||
},
|
||||
onError: function (e) {
|
||||
modalModel.destroy();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
_scrollToBottom: function () {
|
||||
var tbodyHeight = this.$('.js-tbody').get(0).scrollHeight;
|
||||
this.$('.js-tbody').animate({
|
||||
scrollTop: tbodyHeight
|
||||
}, 'slow');
|
||||
},
|
||||
|
||||
clean: function () {
|
||||
this._destroyScrollBinding();
|
||||
CoreView.prototype.clean.apply(this);
|
||||
}
|
||||
|
||||
});
|
||||
3
lib/assets/javascripts/builder/components/table/body/table-body.tpl
Executable file
3
lib/assets/javascripts/builder/components/table/body/table-body.tpl
Executable file
@@ -0,0 +1,3 @@
|
||||
<table>
|
||||
<tbody class="js-tbody"></tbody>
|
||||
</table>
|
||||
21
lib/assets/javascripts/builder/components/table/body/table-no-rows.tpl
Executable file
21
lib/assets/javascripts/builder/components/table/body/table-no-rows.tpl
Executable file
@@ -0,0 +1,21 @@
|
||||
<div class="IntermediateInfo">
|
||||
<div class="LayoutIcon">
|
||||
<i class="CDB-IconFont CDB-IconFont-rows"></i>
|
||||
</div>
|
||||
<h3 class="CDB-Text CDB-Size-large u-mainTextColor u-secondaryTextColor u-bSpace--m u-tSpace-xl">
|
||||
<% if (page !== 0) { %>
|
||||
<%- _t('components.table.rows.result.no-page-title', { page: page }) %>
|
||||
<% } else { %>
|
||||
<%- _t('components.table.rows.result.no-results-title') %>
|
||||
<% } %>
|
||||
</h3>
|
||||
<p class="CDB-Text CDB-Size-medium u-altTextColor">
|
||||
<% if (page !== 0) { %>
|
||||
<%- _t('components.table.rows.result.no-page-desc', { page: page }) %>
|
||||
<% } else { %>
|
||||
<%- _t('components.table.rows.result.no-results-desc', {
|
||||
addRow: _t('components.table.rows.result.no-results-button')
|
||||
}) %>
|
||||
<% } %>
|
||||
</p>
|
||||
</div>
|
||||
44
lib/assets/javascripts/builder/components/table/editors/editor-view-model.js
Executable file
44
lib/assets/javascripts/builder/components/table/editors/editor-view-model.js
Executable file
@@ -0,0 +1,44 @@
|
||||
var Backbone = require('backbone');
|
||||
var CoreView = require('backbone/core-view');
|
||||
|
||||
/**
|
||||
* View model of an editor
|
||||
*/
|
||||
module.exports = Backbone.Model.extend({
|
||||
|
||||
defaults: {
|
||||
show: true,
|
||||
createContentView: function () {
|
||||
return new CoreView();
|
||||
}
|
||||
},
|
||||
|
||||
createContentView: function () {
|
||||
return this.get('createContentView')(this);
|
||||
},
|
||||
|
||||
show: function () {
|
||||
this.set('show', true);
|
||||
},
|
||||
|
||||
hide: function () {
|
||||
this.set('show', false);
|
||||
},
|
||||
|
||||
isHidden: function () {
|
||||
return !this.get('show');
|
||||
},
|
||||
|
||||
confirm: function () {
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
this.trigger.apply(this, ['confirm'].concat(args));
|
||||
},
|
||||
|
||||
/**
|
||||
* @override {Backbone.Model.prototype.destroy}
|
||||
*/
|
||||
destroy: function () {
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
this.trigger.apply(this, ['destroy'].concat(args));
|
||||
}
|
||||
});
|
||||
48
lib/assets/javascripts/builder/components/table/editors/editor-view.js
Executable file
48
lib/assets/javascripts/builder/components/table/editors/editor-view.js
Executable file
@@ -0,0 +1,48 @@
|
||||
var CoreView = require('backbone/core-view');
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
className: 'CDB-Box-modal Table-editor',
|
||||
|
||||
initialize: function () {
|
||||
this.listenTo(this.model, 'change:show', this._onShowChange);
|
||||
this.listenTo(this.model, 'destroy', this._onDestroy);
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
|
||||
var view = this.model.createContentView();
|
||||
this.addView(view);
|
||||
view.render();
|
||||
this.$el.append(view.el);
|
||||
this.$el.css(this.options.position);
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
show: function () {
|
||||
this.model.show();
|
||||
},
|
||||
|
||||
hide: function () {
|
||||
this.model.hide();
|
||||
},
|
||||
|
||||
destroy: function () {
|
||||
this.model.destroy();
|
||||
},
|
||||
|
||||
_onShowChange: function (m, show) {
|
||||
this.$el[show ? 'show' : 'hide']();
|
||||
},
|
||||
|
||||
_onClose: function () {
|
||||
this.destroy();
|
||||
},
|
||||
|
||||
_onDestroy: function () {
|
||||
this.hide();
|
||||
this.clean();
|
||||
}
|
||||
|
||||
});
|
||||
117
lib/assets/javascripts/builder/components/table/editors/editors-service-model.js
Executable file
117
lib/assets/javascripts/builder/components/table/editors/editors-service-model.js
Executable file
@@ -0,0 +1,117 @@
|
||||
var _ = require('underscore');
|
||||
var $ = require('jquery');
|
||||
var Backbone = require('backbone');
|
||||
var EditorView = require('./editor-view');
|
||||
var EditorViewModel = require('./editor-view-model');
|
||||
var ESC_KEY_CODE = 27;
|
||||
var DESTROYED_EDITOR_EVENT = 'destroyedEditor';
|
||||
var CONFIRMED_EDITOR_EVENT = 'confirmedEditor';
|
||||
|
||||
/**
|
||||
* Top-level API to handle editor views.
|
||||
*
|
||||
* Example:
|
||||
* // In some entry-point:
|
||||
* table.editors = new EditorsServiceModel();
|
||||
*
|
||||
* // Later, in any view, calling create will create a new editor viewModel
|
||||
* var editorView = table.editors.create(fn);
|
||||
*
|
||||
* Same concept @viddo introduced with Modals.
|
||||
*/
|
||||
module.exports = Backbone.Model.extend({
|
||||
|
||||
/**
|
||||
* Creates a new editor view
|
||||
*
|
||||
* @param {Function} createContentView
|
||||
* @return {View} the new editor view
|
||||
*/
|
||||
create: function (createContentView, position) {
|
||||
if (!_.isFunction(createContentView)) throw new Error('createContentView is required');
|
||||
position = position || {
|
||||
left: '50%',
|
||||
top: '50%'
|
||||
};
|
||||
|
||||
if (!this._editorView) {
|
||||
this._editorView = this._newEditorView(position);
|
||||
document.body.appendChild(this._editorView.el);
|
||||
}
|
||||
|
||||
this._editorView.model.set('createContentView', createContentView);
|
||||
this._editorView.render();
|
||||
|
||||
return this._editorView;
|
||||
},
|
||||
|
||||
/**
|
||||
* Convenience method to add a listener when current editor is destroyed
|
||||
*
|
||||
* This is the same as doing
|
||||
* editors.create(function (model) {
|
||||
* model.once('destroy', callback, context);
|
||||
* return new MyView({ … });
|
||||
* });
|
||||
*
|
||||
* @param {Function} callback
|
||||
* @param {Object} [context = undefined]
|
||||
*/
|
||||
|
||||
confirm: function () {
|
||||
if (this._editorView) {
|
||||
this._editorView.model.confirm();
|
||||
}
|
||||
},
|
||||
|
||||
destroy: function () {
|
||||
if (this._editorView) {
|
||||
this._editorView.model.destroy();
|
||||
}
|
||||
},
|
||||
|
||||
_newEditorView: function (position) {
|
||||
var viewModel = new EditorViewModel();
|
||||
this._destroyOnEsc(viewModel);
|
||||
this.listenToOnce(viewModel, 'destroy', function () {
|
||||
this._editorView = null;
|
||||
this.trigger.apply(this, [DESTROYED_EDITOR_EVENT].concat(Array.prototype.slice.call(arguments)));
|
||||
this.stopListening(viewModel);
|
||||
});
|
||||
this.listenToOnce(viewModel, 'confirm', function () {
|
||||
this.trigger.apply(this, [CONFIRMED_EDITOR_EVENT].concat(Array.prototype.slice.call(arguments)));
|
||||
this.destroy();
|
||||
});
|
||||
var view = new EditorView({
|
||||
model: viewModel,
|
||||
position: position
|
||||
});
|
||||
this._destroyOnClickOutside(view);
|
||||
return view;
|
||||
},
|
||||
|
||||
_destroyOnClickOutside: function (view) {
|
||||
var destroyOnClickOutside = function (ev) {
|
||||
if ($(ev.target).closest(view.el).length === 0) {
|
||||
view.model.confirm();
|
||||
}
|
||||
};
|
||||
document.addEventListener('click', destroyOnClickOutside);
|
||||
this.listenToOnce(view.model, 'destroy', function () {
|
||||
document.removeEventListener('click', destroyOnClickOutside);
|
||||
});
|
||||
},
|
||||
|
||||
_destroyOnEsc: function (viewModel) {
|
||||
var destroyOnEsc = function (ev) {
|
||||
if (ev.which === ESC_KEY_CODE) {
|
||||
viewModel.destroy();
|
||||
}
|
||||
};
|
||||
document.addEventListener('keydown', destroyOnEsc);
|
||||
this.listenToOnce(viewModel, 'destroy', function () {
|
||||
document.removeEventListener('keydown', destroyOnEsc);
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,73 @@
|
||||
var CoreView = require('backbone/core-view');
|
||||
var $ = require('jquery');
|
||||
var ENTER_KEY_CODE = 13;
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
tagName: 'input',
|
||||
className: 'CDB-InputText CDB-Text',
|
||||
|
||||
events: {
|
||||
'keyup': '_onValueChange',
|
||||
'change': '_onValueChange'
|
||||
},
|
||||
|
||||
initialize: function (opts) {
|
||||
if (!opts.editorModel) throw new Error('editorModel is required');
|
||||
this._editorModel = opts.editorModel;
|
||||
this._onKeyPressed = this._onKeyPressed.bind(this);
|
||||
this._onValueChange = this._onValueChange.bind(this);
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.$el.val(
|
||||
this.model.get('value')
|
||||
);
|
||||
this._setFocus();
|
||||
this._initDocumentBind();
|
||||
return this;
|
||||
},
|
||||
|
||||
_setFocus: function () {
|
||||
setTimeout(function () {
|
||||
this.$el.focus();
|
||||
}.bind(this), 100);
|
||||
},
|
||||
|
||||
_initDocumentBind: function () {
|
||||
$(document).bind('keydown', this._onKeyPressed);
|
||||
},
|
||||
|
||||
_destroyDocumentBind: function () {
|
||||
$(document).unbind('keydown', this._onKeyPressed);
|
||||
},
|
||||
|
||||
_onKeyPressed: function (ev) {
|
||||
if (ev.which === ENTER_KEY_CODE) {
|
||||
if (this.model.isValid()) {
|
||||
this._editorModel.confirm();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_onValueChange: function () {
|
||||
this._setValue();
|
||||
this._checkValidity();
|
||||
},
|
||||
|
||||
_setValue: function () {
|
||||
this.model.set('value', this.$el.val());
|
||||
},
|
||||
|
||||
_checkValidity: function () {
|
||||
this.$el.toggleClass('has-error', !this.model.isValid());
|
||||
},
|
||||
|
||||
clean: function () {
|
||||
this._destroyDocumentBind();
|
||||
CoreView.prototype.clean.apply(this);
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,40 @@
|
||||
var EditorBaseView = require('./editor-base-view');
|
||||
var $ = require('jquery');
|
||||
var template = require('./editor-boolean.tpl');
|
||||
|
||||
module.exports = EditorBaseView.extend({
|
||||
|
||||
tagName: 'div',
|
||||
className: 'u-flex',
|
||||
|
||||
events: {
|
||||
'change input:radio': '_onRadioChanged'
|
||||
},
|
||||
|
||||
render: function () {
|
||||
var value = this.model.get('value');
|
||||
this.$el.html(
|
||||
template({
|
||||
value: value !== null ? value.toString() : 'null'
|
||||
})
|
||||
);
|
||||
return this;
|
||||
},
|
||||
|
||||
_onRadioChanged: function (ev) {
|
||||
var value = $(ev.target).val();
|
||||
switch (value) {
|
||||
case 'true':
|
||||
value = true;
|
||||
break;
|
||||
case 'false':
|
||||
value = false;
|
||||
break;
|
||||
default:
|
||||
value = null;
|
||||
}
|
||||
this.model.set('value', value);
|
||||
this._editorModel.confirm();
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,15 @@
|
||||
<div class="u-iBlock CDB-Text CDB-Size-medium u-rSpace--xl">
|
||||
<input class="CDB-Radio" type="radio" name="value" value="true" <% if (value === 'true') { %>checked<% } %>>
|
||||
<span class="u-iBlock CDB-Radio-face"></span>
|
||||
<label class="u-iBlock u-lSpace">True</label>
|
||||
</div>
|
||||
<div class="u-iBlock CDB-Text CDB-Size-medium u-rSpace--xl">
|
||||
<input class="CDB-Radio" type="radio" name="value" value="false" <% if (value === 'false') { %>checked<% } %>>
|
||||
<span class="u-iBlock CDB-Radio-face"></span>
|
||||
<label class="u-iBlock u-lSpace">False</label>
|
||||
</div>
|
||||
<div class="u-iBlock CDB-Text CDB-Size-medium u-rSpace--xl">
|
||||
<input class="CDB-Radio" type="radio" name="value" value="null" <% if (value === 'null') { %>checked<% } %>>
|
||||
<span class="u-iBlock CDB-Radio-face"></span>
|
||||
<label class="u-iBlock u-lSpace">Null</label>
|
||||
</div>
|
||||
@@ -0,0 +1,105 @@
|
||||
var Backbone = require('backbone');
|
||||
var moment = require('moment');
|
||||
var Utils = require('builder/helpers/utils');
|
||||
var MONTHS = [
|
||||
{
|
||||
val: 0,
|
||||
label: _t('months.january')
|
||||
}, {
|
||||
val: 1,
|
||||
label: _t('months.february')
|
||||
}, {
|
||||
val: 2,
|
||||
label: _t('months.march')
|
||||
}, {
|
||||
val: 3,
|
||||
label: _t('months.april')
|
||||
}, {
|
||||
val: 4,
|
||||
label: _t('months.may')
|
||||
}, {
|
||||
val: 5,
|
||||
label: _t('months.june')
|
||||
}, {
|
||||
val: 6,
|
||||
label: _t('months.july')
|
||||
}, {
|
||||
val: 7,
|
||||
label: _t('months.august')
|
||||
}, {
|
||||
val: 8,
|
||||
label: _t('months.september')
|
||||
}, {
|
||||
val: 9,
|
||||
label: _t('months.october')
|
||||
}, {
|
||||
val: 10,
|
||||
label: _t('months.november')
|
||||
}, {
|
||||
val: 11,
|
||||
label: _t('months.december')
|
||||
}
|
||||
];
|
||||
|
||||
module.exports = Backbone.Model.extend({
|
||||
|
||||
parse: function (attrs) {
|
||||
var date;
|
||||
|
||||
if (!attrs.date) {
|
||||
date = moment().utc();
|
||||
} else {
|
||||
date = moment(attrs.date).utc();
|
||||
}
|
||||
|
||||
return {
|
||||
day: date.date(),
|
||||
month: date.month(),
|
||||
year: date.year(),
|
||||
time: date.format('HH:mm:ss'),
|
||||
utcOffset: date.utcOffset()
|
||||
};
|
||||
},
|
||||
|
||||
initialize: function () {
|
||||
this.schema = {
|
||||
day: {
|
||||
title: '',
|
||||
type: 'Text',
|
||||
validators: [
|
||||
'required',
|
||||
/^([12]?\d{0,1}|3[01]{0,2})$/,
|
||||
function (value, formValues) {
|
||||
var date = moment(Utils.formatDate(formValues));
|
||||
if (!date.isValid()) {
|
||||
return {
|
||||
type: 'date',
|
||||
message: _t('components.datepicker.invalid-date')
|
||||
};
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
month: {
|
||||
type: 'Select',
|
||||
title: '',
|
||||
options: MONTHS
|
||||
},
|
||||
year: {
|
||||
title: '',
|
||||
type: 'Text',
|
||||
validators: ['required', /^([0-9]{0,4})$/]
|
||||
},
|
||||
time: {
|
||||
title: '',
|
||||
type: 'Text',
|
||||
validators: ['required', /^([01]{1}[0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]$/]
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
getFormattedDate: function () {
|
||||
return Utils.formatDate(this.toJSON());
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,48 @@
|
||||
var Backbone = require('backbone');
|
||||
var EditorBaseView = require('./editor-base-view');
|
||||
var EditorDateFormModel = require('./editor-date-form-model');
|
||||
require('builder/components/form-components/index');
|
||||
|
||||
module.exports = EditorBaseView.extend({
|
||||
|
||||
tagName: 'div',
|
||||
className: 'Table-editorDate',
|
||||
|
||||
initialize: function (opts) {
|
||||
EditorBaseView.prototype.initialize.apply(this, arguments);
|
||||
|
||||
this._formModel = new EditorDateFormModel({
|
||||
date: this.model.get('value')
|
||||
}, {
|
||||
parse: true
|
||||
});
|
||||
|
||||
this._setValue();
|
||||
this._formModel.bind('change', this._setValue, this);
|
||||
this.add_related_model(this._formModel);
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this._formView = new Backbone.Form({
|
||||
model: this._formModel
|
||||
});
|
||||
|
||||
this._formView.bind('change', function () {
|
||||
this.commit();
|
||||
});
|
||||
|
||||
this.$el.html(this._formView.render().el);
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_setValue: function () {
|
||||
this.model.set('value', this._formModel.getFormattedDate());
|
||||
},
|
||||
|
||||
clean: function () {
|
||||
this._formView.remove();
|
||||
EditorBaseView.prototype.clean.apply(this);
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
var Backbone = require('backbone');
|
||||
var _ = require('underscore');
|
||||
|
||||
module.exports = Backbone.Model.extend({
|
||||
|
||||
// Error labels will not be showed, not needed to be translated
|
||||
validate: function (attrs, opts) {
|
||||
if (attrs.type === 'number' && isNaN(attrs.value)) {
|
||||
return 'Number not valid';
|
||||
}
|
||||
|
||||
if (attrs.type === 'boolean' && (!_.isBoolean(attrs.value) && attrs.value !== null)) {
|
||||
return 'Boolean not valid';
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
var EditorBaseView = require('./editor-base-view');
|
||||
var ENTER_KEY_CODE = 13;
|
||||
|
||||
module.exports = EditorBaseView.extend({
|
||||
|
||||
tagName: 'textarea',
|
||||
className: 'Table-editorTextarea CDB-Textarea CDB-Text',
|
||||
|
||||
_onKeyPressed: function (ev) {
|
||||
if (!ev.shiftKey && ev.which === ENTER_KEY_CODE) {
|
||||
this._onValueChange();
|
||||
this._editorModel.confirm();
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,37 @@
|
||||
<div class="u-flex u-justifyCenter">
|
||||
<div class="Modal-inner Modal-inner--grid u-flex u-justifyCenter">
|
||||
<div class="Modal-icon">
|
||||
<svg width="25px" height="25px" viewBox="-1 4 25 25" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="Outline_Icons" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" transform="translate(-1.000000, 4.000000)">
|
||||
<path d="M1.9477838,21.5 C1.6977511,21.5 1.5,21.3016354 1.5,21.0431048 L1.5,1.95689523 C1.5,1.70555616 1.70315066,1.5 1.94113901,1.5 L16.058861,1.5 C16.2975949,1.5 16.5,1.70783627 16.5,1.96250326 L16.5,13.2661378 L17.5,13.2661378 L17.5,1.96250326 C17.5,1.16139088 16.8558926,0.5 16.058861,0.5 L1.94113901,0.5 C1.14821378,0.5 0.5,1.15588925 0.5,1.95689523 L0.5,21.0431048 C0.5,21.8532278 1.1447717,22.5 1.9477838,22.5 L10.0155676,22.5 L10.0155676,21.5 L1.9477838,21.5 Z" id="path-1" fill="#F19243"></path>
|
||||
<rect id="Rectangle-2" fill="#F19243" x="1" y="5" width="16" height="1"></rect>
|
||||
<path d="M23.854,14.646 L21.354,12.146 C21.166,11.958 20.834,11.958 20.647,12.146 L13.146,19.648 C13.089,19.705 13.054,19.773 13.03,19.845 C13.028,19.852 13.021,19.857 13.019,19.865 L12.019,23.365 C11.969,23.539 12.018,23.727 12.146,23.856 C12.241,23.951 12.369,24.002 12.5,24.002 C12.546,24.002 12.592,23.996 12.637,23.983 L16.137,22.983 C16.144,22.981 16.149,22.974 16.157,22.972 C16.229,22.948 16.297,22.913 16.354,22.856 L23.855,15.354 C24.05,15.158 24.05,14.842 23.854,14.646 L23.854,14.646 L23.854,14.646 Z M16,21.795 L14.207,20.002 L19.001,15.207 L20.794,17 L16,21.795 L16,21.795 L16,21.795 Z M13.747,20.957 L15.045,22.255 L13.228,22.775 L13.747,20.957 L13.747,20.957 L13.747,20.957 Z M21.501,16.293 L19.708,14.5 L21.001,13.207 L22.794,15 L21.501,16.293 L21.501,16.293 L21.501,16.293 Z" id="Shape" fill="#F19243"></path>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<h2 class=" CDB-Text CDB-Size-huge is-light u-bSpace--xl">
|
||||
<%- _t('components.table.columns.change-type.title', { columnName: columnName, newType: newType }) %>
|
||||
</h2>
|
||||
<p class="CDB-Text CDB-Size-large u-altTextColor">
|
||||
<%- _t('components.table.columns.change-type.desc') %>
|
||||
</p>
|
||||
<ul class="Modal-listActions u-flex u-alignCenter">
|
||||
<li class="Modal-listActionsitem">
|
||||
<button class="CDB-Button CDB-Button--secondary CDB-Button--big js-cancel">
|
||||
<span class="CDB-Button-Text CDB-Text is-semibold CDB-Size-medium u-upperCase">
|
||||
<%- _t('components.table.columns.change-type.cancel') %>
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
<li class="Modal-listActionsitem">
|
||||
<button class="CDB-Button CDB-Button--primary CDB-Button--big js-confirm">
|
||||
<span class="CDB-Button-Text CDB-Text is-semibold CDB-Size-medium u-upperCase">
|
||||
<%- _t('components.table.columns.change-type.confirm') %>
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,31 @@
|
||||
<div class="u-flex u-justifyCenter">
|
||||
<div class="Modal-inner Modal-inner--grid u-flex u-justifyCenter">
|
||||
<div class="Modal-icon">
|
||||
<svg width="24px" height="25px" viewbox="521 436 24 25" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<path d="M524.5,440 L540.5,440 L540.5,460 L524.5,460 L524.5,440 Z M528.5,437 L536.5,437 L536.5,440 L528.5,440 L528.5,437 Z M522,440 L544,440 L522,440 Z M528.5,443.5 L528.5,455.5 L528.5,443.5 Z M532.5,443.5 L532.5,455.5 L532.5,443.5 Z M536.5,443.5 L536.5,455.5 L536.5,443.5 Z" id="Shape" stroke="#F19243" stroke-width="1" fill="none"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<h2 class=" CDB-Text CDB-Size-huge is-light u-bSpace--xl">
|
||||
<%- _t('components.table.columns.destroy.title', { columnName: name }) %>
|
||||
</h2>
|
||||
<p class="CDB-Text CDB-Size-large u-altTextColor"><%- _t('components.table.columns.destroy.desc') %></p>
|
||||
<ul class="Modal-listActions u-flex u-alignCenter">
|
||||
<li class="Modal-listActionsitem">
|
||||
<button class="CDB-Button CDB-Button--secondary CDB-Button--big js-cancel">
|
||||
<span class="CDB-Button-Text CDB-Text is-semibold CDB-Size-medium u-upperCase">
|
||||
<%- _t('components.table.columns.destroy.cancel') %>
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
<li class="Modal-listActionsitem">
|
||||
<button class="CDB-Button CDB-Button--primary CDB-Button--big js-confirm">
|
||||
<span class="CDB-Button-Text CDB-Text is-semibold CDB-Size-medium u-upperCase">
|
||||
<%- _t('components.table.columns.destroy.confirm') %>
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,42 @@
|
||||
<div class="u-flex u-justifyCenter">
|
||||
<div class="Modal-inner Modal-inner--grid u-flex u-justifyCenter">
|
||||
<div class="Modal-icon">
|
||||
<svg width="25px" height="25px" viewBox="67 153 25 25" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="Group-Copy" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" transform="translate(67.000000, 153.000000)">
|
||||
<path d="M16,13 L17,13 L17,5.5 C17,5.433 16.986,5.368 16.961,5.308 C16.936,5.247 16.899,5.192 16.853,5.146 L11.854,0.147 C11.808,0.101 11.753,0.064 11.692,0.039 C11.632,0.014 11.567,0 11.5,0 L0.5,0 C0.224,0 0,0.224 0,0.5 L0,21.5 C0,21.776 0.224,22 0.5,22 L9.5,22 L9.5,21 L1,21 L1,1 L11,1 L11,5.5 C11,5.776 11.224,6 11.5,6 L16,6 L16,13 L16,13 Z M12,1.707 L15.293,5 L12,5 L12,1.707 L12,1.707 Z" id="Shape" fill="#F19243"></path>
|
||||
<path d="M23.854,14.646 L21.354,12.146 C21.166,11.958 20.834,11.958 20.647,12.146 L13.146,19.648 C13.089,19.705 13.054,19.773 13.03,19.845 C13.028,19.852 13.021,19.857 13.019,19.865 L12.019,23.365 C11.969,23.539 12.018,23.727 12.146,23.856 C12.241,23.951 12.369,24.002 12.5,24.002 C12.546,24.002 12.592,23.996 12.637,23.983 L16.137,22.983 C16.144,22.981 16.149,22.974 16.157,22.972 C16.229,22.948 16.297,22.913 16.354,22.856 L23.855,15.354 C24.05,15.158 24.05,14.842 23.854,14.646 L23.854,14.646 Z M16,21.795 L14.207,20.002 L19.001,15.207 L20.794,17 L16,21.795 L16,21.795 Z M13.747,20.957 L15.045,22.255 L13.228,22.775 L13.747,20.957 L13.747,20.957 Z M21.501,16.293 L19.708,14.5 L21.001,13.207 L22.794,15 L21.501,16.293 L21.501,16.293 Z" id="Shape" fill="#F19243"></path>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<h2 class=" CDB-Text CDB-Size-huge is-light u-bSpace--m">
|
||||
<%- _t('components.table.columns.rename.title', {
|
||||
columnName: columnName,
|
||||
newName: newName
|
||||
}) %>
|
||||
</h2>
|
||||
<p class="CDB-Text CDB-Size-medium u-altTextColor">
|
||||
<%- _t('components.table.columns.rename.desc', {
|
||||
columnName: columnName,
|
||||
newName: newName
|
||||
}) %>
|
||||
</p>
|
||||
<ul class="Modal-listActions u-flex u-alignCenter">
|
||||
<li class="Modal-listActionsitem">
|
||||
<button class="CDB-Button CDB-Button--secondary CDB-Button--big js-cancel">
|
||||
<span class="CDB-Button-Text CDB-Text is-semibold CDB-Size-medium u-upperCase">
|
||||
<%- _t('components.table.columns.rename.cancel') %>
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
<li class="Modal-listActionsitem">
|
||||
<button class="CDB-Button CDB-Button--primary CDB-Button--big js-confirm">
|
||||
<span class="CDB-Button-Text CDB-Text is-semibold CDB-Size-medium u-upperCase">
|
||||
<%- _t('components.table.columns.rename.confirm') %>
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
350
lib/assets/javascripts/builder/components/table/head/table-head-item-view.js
Executable file
350
lib/assets/javascripts/builder/components/table/head/table-head-item-view.js
Executable file
@@ -0,0 +1,350 @@
|
||||
var $ = require('jquery');
|
||||
var CoreView = require('backbone/core-view');
|
||||
var template = require('./table-head-item.tpl');
|
||||
var ContextMenuView = require('builder/components/context-menu/context-menu-view');
|
||||
var CustomListCollection = require('builder/components/custom-list/custom-list-collection');
|
||||
var TableHeadOptionsItemView = require('./table-head-options-item-view');
|
||||
var tableHeadOptionsItemTemplate = require('./table-head-options-item.tpl');
|
||||
var ConfirmationModalView = require('builder/components/modals/confirmation/modal-confirmation-view');
|
||||
var QueryColumnModel = require('builder/data/query-column-model');
|
||||
var addColumnOperation = require('builder/components/table/operations/table-add-column');
|
||||
var renameColumnOperation = require('builder/components/table/operations/table-rename-column');
|
||||
var changeColumnTypeOperation = require('builder/components/table/operations/table-change-column-type');
|
||||
var removeTableColumnOperation = require('builder/components/table/operations/table-remove-column');
|
||||
|
||||
var ENTER_KEY_CODE = 13;
|
||||
var ESC_KEY_CODE = 27;
|
||||
|
||||
/*
|
||||
* Main table view
|
||||
*/
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
className: 'Table-headItem',
|
||||
tagName: 'th',
|
||||
|
||||
events: {
|
||||
'dblclick .js-attribute': '_onAttributeDblClicked',
|
||||
'focusout .js-attribute': '_saveNewName',
|
||||
'click .js-columnOptions': '_showContextMenu'
|
||||
},
|
||||
|
||||
initialize: function (opts) {
|
||||
if (!opts.tableViewModel) throw new Error('tableViewModel is required');
|
||||
if (!opts.columnsCollection) throw new Error('columnsCollection is required');
|
||||
if (!opts.modals) throw new Error('modals is required');
|
||||
|
||||
this._tableViewModel = opts.tableViewModel;
|
||||
this._columnsCollection = opts.columnsCollection;
|
||||
this._modals = opts.modals;
|
||||
|
||||
this._hideContextMenu = this._hideContextMenu.bind(this);
|
||||
this._onKeyDown = this._onKeyDown.bind(this);
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
var columnName = this.model.get('name');
|
||||
this.$el.html(
|
||||
template({
|
||||
name: columnName,
|
||||
type: this.model.get('type'),
|
||||
isOrderBy: this._tableViewModel.get('order_by') === columnName,
|
||||
sortBy: this._tableViewModel.get('sort_order'),
|
||||
geometry: this.options.simpleGeometry
|
||||
})
|
||||
);
|
||||
return this;
|
||||
},
|
||||
|
||||
_hasContextMenu: function () {
|
||||
return this._menuView;
|
||||
},
|
||||
|
||||
_hideContextMenu: function () {
|
||||
this._unhighlightHead();
|
||||
this._destroyScrollBinding();
|
||||
this._menuView.collection.unbind(null, null, this);
|
||||
this.removeView(this._menuView);
|
||||
this._menuView.clean();
|
||||
delete this._menuView;
|
||||
},
|
||||
|
||||
_highlightHead: function () {
|
||||
this.$el.addClass('is-highlighted');
|
||||
},
|
||||
|
||||
_unhighlightHead: function () {
|
||||
this.$el.removeClass('is-highlighted');
|
||||
},
|
||||
|
||||
_initScrollBinding: function () {
|
||||
$('.Table').scroll(this._hideContextMenu);
|
||||
},
|
||||
|
||||
_destroyScrollBinding: function () {
|
||||
$('.Table').off('scroll', this._hideContextMenu);
|
||||
},
|
||||
|
||||
_showContextMenu: function (ev) {
|
||||
var self = this;
|
||||
var position = { x: ev.clientX, y: ev.clientY };
|
||||
var elementIndex = this.$el.index();
|
||||
var modelCID = this.model.cid;
|
||||
var columnName = this.model.get('name');
|
||||
|
||||
this._highlightHead();
|
||||
|
||||
var menuItems = [{
|
||||
label: _t('components.table.columns.options.order'),
|
||||
val: 'order',
|
||||
isOrderBy: this._tableViewModel.get('order_by') === columnName,
|
||||
sortBy: this._tableViewModel.get('sort_order'),
|
||||
action: function (model) {
|
||||
self._tableViewModel.set({
|
||||
sort_order: model.get('sort'),
|
||||
order_by: columnName
|
||||
});
|
||||
}
|
||||
}];
|
||||
|
||||
if (!this._tableViewModel.isDisabled()) {
|
||||
if (!this.model.isCartoDBIDColumn() && !this.model.isGeometryColumn()) {
|
||||
menuItems = menuItems.concat([
|
||||
{
|
||||
label: _t('components.table.columns.options.rename'),
|
||||
val: 'rename',
|
||||
action: function () {
|
||||
self._startEditing();
|
||||
}
|
||||
}, {
|
||||
label: _t('components.table.columns.options.change'),
|
||||
type: this.model.get('type'),
|
||||
isLastColumns: (this._columnsCollection.size() - elementIndex) < 3,
|
||||
val: 'change',
|
||||
action: function (model) {
|
||||
self._changeColumnType(model.get('type'));
|
||||
}
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
menuItems.push({
|
||||
label: _t('components.table.columns.options.create'),
|
||||
val: 'create',
|
||||
action: function () {
|
||||
self._addColumn();
|
||||
}
|
||||
});
|
||||
|
||||
if (!this.model.isCartoDBIDColumn() && !this.model.isGeometryColumn()) {
|
||||
menuItems.push(
|
||||
{
|
||||
label: _t('components.table.columns.options.delete'),
|
||||
val: 'delete',
|
||||
destructive: true,
|
||||
action: function () {
|
||||
self._removeColumn();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
var collection = new CustomListCollection(menuItems);
|
||||
|
||||
this._menuView = new ContextMenuView({
|
||||
className: ContextMenuView.prototype.className + ' Table-columnMenu',
|
||||
collection: collection,
|
||||
itemTemplate: tableHeadOptionsItemTemplate,
|
||||
itemView: TableHeadOptionsItemView,
|
||||
triggerElementID: modelCID,
|
||||
position: position
|
||||
});
|
||||
|
||||
collection.bind('change:selected', function (menuItem) {
|
||||
var action = menuItem.get('action');
|
||||
action && action(menuItem);
|
||||
}, this);
|
||||
|
||||
this._menuView.model.bind('change:visible', function (model, isContextMenuVisible) {
|
||||
if (this._hasContextMenu() && !isContextMenuVisible) {
|
||||
this._hideContextMenu();
|
||||
}
|
||||
}, this);
|
||||
|
||||
this._menuView.show();
|
||||
this.addView(this._menuView);
|
||||
|
||||
this._initScrollBinding();
|
||||
},
|
||||
|
||||
_addColumn: function () {
|
||||
addColumnOperation({
|
||||
columnsCollection: this._columnsCollection
|
||||
});
|
||||
},
|
||||
|
||||
_changeColumnType: function (newType) {
|
||||
var self = this;
|
||||
var oldType = this.model.get('type');
|
||||
var isTypeChangeDestructive = QueryColumnModel.isTypeChangeDestructive(oldType, newType);
|
||||
if (!isTypeChangeDestructive) {
|
||||
changeColumnTypeOperation({
|
||||
columnModel: this.model,
|
||||
newType: newType
|
||||
});
|
||||
} else {
|
||||
this._modals.create(
|
||||
function (modalModel) {
|
||||
return new ConfirmationModalView({
|
||||
modalModel: modalModel,
|
||||
template: require('./modals-templates/change-table-column-type.tpl'),
|
||||
renderOpts: {
|
||||
columnName: self.model.get('name'),
|
||||
newType: newType
|
||||
},
|
||||
loadingTitle: _t('components.table.columns.change-type.loading', {
|
||||
columnName: self.model.get('name')
|
||||
}),
|
||||
runAction: function () {
|
||||
changeColumnTypeOperation({
|
||||
columnModel: self.model,
|
||||
newType: newType,
|
||||
onSuccess: function () {
|
||||
modalModel.destroy();
|
||||
},
|
||||
onError: function (e) {
|
||||
modalModel.destroy();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
_removeColumn: function () {
|
||||
var self = this;
|
||||
this._modals.create(
|
||||
function (modalModel) {
|
||||
return new ConfirmationModalView({
|
||||
modalModel: modalModel,
|
||||
template: require('./modals-templates/remove-table-column.tpl'),
|
||||
renderOpts: {
|
||||
name: self.model.get('name')
|
||||
},
|
||||
loadingTitle: _t('components.table.columns.destroy.loading', {
|
||||
columnName: self.model.get('name')
|
||||
}),
|
||||
runAction: function () {
|
||||
removeTableColumnOperation({
|
||||
columnModel: self.model,
|
||||
onSuccess: function () {
|
||||
modalModel.destroy();
|
||||
},
|
||||
onError: function (e) {
|
||||
modalModel.destroy();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
_onAttributeDblClicked: function () {
|
||||
if (this.model.isEditable() && !this._tableViewModel.isDisabled()) {
|
||||
this._startEditing();
|
||||
}
|
||||
},
|
||||
|
||||
_initRenameBinds: function () {
|
||||
$(document).bind('keydown', this._onKeyDown);
|
||||
},
|
||||
|
||||
_destroyRenameBinds: function () {
|
||||
$(document).unbind('keydown', this._onKeyDown);
|
||||
},
|
||||
|
||||
_onKeyDown: function (ev) {
|
||||
var keyCode = ev.which;
|
||||
if (keyCode === ENTER_KEY_CODE) {
|
||||
this._saveNewName();
|
||||
} else if (keyCode === ESC_KEY_CODE) {
|
||||
this._finishEditing();
|
||||
}
|
||||
},
|
||||
|
||||
_startEditing: function () {
|
||||
this.$('.js-attribute')
|
||||
.addClass('is-active')
|
||||
.removeAttr('readonly');
|
||||
|
||||
this._initRenameBinds();
|
||||
},
|
||||
|
||||
_finishEditing: function () {
|
||||
this.$('.js-attribute')
|
||||
.val(this.model.get('name'))
|
||||
.removeClass('is-active')
|
||||
.attr('readonly', '');
|
||||
|
||||
this._destroyRenameBinds();
|
||||
},
|
||||
|
||||
_saveNewName: function () {
|
||||
if (this.model.isEditable()) {
|
||||
var newName = this.$('.js-attribute').val();
|
||||
var columnModel = this.model;
|
||||
var oldName = columnModel.get('name');
|
||||
|
||||
if (oldName !== newName && newName !== '') {
|
||||
this._modals.create(
|
||||
function (modalModel) {
|
||||
return new ConfirmationModalView({
|
||||
modalModel: modalModel,
|
||||
template: require('./modals-templates/rename-table-column.tpl'),
|
||||
renderOpts: {
|
||||
columnName: oldName,
|
||||
newName: newName
|
||||
},
|
||||
loadingTitle: _t('components.table.columns.rename.loading', {
|
||||
columnName: oldName,
|
||||
newName: newName
|
||||
}),
|
||||
runAction: function () {
|
||||
renameColumnOperation({
|
||||
columnModel: columnModel,
|
||||
newName: newName,
|
||||
onSuccess: function () {
|
||||
modalModel.destroy();
|
||||
},
|
||||
onError: function (e) {
|
||||
modalModel.destroy();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Setting old name just in case user cancel it
|
||||
this._finishEditing();
|
||||
},
|
||||
|
||||
focusInput: function () {
|
||||
this._startEditing();
|
||||
this.$('.js-attribute').select();
|
||||
},
|
||||
|
||||
clean: function () {
|
||||
this._destroyRenameBinds();
|
||||
this._destroyScrollBinding();
|
||||
CoreView.prototype.clean.apply(this);
|
||||
}
|
||||
});
|
||||
23
lib/assets/javascripts/builder/components/table/head/table-head-item.tpl
Executable file
23
lib/assets/javascripts/builder/components/table/head/table-head-item.tpl
Executable file
@@ -0,0 +1,23 @@
|
||||
<div class="
|
||||
Table-headItemWrapper
|
||||
<%- name === 'cartodb_id' || (type === 'geometry' && geometry !== 'point') ? 'Table-headItemWrapper--short' : '' %>
|
||||
">
|
||||
<div class="u-flex u-justifySpace">
|
||||
<input class="Table-headItemName CDB-Text CDB-Size-medium is-semibold u-ellipsis js-attribute" value="<%- name %>" title="<%- name %>" readonly />
|
||||
|
||||
<% if (isOrderBy) { %>
|
||||
<i class="CDB-Size CDB-Size-small CDB-IconFont CDB-IconFont-arrowNext
|
||||
Table-columnSorted
|
||||
Table-columnSorted--<% if (sortBy === 'asc') {%>asc<% } else {%>desc<% } %>
|
||||
u-rSpace u-altTextColor
|
||||
"></i>
|
||||
<% } %>
|
||||
|
||||
<button class="CDB-Shape-threePoints is-blue is-small js-columnOptions">
|
||||
<div class="CDB-Shape-threePointsItem"></div>
|
||||
<div class="CDB-Shape-threePointsItem"></div>
|
||||
<div class="CDB-Shape-threePointsItem"></div>
|
||||
</button>
|
||||
</div>
|
||||
<p class="CDB-Size-small Table-headItemInfo u-altTextColor"><%- type %></p>
|
||||
</div>
|
||||
@@ -0,0 +1,127 @@
|
||||
var _ = require('underscore');
|
||||
var CustomListItemView = require('builder/components/custom-list/custom-list-item-view');
|
||||
var CustomListView = require('builder/components/custom-list/custom-view');
|
||||
var QueryColumnModel = require('builder/data/query-column-model');
|
||||
|
||||
module.exports = CustomListItemView.extend({
|
||||
|
||||
events: _.extend(
|
||||
CustomListItemView.prototype.events,
|
||||
{
|
||||
'click .js-desc': '_onDescClick',
|
||||
'click .js-asc': '_onAscClick',
|
||||
'click .js-order': 'killEvent'
|
||||
}
|
||||
),
|
||||
|
||||
render: function () {
|
||||
this.$el.empty();
|
||||
this.clearSubViews();
|
||||
|
||||
this.$el.append(
|
||||
this.options.template(
|
||||
{
|
||||
typeLabel: this.options.typeLabel,
|
||||
isSelected: this.model.get('selected'),
|
||||
isDisabled: this.model.get('disabled'),
|
||||
isDestructive: this.model.get('destructive'),
|
||||
isOrderBy: this.model.get('isOrderBy'),
|
||||
sortBy: this.model.get('sortBy'),
|
||||
name: this.model.getName(),
|
||||
val: this.model.getValue()
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
this.$el
|
||||
.attr('data-val', this.model.getValue())
|
||||
.toggleClass('is-disabled', !!this.model.get('disabled'));
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_onClick: function (ev) {
|
||||
this.killEvent(ev);
|
||||
|
||||
if (this.model.getValue() === 'change') {
|
||||
this._openSubContextMenu(ev);
|
||||
} else {
|
||||
this.model.set('selected', true);
|
||||
}
|
||||
},
|
||||
|
||||
_hideSubContextMenu: function () {
|
||||
if (this._subContextMenu) {
|
||||
this._subContextMenu.clean();
|
||||
this.removeView(this._subContextMenu);
|
||||
delete this.collection;
|
||||
delete this._subContextMenu;
|
||||
}
|
||||
},
|
||||
|
||||
_openSubContextMenu: function (ev) {
|
||||
if (this._subContextMenu) {
|
||||
this._hideSubContextMenu();
|
||||
}
|
||||
|
||||
var subContextClassName = 'Table-columnTypeMenu ';
|
||||
if (this.model.get('isLastColumns')) {
|
||||
subContextClassName += ' Table-columnTypeMenu--toLeft ';
|
||||
}
|
||||
|
||||
this._subContextMenu = new CustomListView({
|
||||
className: subContextClassName + CustomListView.prototype.className,
|
||||
options: [
|
||||
{
|
||||
label: _t('components.table.columns.types.number'),
|
||||
disabled: !QueryColumnModel.isTypeChangeAllowed(this.model.get('type'), 'number'),
|
||||
val: 'number'
|
||||
}, {
|
||||
label: _t('components.table.columns.types.string'),
|
||||
disabled: !QueryColumnModel.isTypeChangeAllowed(this.model.get('type'), 'string'),
|
||||
val: 'string'
|
||||
}, {
|
||||
label: _t('components.table.columns.types.date'),
|
||||
disabled: !QueryColumnModel.isTypeChangeAllowed(this.model.get('type'), 'date'),
|
||||
val: 'date'
|
||||
}, {
|
||||
label: _t('components.table.columns.types.boolean'),
|
||||
disabled: !QueryColumnModel.isTypeChangeAllowed(this.model.get('type'), 'boolean'),
|
||||
val: 'boolean'
|
||||
}
|
||||
],
|
||||
showSearch: false,
|
||||
typeLabel: ''
|
||||
});
|
||||
|
||||
this._subContextMenu.collection.bind('change:selected', function (menuItem) {
|
||||
if (!menuItem.get('disabled')) {
|
||||
this.model.set({
|
||||
type: menuItem.getValue(),
|
||||
selected: true
|
||||
});
|
||||
}
|
||||
}, this);
|
||||
|
||||
this.$el.closest('.Table-columnMenu').append(this._subContextMenu.render().el);
|
||||
this._subContextMenu.show();
|
||||
this.addView(this._subContextMenu);
|
||||
},
|
||||
|
||||
_onAscClick: function (ev) {
|
||||
this.killEvent(ev);
|
||||
this.model.set({
|
||||
sort: 'asc',
|
||||
selected: true
|
||||
});
|
||||
},
|
||||
|
||||
_onDescClick: function (ev) {
|
||||
this.killEvent(ev);
|
||||
this.model.set({
|
||||
sort: 'desc',
|
||||
selected: true
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
<% if (val === 'order') { %>
|
||||
<div class="CDB-ListDecoration-itemLink js-order">
|
||||
<div class="u-flex u-justifySpace">
|
||||
<p class="CDB-Text CDB-Size-medium"><%- name %></p>
|
||||
<ul class="u-flex">
|
||||
<li class="js-asc">
|
||||
<i class="CDB-IconFont
|
||||
CDB-IconFont-arrowNext u-actionTextColor
|
||||
Table-columnSorted
|
||||
Table-columnSorted--asc
|
||||
<% if (isOrderBy && sortBy === 'asc') { %>is-disabled<% } %>
|
||||
<% if (isOrderBy && sortBy === 'desc') { %>is-semibold<% } %>
|
||||
"></i>
|
||||
</li>
|
||||
<li class="js-desc u-lSpace--xl">
|
||||
<i class="CDB-IconFont
|
||||
CDB-IconFont-arrowNext u-actionTextColor
|
||||
Table-columnSorted
|
||||
Table-columnSorted--desc
|
||||
<% if (isOrderBy && sortBy === 'desc') { %>is-disabled<% } %>
|
||||
<% if (isOrderBy && sortBy === 'asc') { %>is-semibold<% } %>
|
||||
"></i>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<% } else if (val === 'change') { %>
|
||||
<div class="CDB-ListDecoration-itemLink u-actionTextColor" title="<%- name %>">
|
||||
<div class="u-flex u-justifySpace u-alignCenter">
|
||||
<span><%- name %></span>
|
||||
<i class="CDB-IconFont CDB-Size-small is-semibold CDB-IconFont-rArrowLight"></i>
|
||||
</div>
|
||||
</div>
|
||||
<% } else { %>
|
||||
<div
|
||||
class="CDB-ListDecoration-itemLink <% if (isDestructive) { %> u-errorTextColor <% } else { %> u-actionTextColor <% } %>"
|
||||
title="<%- name %>"
|
||||
>
|
||||
<%- name %>
|
||||
</div>
|
||||
<% } %>
|
||||
100
lib/assets/javascripts/builder/components/table/head/table-head-view.js
Executable file
100
lib/assets/javascripts/builder/components/table/head/table-head-view.js
Executable file
@@ -0,0 +1,100 @@
|
||||
var $ = require('jquery');
|
||||
var CoreView = require('backbone/core-view');
|
||||
var TableHeadItemView = require('./table-head-item-view');
|
||||
var template = require('./table-head.tpl');
|
||||
var checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
|
||||
var REQUIRED_OPTS = [
|
||||
'columnsCollection',
|
||||
'tableViewModel',
|
||||
'queryGeometryModel',
|
||||
'canHideColumns',
|
||||
'modals'
|
||||
];
|
||||
|
||||
/*
|
||||
* Main table head view
|
||||
*/
|
||||
module.exports = CoreView.extend({
|
||||
className: 'Table-head',
|
||||
tagName: 'table',
|
||||
|
||||
initialize: function (opts) {
|
||||
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
|
||||
this._lastHeadItemView = null;
|
||||
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
this.$el.empty();
|
||||
this.$el.append(template());
|
||||
this._columnsCollection.each(this._renderColumnHead, this);
|
||||
return this;
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this._queryGeometryModel.bind('change:simple_geom', this.render, this);
|
||||
this.add_related_model(this._queryGeometryModel);
|
||||
this._tableViewModel.bind('change:sort_order change:order_by', this.render, this);
|
||||
this.add_related_model(this._tableViewModel);
|
||||
this._columnsCollection.bind('reset', function () {
|
||||
this.render();
|
||||
|
||||
if (this._newColumn) {
|
||||
var scrollWidth = this.$el.get(0).scrollWidth;
|
||||
|
||||
setTimeout(function () {
|
||||
this._scrollToLeft({
|
||||
pos: scrollWidth
|
||||
});
|
||||
this._focusLastHeadItem();
|
||||
}.bind(this), 500);
|
||||
|
||||
delete this._newColumn;
|
||||
}
|
||||
}, this);
|
||||
this._columnsCollection.bind('add', function (model) {
|
||||
this._newColumn = model;
|
||||
}, this);
|
||||
this.add_related_model(this._columnsCollection);
|
||||
},
|
||||
|
||||
_renderColumnHead: function (model) {
|
||||
var columnName = model.get('name');
|
||||
|
||||
if (!this._tableViewModel.isCustomQueryApplied() && columnName === 'the_geom_webmercator') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._canHideColumns && columnName === 'center' && model.isGeometryColumn()) {
|
||||
return;
|
||||
}
|
||||
|
||||
var view = new TableHeadItemView({
|
||||
model: model,
|
||||
modals: this._modals,
|
||||
columnsCollection: this._columnsCollection,
|
||||
tableViewModel: this._tableViewModel,
|
||||
simpleGeometry: this._queryGeometryModel.get('simple_geom')
|
||||
});
|
||||
this.$('.js-headRow').append(view.render().el);
|
||||
this.addView(view);
|
||||
|
||||
this._lastHeadItemView = view;
|
||||
},
|
||||
|
||||
_scrollToLeft: function (opts) {
|
||||
opts = opts || {};
|
||||
$('.Table').animate({
|
||||
scrollLeft: opts.pos || 0
|
||||
}, opts.speed || 'slow');
|
||||
},
|
||||
|
||||
_focusLastHeadItem: function () {
|
||||
if (this._lastHeadItemView) {
|
||||
this._lastHeadItemView.focusInput();
|
||||
}
|
||||
}
|
||||
});
|
||||
3
lib/assets/javascripts/builder/components/table/head/table-head.tpl
Executable file
3
lib/assets/javascripts/builder/components/table/head/table-head.tpl
Executable file
@@ -0,0 +1,3 @@
|
||||
<thead>
|
||||
<tr class="js-headRow"></tr>
|
||||
</thead>
|
||||
@@ -0,0 +1,34 @@
|
||||
var Notifier = require('builder/components/notifier/notifier');
|
||||
var errorParser = require('builder/helpers/error-parser');
|
||||
|
||||
module.exports = function (opts) {
|
||||
if (!opts.columnsCollection) throw new Error('columnsCollection is required');
|
||||
|
||||
var columnsCollection = opts.columnsCollection;
|
||||
var successCallback = opts.onSuccess;
|
||||
var errorCallback = opts.onError;
|
||||
var notification = Notifier.addNotification({
|
||||
status: 'loading',
|
||||
info: _t('components.table.columns.create.loading'),
|
||||
closable: false
|
||||
});
|
||||
|
||||
columnsCollection.addColumn({
|
||||
success: function (model, attrs) {
|
||||
successCallback && successCallback(model, attrs);
|
||||
notification.set({
|
||||
status: 'success',
|
||||
info: _t('components.table.columns.create.success', { columnName: attrs.name }),
|
||||
closable: true
|
||||
});
|
||||
},
|
||||
error: function (model, err) {
|
||||
errorCallback && errorCallback(model, err);
|
||||
notification.set({
|
||||
status: 'error',
|
||||
info: _t('components.table.columns.create.error', { error: errorParser(err) }),
|
||||
closable: true
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
34
lib/assets/javascripts/builder/components/table/operations/table-add-row.js
Executable file
34
lib/assets/javascripts/builder/components/table/operations/table-add-row.js
Executable file
@@ -0,0 +1,34 @@
|
||||
var Notifier = require('builder/components/notifier/notifier');
|
||||
var errorParser = require('builder/helpers/error-parser');
|
||||
|
||||
module.exports = function (opts) {
|
||||
if (!opts.rowsCollection) { throw new Error('rowsCollection is required'); }
|
||||
|
||||
var rowsCollection = opts.rowsCollection;
|
||||
var successCallback = opts.onSuccess;
|
||||
var errorCallback = opts.onError;
|
||||
var notification = Notifier.addNotification({
|
||||
status: 'loading',
|
||||
info: _t('components.table.rows.create.loading'),
|
||||
closable: false
|
||||
});
|
||||
|
||||
rowsCollection.addRow({
|
||||
success: function (model, attrs) {
|
||||
successCallback && successCallback(model, attrs);
|
||||
notification.set({
|
||||
status: 'success',
|
||||
info: _t('components.table.rows.create.success'),
|
||||
closable: true
|
||||
});
|
||||
},
|
||||
error: function (model, err) {
|
||||
errorCallback && errorCallback(model, err);
|
||||
notification.set({
|
||||
status: 'error',
|
||||
info: _t('components.table.rows.create.error', { error: errorParser(err) }),
|
||||
closable: true
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,50 @@
|
||||
var Notifier = require('builder/components/notifier/notifier');
|
||||
var errorParser = require('builder/helpers/error-parser');
|
||||
|
||||
module.exports = function (opts) {
|
||||
if (!opts.columnModel) { throw new Error('columnModel is required'); }
|
||||
if (!opts.newType) { throw new Error('newType is required'); }
|
||||
|
||||
var columnModel = opts.columnModel;
|
||||
var newType = opts.newType;
|
||||
|
||||
var successCallback = opts.onSuccess;
|
||||
var errorCallback = opts.onError;
|
||||
|
||||
var notification = Notifier.addNotification({
|
||||
status: 'loading',
|
||||
info: _t('components.table.columns.change-type.loading', {
|
||||
columnName: columnModel.get('name')
|
||||
}),
|
||||
closable: false
|
||||
});
|
||||
|
||||
columnModel.save({
|
||||
type: newType
|
||||
}, {
|
||||
wait: true,
|
||||
success: function (model, attrs) {
|
||||
successCallback && successCallback(model, attrs);
|
||||
notification.set({
|
||||
status: 'success',
|
||||
info: _t('components.table.columns.change-type.success', {
|
||||
columnName: columnModel.get('name'),
|
||||
newType: newType
|
||||
}),
|
||||
closable: true
|
||||
});
|
||||
},
|
||||
error: function (model, err) {
|
||||
errorCallback && errorCallback(model, err);
|
||||
notification.set({
|
||||
status: 'error',
|
||||
info: _t('components.table.columns.change-type.error', {
|
||||
columnName: columnModel.get('name'),
|
||||
newType: newType,
|
||||
error: errorParser(err)
|
||||
}),
|
||||
closable: true
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,51 @@
|
||||
var Notifier = require('builder/components/notifier/notifier');
|
||||
var errorParser = require('builder/helpers/error-parser');
|
||||
|
||||
module.exports = function (opts) {
|
||||
if (!opts.rowModel) throw new Error('rowModel is required');
|
||||
if (opts.newValue === undefined) throw new Error('newValue is required');
|
||||
if (!opts.attribute) throw new Error('attribute is required');
|
||||
|
||||
var rowModel = opts.rowModel;
|
||||
var newValue = opts.newValue;
|
||||
var attribute = opts.attribute;
|
||||
var cartodbId = rowModel.get('cartodb_id');
|
||||
var successCallback = opts.onSuccess;
|
||||
var errorCallback = opts.onError;
|
||||
var notification = Notifier.addNotification({
|
||||
status: 'loading',
|
||||
info: _t('components.table.rows.edit.loading', {
|
||||
attribute: attribute,
|
||||
cartodbId: cartodbId
|
||||
}),
|
||||
closable: false
|
||||
});
|
||||
|
||||
// In order to preserve "previous" attribute
|
||||
rowModel.set(attribute, newValue);
|
||||
rowModel.save(null, {
|
||||
success: function (model, attrs) {
|
||||
successCallback && successCallback(model, attrs);
|
||||
notification.set({
|
||||
status: 'success',
|
||||
info: _t('components.table.rows.edit.success', {
|
||||
attribute: attribute,
|
||||
cartodbId: cartodbId
|
||||
}),
|
||||
closable: true
|
||||
});
|
||||
},
|
||||
error: function (model, err) {
|
||||
errorCallback && errorCallback(model, err);
|
||||
notification.set({
|
||||
status: 'error',
|
||||
info: _t('components.table.rows.edit.error', {
|
||||
attribute: attribute,
|
||||
cartodbId: cartodbId,
|
||||
error: errorParser(err)
|
||||
}),
|
||||
closable: true
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
var Notifier = require('builder/components/notifier/notifier');
|
||||
var errorParser = require('builder/helpers/error-parser');
|
||||
|
||||
module.exports = function (opts) {
|
||||
if (!opts.columnModel) { throw new Error('columnModel is required'); }
|
||||
|
||||
var columnModel = opts.columnModel;
|
||||
var successCallback = opts.onSuccess;
|
||||
var errorCallback = opts.onError;
|
||||
var notification = Notifier.addNotification({
|
||||
status: 'loading',
|
||||
info: _t('components.table.columns.destroy.loading', { columnName: columnModel.get('name') }),
|
||||
closable: false
|
||||
});
|
||||
|
||||
columnModel.destroy({
|
||||
wait: true,
|
||||
success: function (model, attrs) {
|
||||
successCallback && successCallback(model, attrs);
|
||||
notification.set({
|
||||
status: 'success',
|
||||
info: _t('components.table.columns.destroy.success', { columnName: columnModel.get('name') }),
|
||||
closable: true
|
||||
});
|
||||
},
|
||||
error: function (model, err) {
|
||||
errorCallback && errorCallback(model, err);
|
||||
notification.set({
|
||||
status: 'error',
|
||||
info: _t('components.table.columns.destroy.error', {
|
||||
columnName: columnModel.get('name'),
|
||||
error: errorParser(err)
|
||||
}),
|
||||
closable: true
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
var Notifier = require('builder/components/notifier/notifier');
|
||||
var errorParser = require('builder/helpers/error-parser');
|
||||
|
||||
module.exports = function (opts) {
|
||||
if (!opts.rowModel) { throw new Error('rowModel is required'); }
|
||||
|
||||
var rowModel = opts.rowModel;
|
||||
var successCallback = opts.onSuccess;
|
||||
var errorCallback = opts.onError;
|
||||
var cartodbId = rowModel.get('cartodb_id');
|
||||
var notification = Notifier.addNotification({
|
||||
status: 'loading',
|
||||
info: _t('components.table.rows.destroy.loading', { cartodb_id: cartodbId }),
|
||||
closable: false
|
||||
});
|
||||
|
||||
rowModel.destroy({
|
||||
wait: true,
|
||||
success: function (model, attrs) {
|
||||
successCallback && successCallback(model, attrs);
|
||||
notification.set({
|
||||
status: 'success',
|
||||
info: _t('components.table.rows.destroy.success', { cartodb_id: cartodbId }),
|
||||
closable: true
|
||||
});
|
||||
},
|
||||
error: function (model, err) {
|
||||
errorCallback && errorCallback(model, err);
|
||||
notification.set({
|
||||
status: 'error',
|
||||
info: _t('components.table.rows.destroy.error', { cartodb_id: cartodbId, error: errorParser(err) }),
|
||||
closable: true
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,50 @@
|
||||
var Notifier = require('builder/components/notifier/notifier');
|
||||
var errorParser = require('builder/helpers/error-parser');
|
||||
|
||||
module.exports = function (opts) {
|
||||
if (!opts.columnModel) { throw new Error('columnModel is required'); }
|
||||
if (!opts.newName) { throw new Error('newName is required'); }
|
||||
|
||||
var columnModel = opts.columnModel;
|
||||
var oldName = columnModel.get('name');
|
||||
var newName = opts.newName;
|
||||
|
||||
var successCallback = opts.onSuccess;
|
||||
var errorCallback = opts.onError;
|
||||
|
||||
var notification = Notifier.addNotification({
|
||||
status: 'loading',
|
||||
info: _t('components.table.columns.rename.loading', { oldName: oldName, newName: newName }),
|
||||
closable: false
|
||||
});
|
||||
|
||||
columnModel.save({
|
||||
new_name: newName,
|
||||
old_name: columnModel.get('name')
|
||||
}, {
|
||||
wait: true,
|
||||
success: function (model, attrs) {
|
||||
successCallback && successCallback(model, attrs);
|
||||
notification.set({
|
||||
status: 'success',
|
||||
info: _t('components.table.columns.rename.success', {
|
||||
columnName: oldName,
|
||||
newName: newName
|
||||
}),
|
||||
closable: true
|
||||
});
|
||||
},
|
||||
error: function (model, err) {
|
||||
errorCallback && errorCallback(model, err);
|
||||
notification.set({
|
||||
status: 'error',
|
||||
info: _t('components.table.columns.rename.error', {
|
||||
columnName: oldName,
|
||||
newName: newName,
|
||||
error: errorParser(err)
|
||||
}),
|
||||
closable: true
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
<div class="CDB-LoaderIcon is-dark">
|
||||
<svg class="CDB-LoaderIcon-spinner" viewbox="0 0 50 50">
|
||||
<circle class="CDB-LoaderIcon-path" cx="25" cy="25" r="20" fill="none"/>
|
||||
</svg>
|
||||
</div>
|
||||
@@ -0,0 +1,132 @@
|
||||
var _ = require('underscore');
|
||||
var CoreView = require('backbone/core-view');
|
||||
var template = require('./table-paginator.tpl');
|
||||
var templateLoader = require('./table-paginator-loader.tpl');
|
||||
var Notifier = require('builder/components/notifier/notifier');
|
||||
var errorParser = require('builder/helpers/error-parser');
|
||||
var QueryUtilsModel = require('builder/helpers/query-utils-model');
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
tagName: 'div',
|
||||
className: 'Table-paginator js-tablePaginator',
|
||||
|
||||
events: {
|
||||
'click .js-prev': '_onPrevPage',
|
||||
'click .js-next': '_onNextPage'
|
||||
},
|
||||
|
||||
initialize: function (opts) {
|
||||
if (!opts.rowsCollection) throw new Error('rowsCollection is required');
|
||||
if (!opts.tableViewModel) throw new Error('tableViewModel is required');
|
||||
|
||||
this._tableViewModel = opts.tableViewModel;
|
||||
this._rowsCollection = opts.rowsCollection;
|
||||
this._scrollToBottom = opts.scrollToBottom;
|
||||
this._numRows = -1;
|
||||
|
||||
this._queryUtilsModel = opts.queryUtilsModel || new QueryUtilsModel({
|
||||
subquery: this._rowsCollection._querySchemaModel.get('query'),
|
||||
configModel: this._rowsCollection._configModel
|
||||
});
|
||||
|
||||
this._queryUtilsModel.fetchCount(function (numRows) {
|
||||
// Check total number of rows to evaluate
|
||||
// if there is a next page available
|
||||
this._numRows = numRows;
|
||||
this.render();
|
||||
}.bind(this));
|
||||
|
||||
this._rowsCollection.bind('loading', function (item) {
|
||||
// Don't take into account row model loading triggers
|
||||
if (item.models) {
|
||||
this._isLoading = true;
|
||||
}
|
||||
}, this);
|
||||
this._rowsCollection.bind('add', this._onRowAdded, this);
|
||||
this._rowsCollection.bind('remove', this._onRowRemoved, this);
|
||||
this._tableViewModel.bind('change:page', this._onTablePageChange, this);
|
||||
this.add_related_model(this._tableViewModel);
|
||||
this.add_related_model(this._rowsCollection);
|
||||
},
|
||||
|
||||
render: function () {
|
||||
var rowsSize = this._rowsCollection.size();
|
||||
var page = this._tableViewModel.get('page');
|
||||
var maxRowsPerPage = this._rowsCollection.DEFAULT_FETCH_OPTIONS.rows_per_page;
|
||||
|
||||
this.$el.html(
|
||||
template({
|
||||
page: this._tableViewModel.get('page'),
|
||||
size: rowsSize,
|
||||
isNextAvailable: this._numRows > (page + 1) * maxRowsPerPage,
|
||||
isPrevAvailable: page > 0
|
||||
})
|
||||
);
|
||||
|
||||
this.$el.toggleClass('Table-paginator--relative', !!this._tableViewModel.get('relativePositionated'));
|
||||
return this;
|
||||
},
|
||||
|
||||
_onRowAdded: function () {
|
||||
this._queryUtilsModel.fetchCount(function (numRows) {
|
||||
if (numRows > -1) {
|
||||
// Go to the last page
|
||||
var maxRowsPerPage = this._rowsCollection.DEFAULT_FETCH_OPTIONS.rows_per_page;
|
||||
var lastPage = Math.ceil(numRows / maxRowsPerPage) - 1;
|
||||
if (lastPage !== this._tableViewModel.get('page')) {
|
||||
this._tableViewModel.set('page', lastPage);
|
||||
} else {
|
||||
this.render();
|
||||
}
|
||||
setTimeout(this._scrollToBottom, 200);
|
||||
}
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
_onRowRemoved: function () {
|
||||
this._rowsCollection.fetch({
|
||||
data: this._getData()
|
||||
});
|
||||
},
|
||||
|
||||
_onTablePageChange: function () {
|
||||
this._rowsCollection.fetch({
|
||||
data: this._getData(),
|
||||
error: function (mdl, response) {
|
||||
this._isLoading = false;
|
||||
this.render();
|
||||
|
||||
Notifier.addNotification({
|
||||
status: 'error',
|
||||
info: _t('components.table.rows.paginator.error', { error: errorParser(response) }),
|
||||
closable: true
|
||||
});
|
||||
}.bind(this)
|
||||
});
|
||||
},
|
||||
|
||||
_getData: function () {
|
||||
return _.extend(
|
||||
this._tableViewModel.pick('page', 'order_by', 'sort_order'),
|
||||
{
|
||||
exclude: !this._tableViewModel.isCustomQueryApplied() ? ['the_geom_webmercator'] : []
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
_onPrevPage: function (ev) {
|
||||
if (!this._isLoading) {
|
||||
this.$('.js-prev').html(templateLoader());
|
||||
this._tableViewModel.set('page', this._tableViewModel.get('page') - 1);
|
||||
}
|
||||
},
|
||||
|
||||
_onNextPage: function (ev) {
|
||||
if (!this._isLoading) {
|
||||
this.$('.js-next').html(templateLoader());
|
||||
this._tableViewModel.set('page', this._tableViewModel.get('page') + 1);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,37 @@
|
||||
<button class="Table-paginatorButton Table-paginatorButton--prev
|
||||
<% if (isPrevAvailable) { %>
|
||||
js-prev
|
||||
is-active
|
||||
<% } %>
|
||||
">
|
||||
<i class="CDB-IconFont is-semibold CDB-IconFont-lArrowLight CDB-Size-small
|
||||
<% if (isPrevAvailable) { %>
|
||||
u-actionTextColor
|
||||
<% } else { %>
|
||||
u-hintTextColor
|
||||
<% } %>
|
||||
"></i>
|
||||
</button>
|
||||
<p class="Table-paginatorText CDB-Text CDB-Size-small is-semibold u-upperCase">
|
||||
<% if (!size) {%>
|
||||
<span class="u-mainTextColor">…</span>
|
||||
<% } else { %>
|
||||
<span class="u-mainTextColor"><%- (page * 40) + 1 %></span>
|
||||
<span class="u-altTextColor u-lSpace u-rSpace"><%- _t('components.table.rows.paginator.to') %></span>
|
||||
<span class="u-mainTextColor"><%- (page * 40) + size %></span>
|
||||
<% } %>
|
||||
</p>
|
||||
<button class="Table-paginatorButton Table-paginatorButton--next
|
||||
<% if (isNextAvailable) { %>
|
||||
js-next
|
||||
is-active
|
||||
<% } %>
|
||||
">
|
||||
<i class="CDB-IconFont is-semibold CDB-IconFont-rArrowLight CDB-Size-small
|
||||
<% if (isNextAvailable) { %>
|
||||
u-actionTextColor
|
||||
<% } else { %>
|
||||
u-hintTextColor
|
||||
<% } %>
|
||||
"></i>
|
||||
</button>
|
||||
56
lib/assets/javascripts/builder/components/table/table-manager.js
Executable file
56
lib/assets/javascripts/builder/components/table/table-manager.js
Executable file
@@ -0,0 +1,56 @@
|
||||
var TableView = require('./table-view');
|
||||
var QueryColumnsCollection = require('builder/data/query-columns-collection');
|
||||
var TableNameUtils = require('builder/helpers/table-name-utils');
|
||||
var checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
|
||||
var REQUIRED_OPTS = [
|
||||
'analysisDefinitionNodeModel',
|
||||
'configModel',
|
||||
'modals',
|
||||
'userModel'
|
||||
];
|
||||
|
||||
/**
|
||||
* Table manager that generates necessary models/collections
|
||||
* for the view
|
||||
*
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
create: function (opts) {
|
||||
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
|
||||
|
||||
// Adds the owner username if it's in an organization and the table name is not already qualified
|
||||
var tableName = this._analysisDefinitionNodeModel.get('table_name');
|
||||
var userName = TableNameUtils.getUsername(tableName) || this._userModel.get('username');
|
||||
tableName = TableNameUtils.getQualifiedTableName(
|
||||
tableName,
|
||||
userName,
|
||||
this._userModel.isInsideOrg()
|
||||
);
|
||||
|
||||
var querySchemaModel = this._analysisDefinitionNodeModel.querySchemaModel;
|
||||
|
||||
var columnsCollection = new QueryColumnsCollection(
|
||||
querySchemaModel.columnsCollection.toJSON(),
|
||||
{
|
||||
configModel: this._configModel,
|
||||
tableName: tableName,
|
||||
querySchemaModel: querySchemaModel
|
||||
}
|
||||
);
|
||||
|
||||
return new TableView({
|
||||
relativePositionated: opts.relativePositionated,
|
||||
modals: this._modals,
|
||||
analysisDefinitionNodeModel: this._analysisDefinitionNodeModel,
|
||||
columnsCollection: columnsCollection,
|
||||
tableName: tableName
|
||||
});
|
||||
},
|
||||
|
||||
destroy: function (tableView) {
|
||||
if (!tableView) throw new Error('tableView object is needed');
|
||||
tableView.clean();
|
||||
}
|
||||
};
|
||||
58
lib/assets/javascripts/builder/components/table/table-view-model.js
Executable file
58
lib/assets/javascripts/builder/components/table/table-view-model.js
Executable file
@@ -0,0 +1,58 @@
|
||||
var Backbone = require('backbone');
|
||||
|
||||
/**
|
||||
* Table view model
|
||||
*/
|
||||
|
||||
module.exports = Backbone.Model.extend({
|
||||
defaults: {
|
||||
page: 0,
|
||||
order_by: '',
|
||||
sort_order: '',
|
||||
readonly: false,
|
||||
tableName: ''
|
||||
},
|
||||
|
||||
initialize: function (attrs, opts) {
|
||||
if (!opts.analysisDefinitionNodeModel) throw new Error('analysisDefinitionNodeModel is required');
|
||||
if (!opts.columnsCollection) throw new Error('columnsCollection is required');
|
||||
|
||||
this._analysisDefinitionNodeModel = opts.analysisDefinitionNodeModel;
|
||||
this._columnsCollection = opts.columnsCollection;
|
||||
|
||||
this._setOrderAndSort();
|
||||
|
||||
this.listenTo(this._columnsCollection, 'reset', this._setOrderAndSort);
|
||||
},
|
||||
|
||||
_setOrderAndSort: function () {
|
||||
var hasCartodbId = this._columnsCollection.where({name: 'cartodb_id'}).length;
|
||||
|
||||
if (hasCartodbId && !this.isCustomQueryApplied()) {
|
||||
this.set({
|
||||
'order_by': 'cartodb_id',
|
||||
'sort_order': 'asc'
|
||||
}, {
|
||||
silent: true
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
resetFetchDefaults: function () {
|
||||
this.set({
|
||||
page: this.defaults.page,
|
||||
order_by: '',
|
||||
sort_order: ''
|
||||
}, {
|
||||
silent: true
|
||||
});
|
||||
},
|
||||
|
||||
isDisabled: function () {
|
||||
return this._analysisDefinitionNodeModel.isReadOnly();
|
||||
},
|
||||
|
||||
isCustomQueryApplied: function () {
|
||||
return this._analysisDefinitionNodeModel.isCustomQueryApplied();
|
||||
}
|
||||
});
|
||||
173
lib/assets/javascripts/builder/components/table/table-view.js
Executable file
173
lib/assets/javascripts/builder/components/table/table-view.js
Executable file
@@ -0,0 +1,173 @@
|
||||
var CoreView = require('backbone/core-view');
|
||||
var _ = require('underscore');
|
||||
var $ = require('jquery');
|
||||
var TableViewModel = require('./table-view-model.js');
|
||||
var TableHeadView = require('./head/table-head-view');
|
||||
var TableBodyView = require('./body/table-body-view');
|
||||
var addTableRow = require('./operations/table-add-row');
|
||||
var addTableColumn = require('./operations/table-add-column');
|
||||
var SQLUtils = require('builder/helpers/sql-utils');
|
||||
var checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
const { nodeHasTradeArea, nodeHasSQLFunction } = require('builder/helpers/analysis-node-utils');
|
||||
|
||||
var REQUIRED_OPTS = [
|
||||
'analysisDefinitionNodeModel',
|
||||
'columnsCollection',
|
||||
'modals'
|
||||
];
|
||||
|
||||
/*
|
||||
* Main table view
|
||||
*/
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
className: 'Table-wrapper',
|
||||
tagName: 'div',
|
||||
|
||||
initialize: function (opts) {
|
||||
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
|
||||
|
||||
this._rowsCollection = this._analysisDefinitionNodeModel.queryRowsCollection;
|
||||
this._queryGeometryModel = this._analysisDefinitionNodeModel.queryGeometryModel;
|
||||
this._querySchemaModel = this._analysisDefinitionNodeModel.querySchemaModel;
|
||||
this._tableModel = this._analysisDefinitionNodeModel.isSourceType() && this._analysisDefinitionNodeModel.getTableModel();
|
||||
|
||||
this._tableViewModel = new TableViewModel({
|
||||
relativePositionated: opts.relativePositionated,
|
||||
tableName: opts.tableName
|
||||
}, {
|
||||
analysisDefinitionNodeModel: this._analysisDefinitionNodeModel,
|
||||
columnsCollection: this._columnsCollection
|
||||
});
|
||||
|
||||
this._initBinds();
|
||||
|
||||
if (this._querySchemaModel.shouldFetch()) {
|
||||
this._querySchemaModel.fetch();
|
||||
} else if (this._rowsCollection.shouldFetch()) {
|
||||
this._fetchRowsData();
|
||||
}
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
this.$el.empty();
|
||||
var tableElement = $('<div>').addClass('Table js-table');
|
||||
this.$el.append(tableElement);
|
||||
this._initViews();
|
||||
|
||||
this.$('.js-table').toggleClass('Table--relative', !!this._tableViewModel.get('relativePositionated'));
|
||||
this._setDisableState();
|
||||
return this;
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this._onChangeQueryOrStatus = this._onChangeQueryOrStatus.bind(this);
|
||||
|
||||
this.listenTo(this._querySchemaModel, 'change:query change:status', _.debounce(this._onChangeQueryOrStatus, 100));
|
||||
this.listenTo(this._querySchemaModel, 'change:status', this._setDisableState);
|
||||
this.listenTo(this._querySchemaModel, 'change:query', this._onQueryChanged);
|
||||
this.listenTo(this._querySchemaModel, 'change:status', this._fetchRowsData);
|
||||
this.listenTo(this._tableViewModel, 'change:sort_order change:order_by', this._fetchRowsData);
|
||||
|
||||
if (this._tableModel && this._tableModel.isSync()) {
|
||||
var syncModel = this._tableModel.getSyncModel();
|
||||
this.listenTo(syncModel, 'destroy', this.render);
|
||||
}
|
||||
},
|
||||
|
||||
_setDisableState: function () {
|
||||
this.$('.js-table').toggleClass('is-disabled', !!this._tableViewModel.isDisabled());
|
||||
},
|
||||
|
||||
_fetchRowsData: function (model) {
|
||||
var attrsChanged = model && _.keys(model.changed);
|
||||
var altersData = SQLUtils.altersData(this._querySchemaModel.get('query'));
|
||||
var isCustomQueryApplied = this._tableViewModel.isCustomQueryApplied();
|
||||
var changeComeFromSortOrOrder = false;
|
||||
|
||||
if (attrsChanged && (_.contains(attrsChanged, 'sort_order') || _.contains(attrsChanged, 'order_by'))) {
|
||||
changeComeFromSortOrOrder = true;
|
||||
}
|
||||
|
||||
if (this._querySchemaModel.isFetched() && !altersData) {
|
||||
this._rowsCollection.fetch({
|
||||
data: {
|
||||
page: this._tableViewModel.get('page'),
|
||||
order_by: !isCustomQueryApplied || changeComeFromSortOrOrder ? this._tableViewModel.get('order_by') : '',
|
||||
sort_order: !isCustomQueryApplied || changeComeFromSortOrOrder ? this._tableViewModel.get('sort_order') : '',
|
||||
exclude: !isCustomQueryApplied || changeComeFromSortOrOrder ? ['the_geom_webmercator'] : []
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_initViews: function () {
|
||||
const canHideColumns = nodeHasTradeArea(this._analysisDefinitionNodeModel) &&
|
||||
!nodeHasSQLFunction(this._analysisDefinitionNodeModel);
|
||||
|
||||
var tableHeadView = new TableHeadView({
|
||||
modals: this._modals,
|
||||
queryGeometryModel: this._queryGeometryModel,
|
||||
columnsCollection: this._columnsCollection,
|
||||
tableViewModel: this._tableViewModel,
|
||||
canHideColumns
|
||||
});
|
||||
|
||||
this.addView(tableHeadView);
|
||||
this.$('.js-table').append(tableHeadView.render().el);
|
||||
|
||||
var tableBodyView = new TableBodyView({
|
||||
modals: this._modals,
|
||||
columnsCollection: this._columnsCollection,
|
||||
rowsCollection: this._rowsCollection,
|
||||
queryGeometryModel: this._queryGeometryModel,
|
||||
querySchemaModel: this._querySchemaModel,
|
||||
canHideColumns,
|
||||
tableViewModel: this._tableViewModel
|
||||
});
|
||||
|
||||
this.addView(tableBodyView);
|
||||
this.$('.js-table').append(tableBodyView.render().el);
|
||||
},
|
||||
|
||||
_onChangeQueryOrStatus: function () {
|
||||
var schemaStatus = this._querySchemaModel.get('status');
|
||||
var geometryStatus = this._queryGeometryModel.get('status');
|
||||
|
||||
if (schemaStatus === 'unfetched' && this._querySchemaModel.canFetch()) {
|
||||
this._querySchemaModel.fetch();
|
||||
}
|
||||
|
||||
if (geometryStatus === 'unfetched' && this._queryGeometryModel.canFetch()) {
|
||||
this._queryGeometryModel.fetch();
|
||||
}
|
||||
},
|
||||
|
||||
addRow: function () {
|
||||
addTableRow({
|
||||
rowsCollection: this._rowsCollection
|
||||
});
|
||||
},
|
||||
|
||||
addColumn: function () {
|
||||
addTableColumn({
|
||||
columnsCollection: this._columnsCollection
|
||||
});
|
||||
},
|
||||
|
||||
getColumnsCollection: function () {
|
||||
return this._columnsCollection;
|
||||
},
|
||||
|
||||
_onQueryChanged: function () {
|
||||
this._tableViewModel.resetFetchDefaults();
|
||||
},
|
||||
|
||||
// Remove elements that are not applied in the view
|
||||
remove: function () {
|
||||
$('.Table-paginator').remove();
|
||||
CoreView.prototype.remove.apply(this);
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user