Added undo action on some of the UI changes too.

i.e. Switch between the tabs, opening/closing the subnode in edit mode.

Also, removed the Save, Cancel buttons from the subnode editor, it was
looking very redudant. Ctrl+Z for undo, Ctrl+Shift+Z/Ctrl+Y for Redo
shortcut will be good enough for undoing all the changes in the
properties panel.
pull/3/head
Ashesh Vashi 2015-10-30 02:02:40 +05:30
parent 6c62d9eecd
commit 30c560f33b
3 changed files with 144 additions and 65 deletions

View File

@ -658,7 +658,7 @@ function($, _, S, pgAdmin, Menu, Backbone, Alertify, Backform) {
// Register the Ctrl/Meta+Z -> for Undo operation
// and Ctrl+Shift+Z/Ctrl+Y -> Redo operation in the edit/create
// dialog.
content.on('keydown', function(e) {
content.closest('.wcFrame').attr('tabindex', "1").on('keydown', function(e) {
switch (e.keyCode) {
case 90:
if ((e['ctrlKey'] || e['metaKey'])) {
@ -849,6 +849,79 @@ function($, _, S, pgAdmin, Menu, Backbone, Alertify, Backform) {
self.handler.undoMgr.merge(self.undoMgr);
}
self.undoMgr.addUndoType("pg-sub-node:opened", {
"on": function (model, cell) {
return {
"object": cell,
"before": null,
"after": null
}
},
"undo": function (cell, before, after, opts) {
if (cell && cell.exitEditMode &&
_.isFunction(cell.exitEditMode)) {
cell.exitEditMode();
}
},
"redo": function (cell, before, after, opts) {
if (cell && cell.enterEditMode &&
_.isFunction(cell.enterEditMode)) {
cell.enterEditMode();
}
}
});
self.undoMgr.addUndoType("pg-sub-node:closed", {
"on": function (cell, index) {
return {
"object": cell,
"before": null,
"after": null,
"options": index
}
},
"undo": function (cell, before, after, opts) {
if (cell && cell.enterEditMode &&
_.isFunction(cell.enterEditMode)) {
cell.enterEditMode();
cell.currentEditor.objectView.$el
.find('.nav-tabs').first()
.find('a[data-tab-index="' + opts + '"]').tab('show');
}
},
"redo": function (cell, before, after, opts) {
if (cell && cell.exitEditMode &&
_.isFunction(cell.exitEditMode)) {
cell.exitEditMode();
}
}
});
self.undoMgr.addUndoType("pg-property-tab-changed", {
"ignore": false,
'mgr': self.undoMgr,
"on": function (tabs) {
if (!this.ignore && !this.mgr.stack.isCurrentlyUndoRedoing) {
return {
"object": tabs,
"before": null,
"after": null,
"options": this
}
}
this.igonre = false;
},
"undo": function (obj, before, after, opts) {
if (obj.hidden) {
opts.ignore = true;
$(obj.hidden).tab('show');
}
},
"redo": function (obj, before, after, opts) {
if (obj.shown) {
opts.ignore = true;
$(obj.shown).tab('show');
}
}
});
self.on('add', self.onModelAdd);
self.on('remove', self.onModelRemove);

View File

@ -142,6 +142,9 @@
className: function() {
return 'col-sm-12 col-md-12 col-lg-12 col-xs-12';
},
tabPanelClassName: function() {
return Backform.tabClassName;
},
initialize: function(opts) {
var s = opts.schema;
if (s && _.isArray(s)) {
@ -152,6 +155,9 @@
o.hId = o.hId || _.uniqueId('pgH_');
o.disabled = o.disabled || false;
});
if (opts.tabPanelClassName && _.isFunction(opts.tabPanelClassName)) {
this.tabPanelClassName = opts.tabPanelClassName;
}
}
this.model.errorModel = opts.errorModel || this.model.errorModel || new Backbone.Model();
this.controls = [];
@ -159,7 +165,7 @@
template: {
'header': _.template([
'<li role="presentation" <%=disabled ? "disabled" : ""%>>',
' <a data-toggle="tab" href="#<%=cId%>"',
' <a data-toggle="tab" data-tab-index="<%=tabIndex%>" href="#<%=cId%>"',
' id="<%=hId%>" aria-controls="<%=cId%>">',
'<%=label%></a></li>'].join(" ")),
'panel': _.template(
@ -173,12 +179,14 @@
.first().attr('id'),
m = this.model,
controls = this.controls,
tmpls = this.template;
tmpls = this.template,
self = this,
idx=0;
this.$el
.empty()
.attr('role', 'tabpanel')
.attr('class', Backform.tabClassName);
.attr('class', this.tabPanelClassName());
var tabHead = $('<ul class="nav nav-tabs" role="tablist"></ul>')
.appendTo(this.$el);
@ -186,7 +194,7 @@
.appendTo(this.$el);
_.each(this.schema, function(o) {
var el = $((tmpls['panel'])(o))
var el = $((tmpls['panel'])(_.extend(o, {'tabIndex': idx++})))
.appendTo(tabContent)
.removeClass('collapse').addClass('collapse'),
h = $((tmpls['header'])(o)).appendTo(tabHead);
@ -199,6 +207,14 @@
el.append(cntr.render().$el);
controls.push(cntr);
});
tabHead.find('a[data-toggle="tab"]').on('hidden.bs.tab', function() {
self.hidden_tab = this;
});
tabHead.find('a[data-toggle="tab"]').on('shown.bs.tab', function() {
self.shown_tab = this;
self.curr_tab_index = $(this).data('tabIndex');
m.trigger('pg-property-tab-changed', {'shown': self.shown_tab, 'hidden': self.hidden_tab});
});
});
var makeActive = tabHead.find('[id="' + c + '"]').first();
@ -302,7 +318,7 @@
" <label class='control-label col-sm-4'>" + data.label + "</label>" ,
" <button class='btn-sm btn-default add'>Add</buttton>",
"</div>"].join("\n");
gridBody = $("<div class='pgadmin-control-group backgrid form-group col-xs-12 object subnode' >").append(gridHeader);
gridBody = $("<div class='pgadmin-control-group backgrid form-group col-xs-12 object subnode'></div>").append(gridHeader);
var subnode = data.subnode.schema ? data.subnode : data.subnode.prototype,
columns = [],

View File

@ -24,12 +24,8 @@
} (this, function(root, _, $, Backbone, Backform, Alertify) {
var ObjectCellEditor = Backgrid.Extension.ObjectCellEditor = Backgrid.CellEditor.extend({
modalTemplate: _.template([
'<div class="subnode-dialog">',
'<div class="subnode-dialog" tabindex="1">',
' <div class="subnode-body"></div>',
' <div class="subnode-footer">',
' <button style ="float:right;margin-right:15px;margin-top: 4px;" class="cancel btn btn-danger" type="cancel">Cancel</button>',
' <button style ="float:right;margin-right:10px;margin-top: 4px;" class="save btn btn-primary" type="save">Save</button>',
' </div>',
'</div>'
].join("\n")),
stringTemplate: _.template([
@ -40,11 +36,9 @@
' </div>',
'</div>'
].join("\n")),
extendWithOptions: function(options) {
_.extend(this, options);
},
render: function () {
return this;
},
@ -59,11 +53,6 @@
if (!_.isArray(this.schema)) throw new TypeError("schema must be an array");
// Create a Backbone model from our object if it does not exist
if (!this.origModel) {
this.origModel = this.model;
this.model = this.origModel.clone();
}
var $dialog = this.createDialog(columns_length);
// Add the Bootstrap form
@ -72,36 +61,25 @@
// Call Backform to prepare dialog
back_el = $dialog.find('form.form-dialog');
Backform.tabClassName = "sub-node-form col-sm-12";
objectView = new Backform.Dialog({
this.objectView = new Backform.Dialog({
el: back_el, model: this.model, schema: this.schema,
tabPanelClassName: function() {
return 'sub-node-form col-sm-12';
}
});
objectView.render();
this.objectView.render();
return this;
},
createDialog: function(noofcol) {
var editor1 = this,
$dialog = this.$dialog = $(this.modalTemplate({title: ""})),
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);
noofcol = noofcol || 1;
// Handle close and save events
$dialog.find('button.cancel').click(function(e) {
e.preventDefault();
editor1.cancel();
tr.remove();
return false;
});
$dialog.find('button.save').click(function(e) {
e.preventDefault();
editor1.save();
tr.remove();
return false;
});
this.tr = tr;
// Show the Bootstrap modal dialog
td.append($dialog.css('display', 'block'));
@ -109,38 +87,21 @@
return $dialog;
},
save: function(options) {
options || (options = {});
var model = this.origModel,
column = this.column,
objectModel = this.model,
$form = this.$dialog.find('form');
save: function() {
// Retrieve values from the form, and store inside the object model
var changes = {};
_.each(this.schema, function(field) {
inputType = (field.control == 'datepicker' ? 'input' : field.control);
val = $form.find(inputType + '[name='+field.name+']').first().val()
val = (field.cell == 'integer') ? parseInt(val) :
(field.cell == 'number') ? parseFloat(val) : val
this.model.trigger("backgrid:edited", this.model, this.column, new Backgrid.Command({keyCode:13}));
if (this.tr) {
this.tr.remove();
}
changes[field.name] = val;
});
objectModel.set(changes);
model.set(changes, options);
model.trigger("backgrid:edited", model, column, new Backgrid.Command({keyCode:13}));
return this;
},
cancel: function() {
this.origModel.trigger("backgrid:edited", this.origModel, this.column, new Backgrid.Command({keyCode:27}));
return this;
},
remove: function() {
this.$dialog.modal("hide").remove();
Backgrid.CellEditor.prototype.remove.apply(this, arguments);
if (this.tr) {
this.tr.remove();
}
return this;
}
});
@ -177,7 +138,7 @@
});
editorOptions['el'] = $(this.el);
editorOptions['columns_length'] = this.column.collection.length
editorOptions['columns_length'] = this.column.collection.length;
this.listenTo(this.model, "backgrid:edit", function (model, column, cell, editor) {
if (column.get("name") == this.column.get("name"))
@ -185,16 +146,45 @@
});
},
enterEditMode: function () {
var $content = this.$el.html();
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-minus-square-o'></i>");
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
);
}
},
render: function(){
this.$el.empty();
this.$el.html("<i class='fa fa-pencil-square-o'></i>");
this.delegateEvents();
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
);
},
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();
}
}
});