From b8ad832e74e127786fe970852dfcd682327ef18c Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Sun, 25 Jun 2006 04:11:19 +0000 Subject: [PATCH] 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 --- .../content/scholar/xpcom/data_access.js | 70 +++++++++++++- .../content/scholar/xpcom/history.js | 96 +++++++++++++++++-- 2 files changed, 154 insertions(+), 12 deletions(-) diff --git a/chrome/chromeFiles/content/scholar/xpcom/data_access.js b/chrome/chromeFiles/content/scholar/xpcom/data_access.js index e7a75ec0d..25f5e6694 100644 --- a/chrome/chromeFiles/content/scholar/xpcom/data_access.js +++ b/chrome/chromeFiles/content/scholar/xpcom/data_access.js @@ -366,9 +366,14 @@ Scholar.Item.prototype.save = function(){ try { Scholar.DB.beginTransaction(); + // Begin history transaction + Scholar.History.begin('modify-item', this.getID()); + // // Primary fields // + Scholar.History.modify('items', 'itemID', this.getID()); + var sql = "UPDATE items SET "; var sql2; var sqlValues = []; @@ -408,6 +413,9 @@ Scholar.Item.prototype.save = function(){ var creator = this.getCreator(orderIndex); // Delete at position + Scholar.History.remove('itemCreators', 'itemID-orderIndex', + [this.getID(), orderIndex]); + sql2 = 'DELETE FROM itemCreators' + ' WHERE itemID=' + this.getID() + ' AND orderIndex=' + orderIndex; @@ -430,15 +438,21 @@ Scholar.Item.prototype.save = function(){ creator['firstName'], creator['lastName'] ); + Scholar.History.add('creators', 'creatorID', creatorID); } - // If this creator and creatorType exists elsewhere, move it + sql2 = 'SELECT COUNT(*) FROM itemCreators' + ' WHERE itemID=' + this.getID() + ' AND creatorID=' + creatorID + ' AND creatorTypeID=' + creator['creatorTypeID']; + // If this creator and creatorType exists elsewhere, move it if (Scholar.DB.valueQuery(sql2)){ + Scholar.History.modify('itemCreators', + 'itemID-creatorID-creatorTypeID', + [this.getID(), creatorID, creator['creatorTypeID']]); + sql = 'UPDATE itemCreators SET orderIndex=? ' + "WHERE itemID=? AND creatorID=? AND " + "creatorTypeID=?"; @@ -452,6 +466,7 @@ Scholar.Item.prototype.save = function(){ Scholar.DB.query(sql, sqlValues); } + // Otherwise insert else { sql = "INSERT INTO itemCreators VALUES (?,?,?,?)"; @@ -464,11 +479,21 @@ Scholar.Item.prototype.save = function(){ ]; Scholar.DB.query(sql, sqlValues); + + Scholar.History.add('itemCreators', + 'itemID-creatorID-creatorTypeID', + [this.getID(), creatorID, creator['creatorTypeID']]); } } // 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() + ' AND fieldID=' + fieldID; + // Update if (Scholar.DB.valueQuery(sql2)){ sqlValues = []; + Scholar.History.modify('itemData', 'itemID-fieldID', + [this.getID(), fieldID]); + sql = "UPDATE itemData SET value=?"; // Take advantage of SQLite's manifest typing if (Scholar.ItemFields.isInteger(fieldID)){ @@ -504,7 +533,12 @@ Scholar.Item.prototype.save = function(){ Scholar.DB.query(sql, sqlValues); } + + // Insert else { + Scholar.History.add('itemData', 'itemID-fieldID', + [this.getID(), fieldID]); + sql = "INSERT INTO itemData VALUES (?,?,?)"; sqlValues = [ @@ -522,6 +556,7 @@ Scholar.Item.prototype.save = function(){ Scholar.DB.query(sql, sqlValues); } } + // If field changed and is empty, mark row for deletion else { del.push(fieldID); @@ -530,6 +565,12 @@ Scholar.Item.prototype.save = function(){ // Delete blank fields 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 ' + 'WHERE itemID=' + this.getID() + ' ' + 'AND fieldID IN (' + del.join() + ")"; @@ -537,9 +578,11 @@ Scholar.Item.prototype.save = function(){ } } + Scholar.History.commit(); Scholar.DB.commitTransaction(); } catch (e){ + Scholar.History.cancel(); Scholar.DB.rollbackTransaction(); throw(e); } @@ -569,8 +612,12 @@ Scholar.Item.prototype.save = function(){ try { 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() + ')' + ' VALUES ('; @@ -584,7 +631,12 @@ Scholar.Item.prototype.save = function(){ var itemID = Scholar.DB.query(sql,sqlValues); this._data['itemID'] = itemID; - // Set itemData + Scholar.History.setAssociatedID(itemID); + Scholar.History.add('items', 'itemID', itemID); + + // + // ItemData + // if (this._changedItemData.length){ for (fieldID in this._changedItemData.items){ if (!this.getField(fieldID)){ @@ -609,6 +661,9 @@ Scholar.Item.prototype.save = function(){ } 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['lastName'] ); + Scholar.History.add('creators', 'creatorID', creatorID); } sql = 'INSERT INTO itemCreators VALUES (' @@ -643,9 +699,14 @@ Scholar.Item.prototype.save = function(){ + creator['creatorTypeID'] + ', ' + orderIndex + ")"; Scholar.DB.query(sql); + + Scholar.History.add('itemCreators', + 'itemID-creatorID-creatorTypeID', + [this.getID(), creatorID, creator['creatorTypeID']]); } } + Scholar.History.commit(); Scholar.DB.commitTransaction(); // Reload collection to update isEmpty, @@ -653,6 +714,7 @@ Scholar.Item.prototype.save = function(){ Scholar.Collections.reloadAll(); } catch (e){ + Scholar.History.cancel(); Scholar.DB.rollbackTransaction(); throw(e); } diff --git a/chrome/chromeFiles/content/scholar/xpcom/history.js b/chrome/chromeFiles/content/scholar/xpcom/history.js index 1b88a9b00..1e90e5720 100644 --- a/chrome/chromeFiles/content/scholar/xpcom/history.js +++ b/chrome/chromeFiles/content/scholar/xpcom/history.js @@ -1,5 +1,6 @@ Scholar.History = new function(){ this.begin = begin; + this.setAssociatedID = setAssociatedID; this.add = add; this.modify = modify; this.remove = remove; @@ -18,12 +19,15 @@ Scholar.History = new function(){ var _activeEvent; 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 + * + * 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){ if (_activeID){ @@ -40,8 +44,16 @@ Scholar.History = new function(){ Scholar.debug('Beginning history transaction set ' + event); var sql = "INSERT INTO transactionSets (event, id) VALUES " + "('" + event + "', "; - // If integer, insert natively; if array, insert as string - sql += (typeof id=='object') ? "'" + id.join('-') + "'" : id; + if (!id){ + sql += '0'; + } + // If array, insert hyphen-delimited string + else if (typeof id=='object'){ + sql += "'" + id.join('-') + "'" + } + else { + sql += id; + } sql += ")"; 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 + * + * 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){ return _addTransaction('add', table, key, keyValues); @@ -61,6 +109,14 @@ Scholar.History = new function(){ /** * 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 **/ function modify(table, key, keyValues, field){ @@ -70,6 +126,14 @@ Scholar.History = new function(){ /** * 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){ return _addTransaction('remove', table, key, keyValues); @@ -139,7 +203,7 @@ Scholar.History = new function(){ var undone = _do('undo'); _currentID--; Scholar.DB.commitTransaction(); - _notifyEvent(id); + _reloadAndNotify(id); return true; } @@ -154,7 +218,7 @@ Scholar.History = new function(){ var redone = _do('redo'); _currentID++; Scholar.DB.commitTransaction(); - _notifyEvent(id); + _reloadAndNotify(id, true); return redone; } @@ -421,9 +485,25 @@ Scholar.History = new function(){ } - function _notifyEvent(transactionSetID){ + function _reloadAndNotify(transactionSetID, redo){ var data = _getSetData(transactionSetID); 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']); } }