Updated schema for new collections ("projects"?) model and updated sample data accordingly; added a couple extra indexes; made 'rights' and 'source' regular metadata fields so that items can be used for things like notes

Overhauled data access layer to support new model:

	- Changed Item.getParent to Item.getCollections() to get ids of parent collections
	- Removed Item.setPosition() -- item positions are set internally when items are added to and deleted from collections, but the orderIndex is not currently used or manipulatable externally
	- Item constructor/Items.getNewItemByType()/Items.add() no longer take folderID and orderIndex as parameters
	- Split getTreeRows() into Scholar.getCollections(parent) and Scholar.getItems(parent), which return root collections or all library items, respectively, if no parent given
	- All references to folders in object/method/property names changed to collections
	- New methods Collection.addItem(itemID), Collection.hasItem(itemID), Collection.removeItem(itemID)
	- Collection.erase() takes optional deleteItems parameter to delete items from DB -- otherwise just removes association and leaves item in library (does, however, delete all descendent collections from DB regardless)

* Note: This will break displaying of items until interface code is updated. *
This commit is contained in:
Dan Stillman 2006-06-01 00:22:18 +00:00
parent 041e607dd7
commit 03637a6c63
4 changed files with 365 additions and 393 deletions

View File

@ -6,12 +6,9 @@
Scholar.Item = function(){ Scholar.Item = function(){
this._init(); this._init();
// Accept itemTypeID, folderID and orderIndex in constructor // Accept itemTypeIDin constructor
if (arguments.length){ if (arguments.length){
this.setType(arguments[0]); this.setType(arguments[0]);
if (arguments.length>1){
this.setPosition(arguments[1], arguments[2] ? arguments[2] : false);
}
} }
} }
@ -42,20 +39,17 @@ Scholar.Item.prototype._init = function(){
* Check if the specified field is a primary field from the items table * Check if the specified field is a primary field from the items table
*/ */
Scholar.Item.prototype.isPrimaryField = function(field){ Scholar.Item.prototype.isPrimaryField = function(field){
// Create primaryFields hash array if not yet created
if (!Scholar.Item.primaryFields){ if (!Scholar.Item.primaryFields){
Scholar.Item.primaryFields = Scholar.DB.getColumnHash('items'); Scholar.Item.primaryFields = Scholar.DB.getColumnHash('items');
Scholar.Item.primaryFields['firstCreator'] = true; Scholar.Item.primaryFields['firstCreator'] = true;
Scholar.Item.primaryFields['parentFolderID'] = true;
Scholar.Item.primaryFields['orderIndex'] = true;
} }
return !!Scholar.Item.primaryFields[field]; return !!Scholar.Item.primaryFields[field];
} }
Scholar.Item.editableFields = { Scholar.Item.editableFields = {
title: true, title: true
source: true,
rights: true
}; };
/* /*
@ -70,14 +64,12 @@ Scholar.Item.prototype.isEditableField = function(field){
* Build object from database * Build object from database
*/ */
Scholar.Item.prototype.loadFromID = function(id){ Scholar.Item.prototype.loadFromID = function(id){
var sql = 'SELECT I.*, lastName AS firstCreator, TS.parentFolderID, ' var sql = 'SELECT I.*, lastName AS firstCreator '
+ 'TS.orderIndex '
+ 'FROM items I ' + 'FROM items I '
+ 'LEFT JOIN treeStructure TS ON (I.itemID=TS.id AND isFolder=0) '
+ 'LEFT JOIN itemCreators IC ON (I.itemID=IC.itemID) ' + 'LEFT JOIN itemCreators IC ON (I.itemID=IC.itemID) '
+ 'LEFT JOIN creators C ON (IC.creatorID=C.creatorID) ' + 'LEFT JOIN creators C ON (IC.creatorID=C.creatorID) '
+ 'WHERE itemID=' + id + 'WHERE itemID=' + id
+ ' AND (IC.orderIndex=0 OR IC.orderIndex IS NULL)'; + ' AND (IC.orderIndex=0 OR IC.orderIndex IS NULL)'; // first creator
var row = Scholar.DB.rowQuery(sql); var row = Scholar.DB.rowQuery(sql);
this.loadFromRow(row); this.loadFromRow(row);
} }
@ -89,9 +81,13 @@ Scholar.Item.prototype.loadFromID = function(id){
Scholar.Item.prototype.loadFromRow = function(row){ Scholar.Item.prototype.loadFromRow = function(row){
this._init(); this._init();
for (col in row){ for (col in row){
if (this.isPrimaryField(col) || col=='firstCreator'){ // Only accept primary field data through loadFromRow()
if (this.isPrimaryField(col)){
this._data[col] = row[col]; this._data[col] = row[col];
} }
else {
Scholar.debug(col + ' is not a valid primary field');
}
} }
return true; return true;
} }
@ -116,11 +112,6 @@ Scholar.Item.prototype.getType = function(){
} }
Scholar.Item.prototype.getParent = function(){
return this._data['parentFolderID'] ? this._data['parentFolderID'] : false;
}
/* /*
* Set or change the item's type * Set or change the item's type
*/ */
@ -150,6 +141,15 @@ Scholar.Item.prototype.setType = function(itemTypeID){
} }
/**
* Return an array of collectionIDs for all collections the item belongs to
**/
Scholar.Item.prototype.getCollections = function(){
return Scholar.DB.columnQuery("SELECT collectionID FROM collectionItems "
+ "WHERE itemID=" + this.getID());
}
/* /*
* Returns the number of creators for this item * Returns the number of creators for this item
*/ */
@ -160,6 +160,7 @@ Scholar.Item.prototype.numCreators = function(){
return this._creators.length; return this._creators.length;
} }
/* /*
* Returns an array of the creator data at the given position, or false if none * Returns an array of the creator data at the given position, or false if none
*/ */
@ -313,89 +314,6 @@ Scholar.Item.prototype.setField = function(field, value, loadIn){
} }
} }
/*
* Move item to new position and shift surrounding items
*
* N.B. Unless isNew is set or the item doesn't yet have an id,
* this function updates the DB immediately and
* reloads all cached items -- a save() is not required
*
* If isNew is true, a transaction is not started or committed, so if
* the item has an id it should only be run from an existing transaction
* within Scholar.Item.save()
*/
Scholar.Item.prototype.setPosition = function(newFolder, newPos, isNew){
var oldFolder = this.getField('parentFolderID');
var oldPos = this.getField('orderIndex');
if (this.getID()){
if (!isNew && newFolder==oldFolder && newPos==oldPos){
return true;
}
if (!isNew){
Scholar.DB.beginTransaction();
}
if (!newFolder){
newFolder = 0;
}
// Do a foreign key check manually
else if (!parseInt(Scholar.DB.valueQuery('SELECT COUNT(*) FROM folders '
+ 'WHERE folderID=' + newFolder))){
throw('Attempt to add item to invalid folder');
}
// If no position provided, drop at end of folder
if (!newPos){
newPos = Scholar.DB.valueQuery('SELECT MAX(orderIndex)+1 FROM ' +
'treeStructure WHERE parentFolderID=' + newFolder);
}
// Otherwise shift down above it in old folder and shift up at it or
// above it in new folder
else {
sql = 'UPDATE treeStructure SET orderIndex=orderIndex-1 ' +
'WHERE parentFolderID=' + oldFolder +
' AND orderIndex>' + oldPos + ";\n";
sql += 'UPDATE treeStructure SET orderIndex=orderIndex+1 ' +
'WHERE parentFolderID=' + newFolder +
' AND orderIndex>=' + newPos + ";\n";
Scholar.DB.query(sql);
}
// If a new item, insert
if (isNew){
var sql = 'INSERT INTO treeStructure '
+ '(id, isFolder, orderIndex, parentFolderID) VALUES ('
+ this.getID() + ', 0, ' + newPos + ', ' + newFolder + ')';
}
// Otherwise update
else {
var sql = 'UPDATE treeStructure SET parentFolderID=' + newFolder +
', orderIndex=' + newPos + ' WHERE id=' + this.getID() +
" AND isFolder=0;\n";
}
Scholar.DB.query(sql);
if (!isNew){
Scholar.DB.commitTransaction();
}
}
this._data['parentFolderID'] = newFolder;
this._data['orderIndex'] = newPos;
if (this.getID() && !isNew){
Scholar.Items.reloadAll();
Scholar.Folders.reloadAll(); // needed to recheck isEmpty
}
return true;
}
/* /*
* Save changes back to database * Save changes back to database
*/ */
@ -428,12 +346,6 @@ Scholar.Item.prototype.save = function(){
if (this._changed.has('title')){ if (this._changed.has('title')){
sql += "title='" + this.getField('title') + "', "; sql += "title='" + this.getField('title') + "', ";
} }
if (this._changed.has('source')){
sql += "source='" + this.getField('source') + "', ";
}
if (this._changed.has('rights')){
sql += "rights='" + this.getField('rights') + "', ";
}
// Always update modified time // Always update modified time
sql += "dateModified=CURRENT_TIMESTAMP "; sql += "dateModified=CURRENT_TIMESTAMP ";
@ -583,14 +495,6 @@ Scholar.Item.prototype.save = function(){
sqlColumns.push('title'); sqlColumns.push('title');
sqlValues.push({'string':this.getField('title')}); sqlValues.push({'string':this.getField('title')});
} }
if (this._changed.has('source')){
sqlColumns.push('source');
sqlValues.push({'string':this.getField('source')});
}
if (this._changed.has('rights')){
sqlColumns.push('rights');
sqlValues.push({'string':this.getField('rights')});
}
try { try {
Scholar.DB.beginTransaction(); Scholar.DB.beginTransaction();
@ -668,20 +572,11 @@ Scholar.Item.prototype.save = function(){
} }
} }
// Set the position of the new item
var newFolder = this.getField('parentFolderID')
? this.getField('parentFolderID') : 0;
var newPos = this.getField('orderIndex')
? this.getField('orderIndex') : false;
this.setPosition(newFolder, newPos, true);
Scholar.DB.commitTransaction(); Scholar.DB.commitTransaction();
// Reload folders to update isEmpty, // Reload collection to update isEmpty,
// in case this was the first item in a folder // in case this was the first item in a collection
Scholar.Folders.reloadAll(); Scholar.Collections.reloadAll();
} }
catch (e){ catch (e){
Scholar.DB.rollbackTransaction(); Scholar.DB.rollbackTransaction();
@ -707,15 +602,11 @@ Scholar.Item.prototype.erase = function(){
Scholar.DB.beginTransaction(); Scholar.DB.beginTransaction();
// Remove item from parent collections
var parentFolderID = this.getField('parentFolderID'); var parentCollectionIDs = this.getCollections();
var orderIndex = this.getField('orderIndex'); for (var i=0; i<parentCollectionIDs.length; i++){
Scholar.Collections.get(parentCollectionIDs[i]).removeItem(this.getID());
var sql = 'DELETE FROM treeStructure WHERE id=' + this.getID() + ";\n"; }
sql += 'UPDATE treeStructure SET orderIndex=orderIndex-1 ' +
'WHERE parentFolderID=' + parentFolderID +
' AND orderIndex>' + orderIndex + ";\n\n";
sql += 'DELETE FROM itemCreators WHERE itemID=' + this.getID() + ";\n"; sql += 'DELETE FROM itemCreators WHERE itemID=' + this.getID() + ";\n";
sql += 'DELETE FROM itemKeywords WHERE itemID=' + this.getID() + ";\n"; sql += 'DELETE FROM itemKeywords WHERE itemID=' + this.getID() + ";\n";
@ -744,10 +635,6 @@ Scholar.Item.prototype.toString = function(){
} }
Scholar.Item.prototype.isFolder = function(){
return false;
}
////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////
// //
@ -831,7 +718,6 @@ Scholar.Items = new function(){
// Privileged methods // Privileged methods
this.get = get; this.get = get;
this.getAll = getAll; this.getAll = getAll;
this.getTreeRows = getTreeRows;
this.reload = reload; this.reload = reload;
this.reloadAll = reloadAll; this.reloadAll = reloadAll;
this.getNewItemByType = getNewItemByType; this.getNewItemByType = getNewItemByType;
@ -887,79 +773,14 @@ Scholar.Items = new function(){
* Returns all items in the database * Returns all items in the database
*/ */
function getAll(){ function getAll(){
var sql = 'SELECT I.itemID FROM items I ' var sql = 'SELECT itemID FROM items';
+ 'LEFT JOIN treeStructure TS ON (I.itemID=TS.id AND isFolder=0) ' // DEBUG: default order?
+ 'ORDER BY orderIndex';
var ids = Scholar.DB.columnQuery(sql); var ids = Scholar.DB.columnQuery(sql);
return this.get(ids); return this.get(ids);
} }
/*
* Returns an array of all folders and items that are children of a folder
* as Scholar.Folder and Scholar.Item instances
*
* Takes parent folderID as optional parameter; by default, returns root items
*
* Type can tested with instanceof (e.g. if (obj instanceof Scholar.Folder)) or isFolder()
*/
function getTreeRows(parent, type){
var toReturn = new Array();
/*
// To return all items (no longer used)
var sql = 'SELECT * FROM treeStructure WHERE id>0 ORDER BY orderIndex';
*/
if (!parent){
parent = 0;
}
var sql = 'SELECT * FROM treeStructure TS '
+ 'WHERE parentFolderID=' + parent;
switch (type){
case 'folders':
sql += ' AND isFolder=1';
break;
case 'items':
sql += ' AND isFolder=0';
break;
}
sql += ' ORDER BY orderIndex';
var tree = Scholar.DB.query(sql);
if (!tree){
Scholar.debug('No children of folder ' + parent, 5);
return toReturn;
}
_load('all');
for (var i=0, len=tree.length; i<len; i++){
if (parseInt(tree[i]['isFolder'])){
var obj = Scholar.Folders.get(tree[i]['id']);
if (!obj){
throw ('Folder ' + tree[i]['id'] + ' not found');
}
}
else {
var obj = Scholar.Items.get(tree[i]['id']);
if (!obj){
throw ('Item ' + tree[i]['id'] + ' not found');
}
}
toReturn.push(obj);
}
return toReturn;
}
/* /*
* Reloads data for specified items into internal array * Reloads data for specified items into internal array
* *
@ -991,15 +812,15 @@ Scholar.Items = new function(){
} }
function getNewItemByType(itemTypeID, parentFolderID, orderIndex){ function getNewItemByType(itemTypeID){
return new Scholar.Item(itemTypeID, parentFolderID, orderIndex); return new Scholar.Item(itemTypeID);
} }
function add(data, itemTypeID, folderID, orderIndex){ function add(data, itemTypeID){
var insert = new Array(); var insert = new Array();
var obj = new Scholar.Item(itemTypeID, folderID, orderIndex); var obj = new Scholar.Item(itemTypeID);
for (field in data){ for (field in data){
obj.setField(data[field]); obj.setField(data[field]);
@ -1038,10 +859,8 @@ Scholar.Items = new function(){
// Should be the same as query in Scholar.Item.loadFromID, just // Should be the same as query in Scholar.Item.loadFromID, just
// without itemID clause // without itemID clause
var sql = 'SELECT I.*, lastName AS firstCreator, TS.parentFolderID, ' var sql = 'SELECT I.*, lastName AS firstCreator '
+ 'TS.orderIndex '
+ 'FROM items I ' + 'FROM items I '
+ 'LEFT JOIN treeStructure TS ON (I.itemID=TS.id AND isFolder=0) '
+ 'LEFT JOIN itemCreators IC ON (I.itemID=IC.itemID) ' + 'LEFT JOIN itemCreators IC ON (I.itemID=IC.itemID) '
+ 'LEFT JOIN creators C ON (IC.creatorID=C.creatorID) ' + 'LEFT JOIN creators C ON (IC.creatorID=C.creatorID) '
+ 'WHERE IC.orderIndex=0 OR IC.orderIndex IS NULL'; + 'WHERE IC.orderIndex=0 OR IC.orderIndex IS NULL';
@ -1068,30 +887,33 @@ Scholar.Items = new function(){
/* /*
* Constructor for Folder object * Constructor for Collection object
* *
* Generally should be called from Scholar.Folders rather than directly * Generally should be called from Scholar.Collection rather than directly
*/ */
Scholar.Folder = function(){ Scholar.Collection = function(){
this._id; this._id;
this._name; this._name;
this._parent; this._parent;
this._hasChildCollections;
this._hasChildItems;
this._childItems = new Scholar.Hash();
this._childItemsLoaded;
} }
/* /*
* Build folder from database * Build collection from database
*/ */
Scholar.Folder.prototype.loadFromID = function(id){ Scholar.Collection.prototype.loadFromID = function(id){
// Should be same as query in Scholar.Folders, just with folderID // Should be same as query in Scholar.Collections, just with collectionID
var sql = "SELECT folderID, folderName, parentFolderID, " var sql = "SELECT collectionID, collectionName, parentCollectionID, "
+ "(SELECT COUNT(*) FROM treeStructure WHERE " + "(SELECT COUNT(*) FROM collections WHERE "
+ "parentFolderID=TS.id AND isFolder=1)!=0 AS hasChildFolders, " + "parentCollectionID=C.collectionID)!=0 AS hasChildCollections, "
+ "(SELECT COUNT(*) FROM treeStructure WHERE " + "(SELECT COUNT(*) FROM collectionItems WHERE "
+ "parentFolderID=TS.id AND isFolder=0)!=0 AS hasChildItems " + "collectionID=C.collectionID)!=0 AS hasChildItems "
+ "FROM folders F " + "FROM collections C "
+ "JOIN treeStructure TS ON (F.folderID=TS.id AND TS.isFolder=1) " + "WHERE collectionID=" + id;
+ "WHERE folderID=" + id;
var row = Scholar.DB.rowQuery(sql); var row = Scholar.DB.rowQuery(sql);
this.loadFromRow(row); this.loadFromRow(row);
@ -1099,95 +921,183 @@ Scholar.Folder.prototype.loadFromID = function(id){
/* /*
* Populate folder data from a database row * Populate collection data from a database row
*/ */
Scholar.Folder.prototype.loadFromRow = function(row){ Scholar.Collection.prototype.loadFromRow = function(row){
this._id = row['folderID']; this._id = row['collectionID'];
this._name = row['folderName']; this._name = row['collectionName'];
this._parent = row['parentFolderID']; this._parent = row['parentCollectionID'];
this._hasChildFolders = row['hasChildFolders']; this._hasChildCollections = row['hasChildCollections'];
this._hasChildItems = row['hasChildItems']; this._hasChildItems = row['hasChildItems'];
} }
Scholar.Folder.prototype.getID = function(){
Scholar.Collection.prototype.getID = function(){
return this._id; return this._id;
} }
Scholar.Folder.prototype.getName = function(){ Scholar.Collection.prototype.getName = function(){
return this._name; return this._name;
} }
Scholar.Folder.prototype.isFolder = function(){ Scholar.Collection.prototype.getParent = function(){
return true;
}
Scholar.Folder.prototype.getParent = function(){
return this._parent; return this._parent;
} }
Scholar.Folder.prototype.isEmpty = function(){
return !(parseInt(this._hasChildFolders)) && !(parseInt(this._hasChildItems)); Scholar.Collection.prototype.isEmpty = function(){
return !(parseInt(this._hasChildCollections)) && !(parseInt(this._hasChildItems));
} }
Scholar.Folder.prototype.hasChildFolders = function(){ Scholar.Collection.prototype.hasChildCollections = function(){
return !!(parseInt(this._hasChildFolders)); return !!(parseInt(this._hasChildCollections));
} }
Scholar.Folder.prototype.hasChildItems = function(){ Scholar.Collection.prototype.hasChildItems = function(){
return !!(parseInt(this._hasChildItems)); return !!(parseInt(this._hasChildItems));
} }
Scholar.Collection.prototype.addItem = function(itemID){
Scholar.DB.beginTransaction();
if (!Scholar.Items.get(itemID)){
Scholar.DB.rollbackTransaction();
throw(itemID + ' is not a valid item id');
}
var nextOrderIndex = Scholar.DB.valueQuery("SELECT IFNULL(MAX(orderIndex)+1, 0) "
+ "FROM collectionItems WHERE collectionID=" + this._id);
var sql = "INSERT OR IGNORE INTO collectionItems VALUES "
+ "(" + this._id + ", " + itemID + ", " + nextOrderIndex + ")";
Scholar.DB.query(sql);
Scholar.DB.commitTransaction();
this._childItems.set(itemID);
this._hasChildItems = true;
}
Scholar.Collection.prototype.removeItem = function(itemID){
Scholar.DB.beginTransaction();
var sql = "SELECT orderIndex FROM collectionItems "
+ "WHERE collectionID=" + this._id + " AND itemID=" + itemID;
var orderIndex = Scholar.DB.valueQuery(sql);
if (orderIndex===false){
Scholar.debug('Item ' + itemID + ' is not a child of collection '
+ this._id);
Scholar.DB.rollbackTransaction();
return false;
}
var sql = "DELETE FROM collectionItems WHERE collectionID=" + this._id
+ " AND itemID=" + itemID;
Scholar.DB.query(sql);
// Move down items above deleted item in collection
sql = 'UPDATE collectionItems SET orderIndex=orderIndex-1 '
+ 'WHERE collectionID=' + this._id
+ ' AND orderIndex>' + orderIndex;
Scholar.DB.query(sql);
Scholar.DB.commitTransaction();
this._childItems.remove(itemID);
// If this was the last item, set collection to empty
if (!this._childItems.length){
this._hasChildItems = false;
}
}
Scholar.Collection.prototype.hasItem = function(itemID){
if (!this._childItemsLoaded){
this._loadChildItems();
}
return this._childItems.has(itemID);
}
/** /**
* Deletes a folder and all descendent folders and items * Deletes a collection and all descendent collections and items
**/ **/
Scholar.Folder.prototype.erase = function(){ Scholar.Collection.prototype.erase = function(deleteItems){
Scholar.DB.beginTransaction(); Scholar.DB.beginTransaction();
var descendents = this._getDescendents(); var descendents = this._getDescendents();
var folders = new Array(this._id); var collections = new Array(this._id);
for(var i=0, len=descendents.length; i<len; i++){ for(var i=0, len=descendents.length; i<len; i++){
if (!descendents[i]['isFolder']){ // Descendent collections
// Have items delete themselves if (descendents[i]['isCollection']){
Scholar.Items.get(descendents[i]['id']).erase(); collections.push(descendents[i]['id']);
} }
// Descendent items
else { else {
folders.push(descendents[i]['id']); if (deleteItems){
// Delete items from DB
Scholar.Items.get(descendents[i]['id']).erase();
}
} }
} }
Scholar.DB.query('DELETE FROM folders WHERE folderID IN (' // Remove item associations for all descendent collections
+ folders.join() + ')'); Scholar.DB.query('DELETE FROM itemCollections WHERE collectionID IN ('
+ collections.join() + ')');
Scholar.DB.query('DELETE FROM treeStructure WHERE id IN (' // And delete all descendent collections
+ folders.join() + ') AND isFolder=1'); Scholar.DB.query('DELETE FROM collection WHERE collectionID IN ('
+ collections.join() + ')');
// Clear deleted folder from internal memory // Clear deleted collection from internal memory
Scholar.Folders.unload(folders); Scholar.Collections.unload(collections);
Scholar.DB.commitTransaction(); Scholar.DB.commitTransaction();
} }
Scholar.Collection.prototype._loadChildItems = function(){
this._childItems = new Scholar.Hash();
var sql = "SELECT itemID FROM collectionItems WHERE collectionID=" + this._id;
var itemIDs = Scholar.DB.columnQuery(sql);
if (!itemIDs){
Scholar.debug('Collection ' + this._id + ' has no child items');
}
for (var i=0; i<itemIDs.length; i++){
this._childItems.set(itemIDs[i]);
}
this._childItemsLoaded = true;
}
/** /**
* Returns an array of descendent folders and items (rows of 'id' and 'isFolder') * Returns an array of descendent collections and items (rows of 'id' and 'isCollection')
**/ **/
Scholar.Folder.prototype._getDescendents = function(){ Scholar.Collection.prototype._getDescendents = function(){
var toReturn = new Array(); var toReturn = new Array();
var children = Scholar.DB.query('SELECT id, isFolder FROM treeStructure ' var children = Scholar.DB.query('SELECT collectionID AS id, '
+ 'WHERE parentFolderID=' + this._id); + '1 AS isCollection FROM collections '
+ 'WHERE parentCollectionID=' + this._id
+ ' UNION SELECT itemID AS id, 0 AS isCollection FROM collectionItems '
+ 'WHERE collectionID=' + this._id);
for(var i=0, len=children.length; i<len; i++){ for(var i=0, len=children.length; i<len; i++){
if (parseInt(children[i]['isFolder'])){ if (parseInt(children[i]['isCollection'])){
toReturn.push({ toReturn.push({
id: children[i]['id'], id: children[i]['id'],
isFolder: 1 isCollection: true
}); });
var descendents = var descendents =
Scholar.Folders.get(children[i]['id'])._getDescendents(); Scholar.Collections.get(children[i]['id'])._getDescendents();
for(var j=0, len=descendents.length; j<len; j++){ for(var j=0, len=descendents.length; j<len; j++){
toReturn.push(descendents[j]); toReturn.push(descendents[j]);
@ -1196,7 +1106,7 @@ Scholar.Folder.prototype._getDescendents = function(){
else { else {
toReturn.push({ toReturn.push({
id: children[i]['id'], id: children[i]['id'],
isFolder: 0 isCollection: false
}); });
} }
} }
@ -1206,38 +1116,38 @@ Scholar.Folder.prototype._getDescendents = function(){
/* /*
* Primary interface for accessing Scholar folders * Primary interface for accessing Scholar collection
*/ */
Scholar.Folders = new function(){ Scholar.Collections = new function(){
var _folders = new Array(); var _collections = new Array();
var _foldersLoaded = false; var _collectionsLoaded = false;
this.get = get; this.get = get;
this.reloadAll = reloadAll; this.reloadAll = reloadAll;
this.unload = unload; this.unload = unload;
/* /*
* Returns a Scholar.Folder object for a folderID * Returns a Scholar.Collection object for a collectionID
*/ */
function get(id){ function get(id){
if (!_foldersLoaded){ if (!_collectionsLoaded){
_load(); _load();
} }
return (typeof _folders[id]!='undefined') ? _folders[id] : false; return (typeof _collections[id]!='undefined') ? _collections[id] : false;
} }
/** /**
* Clears internal cache and reloads folder data from DB * Clears internal cache and reloads collection data from DB
**/ **/
function reloadAll(){ function reloadAll(){
_folders = new Array(); _collections = new Array();
_load(); _load();
} }
/** /**
* Clear folder from internal cache (used by Scholar.Folder.erase()) * Clear collection from internal cache (used by Scholar.Collection.erase())
* *
* Can be passed ids as individual parameters or as an array of ids, or both * Can be passed ids as individual parameters or as an array of ids, or both
**/ **/
@ -1245,42 +1155,40 @@ Scholar.Folders = new function(){
var ids = Scholar.flattenArguments(arguments); var ids = Scholar.flattenArguments(arguments);
for(var i=0; i<ids.length; i++){ for(var i=0; i<ids.length; i++){
delete _folders[ids[i]]; delete _collections[ids[i]];
} }
} }
/** /**
* Loads folder data from DB and adds to internal cache * Loads collection data from DB and adds to internal cache
**/ **/
function _load(){ function _load(){
var sql = "SELECT folderID, folderName, parentFolderID, " // This should be the same as the query in Scholar.Collection.loadFromID,
+ "(SELECT COUNT(*) FROM treeStructure WHERE " // just without a specific collectionID
+ "parentFolderID=TS.id AND isFolder=1)!=0 AS hasChildFolders, " var sql = "SELECT collectionID, collectionName, parentCollectionID, "
+ "(SELECT COUNT(*) FROM treeStructure WHERE " + "(SELECT COUNT(*) FROM collections WHERE "
+ "parentFolderID=TS.id AND isFolder=0)!=0 AS hasChildItems " + "parentCollectionID=C.collectionID)!=0 AS hasChildCollections, "
+ "FROM folders F " + "(SELECT COUNT(*) FROM collectionItems WHERE "
+ "JOIN treeStructure TS ON (F.folderID=TS.id AND TS.isFolder=1) " + "collectionID=C.collectionID)!=0 AS hasChildItems "
+ "WHERE folderID>0"; // skip 'root' folder + "FROM collections C";
var result = Scholar.DB.query(sql); var result = Scholar.DB.query(sql);
if (!result){ if (!result){
throw ('No folders exist'); throw ('No collections exist');
} }
for (var i=0; i<result.length; i++){ for (var i=0; i<result.length; i++){
var folder = new Scholar.Folder(); var collection = new Scholar.Collection();
folder.loadFromRow(result[i]); collection.loadFromRow(result[i]);
_folders[folder.getID()] = folder; _collections[collection.getID()] = collection;
} }
_foldersLoaded = true; _collectionsLoaded = true;
} }
} }
Scholar.Creators = new function(){ Scholar.Creators = new function(){
var _creators = new Array; // indexed by first%%%last hash var _creators = new Array; // indexed by first%%%last hash
var _creatorsByID = new Array; // indexed by creatorID var _creatorsByID = new Array; // indexed by creatorID
@ -1581,18 +1489,87 @@ Scholar.CreatorTypes = new function(){
/* /*
var items = Scholar.Items.getAll(); * Scholar.getCollections(parent)
*
var obj = items[9]; * Returns an array of all collections are children of a collection
for (var i=0,len=obj.numCreators(); i<len; i++){ * as Scholar.Collection instances
Scholar.debug(Scholar.varDump(obj.getCreator(i))); *
* Takes parent collectionID as optional parameter;
* by default, returns root collections
*/
Scholar.getCollections = function(parent){
var toReturn = new Array();
if (!parent){
parent = null;
}
var sql = 'SELECT collectionID FROM collections C WHERE parentCollectionID';
sql += parent ? '=' + parent : ' IS NULL';
sql += ' ORDER BY collectionName';
var children = Scholar.DB.columnQuery(sql);
if (!children){
Scholar.debug('No child collections of collection ' + parent, 5);
return toReturn;
}
for (var i=0, len=children.length; i<len; i++){
var obj = Scholar.Collections.get(children[i]);
if (!obj){
throw ('Collection ' + children[i] + ' not found');
}
toReturn.push(obj);
}
return toReturn;
} }
obj.setCreator(2,'bob','smith');
for (var i=0,len=obj.numCreators(); i<len; i++){ /*
Scholar.debug(Scholar.varDump(obj.getCreator(i))); * Scholar.getItems(parent)
*
* Returns an array of all items that are children of a collection--or all
* items if no parent provided--as Scholar.Item instances
*/
Scholar.getItems = function(parent){
var toReturn = new Array();
if (!parent){
var sql = 'SELECT itemID FROM items';
}
else {
var sql = 'SELECT itemID FROM collectionItems '
+ 'WHERE collectionID=' + parent;
}
var children = Scholar.DB.columnQuery(sql);
if (!children){
if (!parent){
Scholar.debug('No items in library', 5);
}
else {
Scholar.debug('No child items of collection ' + parent, 5);
}
return toReturn;
}
for (var i=0, len=children.length; i<len; i++){
var obj = Scholar.Items.get(children[i]);
if (!obj){
throw ('Item ' + children[i] + ' not found');
}
toReturn.push(obj);
}
return toReturn;
} }
obj.save();
*/

View File

@ -449,11 +449,13 @@ Scholar.DB = new function(){
} }
// For now, just wipe and recreate // For now, just wipe and recreate
if (i==10){ if (i==11){
Scholar.DB.query("DROP TABLE IF EXISTS folders; "
+ "DROP TABLE IF EXISTS treeStructure;");
_initializeSchema(); _initializeSchema();
} }
if (i==11){ if (i==12){
// do stuff // do stuff
// _updateDBVersion(i); // _updateDBVersion(i);
} }

View File

@ -1,7 +1,7 @@
const SCHOLAR_CONFIG = { const SCHOLAR_CONFIG = {
GUID: 'scholar@chnm.gmu.edu', GUID: 'scholar@chnm.gmu.edu',
DB_FILE: 'scholar.sqlite', DB_FILE: 'scholar.sqlite',
DB_VERSION: 10, // must match version at top of schema.sql DB_VERSION: 11, // must match version at top of schema.sql
DB_REBUILD: false, // erase DB and recreate from schema DB_REBUILD: false, // erase DB and recreate from schema
DEBUG_LOGGING: true, DEBUG_LOGGING: true,
DEBUG_TO_CONSOLE: true // dump debug messages to console rather than (much slower) Debug Logger DEBUG_TO_CONSOLE: true // dump debug messages to console rather than (much slower) Debug Logger

View File

@ -1,4 +1,4 @@
-- 10 -- 11
DROP TABLE IF EXISTS version; DROP TABLE IF EXISTS version;
CREATE TABLE version ( CREATE TABLE version (
@ -11,9 +11,7 @@
itemTypeID INT, itemTypeID INT,
title TEXT, title TEXT,
dateAdded DATETIME DEFAULT CURRENT_TIMESTAMP, dateAdded DATETIME DEFAULT CURRENT_TIMESTAMP,
dateModified DATETIME DEFAULT CURRENT_TIMESTAMP, dateModified DATETIME DEFAULT CURRENT_TIMESTAMP
source TEXT,
rights TEXT
); );
DROP TABLE IF EXISTS itemTypes; DROP TABLE IF EXISTS itemTypes;
@ -57,13 +55,15 @@
FOREIGN KEY (fieldID) REFERENCES fields(fieldID) FOREIGN KEY (fieldID) REFERENCES fields(fieldID)
); );
DROP INDEX IF EXISTS value; DROP INDEX IF EXISTS value;
CREATE INDEX value ON itemData (value); CREATE INDEX value ON itemData(value);
DROP TABLE IF EXISTS keywords; DROP TABLE IF EXISTS keywords;
CREATE TABLE keywords ( CREATE TABLE keywords (
keywordID INTEGER PRIMARY KEY, keywordID INTEGER PRIMARY KEY,
keyword TEXT keyword TEXT
); );
DROP INDEX IF EXISTS keyword;
CREATE INDEX keyword ON keywords(keyword);
DROP TABLE IF EXISTS itemKeywords; DROP TABLE IF EXISTS itemKeywords;
CREATE TABLE itemKeywords ( CREATE TABLE itemKeywords (
@ -73,6 +73,8 @@
FOREIGN KEY (itemID) REFERENCES items(itemID), FOREIGN KEY (itemID) REFERENCES items(itemID),
FOREIGN KEY (keywordID) REFERENCES keywords(keywordID) FOREIGN KEY (keywordID) REFERENCES keywords(keywordID)
); );
DROP INDEX IF EXISTS keywordID;
CREATE INDEX keywordID ON itemKeywords(keywordID);
DROP TABLE IF EXISTS creators; DROP TABLE IF EXISTS creators;
CREATE TABLE creators ( CREATE TABLE creators (
@ -100,26 +102,26 @@
FOREIGN KEY (creatorTypeID) REFERENCES creatorTypes(creatorTypeID) FOREIGN KEY (creatorTypeID) REFERENCES creatorTypes(creatorTypeID)
); );
DROP TABLE IF EXISTS folders; DROP TABLE IF EXISTS collections;
CREATE TABLE folders ( CREATE TABLE collections (
folderID INT, collectionID INT,
folderName TEXT, collectionName TEXT,
PRIMARY KEY (folderID) parentCollectionID INT,
PRIMARY KEY (collectionID),
FOREIGN KEY (parentCollectionID) REFERENCES collections(collectionID)
); );
INSERT INTO folders VALUES (0, 'root');
DROP TABLE IF EXISTS treeStructure; DROP TABLE IF EXISTS collectionItems;
CREATE TABLE treeStructure ( CREATE TABLE collectionItems (
id INT, collectionID INT,
isFolder INT, itemID INT,
orderIndex INT, orderIndex INT DEFAULT 0,
parentFolderID INT DEFAULT 0, PRIMARY KEY (collectionID, itemID),
PRIMARY KEY (id, isFolder), FOREIGN KEY (collectionID) REFERENCES collections(collectionID),
FOREIGN KEY (parentFolderID) REFERENCES folders(folderID) FOREIGN KEY (itemID) REFERENCES items(itemID)
); );
DROP INDEX IF EXISTS parentFolderID; DROP INDEX IF EXISTS itemID;
CREATE INDEX parentFolderID ON treeStructure(parentFolderID); CREATE INDEX itemID ON collectionItems(itemID);
INSERT INTO treeStructure VALUES (0, 1, 0, NULL);
-- Some sample data -- Some sample data
INSERT INTO itemTypes VALUES (1,'book'); INSERT INTO itemTypes VALUES (1,'book');
@ -129,17 +131,19 @@
INSERT INTO "fieldFormats" VALUES(2, '[0-9]*', 1); INSERT INTO "fieldFormats" VALUES(2, '[0-9]*', 1);
INSERT INTO "fieldFormats" VALUES(3, '[0-9]{4}', 1); INSERT INTO "fieldFormats" VALUES(3, '[0-9]{4}', 1);
INSERT INTO fields VALUES (1,'series',NULL); INSERT INTO fields VALUES (1,'source',NULL);
INSERT INTO fields VALUES (2,'volume',NULL); INSERT INTO fields VALUES (2,'rights',NULL);
INSERT INTO fields VALUES (3,'number',NULL); INSERT INTO fields VALUES (3,'series',NULL);
INSERT INTO fields VALUES (4,'edition',NULL); INSERT INTO fields VALUES (4,'volume',NULL);
INSERT INTO fields VALUES (5,'place',NULL); INSERT INTO fields VALUES (5,'number',NULL);
INSERT INTO fields VALUES (6,'publisher',NULL); INSERT INTO fields VALUES (6,'edition',NULL);
INSERT INTO fields VALUES (7,'year',3); INSERT INTO fields VALUES (7,'place',NULL);
INSERT INTO fields VALUES (8,'pages',2); INSERT INTO fields VALUES (8,'publisher',NULL);
INSERT INTO fields VALUES (9,'ISBN',NULL); INSERT INTO fields VALUES (9,'year',3);
INSERT INTO fields VALUES (10,'publication',NULL); INSERT INTO fields VALUES (10,'pages',2);
INSERT INTO fields VALUES (11,'ISSN',NULL); INSERT INTO fields VALUES (11,'ISBN',NULL);
INSERT INTO fields VALUES (12,'publication',NULL);
INSERT INTO fields VALUES (13,'ISSN',NULL);
INSERT INTO itemTypeFields VALUES (1,1,1); INSERT INTO itemTypeFields VALUES (1,1,1);
INSERT INTO itemTypeFields VALUES (1,2,2); INSERT INTO itemTypeFields VALUES (1,2,2);
@ -150,34 +154,38 @@
INSERT INTO itemTypeFields VALUES (1,7,7); INSERT INTO itemTypeFields VALUES (1,7,7);
INSERT INTO itemTypeFields VALUES (1,8,8); INSERT INTO itemTypeFields VALUES (1,8,8);
INSERT INTO itemTypeFields VALUES (1,9,9); INSERT INTO itemTypeFields VALUES (1,9,9);
INSERT INTO itemTypeFields VALUES (2,10,1); INSERT INTO itemTypeFields VALUES (1,10,10);
INSERT INTO itemTypeFields VALUES (1,11,11);
INSERT INTO itemTypeFields VALUES (2,1,1);
INSERT INTO itemTypeFields VALUES (2,2,2); INSERT INTO itemTypeFields VALUES (2,2,2);
INSERT INTO itemTypeFields VALUES (2,3,3); INSERT INTO itemTypeFields VALUES (2,12,3);
INSERT INTO itemTypeFields VALUES (2,8,4); INSERT INTO itemTypeFields VALUES (2,4,4);
INSERT INTO itemTypeFields VALUES (2,5,5);
INSERT INTO itemTypeFields VALUES (2,10,6);
INSERT INTO "items" VALUES(1, 1, 'Online connections: Internet interpersonal relationships', '2006-03-12 05:24:40', '2006-03-12 05:24:40', NULL, NULL); INSERT INTO "items" VALUES(1, 1, 'Online connections: Internet interpersonal relationships', '2006-03-12 05:24:40', '2006-03-12 05:24:40');
INSERT INTO "items" VALUES(2, 1, 'Computer-Mediated Communication: Human-to-Human Communication Across the Internet', '2006-03-12 05:25:50', '2006-03-12 05:25:50', NULL, NULL); INSERT INTO "items" VALUES(2, 1, 'Computer-Mediated Communication: Human-to-Human Communication Across the Internet', '2006-03-12 05:25:50', '2006-03-12 05:25:50');
INSERT INTO "items" VALUES(3, 2, 'Residential propinquity as a factor in marriage selection', '2006-03-12 05:26:37', '2006-03-12 05:26:37', NULL, NULL); INSERT INTO "items" VALUES(3, 2, 'Residential propinquity as a factor in marriage selection', '2006-03-12 05:26:37', '2006-03-12 05:26:37');
INSERT INTO "items" VALUES(4, 1, 'Connecting: how we form social bonds and communities in the Internet age', '2006-03-12 05:27:15', '2006-03-12 05:27:15', NULL, NULL); INSERT INTO "items" VALUES(4, 1, 'Connecting: how we form social bonds and communities in the Internet age', '2006-03-12 05:27:15', '2006-03-12 05:27:15');
INSERT INTO "items" VALUES(5, 1, 'Male, Female, Email: The Struggle for Relatedness in a Paranoid Society', '2006-03-12 05:27:36', '2006-03-12 05:27:36', NULL, NULL); INSERT INTO "items" VALUES(5, 1, 'Male, Female, Email: The Struggle for Relatedness in a Paranoid Society', '2006-03-12 05:27:36', '2006-03-12 05:27:36');
INSERT INTO "items" VALUES(6, 2, 'Social Implications of Sociology', '2006-03-12 05:27:53', '2006-03-12 05:27:53', NULL, NULL); INSERT INTO "items" VALUES(6, 2, 'Social Implications of Sociology', '2006-03-12 05:27:53', '2006-03-12 05:27:53');
INSERT INTO "items" VALUES(7, 1, 'Social Pressures in Informal Groups: A Study of Human Factors in Housing', '2006-03-12 05:28:05', '2006-03-12 05:28:05', NULL, NULL); INSERT INTO "items" VALUES(7, 1, 'Social Pressures in Informal Groups: A Study of Human Factors in Housing', '2006-03-12 05:28:05', '2006-03-12 05:28:05');
INSERT INTO "items" VALUES(8, 1, 'Cybersociety 2.0: Revisiting Computer-Mediated Community and Technology', '2006-03-12 05:28:37', '2006-03-12 05:28:37', NULL, NULL); INSERT INTO "items" VALUES(8, 1, 'Cybersociety 2.0: Revisiting Computer-Mediated Community and Technology', '2006-03-12 05:28:37', '2006-03-12 05:28:37');
INSERT INTO "items" VALUES(9, 2, 'The Computer as a Communication Device', '2006-03-12 05:29:03', '2006-03-12 05:29:03', NULL, NULL); INSERT INTO "items" VALUES(9, 2, 'The Computer as a Communication Device', '2006-03-12 05:29:03', '2006-03-12 05:29:03');
INSERT INTO "items" VALUES(10, 2, 'What Does Research Say about the Nature of Computer-mediated Communication: Task-Oriented, Social-Emotion-Oriented, or Both?', '2006-03-12 05:29:12', '2006-03-12 05:29:12', NULL, NULL); INSERT INTO "items" VALUES(10, 2, 'What Does Research Say about the Nature of Computer-mediated Communication: Task-Oriented, Social-Emotion-Oriented, or Both?', '2006-03-12 05:29:12', '2006-03-12 05:29:12');
INSERT INTO "items" VALUES(11, 1, 'The second self: computers and the human spirit', '2006-03-12 05:30:38', '2006-03-12 05:30:38', NULL, NULL); INSERT INTO "items" VALUES(11, 1, 'The second self: computers and the human spirit', '2006-03-12 05:30:38', '2006-03-12 05:30:38');
INSERT INTO "items" VALUES(12, 1, 'Life on the screen: identity in the age of the Internet', '2006-03-12 05:30:49', '2006-03-12 05:30:49', NULL, NULL); INSERT INTO "items" VALUES(12, 1, 'Life on the screen: identity in the age of the Internet', '2006-03-12 05:30:49', '2006-03-12 05:30:49');
INSERT INTO "items" VALUES(13, 2, 'The computer conference: An altered state of communication', '2006-03-12 05:31:00', '2006-03-12 05:31:00', NULL, NULL); INSERT INTO "items" VALUES(13, 2, 'The computer conference: An altered state of communication', '2006-03-12 05:31:00', '2006-03-12 05:31:00');
INSERT INTO "items" VALUES(14, 2, 'Computer Networks as Social Networks: Collaborative Work, Telework, and Community', '2006-03-12 05:31:17', '2006-03-12 05:31:17', NULL, NULL); INSERT INTO "items" VALUES(14, 2, 'Computer Networks as Social Networks: Collaborative Work, Telework, and Community', '2006-03-12 05:31:17', '2006-03-12 05:31:17');
INSERT INTO "items" VALUES(15, 1, 'The Internet in everyday life', '2006-03-12 05:31:41', '2006-03-12 05:31:41', NULL, NULL); INSERT INTO "items" VALUES(15, 1, 'The Internet in everyday life', '2006-03-12 05:31:41', '2006-03-12 05:31:41');
INSERT INTO "itemData" VALUES(1, 7, 2001); INSERT INTO "itemData" VALUES(1, 9, 2001);
INSERT INTO "itemData" VALUES(1, 5, 'Cresskill, N.J.'); INSERT INTO "itemData" VALUES(1, 7, 'Cresskill, N.J.');
INSERT INTO "itemData" VALUES(1, 6, 'Hampton Press'); INSERT INTO "itemData" VALUES(1, 8, 'Hampton Press');
INSERT INTO "itemData" VALUES(2, 7, 2002); INSERT INTO "itemData" VALUES(2, 9, 2002);
INSERT INTO "itemData" VALUES(2, 6, 'Allyn & Bacon Publishers'); INSERT INTO "itemData" VALUES(2, 8, 'Allyn & Bacon Publishers');
INSERT INTO "itemData" VALUES(2, 8, 347); INSERT INTO "itemData" VALUES(2, 10, 347);
INSERT INTO "itemData" VALUES(2, 9, '0-205-32145-3'); INSERT INTO "itemData" VALUES(2, 11, '0-205-32145-3');
INSERT INTO "creatorTypes" VALUES(1, "author"); INSERT INTO "creatorTypes" VALUES(1, "author");
INSERT INTO "creatorTypes" VALUES(2, "contributor"); INSERT INTO "creatorTypes" VALUES(2, "contributor");
@ -218,29 +226,14 @@
INSERT INTO "itemCreators" VALUES(7, 8, 1, 2); INSERT INTO "itemCreators" VALUES(7, 8, 1, 2);
INSERT INTO "itemCreators" VALUES(9, 11, 1, 1); INSERT INTO "itemCreators" VALUES(9, 11, 1, 1);
INSERT INTO folders VALUES (1241, 'Test Folder');
INSERT INTO folders VALUES (3262, 'Another Test Folder');
INSERT INTO folders VALUES (6856, 'Yet Another Folder');
INSERT INTO folders VALUES (7373, 'A Subfolder!');
INSERT INTO folders VALUES (9233, 'A Sub-subfolder!');
INSERT INTO treeStructure VALUES (1, 0, 1, 0); INSERT INTO collections VALUES (1241, 'Test Project', NULL);
INSERT INTO treeStructure VALUES (3262, 1, 2, 0); INSERT INTO collections VALUES (3262, 'Another Test Project', NULL);
INSERT INTO treeStructure VALUES (2, 0, 3, 0); INSERT INTO collections VALUES (6856, 'Yet Another Project', NULL);
INSERT INTO treeStructure VALUES (3, 0, 4, 0); INSERT INTO collections VALUES (7373, 'A Sub-project!', 6856);
INSERT INTO treeStructure VALUES (4, 0, 5, 0); INSERT INTO collections VALUES (9233, 'A Sub-sub-project!', 7373);
INSERT INTO treeStructure VALUES (5, 0, 6, 0);
INSERT INTO treeStructure VALUES (6, 0, 7, 0); INSERT INTO collectionItems VALUES (6856, 14, 0);
INSERT INTO treeStructure VALUES (7, 0, 8, 0); INSERT INTO collectionItems VALUES (6856, 13, 1);
INSERT INTO treeStructure VALUES (8, 0, 9, 0); INSERT INTO collectionItems VALUES (7373, 15, 0);
INSERT INTO treeStructure VALUES (9, 0, 10, 0); INSERT INTO collectionItems VALUES (1241, 12, 0);
INSERT INTO treeStructure VALUES (6856, 1, 11, 0);
INSERT INTO treeStructure VALUES (14, 0, 12, 6856);
INSERT INTO treeStructure VALUES (13, 0, 13, 6856);
INSERT INTO treeStructure VALUES (7373, 1, 14, 6856);
INSERT INTO treeStructure VALUES (15, 0, 15, 7373);
INSERT INTO treeStructure VALUES (9233, 1, 16, 7373);
INSERT INTO treeStructure VALUES (11, 0, 17, 0);
INSERT INTO treeStructure VALUES (10, 0, 18, 0);
INSERT INTO treeStructure VALUES (1241, 1, 19, 0);
INSERT INTO treeStructure VALUES (12, 0, 20, 1241);