This commit is contained in:
zhongjin
2020-06-15 12:07:54 +08:00
parent 610ed21a90
commit a96ef233c9
444 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,166 @@
/**
* generic dialog
*
* this opens a dialog in the middle of the screen rendering
* a dialog using cdb.templates 'common/dialog' or template_base option.
*
* inherit class should implement render_content (it could return another widget)
*
* usage example:
*
* var MyDialog = cdb.ui.common.Dialog.extend({
* render_content: function() {
* return "my content";
* },
* })
* var dialog = new MyDialog({
* title: 'test',
* description: 'long description here',
* template_base: $('#base_template').html(),
* width: 500
* });
*
* $('body').append(dialog.render().el);
* dialog.open();
*
* TODO: implement draggable
* TODO: modal
* TODO: document modal_type
*/
cdb.ui.common.Dialog = cdb.core.View.extend({
tagName: 'div',
className: 'dialog',
events: {
'click .ok': '_ok',
'click .cancel': '_cancel',
'click .close': '_cancel'
},
default_options: {
title: 'title',
description: '',
ok_title: 'Ok',
cancel_title: 'Cancel',
width: 300,
height: 200,
clean_on_hide: false,
enter_to_confirm: false,
template_name: 'old_common/views/dialog_base',
ok_button_classes: 'button green',
cancel_button_classes: '',
modal_type: '',
modal_class: '',
include_footer: true,
additionalButtons: []
},
initialize: function() {
_.defaults(this.options, this.default_options);
_.bindAll(this, 'render', '_keydown');
// Keydown bindings for the dialog
$(document).bind('keydown', this._keydown);
// After removing the dialog, cleaning other bindings
this.bind("clean", this._reClean);
this.template_base = this.options.template_base ? _.template(this.options.template_base) : cdb.templates.getTemplate(this.options.template_name);
},
render: function() {
var $el = this.$el;
$el.html(this.template_base(this.options));
$el.find(".modal").css({
width: this.options.width
//height: this.options.height
//'margin-left': -this.options.width>>1,
//'margin-top': -this.options.height>>1
});
if(this.render_content) {
this.$('.content').append(this.render_content());
}
if(this.options.modal_class) {
this.$el.addClass(this.options.modal_class);
}
return this;
},
_keydown: function(e) {
// If clicks esc, goodbye!
if (e.keyCode === 27) {
this._cancel();
// If clicks enter, same as you click on ok button.
} else if (e.keyCode === 13 && this.options.enter_to_confirm) {
this._ok();
}
},
/**
* helper method that renders the dialog and appends it to body
*/
appendToBody: function() {
$('body').append(this.render().el);
return this;
},
_ok: function(ev) {
if(ev) ev.preventDefault();
if (this.ok) {
this.ok(this.result);
}
this.hide();
},
_cancel: function(ev) {
if (ev) {
ev.preventDefault();
ev.stopPropagation();
}
if (this.cancel) {
this.cancel();
}
this.hide();
},
hide: function() {
this.$el.hide();
if (this.options.clean_on_hide) {
this.clean();
}
},
open: function() {
this.$el.show();
},
_reClean: function() {
$(document).unbind('keydown', this._keydown);
}
});

View File

