pgadmin4/web/pgadmin/static/js/backgrid.pgadmin.js
Akshay Joshi c98b64fd82 1) Edit cellEditing function, in some cases grid object is undefined.
2) Modify SubNodeCollectionControl, so that user can pass custom backgrid row.
3) Handling of visible parameter in SubNodeCollectionControl.
4) Customise title and delete message for Delete Cell.
5) Added Backgrid.Extension.Select2DepCell and moved Backgrid.Extension.StringDepCell from user management to backgrid.pgadmin.js.
2017-06-29 19:01:29 +05:30

1439 lines
45 KiB
JavaScript

(function(root, factory) {
// Set up Backform appropriately for the environment. Start with AMD.
if (typeof define === 'function' && define.amd) {
define([
'sources/gettext', 'underscore', 'jquery', 'backbone', 'backform', 'backgrid', 'alertify',
'moment', 'bignumber', 'bootstrap.datetimepicker'
],
function(gettext, _, $, Backbone, Backform, Backgrid, Alertify, moment, BigNumber) {
// Export global even in AMD case in case this script is loaded with
// others that may still expect a global Backform.
return factory(root, gettext, _, $, Backbone, Backform, Alertify, moment, BigNumber);
});
// Next for Node.js or CommonJS. jQuery may not be needed as a module.
} else if (typeof exports !== 'undefined') {
var _ = require('underscore') || root._,
$ = root.jQuery || root.$ || root.Zepto || root.ender,
Backbone = require('backbone') || root.Backbone,
Backform = require('backform') || root.Backform;
Alertify = require('alertify') || root.Alertify;
moment = require('moment') || root.moment;
factory(root, gettext, _, $, Backbone, Backform, Alertify, moment);
// Finally, as a browser global.
} else {
factory(root, root.gettext, root._, (root.jQuery || root.Zepto || root.ender || root.$), root.Backbone, root.Backform);
}
} (this, function(root, gettext, _, $, Backbone, Backform, Alertify, moment, BigNumber) {
/*
* Add mechanism in backgrid to render different types of cells in
* same column;
*/
// Add new property cellFunction in Backgrid.Column.
_.extend(Backgrid.Column.prototype.defaults, { cellFunction: undefined });
// Add tooltip to cell if cell content is larger than
// cell width
_.extend(Backgrid.Cell.prototype.events, {
'mouseover': function(e) {
var $el = $(this.el);
if($el.text().length > 0 && !$el.attr('title') &&
($el.innerWidth() + 1) < $el[0].scrollWidth
) {
$el.attr('title', $.trim($el.text()));
}
}
});
/* Overriding backgrid sort method.
* As we are getting numeric, integer values as string
* from server side, but on client side javascript truncates
* large numbers automatically due to which backgrid was unable
* to sort numeric values properly in the grid.
* To fix this issue, now we check if cell type is integer/number
* convert it into BigNumber object and make comparison to perform sorting.
*/
_.extend(Backgrid.Body.prototype, {
sort: function (column, direction) {
if (!_.contains(["ascending", "descending", null], direction)) {
throw new RangeError('direction must be one of "ascending", "descending" or `null`');
}
if (_.isString(column)) column = this.columns.findWhere({name: column});
var collection = this.collection;
var order;
if (direction === "ascending") order = -1;
else if (direction === "descending") order = 1;
else order = null;
// Get column type and pass it to comparator.
var col_type = column.get('cell').prototype.className || 'string-cell',
comparator = this.makeComparator(column.get("name"), order,
order ?
column.sortValue() :
function (model) {
return model.cid.replace('c', '') * 1;
}, col_type);
if (Backbone.PageableCollection &&
collection instanceof Backbone.PageableCollection) {
collection.setSorting(order && column.get("name"), order,
{sortValue: column.sortValue()});
if (collection.fullCollection) {
// If order is null, pageable will remove the comparator on both sides,
// in this case the default insertion order comparator needs to be
// attached to get back to the order before sorting.
if (collection.fullCollection.comparator == null) {
collection.fullCollection.comparator = comparator;
}
collection.fullCollection.sort();
collection.trigger("backgrid:sorted", column, direction, collection);
}
else collection.fetch({reset: true, success: function () {
collection.trigger("backgrid:sorted", column, direction, collection);
}});
}
else {
collection.comparator = comparator;
collection.sort();
collection.trigger("backgrid:sorted", column, direction, collection);
}
column.set("direction", direction);
return this;
},
makeComparator: function (attr, order, func, type) {
return function (left, right) {
// extract the values from the models
var l = func(left, attr), r = func(right, attr), t;
var types = ['number-cell', 'integer-cell'];
if (_.include(types, type)) {
var _l, _r;
// NaN if invalid number
try {
_l = new BigNumber(l);
} catch(err) {
_l = NaN;
}
try {
_r = new BigNumber(r);
} catch(err) {
_r = NaN;
}
// if descending order, swap left and right
if (order === 1) t = _l, _l = _r, _r = t;
if (_l.eq(_r)) // If both are equals
return 0;
else if (_l.lt(_r)) // If left is less than right
return -1;
else
return 1;
}
else {
// if descending order, swap left and right
if (order === 1) t = l, l = r, r = t;
// compare as usual
if (l === r) return 0;
else if (l < r) return -1;
return 1;
}
};
}
});
_.extend(Backgrid.Row.prototype, {
makeCell: function (column) {
return new (this.getCell(column))({
column: column,
model: this.model
});
},
/*
* getCell function will check and execute user given cellFunction to get
* appropriate cell class for current cell being rendered.
* User provided cellFunction must return valid cell class.
* cellFunction will be called with context (this) as column and model as
* argument.
*/
getCell: function (column) {
var cf = column.get("cellFunction");
if (_.isFunction(cf)){
var cell = cf.apply(column, [this.model]);
try {
return Backgrid.resolveNameToClass(cell, "Cell");
} catch (e) {
if (e instanceof ReferenceError) {
// Fallback to column cell.
return column.get("cell");
} else {
throw e; // Let other exceptions bubble up
}
}
} else {
return column.get("cell");
}
}
});
var ObjectCellEditor = Backgrid.Extension.ObjectCellEditor = Backgrid.CellEditor.extend({
modalTemplate: _.template([
'<div class="subnode-dialog" tabindex="1">',
' <div class="subnode-body"></div>',
'</div>'
].join("\n")),
stringTemplate: _.template([
'<div class="form-group">',
' <label class="control-label col-sm-4"><%=label%></label>',
' <div class="col-sm-8">',
' <input type="text" class="form-control" name="<%=name%>" value="<%=value%>" placeholder="<%=placeholder%>" />',
' </div>',
'</div>'
].join("\n")),
extendWithOptions: function(options) {
_.extend(this, options);
},
render: function () {
return this;
},
postRender: function(model, column) {
var editor = this,
el = this.el;
columns_length = this.columns_length;
if (column != null && column.get("name") != this.column.get("name"))
return false;
if (!_.isArray(this.schema)) throw new TypeError("schema must be an array");
// Create a Backbone model from our object if it does not exist
var $dialog = this.createDialog(columns_length);
// Add the Bootstrap form
var $form = $('<form class="form-dialog"></form>');
$dialog.find('div.subnode-body').append($form);
// Call Backform to prepare dialog
back_el = $dialog.find('form.form-dialog');
this.objectView = new Backform.Dialog({
el: back_el, model: this.model, schema: this.schema,
tabPanelClassName: function() {
return 'sub-node-form col-sm-12';
}
});
this.objectView.render();
return this;
},
createDialog: function(noofcol) {
var $dialog = this.$dialog = $(this.modalTemplate({title: ""})),
tr = $("<tr>"),
noofcol = noofcol || 1,
td = $("<td>", {class: 'editable sortable renderable', style: 'height: auto', colspan: noofcol+2}).appendTo(tr);
this.tr = tr;
// Show the Bootstrap modal dialog
td.append($dialog.css('display', 'block'));
this.el.parent('tr').after(tr);
return $dialog;
},
save: function() {
// Retrieve values from the form, and store inside the object model
this.model.trigger("backgrid:edited", this.model, this.column, new Backgrid.Command({keyCode:13}));
if (this.tr) {
this.tr.remove();
}
return this;
},
remove: function() {
this.objectView.remove();
Backgrid.CellEditor.prototype.remove.apply(this, arguments);
if (this.tr) {
this.tr.remove();
}
return this;
}
});
var PGSelectCell = Backgrid.Extension.PGSelectCell = Backgrid.SelectCell.extend({
// It's possible to render an option group or use a
// function to provide option values too.
optionValues: function() {
var res = [];
opts = _.result(this.column.attributes, 'options');
_.each(opts, function(o) {
res.push([o.label, o.value]);
});
return res;
}
});
var ObjectCell = Backgrid.Extension.ObjectCell = Backgrid.Cell.extend({
editorOptionDefaults: {
schema: []
},
className: "edit-cell",
editor: ObjectCellEditor,
initialize: function(options) {
Backgrid.Cell.prototype.initialize.apply(this, arguments);
// Pass on cell options to the editor
var cell = this,
editorOptions = {};
_.each(this.editorOptionDefaults, function(def, opt) {
if (!cell[opt]) cell[opt] = def;
if (options && options[opt]) cell[opt] = options[opt];
editorOptions[opt] = cell[opt];
});
editorOptions['el'] = $(this.el);
editorOptions['columns_length'] = this.column.collection.length;
editorOptions['el'].attr('tabindex' , 1);
this.listenTo(this.model, "backgrid:edit", function (model, column, cell, editor) {
if (column.get("name") == this.column.get("name"))
editor.extendWithOptions(editorOptions);
});
},
enterEditMode: function () {
// Notify that we are about to enter in edit mode for current cell.
// We will check if this row is editable first
var canEditRow = (!_.isUndefined(this.column.get('canEditRow')) &&
_.isFunction(this.column.get('canEditRow'))) ?
Backgrid.callByNeed(this.column.get('canEditRow'),
this.column, this.model) : true;
if (canEditRow) {
// Notify that we are about to enter in edit mode for current cell.
this.model.trigger("enteringEditMode", [this]);
Backgrid.Cell.prototype.enterEditMode.apply(this, arguments);
/* Make sure - we listen to the click event */
this.delegateEvents();
var editable = Backgrid.callByNeed(this.column.editable(), this.column, this.model);
if (editable) {
this.$el.html(
"<i class='fa fa-pencil-square subnode-edit-in-process'></i>"
);
this.model.trigger(
"pg-sub-node:opened", this.model, this
);
}
} else {
Alertify.alert("This object is not editable by user",
function(){
return true;
});
}
},
render: function(){
this.$el.empty();
this.$el.html("<i class='fa fa-pencil-square-o'></i>");
this.delegateEvents();
if (this.grabFocus)
this.$el.focus();
return this;
},
exitEditMode: function() {
var index = $(this.currentEditor.objectView.el)
.find('.nav-tabs > .active > a[data-toggle="tab"]').first()
.data('tabIndex');
Backgrid.Cell.prototype.exitEditMode.apply(this, arguments);
this.model.trigger(
"pg-sub-node:closed", this, index
);
this.grabFocus = true;
},
events: {
'click': function(e) {
if (this.$el.find('i').first().hasClass('subnode-edit-in-process')) {
// Need to redundantly undelegate events for Firefox
this.undelegateEvents();
this.currentEditor.save();
} else {
this.enterEditMode.call(this, []);
}
e.preventDefault();
}
}
});
var DeleteCell = Backgrid.Extension.DeleteCell = Backgrid.Cell.extend({
defaults: _.defaults({
defaultDeleteMsg: gettext('Are you sure you wish to delete this row?'),
defaultDeleteTitle: gettext('Delete Row')
}, Backgrid.Cell.prototype.defaults),
/** @property */
className: "delete-cell",
events: {
"click": "deleteRow"
},
deleteRow: function (e) {
e.preventDefault();
that = this;
// We will check if row is deletable or not
var canDeleteRow = (!_.isUndefined(this.column.get('canDeleteRow')) &&
_.isFunction(this.column.get('canDeleteRow')) ) ?
Backgrid.callByNeed(this.column.get('canDeleteRow'),
this.column, this.model) : true;
if (canDeleteRow) {
var delete_msg = !_.isUndefined(this.column.get('customDeleteMsg')) ?
this.column.get('customDeleteMsg'): that.defaults.defaultDeleteMsg;
var delete_title = !_.isUndefined(this.column.get('customDeleteTitle')) ?
this.column.get('customDeleteTitle'): that.defaults.defaultDeleteTitle;
Alertify.confirm(
delete_title,
delete_msg,
function(evt) {
that.model.collection.remove(that.model);
},
function(evt) {
return true;
}
);
} else {
Alertify.alert("This object cannot be deleted",
function(){
return true;
}
);
}
},
initialize: function () {
Backgrid.Cell.prototype.initialize.apply(this, arguments);
},
render: function () {
this.$el.empty();
this.$el.html("<i class='fa fa-trash'></i>");
this.delegateEvents();
return this;
}
});
var CustomHeaderCell = Backgrid.Extension.CustomHeaderCell = Backgrid.HeaderCell.extend({
initialize: function () {
// Here, we will add custom classes to header cell
Backgrid.HeaderCell.prototype.initialize.apply(this, arguments);
var getClassName = this.column.get('cellHeaderClasses');
if (getClassName) {
this.$el.addClass(getClassName);
}
}
});
/**
SwitchCell renders a Bootstrap Switch in backgrid cell
*/
var SwitchCell = Backgrid.Extension.SwitchCell = Backgrid.BooleanCell.extend({
defaults: {
options: _.defaults({
onText: 'True',
offText: 'False',
onColor: 'success',
offColor: 'default',
size: 'mini'
}, $.fn.bootstrapSwitch.defaults)
},
className: 'switch-cell',
initialize: function() {
Backgrid.BooleanCell.prototype.initialize.apply(this, arguments);
this.onChange = this.onChange.bind(this);
},
enterEditMode: function() {
this.$el.addClass('editor');
$(this.$el.find('input')).focus();
},
exitEditMode: function() {
this.$el.removeClass('editor');
},
events: {
'switchChange.bootstrapSwitch': 'onChange'
},
onChange: function () {
var model = this.model,
column = this.column,
val = this.formatter.toRaw(this.$input.prop('checked'), model);
// on bootstrap change we also need to change model's value
model.set(column.get("name"), val);
},
render: function () {
var self = this, col = _.defaults(this.column.toJSON(), this.defaults),
attributes = this.model.toJSON(),
attrArr = col.name.split('.'),
name = attrArr.shift(),
path = attrArr.join('.'),
model = this.model, column = this.column,
rawValue = this.formatter.fromRaw(
model.get(column.get("name")), model
),
editable = Backgrid.callByNeed(col.editable, column, model);
this.undelegateEvents();
this.$el.empty();
this.$el.append(
$("<input>", {
tabIndex: -1,
type: "checkbox"
}).prop('checked', rawValue).prop('disabled', !editable));
this.$input = this.$el.find('input[type=checkbox]').first();
// Override BooleanCell checkbox with Bootstrapswitch
this.$input.bootstrapSwitch(
_.defaults(
{'state': rawValue, 'disabled': !editable}, col.options,
this.defaults.options
));
// Listen for Tab key
this.$el.on('keydown', function(e) {
var gotoCell;
if(e.keyCode == 9) {
// go to Next Cell & if Shift is also pressed go to Previous Cell
gotoCell = e.shiftKey ? self.$el.prev() : self.$el.next();
}
if(gotoCell) {
setTimeout(function() {
if(gotoCell.hasClass('editable')) {
e.preventDefault();
e.stopPropagation();
var command = new Backgrid.Command({
key: "Tab", keyCode: 9,
which: 9, shiftKey: e.shiftKey
});
self.model.trigger("backgrid:edited", self.model,
self.column, command);
gotoCell.focus();
}
}, 20);
}
});
this.delegateEvents();
return this;
}
});
/*
* Select2Cell for backgrid.
*/
var Select2Cell = Backgrid.Extension.Select2Cell =
Backgrid.SelectCell.extend({
className: "select2-cell",
/** @property */
editor: null,
defaults: _.defaults({
select2: {},
opt: {
label: null,
value: null,
selected: false
}
}, Backgrid.SelectCell.prototype.defaults),
enterEditMode: function() {
if (!this.$el.hasClass('editor'))
this.$el.addClass('editor');
this.$select.select2('focus');
this.$select.select2('open');
this.$select.on('blur', this.exitEditMode);
},
exitEditMode: function() {
this.$select.off('blur', this.exitEditMode);
this.$select.select2('close');
this.$el.removeClass('editor');
},
events: {
"select2:open": "enterEditMode",
"select2:close": "exitEditMode",
"change": "onSave",
"select2:unselect": "onSave"
},
/** @property {function(Object, ?Object=): string} template */
template: _.template([
'<option value="<%- value %>" ',
'<%= selected ? \'selected="selected"\' : "" %>>',
'<%- label %></option>'].join(''),
null,{
variable: null
}),
initialize: function() {
Backgrid.SelectCell.prototype.initialize.apply(this, arguments);
this.onSave = this.onSave.bind(this);
this.enterEditMode = this.enterEditMode.bind(this);
this.exitEditMode = this.exitEditMode.bind(this);
},
render: function () {
var col = _.defaults(this.column.toJSON(), this.defaults),
model = this.model, column = this.column,
editable = Backgrid.callByNeed(col.editable, column, model),
optionValues = _.clone(this.optionValues ||
(_.isFunction(this.column.get('options')) ?
(this.column.get('options'))(this) :
this.column.get('options')));
this.undelegateEvents();
if (this.$select) {
if ( this.$select.data('select2')) {
this.$select.select2('destroy');
}
delete this.$select;
this.$select = null;
}
this.$el.empty();
if (!_.isArray(optionValues))
throw new TypeError("optionValues must be an array");
/*
* Add empty option as Select2 requires any empty '<option><option>' for
* some of its functionality to work.
*/
optionValues.unshift(this.defaults.opt);
var optionText = null,
optionValue = null,
self = this,
model = this.model,
selectedValues = model.get(this.column.get("name")),
select2_opts = _.extend(
{openOnEnter: false, multiple:false}, self.defaults.select2,
(col.select2 || {})
),
selectTpl = _.template('<select <%=multiple ? "multiple" : "" %>></select>');
$select = self.$select = $(selectTpl({
multiple: select2_opts.multiple
})).appendTo(self.$el);
for (var i = 0; i < optionValues.length; i++) {
var opt = optionValues[i];
if (_.isArray(opt)) {
optionText = opt[0];
optionValue = opt[1];
$select.append(
self.template({
label: optionText,
value: optionValue,
selected: (selectedValues == optionValue) ||
(select2_opts.multiple && _.indexOf(selectedValues, optionValue) > -1)
}));
} else {
opt = _.defaults({}, opt, {
selected: ((selectedValues == opt.value) ||
(select2_opts.multiple && _.indexOf(selectedValues, opt.value) > -1)),
}, self.defaults.opt);
$select.append(self.template(opt));
}
}
if(col && _.has(col.disabled)) {
_.extend(select2_opts, {
disabled: evalF(col.disabled, col, model)
});
} else {
_.extend(select2_opts, {disabled: !editable});
}
this.delegateEvents();
// If disabled then no need to show placeholder
if(!editable || col.mode === 'properties') {
select2_opts['placeholder'] = '';
}
// Initialize select2 control.
this.$sel = this.$select.select2(select2_opts);
// Select the highlighted item on Tab press.
if (this.$sel) {
this.$sel.data('select2').on("keypress", function(ev) {
var self = this;
// keycode 9 is for TAB key
if (ev.which === 9 && self.isOpen()) {
self.trigger('results:select', {});
ev.preventDefault();
}
});
}
return this;
},
/**
Saves the value of the selected option to the model attribute.
*/
onSave: function (e) {
var model = this.model;
var column = this.column;
model.set(column.get("name"), this.$select.val());
},
remove: function() {
this.$select.off('change', this.onSave);
if (this.$select.data('select2')) {
this.$select.select2('destroy');
}
this.$el.empty();
Backgrid.SelectCell.prototype.remove.apply(this, arguments);
}
});
/**
TextareaCellEditor the cell editor renders a textarea multi-line text input
box as its editor.
@class Backgrid.TextareaCellEditor
@extends Backgrid.InputCellEditor
*/
var TextareaCellEditor = Backgrid.TextareaCellEditor = Backgrid.InputCellEditor.extend({
/** @property */
tagName: "textarea",
events: {
"blur": "saveOrCancel",
"keydown": ""
}
});
/**
TextareaCell displays multiline HTML strings.
@class Backgrid.Extension.TextareaCell
@extends Backgrid.Cell
*/
var TextareaCell = Backgrid.Extension.TextareaCell = Backgrid.Cell.extend({
/** @property */
className: "textarea-cell",
editor: TextareaCellEditor
});
/**
* Custom header icon cell to add the icon in table header.
*/
var CustomHeaderIconCell = Backgrid.Extension.CustomHeaderIconCell = Backgrid.HeaderCell.extend({
/** @property */
className: "header-icon-cell",
events: {
"click": "addHeaderIcon"
},
addHeaderIcon: function (e) {
self = this;
m = new (this.collection.model);
this.collection.add(m)
e.preventDefault();
},
render: function () {
this.$el.empty();
//this.$el.html("<i class='fa fa-plus-circle'></i>");
this.$el.html("<label><a><span style='font-weight:normal;'>Array Values</a></span></label> <button class='btn-sm btn-default add'>Add</button>");
this.delegateEvents();
return this;
}
});
var arrayCellModel = Backbone.Model.extend({
defaults: {
value: undefined
}
});
/**
Custom InputArrayCellEditor for editing user input array for debugger.
*/
var InputArrayCellEditor = Backgrid.Extension.InputArrayCellEditor =
Backgrid.CellEditor.extend({
tagName: "div",
events: {
'blur': 'lostFocus'
},
render: function () {
var self = this,
arrayValuesCol = this.model.get(this.column.get("name")),
tbl = $("<table></table>").appendTo(this.$el),
gridCols = [
{name: 'value', label:'Array Values', type: 'text', cell:'string', headerCell: Backgrid.Extension.CustomHeaderIconCell, cellHeaderClasses: 'width_percent_100'},
],
gridBody = $("<div class='pgadmin-control-group backgrid form-group col-xs-12 object subnode'></div>");
this.$el.attr('tabindex', '1');
gridCols.unshift({
name: "pg-backform-delete", label: "",
cell: Backgrid.Extension.DeleteCell,
//headerCell: Backgrid.Extension.CustomHeaderIconCell,
editable: false, cell_priority: -1
});
this.$el.empty();
var grid = self.grid = new Backgrid.Grid({
columns: gridCols,
collection:arrayValuesCol
});
grid.render();
gridBody.append(grid.$el)
this.$el.html(gridBody);
$(self.$el).pgMakeVisible('backform-tab');
self.delegateEvents();
return this;
},
/*
* Call back function when the grid lost the focus
*/
lostFocus: function(ev) {
var self = this,
/*
* Function to determine whether one dom element is descendant of another
* dom element.
*/
isDescendant = function (parent, child) {
var node = child.parentNode;
while (node != null) {
if (node == parent) {
return true;
}
node = node.parentNode;
}
return false;
}
/*
* Between leaving the old element focus and entering the new element focus the
* active element is the document/body itself so add timeout to get the proper
* focused active element.
*/
setTimeout(function() {
if (self.$el[0] != document.activeElement && !isDescendant(self.$el[0], document.activeElement)){
var m = self.model,
column = self.column;
m.trigger('backgrid:edited', m, column, new Backgrid.Command(ev));
setTimeout(function(){
if (self.grid) {
self.grid.remove();
self.grid = null;
}
}, 10);
}},10);
return;
}
});
/*
* This will help us transform the user input string array values in proper format to be
* displayed in the cell.
*/
var InputStringArrayCellFormatter = Backgrid.Extension.InputStringArrayCellFormatter =
function () {};
_.extend(InputStringArrayCellFormatter.prototype, {
/**
* Takes a raw value from a model and returns an optionally formatted
* string for display.
*/
fromRaw: function (rawData, model) {
var values = []
rawData.each(function(m){
var val = m.get('value');
if (_.isUndefined(val)) {
values.push("null");
} else {
values.push(m.get("value"));
}
}
);
return values.toString();
},
toRaw: function (formattedData, model) {
return formattedData;
}
});
/*
* This will help us transform the user input integer array values in proper format to be
* displayed in the cell.
*/
var InputIntegerArrayCellFormatter = Backgrid.Extension.InputIntegerArrayCellFormatter =
function () {};
_.extend(InputIntegerArrayCellFormatter.prototype, {
/**
* Takes a raw value from a model and returns an optionally formatted
* string for display.
*/
fromRaw: function (rawData, model) {
var values = []
rawData.each(function(m){
var val = m.get('value');
if (_.isUndefined(val)) {
values.push("null");
} else {
values.push(m.get("value"));
}
}
);
return values.toString();
},
toRaw: function (formattedData, model) {
formattedData.each(function(m){
m.set("value", parseInt(m.get('value')), {silent: true});
});
return formattedData;
}
});
/*
* InputStringArrayCell for rendering and taking input for string array type in debugger
*/
var InputStringArrayCell = Backgrid.Extension.InputStringArrayCell = Backgrid.Cell.extend({
className: "width_percent_25",
formatter: InputStringArrayCellFormatter,
editor: InputArrayCellEditor,
initialize: function() {
Backgrid.Cell.prototype.initialize.apply(this, arguments);
// set value to empty array.
var m = arguments[0].model;
if (_.isUndefined(this.collection)) {
this.collection = new (Backbone.Collection.extend({
model: arrayCellModel}))(m.get('value'));
}
this.model.set(this.column.get('name'), this.collection);
this.listenTo(this.collection, "remove", this.render);
},
});
/*
* InputIntegerArrayCell for rendering and taking input for integer array type in debugger
*/
var InputIntegerArrayCell = Backgrid.Extension.InputIntegerArrayCell = Backgrid.Cell.extend({
className: "width_percent_25",
formatter: InputIntegerArrayCellFormatter,
editor: InputArrayCellEditor,
initialize: function() {
Backgrid.Cell.prototype.initialize.apply(this, arguments);
// set value to empty array.
var m = arguments[0].model;
if (_.isUndefined(this.collection)) {
this.collection = new (Backbone.Collection.extend({
model: arrayCellModel}))(m.get('value'));
}
this.model.set(this.column.get('name'),this.collection);
this.listenTo(this.collection, "remove", this.render);
},
});
/**
* DependentCell functions can be used with the different cell type in order
* to setup the callback for the depedent attribute change in the model.
*
* Please implement the 'dependentChanged' as the callback in the used cell.
*
* @class Backgrid.Extension.DependentCell
**/
var DependentCell = Backgrid.Extension.DependentCell = function() {};
_.extend(
DependentCell.prototype, {
initialize: function(){
// Listen to the dependent fields in the model for any change
var deps = this.column.get('deps');
var self = this;
if (deps && _.isArray(deps)) {
_.each(deps, function(d) {
attrArr = d.split('.');
name = attrArr.shift();
self.listenTo(self.model, "change:" + name, self.dependentChanged);
});
}
},
remove: function() {
// Remove the events for the dependent fields in the model
var self = this,
deps = self.column.get('deps');
if (deps && _.isArray(deps)) {
_.each(deps, function(d) {
attrArr = d.split('.');
name = attrArr.shift();
self.stopListening(self.model, "change:" + name, self.dependentChanged);
});
}
}
});
/**
Formatter for PasswordCell.
@class Backgrid.PasswordFormatter
@extends Backgrid.CellFormatter
@constructor
*/
var PasswordFormatter = Backgrid.PasswordFormatter = function () {};
PasswordFormatter.prototype = new Backgrid.CellFormatter();
_.extend(PasswordFormatter.prototype, {
fromRaw: function (rawValue, model) {
if (_.isUndefined(rawValue) || _.isNull(rawValue)) return '';
var pass = '';
for(var i = 0; i < rawValue.length; i++) {
pass += '*';
}
return pass;
}
});
var PasswordCell = Backgrid.Extension.PasswordCell = Backgrid.StringCell.extend({
formatter: PasswordFormatter,
editor: Backgrid.InputCellEditor.extend({
attributes: {
type: "password"
},
render: function () {
var model = this.model;
this.$el.val(model.get(this.column.get("name")));
return this;
}
})
});
/*
* Override NumberFormatter to support NaN, Infinity values.
* On client side, JSON do not directly support NaN & Infinity,
* we explicitly converted it into string format at server side
* and we need to parse it again in float at client side.
*/
_.extend(Backgrid.NumberFormatter.prototype, {
fromRaw: function (number, model) {
if (_.isNull(number) || _.isUndefined(number)) return '';
number = parseFloat(number).toFixed(~~this.decimals);
var parts = number.split('.');
var integerPart = parts[0];
var decimalPart = parts[1] ? (this.decimalSeparator || '.') + parts[1] : '';
return integerPart.replace(this.HUMANIZED_NUM_RE, '$1' + this.orderSeparator) + decimalPart;
}
});
/*
* JSONBCell Formatter.
*/
var JSONBCellFormatter = Backgrid.Extension.JSONBCellFormatter =
function () {};
_.extend(JSONBCellFormatter.prototype, {
fromRaw: function (rawData, model) {
// json data
if(_.isArray(rawData)) {
var converted_data = '';
converted_data = _.map(rawData, function(data) {
return JSON.stringify(JSON.stringify(data));
});
return '{' + converted_data.join() + '}';
} else if(_.isObject(rawData)) {
return JSON.stringify(rawData);
} else {
return rawData;
}
},
toRaw: function (formattedData, model) {
return formattedData;
}
});
/*
* JSONBCell for backgrid.
*/
var JSONBCell = Backgrid.Extension.JSONBCell =
Backgrid.StringCell.extend({
className: "jsonb-cell",
formatter: JSONBCellFormatter
});
var DatepickerCell = Backgrid.Extension.DatepickerCell = Backgrid.Cell.extend({
editor: DatepickerCellEditor
});
var DatepickerCellEditor = Backgrid.InputCellEditor.extend({
events:{},
initialize:function() {
Backgrid.InputCellEditor.prototype.initialize.apply(this, arguments);
var input = this;
$(this.el).prop('readonly', true);
$(this.el).datepicker({
onClose: function(newValue){
var command = new Backgrid.Command({});
input.model.set(input.column.get("name"), newValue);
input.model.trigger(
"backgrid:edited", input.model, input.column, command
);
command = input = null;
}
});
}
});
// Reference:
// https://github.com/wyuenho/backgrid-moment-cell/blob/master/backgrid-moment-cell.js
/**
MomentFormatter converts bi-directionally any datetime values in any format
supported by [moment()](http://momentjs.com/docs/#/parsing/) to any
datetime format
[moment.fn.format()](http://momentjs.com/docs/#/displaying/format/)
supports.
@class Backgrid.Extension.MomentFormatter
@extends Backgrid.CellFormatter
@constructor
*/
var MomentFormatter = Backgrid.Extension.MomentFormatter = function (options) {
_.extend(this, this.defaults, options);
};
MomentFormatter.prototype = new Backgrid.CellFormatter;
_.extend(MomentFormatter.prototype, {
/**
@cfg {Object} options
@cfg {boolean} [options.modelInUnixOffset=false] Whether the model values
should be read/written as the number of milliseconds since UNIX Epoch.
@cfg {boolean} [options.modelInUnixTimestamp=false] Whether the model
values should be read/written as the number of seconds since UNIX Epoch.
@cfg {boolean} [options.modelInUTC=true] Whether the model values should
be read/written in UTC mode or local mode.
@cfg {string} [options.modelLang=moment.locale() moment>=2.8.0 |
moment.lang() moment<2.8.0] The locale the model values should be
read/written in.
@cfg {string} [options.modelFormat=moment.defaultFormat] The format this
moment formatter should use to read/write model values. Only meaningful if
the values are strings.
@cfg {boolean} [options.displayInUnixOffset=false] Whether the display
values should be read/written as the number of milliseconds since UNIX
Epoch.
@cfg {boolean} [options.displayInUnixTimestamp=false] Whether the display
values should be read/written as the number of seconds since UNIX Epoch.
@cfg {boolean} [options.displayInUTC=true] Whether the display values
should be read/written in UTC mode or local mode.
@cfg {string} [options.displayLang=moment.locale() moment>=2.8.0 |
moment.lang() moment<2.8.0] The locale the display values should be
read/written in.
@cfg {string} [options.displayFormat=moment.defaultFormat] The format
this moment formatter should use to read/write dislay values.
*/
defaults: {
modelInUnixOffset: false,
modelInUnixTimestamp: false,
modelInUTC: true,
modelLang: moment.locale(),
modelFormat: moment.defaultFormat,
displayInUnixOffset: false,
displayInUnixTimestamp: false,
displayInUTC: true,
displayLang: moment.locale(),
displayFormat: moment.defaultFormat,
allowEmpty: false
},
/**
Converts datetime values from the model for display.
@member Backgrid.Extension.MomentFormatter
@param {*} rawData
@return {string}
*/
fromRaw: function (rawData) {
if (rawData == null) return '';
var m = this.modelInUnixOffset ? moment(rawData) :
this.modelInUnixTimestamp ? moment.unix(rawData) :
this.modelInUTC ?
moment.utc(rawData, this.modelFormat, this.modelLang) :
moment(rawData, this.modelFormat, this.modelLang);
if (this.displayInUnixOffset) return +m;
if (this.displayInUnixTimestamp) return m.unix();
if (this.displayLang) m.locale(this.displayLang);
if (this.displayInUTC) m.utc(); else m.local();
if (this.displayFormat != moment.defaultFormat) {
return m.format(this.displayFormat);
}
return m.format();
},
/**
Converts datetime values from user input to model values.
@member Backgrid.Extension.MomentFormatter
@param {string} formattedData
@return {string}
*/
toRaw: function (formattedData) {
var m = this.displayInUnixOffset ? moment(+formattedData) :
this.displayInUnixTimestamp ? moment.unix(+formattedData) :
this.displayInUTC ?
moment.utc(formattedData, this.displayFormat, this.displayLang) :
moment(formattedData, this.displayFormat, this.displayLang);
if (!m || !m.isValid()) return (this.allowEmpty && formattedData === '') ? null : undefined;
if (this.modelInUnixOffset) return +m;
if (this.modelInUnixTimestamp) return m.unix();
if (this.modelLang) m.locale(this.modelLang);
if (this.modelInUTC) m.utc(); else m.local()
if (this.modelFormat != moment.defaultFormat) {
return m.format(this.modelFormat);
}
return m.format();
}
});
var MomentCell = Backgrid.Extension.MomentCell = Backgrid.Cell.extend({
editor: Backgrid.InputCellEditor,
/** @property */
className: "datetime-cell",
/** @property {Backgrid.CellFormatter} [formatter=Backgrid.Extension.MomentFormatter] */
formatter: MomentFormatter,
/**
Initializer. Accept Backgrid.Extension.MomentFormatter.options and
Backgrid.Cell.initialize required parameters.
*/
initialize: function (options) {
MomentCell.__super__.initialize.apply(this, arguments);
var formatterDefaults = MomentFormatter.prototype.defaults;
var formatterDefaultKeys = _.keys(formatterDefaults);
var classAttrs = _.pick(this, formatterDefaultKeys);
var formatterOptions = _.pick(options, formatterDefaultKeys);
var columnsAttrs = _.pick(this.column.toJSON(), formatterDefaultKeys);
// Priority of the options for the formatter, from highest to lowerest
// 1. MomentCell instance options
// 2. MomentCell class attributes
// 3. MomentFormatter defaults
// this.formatter will have been instantiated now
_.extend(this.formatter, formatterDefaults, classAttrs, formatterOptions, columnsAttrs);
this.editor = this.editor.extend({
attributes: _.extend({}, this.editor.prototype.attributes || this.editor.attributes || {}, {
placeholder: this.formatter.displayFormat
}),
options: this.column.get('options')
});
}
});
var DatetimePickerEditor = Backgrid.Extension.DatetimePickerEditor = Backgrid.InputCellEditor.extend({
postRender: function() {
var self = this,
evalF = function() {
var args = [];
Array.prototype.push.apply(args, arguments);
var f = args.shift();
if (typeof(f) === 'function') {
return f.apply(self, args);
}
return f;
},
options = _.extend({
format: "YYYY-MM-DD HH:mm:ss Z",
showClear: true,
showTodayButton: true,
toolbarPlacement: 'top'
}, evalF(this.column.get('options')), {
keyBinds: {
"shift tab": function(widget) {
if (widget) {
// blur the input
setTimeout(
function() {
self.closeIt({keyCode: 9, shiftKey: true});
}, 10
);
}
},
tab: function(widget) {
if (widget) {
// blur the input
setTimeout(
function() {
self.closeIt({keyCode: 9});
}, 10
);
}
}
}
});
this.$el.datetimepicker(options);
this.$el.datetimepicker('show');
this.picker = this.$el.data('DateTimePicker');
},
events: {
'dp.hide': 'closeIt'
},
closeIt: function(ev) {
var formatter = this.formatter,
model = this.model;
column = this.column;
val = this.$el.val();
newValue = formatter.toRaw(val, model);
if (this.is_closing)
return;
this.is_closing = true;
this.$el.datetimepicker('destroy');
this.is_closing = false;
var command = new Backgrid.Command(ev);
if (_.isUndefined(newValue)) {
model.trigger("backgrid:error", model, column, val);
} else {
model.set(column.get("name"), newValue);
model.trigger("backgrid:edited", model, column, command);
}
}
});
_.extend(MomentCell.prototype, MomentFormatter.prototype.defaults);
Backgrid.Extension.StringDepCell = Backgrid.StringCell.extend({
initialize: function() {
Backgrid.StringCell.prototype.initialize.apply(this, arguments);
Backgrid.Extension.DependentCell.prototype.initialize.apply(this, arguments);
},
dependentChanged: function () {
this.$el.empty();
var self = this,
model = this.model,
column = this.column,
editable = this.column.get("editable");
this.render();
is_editable = _.isFunction(editable) ? !!editable.apply(column, [model]) : !!editable;
setTimeout(function() {
self.$el.removeClass("editor");
if (is_editable){ self.$el.addClass("editable"); }
else { self.$el.removeClass("editable"); }
}, 10);
this.delegateEvents();
return this;
},
remove: Backgrid.Extension.DependentCell.prototype.remove
});
Backgrid.Extension.Select2DepCell = Backgrid.Extension.Select2Cell.extend({
initialize: function() {
Backgrid.Extension.Select2Cell.prototype.initialize.apply(this, arguments);
Backgrid.Extension.DependentCell.prototype.initialize.apply(this, arguments);
},
dependentChanged: function () {
var model = this.model;
var column = this.column;
editable = this.column.get("editable");
this.render();
is_editable = _.isFunction(editable) ? !!editable.apply(column, [model]) : !!editable;
if (is_editable){ this.$el.addClass("editable"); }
else { this.$el.removeClass("editable"); }
this.delegateEvents();
return this;
},
remove: Backgrid.Extension.DependentCell.prototype.remove
});
return Backgrid;
}));