(function(root, factory) { // Set up Backform appropriately for the environment. Start with AMD. if (typeof define === 'function' && define.amd) { define([ 'underscore', 'jquery', 'backbone', 'backform', 'backgrid', 'alertify', 'moment', 'bootstrap.datetimepicker' ], function(_, $, Backbone, Backform, Backgrid, Alertify, moment) { // Export global even in AMD case in case this script is loaded with // others that may still expect a global Backform. return factory(root, _, $, Backbone, Backform, Alertify, moment); }); // 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, _, $, Backbone, Backform, Alertify, moment); // Finally, as a browser global. } else { factory(root, root._, (root.jQuery || root.Zepto || root.ender || root.$), root.Backbone, root.Backform); } } (this, function(root, _, $, Backbone, Backform, Alertify, moment) { /* * 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())); } } }); _.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([ '
', '
', '
' ].join("\n")), stringTemplate: _.template([ '
', ' ', '
', ' ', '
', '
' ].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 = $('
'); $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 = $(""), noofcol = noofcol || 1, 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( "" ); 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(""); 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: 'Are you sure you wish to delete this 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; Alertify.confirm( 'Delete Row', delete_msg, function(evt) { that.model.collection.remove(that.model); }, function(evt) { return true; } ); } else { Alertify.alert("This object can not be deleted", function(){ return true; } ); } }, initialize: function () { Backgrid.Cell.prototype.initialize.apply(this, arguments); }, render: function () { this.$el.empty(); this.$el.html(""); 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'); }, 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 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( $("", { 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 )); 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([ ''].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 '