@@ -0,0 +1,150 @@
/**
* Show a dropdown from the target
*
* It shows the several options of the user settings
*
* usage example:
*
* var settings = new cdb.ui.common.Dropdown({
* el: "#settings_element",
* speedIn: 300,
* speedOut: 200
* });
* // show it
* settings.show();
* // close it
* settings.close();
*/
cdb.ui.common.Dropdown = cdb.core.View.extend({
tagName: 'div',
className: 'dropdown',
events: {
"click ul li a" : "_fireClick"
},
default_options: {
width: 160,
speedIn: 150,
speedOut: 300,
vertical_position: "down",
horizontal_position: "right",
tick: "right",
vertical_offset: 0,
horizontal_offset: 0
},
initialize: function() {
_.bindAll(this, "open", "hide", "_handleClick", "_keydown");
// Extend options
_.defaults(this.options, this.default_options);
// Dropdown template
if (this.options.template_base) {
this.template_base = cdb.templates.getTemplate(this.options.template_base);
} else if (this.options.template) {
this.template_base = this.options.template;
}
// Bind to target
$(this.options.target).bind({"click": this._handleClick});
// Bind ESC key
$(document).bind('keydown', this._keydown);
// Is open flag
this.isOpen = false;
},
render: function() {
// Render
var $el = this.$el;
$el
.html(this.template_base(this.options))
.css({
width: this.options.width
})
return this;
},
_handleClick: function(ev) {
//Check if the dropdown is visible to hiding with the click on the target
if (ev){
ev.preventDefault();
ev.stopPropagation();
}
// If visible
if (this.isOpen){
this.hide();
}else{
this.open();
}
},
_keydown: function(e) {
if (e.keyCode === 27) {
this.hide();
}
},
hide: function() {
this.isOpen = false;
this.$el.hide();
},
show: function() {
this.$el.css({
display: "block",
opacity: 1
});
this.isOpen = true;
},
open: function(ev,target) {
// Target
var $target = target && $(target) || this.options.target;
this.options.target = $target;
// Positionate
var targetPos = $target[this.options.position || 'offset']()
, targetWidth = $target.outerWidth()
, targetHeight = $target.outerHeight()
, elementWidth = this.$el.outerWidth()
, elementHeight = this.$el.outerHeight()
, self = this;
this.$el.css({
top: targetPos.top + parseInt((self.options.vertical_position == "up") ? (- elementHeight - 10 - self.options.vertical_offset) : (targetHeight + 10 - self.options.vertical_offset)),
left: targetPos.left + parseInt((self.options.horizontal_position == "left") ? (self.options.horizontal_offset - 15) : (targetWidth - elementWidth + 15 - self.options.horizontal_offset))
})
.addClass(
// Add vertical and horizontal position class
(this.options.vertical_position == "up" ? "vertical_top" : "vertical_bottom" )
+ " " +
(this.options.horizontal_position == "right" ? "horizontal_right" : "horizontal_left" )
+ " " +
// Add tick class
"tick_" + this.options.tick
)
// Show it
this.show();
// Dropdown openned
this.isOpen = true;
},
clean: function() {
$(this.options.target).unbind({"click": this._handleClick});
$(document).unbind('keydown', this._keydown);
cdb.core.View.prototype.clean.apply(this, arguments);
},
_fireClick: function(ev) {
this.trigger("optionClicked", ev, this.el);
}
});

View File

@@ -0,0 +1,81 @@
/**
* generic embbed notification, like twitter "new notifications"
*
* it shows slowly the notification with a message and a close button.
* Optionally you can set a timeout to close
*
* usage example:
*
var notification = new cdb.ui.common.Notificaiton({
el: "#notification_element",
msg: "error!",
timeout: 1000
});
notification.show();
// close it
notification.close();
*/
cdb.ui.common.Notification = cdb.core.View.extend({
tagName: 'div',
className: 'dialog',
events: {
'click .close': 'hide'
},
default_options: {
timeout: 0,
msg: '',
hideMethod: '',
duration: 'normal'
},
initialize: function() {
this.closeTimeout = -1;
_.defaults(this.options, this.default_options);
this.template = this.options.template ? _.template(this.options.template) : cdb.templates.getTemplate('common/notification');
this.$el.hide();
},
render: function() {
var $el = this.$el;
$el.html(this.template(this.options));
if(this.render_content) {
this.$('.content').append(this.render_content());
}
return this;
},
hide: function(ev) {
var self = this;
if (ev)
ev.preventDefault();
clearTimeout(this.closeTimeout);
if(this.options.hideMethod != '' && this.$el.is(":visible") ) {
this.$el[this.options.hideMethod](this.options.duration, 'swing', function() {
self.$el.html('');
self.trigger('notificationDeleted');
self.remove();
});
} else {
this.$el.hide();
self.$el.html('');
self.trigger('notificationDeleted');
self.remove();
}
},
open: function(method, options) {
this.render();
this.$el.show(method, options);
if(this.options.timeout) {
this.closeTimeout = setTimeout(_.bind(this.hide, this), this.options.timeout);
}
}
});

View File

