578 lines
16 KiB
JavaScript
578 lines
16 KiB
JavaScript
|
|
||
|
/**
|
||
|
* Common header for vis view ( table | derived )
|
||
|
*
|
||
|
* - It needs a visualization model, config and user data.
|
||
|
*
|
||
|
* var header = new cdb.admin.Header({
|
||
|
* el: this.$('header'),
|
||
|
* model: visusalization_model,
|
||
|
* user: user_model,
|
||
|
* config: config,
|
||
|
* geocoder: geocoder
|
||
|
* });
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
cdb.admin.Header = cdb.core.View.extend({
|
||
|
|
||
|
_TEXTS: {
|
||
|
saving: _t('Saving...'),
|
||
|
saved: _t('Saved'),
|
||
|
error: _t('Something went wrong, try again later'),
|
||
|
metadata: {
|
||
|
edit: _t('Edit metadata...'),
|
||
|
view: _t('View metadata...')
|
||
|
},
|
||
|
visualization: {
|
||
|
loader: _t('Changing to visualization'),
|
||
|
created: _t('Visualization created')
|
||
|
},
|
||
|
share: {
|
||
|
publish: _t('PUBLISH'),
|
||
|
visualize: _t('VISUALIZE')
|
||
|
},
|
||
|
share_privacy: {
|
||
|
ok_next: _t('Share it now!')
|
||
|
},
|
||
|
rename: {
|
||
|
readonly: _t('It is not possible to rename<br/>the dataset in <%- mode %> mode'),
|
||
|
owner: _t('It is not possible to rename<br/>the dataset if you are not the owner')
|
||
|
}
|
||
|
},
|
||
|
|
||
|
_MAX_DESCRIPTION_LENGTH: 200,
|
||
|
|
||
|
events: {
|
||
|
'click a.title': '_changeTitle',
|
||
|
'click .metadata a': '_changeMetadata',
|
||
|
'click a.options': '_openOptionsMenu',
|
||
|
'click a.share': '_shareVisualization',
|
||
|
'click a.privacy': '_showPrivacyDialog',
|
||
|
'click header nav a': '_onTabClick'
|
||
|
},
|
||
|
|
||
|
initialize: function(options) {
|
||
|
|
||
|
_.bindAll(this, '_changeTitle', '_setPrivacy');
|
||
|
|
||
|
this.$body = $('body');
|
||
|
this.dataLayer = null;
|
||
|
this.globalError = this.options.globalError;
|
||
|
this._initBinds();
|
||
|
|
||
|
// Display all the visualization info
|
||
|
this.setInfo();
|
||
|
},
|
||
|
|
||
|
// Set new dataLayer from the current layerView
|
||
|
setActiveLayer: function(layerView) {
|
||
|
// Clean before bindings
|
||
|
if (this.dataLayer) {
|
||
|
this.dataLayer.unbind('applySQLView applyFilter errorSQLView clearSQLView', this.setEditableInfo, this);
|
||
|
this.dataLayer.table.unbind('change:isSync', this.setEditableInfo, this);
|
||
|
this.dataLayer.table.unbind('change:permission', this.setInfo, this);
|
||
|
}
|
||
|
|
||
|
// Set new datalayer
|
||
|
this.dataLayer = layerView.model;
|
||
|
|
||
|
// Apply bindings if model is not a visualization
|
||
|
if (!this.model.isVisualization()) {
|
||
|
this.dataLayer.bind('applySQLView applyFilter errorSQLView clearSQLView', this.setEditableInfo, this);
|
||
|
this.dataLayer.table.bind('change:isSync', this.setEditableInfo, this);
|
||
|
this.dataLayer.table.bind('change:permission', this.setInfo, this);
|
||
|
this.setEditableInfo();
|
||
|
}
|
||
|
|
||
|
},
|
||
|
|
||
|
_initBinds: function() {
|
||
|
this.model.bind('change:name', this._setName, this);
|
||
|
this.model.bind('change:type', this.setInfo, this);
|
||
|
this.model.bind('change:privacy', this._setPrivacy, this);
|
||
|
this.model.bind('change:permission', this._setSharedCount, this);
|
||
|
},
|
||
|
|
||
|
_openOptionsMenu: function(e) {
|
||
|
this.killEvent(e);
|
||
|
|
||
|
var self = this;
|
||
|
var $target = $(e.target);
|
||
|
|
||
|
// Options menu
|
||
|
this.options_menu = new cdb.admin.HeaderOptionsMenu({
|
||
|
target: $(e.target),
|
||
|
model: this.model, // master_vis
|
||
|
dataLayer: this.dataLayer,
|
||
|
user: this.options.user,
|
||
|
private_tables: this.options.user.get("actions").private_tables,
|
||
|
geocoder: this.options.geocoder,
|
||
|
backgroundPollingModel: this.options.backgroundPollingModel,
|
||
|
globalError: this.options.globalError,
|
||
|
template_base: 'table/header/views/options_menu'
|
||
|
}).bind("onDropdownShown",function(ev) {
|
||
|
cdb.god.unbind("closeDialogs", self.options_menu.hide, self.options_menu);
|
||
|
cdb.god.trigger("closeDialogs");
|
||
|
cdb.god.bind("closeDialogs", self.options_menu.hide, self.options_menu);
|
||
|
}).bind('onDropdownHidden', function() {
|
||
|
this.clean();
|
||
|
$target.unbind('click');
|
||
|
cdb.god.unbind(null, null, self.options_menu);
|
||
|
});
|
||
|
|
||
|
this.$body.append(this.options_menu.render().el);
|
||
|
this.options_menu.open(e);
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Share visualization function, it could show
|
||
|
* the name dialog to create a new visualization
|
||
|
* or directly the share dialog :).
|
||
|
*/
|
||
|
_shareVisualization: function(e) {
|
||
|
this.killEvent(e);
|
||
|
|
||
|
var view;
|
||
|
if (this.model.isVisualization()) {
|
||
|
view = new cdb.editor.PublishView({
|
||
|
clean_on_hide: true,
|
||
|
enter_to_confirm: true,
|
||
|
user: this.options.user,
|
||
|
model: this.model // vis
|
||
|
});
|
||
|
} else {
|
||
|
view = new cdb.editor.CreateVisFirstView({
|
||
|
clean_on_hide: true,
|
||
|
enter_to_confirm: true,
|
||
|
model: this.model,
|
||
|
router: window.table_router,
|
||
|
title: 'A map is required to publish',
|
||
|
explanation: 'A map is a shareable mix of layers, styles and queries. You can view all your maps in your dashboard.'
|
||
|
});
|
||
|
}
|
||
|
view.appendToBody();
|
||
|
},
|
||
|
|
||
|
_showPrivacyDialog: function(e) {
|
||
|
if (e) this.killEvent(e);
|
||
|
|
||
|
if (this.model.isOwnedByUser(this.options.user)) {
|
||
|
var dialog = new cdb.editor.ChangePrivacyView({
|
||
|
vis: this.model, //vis
|
||
|
user: this.options.user,
|
||
|
enter_to_confirm: true,
|
||
|
clean_on_hide: true
|
||
|
});
|
||
|
dialog.appendToBody();
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Set visualization info
|
||
|
*/
|
||
|
setInfo: function() {
|
||
|
this._setName();
|
||
|
this._setSyncInfo();
|
||
|
this._setVisualization();
|
||
|
this._setMetadata();
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Set editable visualization info
|
||
|
*/
|
||
|
setEditableInfo: function() {
|
||
|
this._setName();
|
||
|
this._setSyncInfo();
|
||
|
this._setMetadata();
|
||
|
},
|
||
|
|
||
|
_setPrivacy: function() {
|
||
|
|
||
|
var $share = this.$('a.privacy');
|
||
|
|
||
|
// Update shared count if it is neccessary
|
||
|
this._setSharedCount();
|
||
|
|
||
|
var privacy = this.model.get("privacy").toLowerCase();
|
||
|
|
||
|
if (privacy == "public") {
|
||
|
|
||
|
$share
|
||
|
.removeClass("private")
|
||
|
.removeClass("link_protected")
|
||
|
.removeClass("password_protected")
|
||
|
.removeClass("organization")
|
||
|
.addClass("public");
|
||
|
|
||
|
} else if (privacy == "link"){
|
||
|
|
||
|
$share
|
||
|
.removeClass("public")
|
||
|
.removeClass("private")
|
||
|
.removeClass("password_protected")
|
||
|
.removeClass("organization")
|
||
|
.addClass("link_protected");
|
||
|
|
||
|
} else if (privacy == "private"){
|
||
|
|
||
|
$share
|
||
|
.removeClass("public")
|
||
|
.removeClass("link_protected")
|
||
|
.removeClass("password_protected")
|
||
|
.removeClass("organization")
|
||
|
.addClass("private");
|
||
|
|
||
|
} else if (privacy == "password"){
|
||
|
|
||
|
$share
|
||
|
.removeClass("private")
|
||
|
.removeClass("link_protected")
|
||
|
.removeClass("public")
|
||
|
.removeClass("organization")
|
||
|
.addClass("password_protected");
|
||
|
|
||
|
} else if (privacy == "organization"){
|
||
|
|
||
|
$share
|
||
|
.removeClass("private")
|
||
|
.removeClass("link_protected")
|
||
|
.removeClass("public")
|
||
|
.removeClass("password_protected")
|
||
|
.addClass("organization");
|
||
|
|
||
|
}
|
||
|
|
||
|
// User is owner of this visualization (table or derived)?
|
||
|
var isOwner = this.model.permission.isOwner(this.options.user);
|
||
|
$share.find('i')[ isOwner ? 'removeClass' : 'addClass' ]('disabled');
|
||
|
|
||
|
},
|
||
|
|
||
|
_setSharedCount: function() {
|
||
|
var isOwner = this.model.permission.isOwner(this.options.user);
|
||
|
var $share = this.$('a.privacy i');
|
||
|
|
||
|
$share.empty();
|
||
|
|
||
|
if (isOwner) {
|
||
|
var $count = $('<span>').addClass('shared_users');
|
||
|
|
||
|
if (this.model.permission.acl.size() > 0) {
|
||
|
// Get total shared users or if the whole organization has access
|
||
|
var shared_users = 0;
|
||
|
var users_perm = this.model.permission.getUsersWithAnyPermission();
|
||
|
|
||
|
if (this.model.permission.isSharedWithOrganization()) {
|
||
|
shared_users = 'ORG';
|
||
|
} else {
|
||
|
shared_users = users_perm.length;
|
||
|
}
|
||
|
|
||
|
$count.text( (shared_users !== 0) ? shared_users : '' );
|
||
|
|
||
|
$share.append($count);
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Change metadata link text
|
||
|
*/
|
||
|
_setMetadata: function() {
|
||
|
var isOwner = this.model.permission.isOwner(this.options.user);
|
||
|
var $metadata = this.$('.metadata a');
|
||
|
|
||
|
var text = this._TEXTS.metadata.edit;
|
||
|
var href = "#/edit-metadata";
|
||
|
|
||
|
if (!isOwner) {
|
||
|
text = this._TEXTS.metadata.view;
|
||
|
href = "#/view-metadata";
|
||
|
}
|
||
|
|
||
|
$metadata
|
||
|
.attr('href', href)
|
||
|
.text(text);
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Set layer sync info if it is needed
|
||
|
*/
|
||
|
_setSyncInfo: function() {
|
||
|
this.sync_info && this.sync_info.clean();
|
||
|
|
||
|
if (!this.model.isVisualization() && this.isSyncTable()) {
|
||
|
this.$el.addClass('synced');
|
||
|
|
||
|
this.sync_info = new cdb.admin.SyncInfo({
|
||
|
dataLayer: this.dataLayer,
|
||
|
user: this.options.user
|
||
|
});
|
||
|
|
||
|
this.$('.sync_status').append(this.sync_info.render().el);
|
||
|
this.addView(this.sync_info);
|
||
|
|
||
|
} else {
|
||
|
this.$el.removeClass('synced');
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Set name of the visualization
|
||
|
*/
|
||
|
_setName: function() {
|
||
|
var $title = this.$('h1 a.title');
|
||
|
|
||
|
$title
|
||
|
[(this.isVisEditable() && !this.isSyncTable()) ? 'removeClass' : 'addClass' ]('disabled')
|
||
|
.text(this.model.get('name'))
|
||
|
|
||
|
document.title = this.model.get('name') + " | CARTO";
|
||
|
},
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Set visualization type and change share button
|
||
|
*/
|
||
|
_setVisualization: function() {
|
||
|
// Change visualization type
|
||
|
var $back = this.$('a.back');
|
||
|
var $share = this.$('a.share');
|
||
|
var is_visualization = this.model.isVisualization();
|
||
|
|
||
|
if (is_visualization) {
|
||
|
$share.find("span").text(this._TEXTS.share.publish);
|
||
|
this._setPrivacy();
|
||
|
var route = cdb.config.prefixUrl() + "/dashboard/maps";
|
||
|
$back.attr("href", route );
|
||
|
} else {
|
||
|
$share.find("span").text(this._TEXTS.share.visualize);
|
||
|
this._setPrivacy();
|
||
|
var route = cdb.config.prefixUrl() + "/dashboard/datasets";
|
||
|
$back.attr("href", route );
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Change visualization metadata
|
||
|
*/
|
||
|
_changeMetadata: function(ev) {
|
||
|
ev.preventDefault();
|
||
|
|
||
|
var dlg = new cdb.editor.EditVisMetadataView({
|
||
|
maxLength: this._MAX_DESCRIPTION_LENGTH,
|
||
|
vis: this.model,
|
||
|
dataLayer: this.dataLayer && this.dataLayer.table,
|
||
|
user: this.options.user,
|
||
|
clean_on_hide: true,
|
||
|
enter_to_confirm: false,
|
||
|
onShowPrivacy: this._showPrivacyDialog.bind(this),
|
||
|
onDone: this._onChangeMetadata.bind(this)
|
||
|
});
|
||
|
|
||
|
dlg.appendToBody();
|
||
|
},
|
||
|
|
||
|
_onChangeMetadata: function(nameChanged) {
|
||
|
// Check if attr saved is name to change url when
|
||
|
// visualization is table type
|
||
|
if (nameChanged && !this.model.isVisualization()) {
|
||
|
window.table_router.navigate(this._generateTableUrl(), {trigger: false});
|
||
|
window.table_router.addToHistory();
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Change visualization title
|
||
|
*/
|
||
|
_changeTitle: function(e) {
|
||
|
this.killEvent(e);
|
||
|
|
||
|
var self = this;
|
||
|
var isOwner = this.model.permission.isOwner(this.options.user);
|
||
|
|
||
|
if (this.isVisEditable()) {
|
||
|
this.title_dialog && this.title_dialog.clean();
|
||
|
cdb.god.trigger("closeDialogs");
|
||
|
|
||
|
var title_dialog = this.title_dialog = new cdb.admin.EditTextDialog({
|
||
|
initial_value: this.model.get('name'),
|
||
|
template_name: 'table/views/edit_name',
|
||
|
clean_on_hide: true,
|
||
|
modal_class: 'edit_name_dialog',
|
||
|
onResponse: setTitle
|
||
|
});
|
||
|
|
||
|
cdb.god.bind("closeDialogs", title_dialog.hide, title_dialog);
|
||
|
|
||
|
// Set position and show
|
||
|
var pos = $(e.target).offset();
|
||
|
pos.left -= $(window).scrollLeft()
|
||
|
pos.top -= $(window).scrollTop()
|
||
|
var w = Math.max($(e.target).width() + 100, 280);
|
||
|
title_dialog.showAt(pos.left - 20, pos.top - 10, w);
|
||
|
} else {
|
||
|
var $el = $(e.target);
|
||
|
$el
|
||
|
.bind('mouseleave', destroyTipsy)
|
||
|
.tipsy({
|
||
|
fade: true,
|
||
|
trigger: 'manual',
|
||
|
html: true,
|
||
|
title: function() {
|
||
|
var mode = self.isSyncTable() ? 'sync' : 'read-only';
|
||
|
return _.template(self._TEXTS.rename[ !isOwner ? 'owner' : 'readonly' ])({ mode: mode })
|
||
|
}
|
||
|
})
|
||
|
.tipsy('show')
|
||
|
}
|
||
|
|
||
|
function destroyTipsy() {
|
||
|
var $el = $(this);
|
||
|
var tipsy = $el.data('tipsy');
|
||
|
if (tipsy) {
|
||
|
$el
|
||
|
.tipsy('hide')
|
||
|
.unbind('mouseleave', destroyTipsy);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function setTitle(val) {
|
||
|
if (val !== self.model.get('name') && val != '') {
|
||
|
// Sanitize description (html and events)
|
||
|
var title = cdb.Utils.stripHTML(val,'');
|
||
|
|
||
|
if (self.model.isVisualization()) {
|
||
|
self._onSetAttributes({ name: title });
|
||
|
} else {
|
||
|
// close any prev modal if existing
|
||
|
if (self.change_confirmation) {
|
||
|
self.change_confirmation.clean();
|
||
|
}
|
||
|
self.change_confirmation = cdb.editor.ViewFactory.createDialogByTemplate('common/dialogs/confirm_rename_dataset');
|
||
|
|
||
|
// If user confirms, app set the new name
|
||
|
self.change_confirmation.ok = function() {
|
||
|
self._onSetAttributes({ name: title });
|
||
|
if (_.isFunction(this.close)) {
|
||
|
this.close();
|
||
|
}
|
||
|
};
|
||
|
|
||
|
self.change_confirmation
|
||
|
.appendToBody()
|
||
|
.open();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Wait function before set new visualization attributes
|
||
|
*/
|
||
|
_onSetAttributes: function(d) {
|
||
|
|
||
|
var old_data = this.model.toJSON();
|
||
|
var new_data = d;
|
||
|
|
||
|
this.model.set(d, { silent: true });
|
||
|
|
||
|
// Check if there is any difference
|
||
|
if (this.model.hasChanged()) {
|
||
|
var self = this;
|
||
|
|
||
|
this.globalError.showError(this._TEXTS.saving, 'load', -1);
|
||
|
|
||
|
this.model.save({},{
|
||
|
wait: true,
|
||
|
success: function(m) {
|
||
|
// Check if attr saved is name to change url
|
||
|
if (new_data.name !== old_data.name && !self.model.isVisualization()) {
|
||
|
window.table_router.navigate(self._generateTableUrl(), {trigger: false});
|
||
|
window.table_router.addToHistory();
|
||
|
}
|
||
|
|
||
|
self.globalError.showError(self._TEXTS.saved, 'info', 3000);
|
||
|
},
|
||
|
error: function(msg, resp) {
|
||
|
var err = resp && JSON.parse(resp.responseText).errors[0];
|
||
|
self.globalError.showError(err, 'error', 3000);
|
||
|
self.model.set(old_data, { silent: true });
|
||
|
self.setInfo();
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Check if visualization/table is editable
|
||
|
* (Checking if it is visualization and/or data layer is in sql view)
|
||
|
*/
|
||
|
isVisEditable: function() {
|
||
|
if (this.model.isVisualization()) {
|
||
|
return true;
|
||
|
} else {
|
||
|
var table = this.dataLayer && this.dataLayer.table;
|
||
|
|
||
|
if (!table) {
|
||
|
return false;
|
||
|
} else if (table && (table.isReadOnly() || !table.permission.isOwner(this.options.user))) {
|
||
|
return false;
|
||
|
} else {
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
|
||
|
|
||
|
isSyncTable: function() {
|
||
|
if (this.dataLayer && this.dataLayer.table) {
|
||
|
return this.dataLayer.table.isSync();
|
||
|
}
|
||
|
return false;
|
||
|
},
|
||
|
|
||
|
|
||
|
_generateTableUrl: function(e) {
|
||
|
// Let's create the url ourselves //
|
||
|
var url = '';
|
||
|
|
||
|
// Check visualization type and get table or viz id
|
||
|
if (this.model.isVisualization()) {
|
||
|
url += '/viz/' + this.model.get('id');
|
||
|
} else {
|
||
|
var isOwner = this.model.permission.isOwner(this.options.user);
|
||
|
var table = new cdb.admin.CartoDBTableMetadata(this.model.get('table'));
|
||
|
|
||
|
// Qualify table urls if user is not the owner
|
||
|
if (!isOwner) {
|
||
|
var owner_username = this.model.permission.owner.get('username');
|
||
|
url += '/tables/' + owner_username + '.' + table.getUnqualifiedName();
|
||
|
} else {
|
||
|
url += '/tables/' + table.getUnqualifiedName();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Get scenario parameter from event or current url (table or map)
|
||
|
var current = e ? $(e.target).attr('href') : window.location.pathname;
|
||
|
if (current.search('/map') != -1) {
|
||
|
url += '/map'
|
||
|
} else {
|
||
|
url += '/table'
|
||
|
}
|
||
|
|
||
|
return url;
|
||
|
},
|
||
|
|
||
|
|
||
|
_onTabClick: function(e) {
|
||
|
e.preventDefault();
|
||
|
window.table_router.navigate(this._generateTableUrl(e), {trigger: true});
|
||
|
}
|
||
|
});
|