/* ***** BEGIN LICENSE BLOCK ***** Copyright (c) 2006 Center for History and New Media George Mason University, Fairfax, Virginia, USA http://chnm.gmu.edu Licensed under the Educational Community License, Version 1.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.opensource.org/licenses/ecl1.php Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ***** END LICENSE BLOCK ***** */ //////////////////////////////////////////////////////////////////////////////// /// /// ItemTreeView /// -- handles the link between an individual tree and the data layer /// -- displays only items (no collections, no hierarchy) /// //////////////////////////////////////////////////////////////////////////////// /* * Constructor for the ItemTreeView object */ Zotero.ItemTreeView = function(itemGroup, sourcesOnly) { this._initialized = false; this._itemGroup = itemGroup; this._sourcesOnly = sourcesOnly; this._callbacks = []; this._treebox = null; this._ownerDocument = null; this._needsSort = false; this._dataItems = []; this.rowCount = 0; this._unregisterID = Zotero.Notifier.registerObserver(this, ['item', 'collection-item', 'share-items']); } Zotero.ItemTreeView.prototype.addCallback = function(callback) { this._callbacks.push(callback); } Zotero.ItemTreeView.prototype._runCallbacks = function() { for each(var cb in this._callbacks) { cb(); } } /* * Called by the tree itself */ Zotero.ItemTreeView.prototype.setTree = function(treebox) { //Zotero.debug("Calling setTree()"); // Try to set the window document if not yet set if (treebox && !this._ownerDocument) { try { this._ownerDocument = treebox.treeBody.ownerDocument; } catch (e) {} } if (this._treebox) { if (this._needsSort) { this.sort(); } return; } if (!treebox) { Components.utils.reportError("Passed treebox empty in setTree()"); } this._treebox = treebox; if (this._ownerDocument.defaultView.ZoteroPane) { this._ownerDocument.defaultView.ZoteroPane.setItemsPaneMessage(Zotero.getString('pane.items.loading')); } // Generate the tree contents in a timer to allow message above to display var paneLoader = function(obj) { // If a DB transaction is open, display error message and bail if (!Zotero.stateCheck()) { if (obj._ownerDocument.defaultView.ZoteroPane) { obj._ownerDocument.defaultView.ZoteroPane.displayErrorMessage(); } return; } obj.refresh(); // Add a keypress listener for expand/collapse var expandAllRows = obj.expandAllRows; var collapseAllRows = obj.collapseAllRows; var tree = obj._treebox.treeBody.parentNode; tree.addEventListener('keypress', function(event) { var key = String.fromCharCode(event.which); if (key == '+' && !(event.ctrlKey || event.altKey || event.metaKey)) { obj.expandAllRows(treebox); return; } else if (key == '-' && !(event.shiftKey || event.ctrlKey || event.altKey || event.metaKey)) { obj.collapseAllRows(treebox); return; } }, false); obj.sort(); obj.expandMatchParents(); //Zotero.debug('Running callbacks in itemTreeView.setTree()', 4); obj._runCallbacks(); if (obj._ownerDocument.defaultView.ZoteroPane) { obj._ownerDocument.defaultView.ZoteroPane.clearItemsPaneMessage(); } // Select a queued item from selectItem() if (obj._itemGroup && obj._itemGroup.itemToSelect) { var item = obj._itemGroup.itemToSelect; obj.selectItem(item['id'], item['expand']); obj._itemGroup.itemToSelect = null; } } this._ownerDocument.defaultView.setTimeout(paneLoader, 50, this); } /* * Reload the rows from the data access methods * (doesn't call the tree.invalidate methods, etc.) */ Zotero.ItemTreeView.prototype.refresh = function() { Zotero.debug('Refreshing items list'); this._searchMode = this._itemGroup.isSearchMode(); var oldRows = this.rowCount; this._dataItems = []; this._searchItemIDs = {}; // items matching the search this._searchParentIDs = {}; this.rowCount = 0; var cacheFields = ['title', 'date']; // Cache the visible fields so they don't load individually try { var visibleFields = this.getVisibleFields(); } // If treebox isn't ready, skip refresh catch (e) { return; } for (var i=0; i 0) { var selectItem = splitIDs[splitIDs.length - 1]; } } if ((action == 'remove' && !this._itemGroup.isLibrary()) || action == 'delete' || action == 'id-change') { // We only care about the old ids if (action == 'id-change') { for (var i=0, len=ids.length; i 0) { rows.sort(function(a,b) { return a-b }); for(var i=0, len=rows.length; i 0) { val = c; } } else if(column.id == "zotero-items-column-type") { val = Zotero.getString('itemTypes.'+Zotero.ItemTypes.getName(obj.ref.itemTypeID)); } // Year column is just date field truncated else if (column.id == "zotero-items-column-year") { val = obj.getField('date', true).substr(0, 4) } else { var col = column.id.substring(20); if (col == 'title') { val = obj.ref.getDisplayTitle(); } else { val = obj.getField(col); } } if(column.id == 'zotero-items-column-dateAdded' || column.id == 'zotero-items-column-dateModified') //this is not so much that we will use this format for date, but a simple template for later revisions. { // Format date as short date in proper locale order and locale time // (e.g. "4/4/07 14:27:23") var order = Zotero.Date.getLocaleDateOrder(); var date = Zotero.Date.sqlToDate(val, true); var parts = []; for (var i=0; i<3; i++) { switch (order[i]) { case 'y': parts.push(date.getFullYear().toString().substr(2)); break; case 'm': parts.push((date.getMonth() + 1)); break; case 'd': parts.push(date.getDate()); break; } val = parts.join('/'); val += ' ' + date.toLocaleTimeString(); } } return val; } Zotero.ItemTreeView.prototype.getImageSrc = function(row, col) { if(col.id == 'zotero-items-column-title') { return this._getItemAtRow(row).ref.getImageSrc(); } } Zotero.ItemTreeView.prototype.isContainer = function(row) { return this._getItemAtRow(row).ref.isRegularItem(); } Zotero.ItemTreeView.prototype.isContainerOpen = function(row) { return this._dataItems[row].isOpen; } Zotero.ItemTreeView.prototype.isContainerEmpty = function(row) { if(this._sourcesOnly) { return true; } else { return (this._getItemAtRow(row).numNotes() == 0 && this._getItemAtRow(row).numAttachments() == 0); } } Zotero.ItemTreeView.prototype.getLevel = function(row) { return this._getItemAtRow(row).level; } // Gets the index of the row's container, or -1 if none (top-level) Zotero.ItemTreeView.prototype.getParentIndex = function(row) { if (row==-1) { return -1; } var thisLevel = this.getLevel(row); if(thisLevel == 0) return -1; for(var i = row - 1; i >= 0; i--) if(this.getLevel(i) < thisLevel) return i; return -1; } Zotero.ItemTreeView.prototype.hasNextSibling = function(row,afterIndex) { var thisLevel = this.getLevel(row); for(var i = afterIndex + 1; i < this.rowCount; i++) { var nextLevel = this.getLevel(i); if(nextLevel == thisLevel) return true; else if(nextLevel < thisLevel) return false; } } Zotero.ItemTreeView.prototype.toggleOpenState = function(row, skipItemMapRefresh) { // Shouldn't happen but does if an item is dragged over a closed // container until it opens and then released, since the container // is no longer in the same place when the spring-load closes if (!this.isContainer(row)) { return; } var count = 0; //used to tell the tree how many rows were added/removed var thisLevel = this.getLevel(row); // Close if (this.isContainerOpen(row)) { while((row + 1 < this._dataItems.length) && (this.getLevel(row + 1) > thisLevel)) { this._hideItem(row+1); count--; //count is negative when closing a container because we are removing rows } } // Open else { var item = this._getItemAtRow(row).ref; //Get children var attachments = item.getAttachments(); var notes = item.getNotes(); var newRows; if(attachments && notes) newRows = notes.concat(attachments); else if(attachments) newRows = attachments; else if(notes) newRows = notes; if (newRows) { newRows = Zotero.Items.get(newRows); for(var i = 0; i < newRows.length; i++) { // If item already exists elsewhere in the tree, we have to // remove it -- this can happen when moving an item into a // collection if the collection gets the modify event before // the item var existingRow = this._itemRowMap[newRows[i].id]; if (existingRow != null) { /* this._hideItem(existingRow); this._treebox.rowCountChanged(existingRow + 1, -1); if (existingRow < row) { row--; } */ throw ("Item already exists outside of collection in Zotero.ItemTreeView.toggleOpenRow()"); } count++; this._showItem(new Zotero.ItemTreeView.TreeRow(newRows[i], thisLevel + 1, false), row + i + 1); // item ref, before row } } } this._dataItems[row].isOpen = !this._dataItems[row].isOpen; this._treebox.rowCountChanged(row+1, count); //tell treebox to repaint these this._treebox.invalidateRow(row); if (!skipItemMapRefresh) { Zotero.debug('Refreshing hash map'); this._refreshHashMap(); } } Zotero.ItemTreeView.prototype.isSorted = function() { // We sort by the first column if none selected, so return true return true; } Zotero.ItemTreeView.prototype.cycleHeader = function(column) { for(var i=0, len=this._treebox.columns.count; i typeB) ? -1 : (typeA < typeB) ? 1 : 0; if (cmp) { return cmp; } break; case 'numChildren': cmp = b.numChildren() - a.numChildren(); if (cmp) { return cmp; } break; default: if (fieldA == undefined) { fieldA = getField(a); cache[aItemID] = fieldA; } if (fieldB == undefined) { fieldB = getField(b); cache[bItemID] = fieldB; } // Display rows with empty values last if (!emptyFirst[columnField]) { cmp = (fieldA == '' && fieldB != '') ? -1 : (fieldA != '' && fieldB == '') ? 1 : 0; if (cmp) { return cmp; } } cmp = collation.compareString(1, fieldB, fieldA); if (cmp) { return cmp; } } if (columnField != 'firstCreator') { fieldA = a.getField('firstCreator'); fieldB = b.getField('firstCreator'); // Display rows with empty values last cmp = (fieldA == '' && fieldB != '') ? -1 : (fieldA != '' && fieldB == '') ? 1 : 0; if (cmp) { return cmp; } //cmp = (fieldA > fieldB) ? -1 : (fieldA < fieldB) ? 1 : 0; cmp = collation.compareString(1, fieldB, fieldA); if (cmp) { return cmp; } } if (columnField != 'date') { fieldA = a.getField('date', true, true); fieldB = b.getField('date', true, true); // Display rows with empty values last cmp = (fieldA == '' && fieldB != '') ? -1 : (fieldA != '' && fieldB == '') ? 1 : 0; if (cmp) { return cmp; } cmp = (fieldA > fieldB) ? -1 : (fieldA < fieldB) ? 1 : 0; if (cmp) { return cmp; } } fieldA = a.getField('dateModified'); fieldB = b.getField('dateModified'); return (fieldA > fieldB) ? -1 : (fieldA < fieldB) ? 1 : 0; } function doSort(a,b) { return rowSort(a,b); } function reverseSort(a,b) { return rowSort(a,b) * -1; } // Need to close all containers before sorting var openRows = new Array(); for (var i=0; i