@@ -0,0 +1,183 @@
cdb.ui.common.ShareDialog = cdb.ui.common.Dialog.extend({
tagName: 'div',
className: 'cartodb-share-dialog',
events: {
'click .ok': '_ok',
'click .cancel': '_cancel',
'click .close': '_cancel',
"click": '_stopPropagation',
"dblclick": '_stopPropagation',
"mousedown": '_stopPropagation'
},
default_options: {
title: '',
description: '',
ok_title: 'Ok',
cancel_title: 'Cancel',
width: 300,
height: 200,
clean_on_hide: false,
enter_to_confirm: false,
template_name: 'old_common/views/dialog_base',
ok_button_classes: 'button green',
cancel_button_classes: '',
modal_type: '',
modal_class: '',
include_footer: true,
additionalButtons: []
},
initialize: function() {
_.defaults(this.options, this.default_options);
_.bindAll(this, 'render', '_keydown');
this.isOpen = false;
var self = this;
if (this.options.target) {
this.options.target.on("click", function(e) {
e.preventDefault();
e.stopPropagation();
self.open();
})
}
// Keydown bindings for the dialog
$(document).bind('keydown', this._keydown);
// After removing the dialog, cleaning other bindings
this.bind("clean", this._reClean);
},
_stopPropagation: function(ev) {
ev.stopPropagation();
},
_stripHTML: function(input, allowed) {
allowed = (((allowed || "") + "").toLowerCase().match(/<[a-z][a-z0-9]*>/g) || []).join('');
var tags = /<\/?([a-z][a-z0-9]*)\b[^>]*>/gi;
if (!input || (typeof input != "string")) return '';
return input.replace(tags, function ($0, $1) {
return allowed.indexOf('<' + $1.toLowerCase() + '>') > -1 ? $0 : '';
});
},
open: function() {
var self = this;
this.$el.show(0, function(){
self.isOpen = true;
});
},
hide: function() {
var self = this;
this.$el.hide(0, function(){
self.isOpen = false;
});
if (this.options.clean_on_hide) {
this.clean();
}
},
toggle: function() {
if (this.isOpen) {
this.hide();
} else {
this.open();
}
},
_truncateTitle: function(s, length) {
return s.substr(0, length-1) + (s.length > length ? '…' : '');
},
render: function() {
var $el = this.$el;
var title = cdb.core.sanitize.html(this.options.title);
var description = cdb.core.sanitize.html(this.options.description);
var clean_description = this._stripHTML(this.options.description);
var share_url = this.options.share_url;
var facebook_url, twitter_url;
this.$el.addClass(this.options.size);
var full_title = title + ": " + clean_description;
var twitter_title;
if (title && clean_description) {
twitter_title = this._truncateTitle(title + ": " + clean_description, 112) + " %23map "
} else if (title) {
twitter_title = this._truncateTitle(title, 112) + " %23map"
} else if (clean_description){
twitter_title = this._truncateTitle(clean_description, 112) + " %23map"
} else {
twitter_title = "%23map"
}
if (this.options.facebook_url) {
facebook_url = this.options.facebook_url;
} else {
facebook_url = "http://www.facebook.com/sharer.php?u=" + share_url + "&text=" + full_title;
}
if (this.options.twitter_url) {
twitter_url = this.options.twitter_url;
} else {
twitter_url = "https://twitter.com/share?url=" + share_url + "&text=" + twitter_title;
}
var options = _.extend(this.options, { facebook_url: facebook_url, twitter_url: twitter_url });
$el.html(this.options.template(options));
$el.find(".modal").css({
width: this.options.width
});
if (this.render_content) {
this.$('.content').append(this.render_content());
}
if(this.options.modal_class) {
this.$el.addClass(this.options.modal_class);
}
if (this.options.disableLinks) {
this.$el.find("a").attr("target", "");
}
return this;
}
});

View File

