Begin to work undo functionality into data layer -- currently just for Item.save()

History.undo()/redo() now reload the item, though changes won't show in open metadata pane due to #71

refs #67
This commit is contained in:
Dan Stillman 2006-06-25 04:11:19 +00:00
parent f897564f0e
commit b8ad832e74
2 changed files with 154 additions and 12 deletions

View File

@ -366,9 +366,14 @@ Scholar.Item.prototype.save = function(){
try { try {
Scholar.DB.beginTransaction(); Scholar.DB.beginTransaction();
// Begin history transaction
Scholar.History.begin('modify-item', this.getID());
// //
// Primary fields // Primary fields
// //
Scholar.History.modify('items', 'itemID', this.getID());
var sql = "UPDATE items SET "; var sql = "UPDATE items SET ";
var sql2; var sql2;
var sqlValues = []; var sqlValues = [];
@ -408,6 +413,9 @@ Scholar.Item.prototype.save = function(){
var creator = this.getCreator(orderIndex); var creator = this.getCreator(orderIndex);
// Delete at position // Delete at position
Scholar.History.remove('itemCreators', 'itemID-orderIndex',
[this.getID(), orderIndex]);
sql2 = 'DELETE FROM itemCreators' sql2 = 'DELETE FROM itemCreators'
+ ' WHERE itemID=' + this.getID() + ' WHERE itemID=' + this.getID()
+ ' AND orderIndex=' + orderIndex; + ' AND orderIndex=' + orderIndex;
@ -430,15 +438,21 @@ Scholar.Item.prototype.save = function(){
creator['firstName'], creator['firstName'],
creator['lastName'] creator['lastName']
); );
Scholar.History.add('creators', 'creatorID', creatorID);
} }
// If this creator and creatorType exists elsewhere, move it
sql2 = 'SELECT COUNT(*) FROM itemCreators' sql2 = 'SELECT COUNT(*) FROM itemCreators'
+ ' WHERE itemID=' + this.getID() + ' WHERE itemID=' + this.getID()
+ ' AND creatorID=' + creatorID + ' AND creatorID=' + creatorID
+ ' AND creatorTypeID=' + creator['creatorTypeID']; + ' AND creatorTypeID=' + creator['creatorTypeID'];
// If this creator and creatorType exists elsewhere, move it
if (Scholar.DB.valueQuery(sql2)){ if (Scholar.DB.valueQuery(sql2)){
Scholar.History.modify('itemCreators',
'itemID-creatorID-creatorTypeID',
[this.getID(), creatorID, creator['creatorTypeID']]);
sql = 'UPDATE itemCreators SET orderIndex=? ' sql = 'UPDATE itemCreators SET orderIndex=? '
+ "WHERE itemID=? AND creatorID=? AND " + "WHERE itemID=? AND creatorID=? AND "
+ "creatorTypeID=?"; + "creatorTypeID=?";
@ -452,6 +466,7 @@ Scholar.Item.prototype.save = function(){
Scholar.DB.query(sql, sqlValues); Scholar.DB.query(sql, sqlValues);
} }
// Otherwise insert // Otherwise insert
else { else {
sql = "INSERT INTO itemCreators VALUES (?,?,?,?)"; sql = "INSERT INTO itemCreators VALUES (?,?,?,?)";
@ -464,11 +479,21 @@ Scholar.Item.prototype.save = function(){
]; ];
Scholar.DB.query(sql, sqlValues); Scholar.DB.query(sql, sqlValues);
Scholar.History.add('itemCreators',
'itemID-creatorID-creatorTypeID',
[this.getID(), creatorID, creator['creatorTypeID']]);
} }
} }
// Delete obsolete creators // Delete obsolete creators
Scholar.Creators.purge(); var deleted;
if (deleted = Scholar.Creators.purge()){
for (var i in deleted){
// Add purged creators to history
Scholar.History.remove('creators', 'creatorID', i);
}
}
} }
@ -484,9 +509,13 @@ Scholar.Item.prototype.save = function(){
+ 'WHERE itemID=' + this.getID() + 'WHERE itemID=' + this.getID()
+ ' AND fieldID=' + fieldID; + ' AND fieldID=' + fieldID;
// Update
if (Scholar.DB.valueQuery(sql2)){ if (Scholar.DB.valueQuery(sql2)){
sqlValues = []; sqlValues = [];
Scholar.History.modify('itemData', 'itemID-fieldID',
[this.getID(), fieldID]);
sql = "UPDATE itemData SET value=?"; sql = "UPDATE itemData SET value=?";
// Take advantage of SQLite's manifest typing // Take advantage of SQLite's manifest typing
if (Scholar.ItemFields.isInteger(fieldID)){ if (Scholar.ItemFields.isInteger(fieldID)){
@ -504,7 +533,12 @@ Scholar.Item.prototype.save = function(){
Scholar.DB.query(sql, sqlValues); Scholar.DB.query(sql, sqlValues);
} }
// Insert
else { else {
Scholar.History.add('itemData', 'itemID-fieldID',
[this.getID(), fieldID]);
sql = "INSERT INTO itemData VALUES (?,?,?)"; sql = "INSERT INTO itemData VALUES (?,?,?)";
sqlValues = [ sqlValues = [
@ -522,6 +556,7 @@ Scholar.Item.prototype.save = function(){
Scholar.DB.query(sql, sqlValues); Scholar.DB.query(sql, sqlValues);
} }
} }
// If field changed and is empty, mark row for deletion // If field changed and is empty, mark row for deletion
else { else {
del.push(fieldID); del.push(fieldID);
@ -530,6 +565,12 @@ Scholar.Item.prototype.save = function(){
// Delete blank fields // Delete blank fields
if (del.length){ if (del.length){
// Add to history
for (var i in del){
Scholar.History.remove('itemData', 'itemID-fieldID',
[this.getID(), del[i]]);
}
sql = 'DELETE from itemData ' sql = 'DELETE from itemData '
+ 'WHERE itemID=' + this.getID() + ' ' + 'WHERE itemID=' + this.getID() + ' '
+ 'AND fieldID IN (' + del.join() + ")"; + 'AND fieldID IN (' + del.join() + ")";
@ -537,9 +578,11 @@ Scholar.Item.prototype.save = function(){
} }
} }
Scholar.History.commit();
Scholar.DB.commitTransaction(); Scholar.DB.commitTransaction();
} }
catch (e){ catch (e){
Scholar.History.cancel();
Scholar.DB.rollbackTransaction(); Scholar.DB.rollbackTransaction();
throw(e); throw(e);
} }
@ -569,8 +612,12 @@ Scholar.Item.prototype.save = function(){
try { try {
Scholar.DB.beginTransaction(); Scholar.DB.beginTransaction();
// Begin history transaction
// No associated id yet, so we use false
Scholar.History.begin('add-item', false);
// //
// itemData fields // Primary fields
// //
var sql = "INSERT INTO items (" + sqlColumns.join() + ')' var sql = "INSERT INTO items (" + sqlColumns.join() + ')'
+ ' VALUES ('; + ' VALUES (';
@ -584,7 +631,12 @@ Scholar.Item.prototype.save = function(){
var itemID = Scholar.DB.query(sql,sqlValues); var itemID = Scholar.DB.query(sql,sqlValues);
this._data['itemID'] = itemID; this._data['itemID'] = itemID;
// Set itemData Scholar.History.setAssociatedID(itemID);
Scholar.History.add('items', 'itemID', itemID);
//
// ItemData
//
if (this._changedItemData.length){ if (this._changedItemData.length){
for (fieldID in this._changedItemData.items){ for (fieldID in this._changedItemData.items){
if (!this.getField(fieldID)){ if (!this.getField(fieldID)){
@ -609,6 +661,9 @@ Scholar.Item.prototype.save = function(){
} }
Scholar.DB.query(sql, sqlValues); Scholar.DB.query(sql, sqlValues);
Scholar.History.add('itemData', 'itemID-fieldID',
this.getField(fieldID));
} }
} }
@ -636,6 +691,7 @@ Scholar.Item.prototype.save = function(){
creator['firstName'], creator['firstName'],
creator['lastName'] creator['lastName']
); );
Scholar.History.add('creators', 'creatorID', creatorID);
} }
sql = 'INSERT INTO itemCreators VALUES (' sql = 'INSERT INTO itemCreators VALUES ('
@ -643,9 +699,14 @@ Scholar.Item.prototype.save = function(){
+ creator['creatorTypeID'] + ', ' + orderIndex + creator['creatorTypeID'] + ', ' + orderIndex
+ ")"; + ")";
Scholar.DB.query(sql); Scholar.DB.query(sql);
Scholar.History.add('itemCreators',
'itemID-creatorID-creatorTypeID',
[this.getID(), creatorID, creator['creatorTypeID']]);
} }
} }
Scholar.History.commit();
Scholar.DB.commitTransaction(); Scholar.DB.commitTransaction();
// Reload collection to update isEmpty, // Reload collection to update isEmpty,
@ -653,6 +714,7 @@ Scholar.Item.prototype.save = function(){
Scholar.Collections.reloadAll(); Scholar.Collections.reloadAll();
} }
catch (e){ catch (e){
Scholar.History.cancel();
Scholar.DB.rollbackTransaction(); Scholar.DB.rollbackTransaction();
throw(e); throw(e);
} }

View File

@ -1,5 +1,6 @@
Scholar.History = new function(){ Scholar.History = new function(){
this.begin = begin; this.begin = begin;
this.setAssociatedID = setAssociatedID;
this.add = add; this.add = add;
this.modify = modify; this.modify = modify;
this.remove = remove; this.remove = remove;
@ -18,12 +19,15 @@ Scholar.History = new function(){
var _activeEvent; var _activeEvent;
var _maxID = 0; var _maxID = 0;
// event: ('item-add', 'item-delete', 'item-modify', 'collection-add', 'collection-modify', 'collection-delete')
// context: (itemCreators.itemID-creatorID.1-1)
// action: ('add', 'delete', 'modify')
/** /**
* Begin a transaction set * Begin a transaction set
*
* event: 'item-add', 'item-delete', 'item-modify', 'collection-add',
* 'collection-modify', 'collection-delete'...
*
* id: An id or array of ids that will be passed to
* Scholar.Notifier.trigger() on an undo or redo
**/ **/
function begin(event, id){ function begin(event, id){
if (_activeID){ if (_activeID){
@ -40,8 +44,16 @@ Scholar.History = new function(){
Scholar.debug('Beginning history transaction set ' + event); Scholar.debug('Beginning history transaction set ' + event);
var sql = "INSERT INTO transactionSets (event, id) VALUES " var sql = "INSERT INTO transactionSets (event, id) VALUES "
+ "('" + event + "', "; + "('" + event + "', ";
// If integer, insert natively; if array, insert as string if (!id){
sql += (typeof id=='object') ? "'" + id.join('-') + "'" : id; sql += '0';
}
// If array, insert hyphen-delimited string
else if (typeof id=='object'){
sql += "'" + id.join('-') + "'"
}
else {
sql += id;
}
sql += ")"; sql += ")";
Scholar.DB.beginTransaction(); Scholar.DB.beginTransaction();
@ -50,8 +62,44 @@ Scholar.History = new function(){
} }
/**
* Associate an id or array of ids with the transaction set --
* for use if the ids weren't available at when begin() was called
*
* id: An id or array of ids that will be passed to
* Scholar.Notifier.trigger() on an undo or redo
**/
function setAssociatedID(id){
if (!_activeID){
throw('Cannot call setAssociatedID() with no history transaction set in progress');
}
var sql = "UPDATE transactionSets SET id=";
if (!id){
sql += '0';
}
// If array, insert hyphen-delimited string
else if (typeof id=='object'){
sql += "'" + id.join('-') + "'"
}
else {
sql += id;
}
sql += " WHERE transactionSetID=" + _activeID;
Scholar.DB.query(sql);
}
/** /**
* Add an add transaction to the current set * Add an add transaction to the current set
*
* Can be called before or after an INSERT statement
*
* key is a hyphen-delimited list of columns identifying the row
* e.g. 'itemID-creatorID'
*
* keyValues is a hyphen-delimited list of values matching the key parts
* e.g. '1-1'
**/ **/
function add(table, key, keyValues){ function add(table, key, keyValues){
return _addTransaction('add', table, key, keyValues); return _addTransaction('add', table, key, keyValues);
@ -61,6 +109,14 @@ Scholar.History = new function(){
/** /**
* Add a modify transaction to the current set * Add a modify transaction to the current set
* *
* Must be called before an UPDATE statement
*
* key is a hyphen-delimited list of columns identifying the row
* e.g. 'itemID-creatorID'
*
* keyValues is a hyphen-delimited list of values matching the key parts
* e.g. '1-1'
*
* _field_ is optional -- otherwise all fields are saved * _field_ is optional -- otherwise all fields are saved
**/ **/
function modify(table, key, keyValues, field){ function modify(table, key, keyValues, field){
@ -70,6 +126,14 @@ Scholar.History = new function(){
/** /**
* Add a remove transaction to the current set * Add a remove transaction to the current set
*
* Must be called before a DELETE statement
*
* key is a hyphen-delimited list of columns identifying the row
* e.g. 'itemID-creatorID'
*
* keyValues is a hyphen-delimited list of values matching the key parts
* e.g. '1-1'
**/ **/
function remove(table, key, keyValues){ function remove(table, key, keyValues){
return _addTransaction('remove', table, key, keyValues); return _addTransaction('remove', table, key, keyValues);
@ -139,7 +203,7 @@ Scholar.History = new function(){
var undone = _do('undo'); var undone = _do('undo');
_currentID--; _currentID--;
Scholar.DB.commitTransaction(); Scholar.DB.commitTransaction();
_notifyEvent(id); _reloadAndNotify(id);
return true; return true;
} }
@ -154,7 +218,7 @@ Scholar.History = new function(){
var redone = _do('redo'); var redone = _do('redo');
_currentID++; _currentID++;
Scholar.DB.commitTransaction(); Scholar.DB.commitTransaction();
_notifyEvent(id); _reloadAndNotify(id, true);
return redone; return redone;
} }
@ -421,9 +485,25 @@ Scholar.History = new function(){
} }
function _notifyEvent(transactionSetID){ function _reloadAndNotify(transactionSetID, redo){
var data = _getSetData(transactionSetID); var data = _getSetData(transactionSetID);
var eventParts = data['event'].split('-'); // e.g. modify-item var eventParts = data['event'].split('-'); // e.g. modify-item
if (redo){
switch (eventParts[0]){
case 'add':
eventParts[0] = 'remove';
break;
case 'remove':
eventParts[0] = 'add';
break;
}
}
switch (eventParts[1]){
case 'item':
Scholar.Items.reload(data['id']);
break;
}
Scholar.Notifier.trigger(eventParts[0], eventParts[1], data['id']); Scholar.Notifier.trigger(eventParts[0], eventParts[1], data['id']);
} }
} }