commit b8ad832e74e127786fe970852dfcd682327ef18c
parent f897564f0ef2233b7a7f940fee9547053332e37e
Author: Dan Stillman <dstillman@zotero.org>
Date: Sun, 25 Jun 2006 04:11:19 +0000
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
Diffstat:
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
@@ -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
@@ -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();
@@ -51,7 +63,43 @@ 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']);
}
}