@@ -0,0 +1,448 @@
/**
* generic table
*
* this class creates a HTML table based on Table model (see below) and modify it based on model changes
*
* usage example:
*
var table = new Table({
model: table
});
$('body').append(table.render().el);
* model should be a collection of Rows
*/
/**
* represents a table row
*/
cdb.ui.common.Row = cdb.core.Model.extend({
});
cdb.ui.common.TableData = Backbone.Collection.extend({
model: cdb.ui.common.Row,
fetched: false,
initialize: function() {
var self = this;
this.bind('reset', function() {
self.fetched = true;
})
},
/**
* get value for row index and columnName
*/
getCell: function(index, columnName) {
var r = this.at(index);
if(!r) {
return null;
}
return r.get(columnName);
},
isEmpty: function() {
return this.length === 0;
}
});
/**
* contains information about the table, mainly the schema
*/
cdb.ui.common.TableProperties = cdb.core.Model.extend({
columnNames: function() {
return _.map(this.get('schema'), function(c) {
return c[0];
});
},
columnName: function(idx) {
return this.columnNames()[idx];
}
});
/**
* renders a table row
*/
cdb.ui.common.RowView = cdb.core.View.extend({
tagName: 'tr',
initialize: function() {
this.model.bind('change', this.render, this);
this.model.bind('destroy', this.clean, this);
this.model.bind('remove', this.clean, this);
this.model.bind('change', this.triggerChange, this);
this.model.bind('sync', this.triggerSync, this);
this.model.bind('error', this.triggerError, this);
this.add_related_model(this.model);
this.order = this.options.order;
},
triggerChange: function() {
this.trigger('changeRow');
},
triggerSync: function() {
this.trigger('syncRow');
},
triggerError: function() {
this.trigger('errorRow')
},
valueView: function(colName, value) {
return value;
},
render: function() {
var self = this;
var row = this.model;
var tr = '';
var tdIndex = 0;
var td;
if(this.options.row_header) {
td = '<td class="rowHeader" data-x="' + tdIndex + '">';
} else {
td = '<td class="EmptyRowHeader" data-x="' + tdIndex + '">';
}
var v = self.valueView('', '');
if(v.html) {
v = v[0].outerHTML;
}
td += v;
td += '</td>';
tdIndex++;
tr += td
var attrs = this.order || _.keys(row.attributes);
var tds = '';
var row_attrs = row.attributes;
for(var i = 0, len = attrs.length; i < len; ++i) {
var key = attrs[i];
var value = row_attrs[key];
if(value !== undefined) {
var td = '<td id="cell_' + row.id + '_' + key + '" data-x="' + tdIndex + '">';
var v = self.valueView(key, value);
if(v.html) {
v = v[0].outerHTML;
}
td += v;
td += '</td>';
tdIndex++;
tds += td;
}
}
tr += tds;
this.$el.html(tr).attr('id', 'row_' + row.id);
return this;
},
getCell: function(x) {
var childNo = x;
if(this.options.row_header) {
++x;
}
return this.$('td:eq(' + x + ')');
},
getTableView: function() {
return this.tableView;
}
});
/**
* render a table
* this widget needs two data sources
* - the table model which contains information about the table (columns and so on). See TableProperties
* - the model with the data itself (TableData)
*/
cdb.ui.common.Table = cdb.core.View.extend({
tagName: 'table',
rowView: cdb.ui.common.RowView,
events: {
'click td': '_cellClick',
'dblclick td': '_cellDblClick'
},
default_options: {
},
initialize: function() {
var self = this;
_.defaults(this.options, this.default_options);
this.dataModel = this.options.dataModel;
this.rowViews = [];
// binding
this.setDataSource(this.dataModel);
this.model.bind('change', this.render, this);
this.model.bind('change:dataSource', this.setDataSource, this);
// assert the rows are removed when table is removed
this.bind('clean', this.clear_rows, this);
// prepare for cleaning
this.add_related_model(this.dataModel);
this.add_related_model(this.model);
// we need to use custom signals to make the tableview aware of a row being deleted,
// because when you delete a point from the map view, sometimes it isn't on the dataModel
// collection, so its destroy doesn't bubble throught there.
// Also, the only non-custom way to acknowledge that a row has been correctly deleted from a server is with
// a sync, that doesn't bubble through the table
this.model.bind('removing:row', function() {
self.rowsBeingDeleted = self.rowsBeingDeleted ? self.rowsBeingDeleted +1 : 1;
self.rowDestroying();
});
this.model.bind('remove:row', function() {
if(self.rowsBeingDeleted > 0) {
self.rowsBeingDeleted--;
self.rowDestroyed();
if(self.dataModel.length == 0) {
self.emptyTable();
}
}
});
},
headerView: function(column) {
return column[0];
},
setDataSource: function(dm) {
if(this.dataModel) {
this.dataModel.unbind(null, null, this);
}
this.dataModel = dm;
this.dataModel.bind('reset', this._renderRows, this);
this.dataModel.bind('error', this._renderRows, this);
this.dataModel.bind('add', this.addRow, this);
},
_renderHeader: function() {
var self = this;
var thead = $("<thead>");
var tr = $("<tr>");
if(this.options.row_header) {
tr.append($("<th>").append(self.headerView(['', 'header'])));
} else {
tr.append($("<th>").append(self.headerView(['', 'header'])));
}
_(this.model.get('schema')).each(function(col) {
tr.append($("<th>").append(self.headerView(col)));
});
thead.append(tr);
return thead;
},
/**
* remove all rows
*/
clear_rows: function() {
this.$('tfoot').remove();
this.$('tr.noRows').remove();
// unbind rows before cleaning them when all are gonna be removed
var rowView = null;
while(rowView = this.rowViews.pop()) {
// this is a hack to avoid all the elements are removed one by one
rowView.unbind(null, null, this);
// each element removes itself from rowViews
rowView.clean();
}
// clean all the html at the same time
this.rowViews = [];
},
/**
* add rows
*/
addRow: function(row, collection, options) {
var self = this;
var tr = new self.rowView({
model: row,
order: this.model.columnNames(),
row_header: this.options.row_header
});
tr.tableView = this;
tr.bind('clean', function() {
var idx = _.indexOf(self.rowViews, tr);
self.rowViews.splice(idx, 1);
// update index
for(var i = idx; i < self.rowViews.length; ++i) {
self.rowViews[i].$el.attr('data-y', i);
}
}, this);
tr.bind('changeRow', this.rowChanged, this);
tr.bind('saved', this.rowSynched, this);
tr.bind('errorSaving', this.rowFailed, this);
tr.bind('saving', this.rowSaving, this);
this.retrigger('saving', tr);
tr.render();
if(options && options.index !== undefined && options.index != self.rowViews.length) {
tr.$el.insertBefore(self.rowViews[options.index].$el);
self.rowViews.splice(options.index, 0, tr);
//tr.$el.attr('data-y', options.index);
// change others view data-y attribute
for(var i = options.index; i < self.rowViews.length; ++i) {
self.rowViews[i].$el.attr('data-y', i);
}
} else {
// at the end
tr.$el.attr('data-y', self.rowViews.length);
self.$el.append(tr.el);
self.rowViews.push(tr);
}
this.trigger('createRow');
},
/**
* Callback executed when a row change
* @method rowChanged
* @abstract
*/
rowChanged: function() {},
/**
* Callback executed when a row is sync
* @method rowSynched
* @abstract
*/
rowSynched: function() {},
/**
* Callback executed when a row fails to reach the server
* @method rowFailed
* @abstract
*/
rowFailed: function() {},
/**
* Callback executed when a row send a POST to the server
* @abstract
*/
rowSaving: function() {},
/**
* Callback executed when a row is being destroyed
* @method rowDestroyed
* @abstract
*/
rowDestroying: function() {},
/**
* Callback executed when a row gets destroyed
* @method rowDestroyed
* @abstract
*/
rowDestroyed: function() {},
/**
* Callback executed when a row gets destroyed and the table data is empty
* @method emptyTable
* @abstract
*/
emptyTable: function() {},
/**
* Checks if the table is empty
* @method isEmptyTable
* @returns boolean
*/
isEmptyTable: function() {
return (this.dataModel.length === 0 && this.dataModel.fetched)
},
/**
* render only data rows
*/
_renderRows: function() {
this.clear_rows();
if(! this.isEmptyTable()) {
if(this.dataModel.fetched) {
var self = this;
this.dataModel.each(function(row) {
self.addRow(row);
});
} else {
this._renderLoading();
}
} else {
this._renderEmpty();
}
},
_renderLoading: function() {
},
_renderEmpty: function() {
},
/**
* Method for the children to redefine with the table behaviour when it has no rows.
* @method addEmptyTableInfo
* @abstract
*/
addEmptyTableInfo: function() {
// #to be overwrite by descendant classes
},
/**
* render table
*/
render: function() {
var self = this;
// render header
self.$el.html(self._renderHeader());
// render data
self._renderRows();
return this;
},
/**
* return jquery cell element of cell x,y
*/
getCell: function(x, y) {
if(this.options.row_header) {
++y;
}
return this.rowViews[y].getCell(x);
},
_cellClick: function(e, evtName) {
evtName = evtName || 'cellClick';
e.preventDefault();
var cell = $(e.currentTarget || e.target);
var x = parseInt(cell.attr('data-x'), 10);
var y = parseInt(cell.parent().attr('data-y'), 10);
this.trigger(evtName, e, cell, x, y);
},
_cellDblClick: function(e) {
this._cellClick(e, 'cellDblClick');
}
});

View File

@@ -0,0 +1,165 @@
/**
* tabbed pane.
* if contains n views inside it and shows only one at once
*
* usage:
*
* var pane = new cdb.ui.common.TabPane({
* el: $("#container")
* });
*
* pane.addTab('tab1', new OtherView());
* pane.addTab('tab2', new OtherView2());
* pane.addTab('tab3', new OtherView3());
*
* pane.active('tab1');
*
* pane.bind('tabEnabled', function(tabName, tabView) {
* pane.bind('tabDisabled', function(tabName, tabView) {
* });
*/
cdb.ui.common.TabPane = cdb.core.View.extend({
initialize: function() {
this.tabs = {};
this.activeTab = null;
this.activePane = null;
},
addTab: function(name, view, options) {
options = options || { active: true };
if(this.tabs[name] !== undefined) {
cdb.log.debug(name + "already added");
} else {
this.tabs[name] = view.cid;
this.addView(view);
if(options.after !== undefined) {
var e = this.$el.children()[options.after];
view.$el.insertAfter(e);
} else if(options.prepend) {
this.$el.prepend(view.el);
} else {
this.$el.append(view.el);
}
this.trigger('tabAdded', name, view);
if(options.active) {
this.active(name);
} else {
view.hide();
}
}
},
getPreviousPane: function() {
var tabs = _.toArray(this.tabs);
var panes = _.toArray(this._subviews);
var i = _.indexOf(tabs, this.activePane.cid) - 1;
if (i < 0) i = panes.length - 1;
return panes[i];
},
getNextPane: function() {
var tabs = _.toArray(this.tabs);
var panes = _.toArray(this._subviews);
var i = 1 + _.indexOf(tabs, this.activePane.cid);
if (i > panes.length - 1) i = 0;
return panes[i];
},
getPane: function(name) {
var vid = this.tabs[name];
return this._subviews[vid];
},
getActivePane: function() {
return this.activePane;
},
size: function() {
return _.size(this.tabs);
},
clean: function() {
this.removeTabs();
cdb.core.View.prototype.clean.call(this)
},
removeTab: function(name) {
if (this.tabs[name] !== undefined) {
var vid = this.tabs[name];
this._subviews[vid].clean();
delete this.tabs[name];
if (this.activeTab == name) {
this.activeTab = null;
}
if (_.size(this.tabs)) {
this.active(_.keys(this.tabs)[0]);
}
}
},
removeTabs: function() {
for(var name in this.tabs) {
var vid = this.tabs[name];
this._subviews[vid].clean();
delete this.tabs[name];
}
this.activeTab = null;
},
active: function(name) {
var
self = this,
vid = this.tabs[name];
if (vid !== undefined) {
if (this.activeTab !== name) {
var v = this._subviews[vid];
if (this.activeTab) {
var vid_old = this._subviews[this.tabs[this.activeTab]];
vid_old.hide();
self.trigger('tabDisabled', this.activeTab , vid_old);
self.trigger('tabDisabled:' + this.activeTab, vid_old);
if(vid_old.deactivated) {
vid_old.deactivated();
}
}
v.show();
if(v.activated) {
v.activated();
}
this.activeTab = name;
this.activePane = v;
self.trigger('tabEnabled', name, v);
self.trigger('tabEnabled:' + name, v);
}
return this.activePane;
}
},
render: function() {
return this;
},
each: function(fn) {
var self = this;
_.each(this.tabs, function(cid, tab) {
fn(tab, self.getPane(tab));
});
}
});