From 14b24f363828fd90045d0e7a7549e924717d6337 Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Thu, 7 Sep 2006 08:07:48 +0000 Subject: [PATCH] Closes #259, auto-complete of tags Addresses #260, Add auto-complete to search window - New XPCOM autocomplete component for Zotero data -- can be used by setting the autocompletesearch attribute of a textbox to 'zotero' and passing a search scope with the autocompletesearchparam attribute. Additional parameters can be passed by appending them to the autocompletesearchparam value with a '/', e.g. 'tag/2732' (to exclude tags that show up in item 2732) - Tag entry now uses more or less the same interface as metadata -- no more popup window -- note that tab isn't working properly yet, and there's no way to quickly enter multiple tags (though it's now considerably quicker than it was before) - Autocomplete for tags, excluding any tags already set for the current item - Standalone note windows now register with the Notifier (since tags needed item modification notifications to work properly), which will help with #282, "Notes opened in separate windows need item notification" - Tags are now retrieved in alphabetical order - Scholar.Item.replaceTag(oldTagID, newTag), with a single notify - Scholar.getAncestorByTagName(elem, tagName) -- walk up the DOM tree from an element until an element with the specified tag name is found (also checks with 'xul:' prefix, for use in XBL), or false if not found -- probably shouldn't be used too widely, since it's doing string comparisons, but better than specifying, say, nine '.parentNode' properties, and makes for more resilient code A few notes: - Autocomplete in Minefield seems to self-destruct after using it in the same field a few times, taking down saving of the field with it -- this may or may not be my fault, but it makes Zotero more or less unusable in 3.0 at the moment. Sorry. (I use 3.0 myself for development, so I'll work on it.) - This would have been much, much easier if having an autocomplete textbox (which uses an XBL-generated popup for the suggestions) within a popup (as it is in the independent note edit panes) didn't introduce all sorts of crazy bugs that had to be defeated with annoying hackery -- one side effect of this is that at the moment you can't close the tags popup with the Escape key - Independent note windows now need to pull in itemPane.js to function properly, which is a bit messy and not ideal, but less messy and more ideal than duplicating all the dual-state editor and tabindex logic would be - Hitting tab in a tag field not only doesn't work but also breaks things until the next window refresh. - There are undoubtedly other bugs. --- .../content/scholar/bindings/noteeditor.xml | 21 +- .../content/scholar/bindings/tagsbox.xml | 156 ++++++++++---- .../chromeFiles/content/scholar/itemPane.js | 105 ++++++++- chrome/chromeFiles/content/scholar/note.js | 11 + chrome/chromeFiles/content/scholar/note.xul | 1 + .../content/scholar/xpcom/data_access.js | 47 ++++- .../content/scholar/xpcom/scholar.js | 12 ++ .../skin/default/scholar/scholar.css | 9 +- components/chnmIZoteroAutoComplete.js | 199 ++++++++++++++++++ 9 files changed, 495 insertions(+), 66 deletions(-) create mode 100644 components/chnmIZoteroAutoComplete.js diff --git a/chrome/chromeFiles/content/scholar/bindings/noteeditor.xml b/chrome/chromeFiles/content/scholar/bindings/noteeditor.xml index ee2815fe79..ffacc836ac 100644 --- a/chrome/chromeFiles/content/scholar/bindings/noteeditor.xml +++ b/chrome/chromeFiles/content/scholar/bindings/noteeditor.xml @@ -112,17 +112,15 @@ 0) - this.id('tagsPopup').showPopup(this.id('tagsLabel'),-1,-1,-1,'popup',0,0); - else - this.id('tags').add(); + this.id('tags').reload(); + this.id('tagsPopup').showPopup(this.id('tagsLabel'),-1,-1,'popup'); ]]> - + + diff --git a/chrome/chromeFiles/content/scholar/bindings/tagsbox.xml b/chrome/chromeFiles/content/scholar/bindings/tagsbox.xml index b8e3ce2b42..0e3a1d6bd2 100644 --- a/chrome/chromeFiles/content/scholar/bindings/tagsbox.xml +++ b/chrome/chromeFiles/content/scholar/bindings/tagsbox.xml @@ -18,6 +18,7 @@ ]]> + + + + + + + + + + + + + + + + + + + + + @@ -102,13 +154,7 @@ @@ -118,7 +164,7 @@ @@ -137,12 +184,31 @@ ]]> + + + + + - + diff --git a/chrome/chromeFiles/content/scholar/itemPane.js b/chrome/chromeFiles/content/scholar/itemPane.js index f4c6c5c743..5a765dc509 100644 --- a/chrome/chromeFiles/content/scholar/itemPane.js +++ b/chrome/chromeFiles/content/scholar/itemPane.js @@ -33,11 +33,11 @@ var ScholarItemPane = new function() this.onOpenURLClick = onOpenURLClick; this.addCreatorRow = addCreatorRow; this.disableButton = disableButton; + this.createValueElement = createValueElement; this.removeCreator = removeCreator; this.showEditor = showEditor; this.handleKeyPress = handleKeyPress; this.hideEditor = hideEditor; - this.modifyField = modifyField; this.getCreatorFields = getCreatorFields; this.modifyCreator = modifyCreator; this.removeNote = removeNote; @@ -50,6 +50,13 @@ var ScholarItemPane = new function() function onLoad() { _tabs = document.getElementById('scholar-view-tabs'); + + // Not in item pane, so skip the introductions + if (!_tabs) + { + return false; + } + _dynamicFields = document.getElementById('editpane-dynamic-fields'); _itemTypeMenu = document.getElementById('editpane-type-menu'); _creatorTypeMenu = document.getElementById('creatorTypeMenu'); @@ -88,7 +95,19 @@ var ScholarItemPane = new function() // pane, since for some reason it's not being called automatically if (_itemBeingEdited && _itemBeingEdited!=thisItem) { - var boxes = _dynamicFields.getElementsByTagName('textbox'); + switch (_tabs.selectedIndex) + { + // Info + case 0: + var boxes = _dynamicFields.getElementsByTagName('textbox'); + break; + + // Tags + case 3: + var boxes = document.getAnonymousNodes(_tagsBox)[0].getElementsByTagName('textbox'); + break; + } + if (boxes.length==1) { boxes[0].inputField.blur(); @@ -459,25 +478,33 @@ var ScholarItemPane = new function() var fieldName = elem.getAttribute('fieldname'); var tabindex = elem.getAttribute('tabindex'); - var value = ''; var creatorFields = fieldName.split('-'); if(creatorFields[0] == 'creator') { var c = _itemBeingEdited.getCreator(creatorFields[1]); - if(c) - value = c[creatorFields[2]]; + var value = c ? c[creatorFields[2]] : ''; + var itemID = _itemBeingEdited.getID(); + } + else if (fieldName=='tag') + { + var tagID = elem.parentNode.getAttribute('id').split('-')[1]; + var value = tagID ? Scholar.Tags.getName(tagID) : ''; + var itemID = Scholar.getAncestorByTagName(elem, 'tagsbox').item.getID(); } else { - value = _itemBeingEdited.getField(fieldName); + var value = _itemBeingEdited.getField(fieldName); + var itemID = _itemBeingEdited.getID(); } var t = document.createElement("textbox"); + t.setAttribute('type', 'autocomplete'); + t.setAttribute('autocompletesearch', 'zotero'); + t.setAttribute('autocompletesearchparam', fieldName + (itemID ? '/' + itemID : '')); t.setAttribute('value',value); - t.setAttribute('fieldname',fieldName); + t.setAttribute('fieldname', fieldName); t.setAttribute('tabindex', tabindex); t.setAttribute('flex','1'); - t.className = 'fieldeditor'; var box = elem.parentNode; box.replaceChild(t,elem); @@ -514,18 +541,24 @@ var ScholarItemPane = new function() function hideEditor(t, saveChanges) { //Scholar.debug('Hiding editor'); - var textbox = t.parentNode.parentNode; + var textbox = Scholar.getAncestorByTagName(t, 'textbox'); + if (!textbox){ + Scholar.debug('Textbox not found in hideEditor'); + return; + } var fieldName = textbox.getAttribute('fieldname'); var tabindex = textbox.getAttribute('tabindex'); var value = t.value; var elem; var creatorFields = fieldName.split('-'); + + // Creator fields if(creatorFields[0] == 'creator') { if (saveChanges){ var otherFields = - this.getCreatorFields(textbox.parentNode.parentNode.parentNode); + getCreatorFields(textbox.parentNode.parentNode.parentNode); modifyCreator(creatorFields[1], creatorFields[2], value, otherFields); } @@ -548,6 +581,58 @@ var ScholarItemPane = new function() elem = createValueElement(val, fieldName, tabindex); } + + // Tags + else if (fieldName=='tag') + { + var tagsbox = Scholar.getAncestorByTagName(textbox, 'tagsbox'); + if (!tagsbox) + { + Scholar.debug('Tagsbox not found', 1); + return; + } + + var row = textbox.parentNode; + var rows = row.parentNode; + + // Tag id encoded as 'tag-1234' + var id = row.getAttribute('id').split('-')[1]; + + if (saveChanges) + { + if (id) + { + if (value) + { + tagsbox.replace(id, value); + return; + } + else + { + tagsbox.remove(id); + return; + } + } + else + { + var id = tagsbox.add(value); + } + } + + if (id) + { + elem = createValueElement(value, 'tag', tabindex); + } + else + { + // Just remove the row + var row = rows.removeChild(row); + tagsbox.fixPopup(); + return; + } + } + + // Fields else { if(saveChanges) diff --git a/chrome/chromeFiles/content/scholar/note.js b/chrome/chromeFiles/content/scholar/note.js index bace742888..b39d3f67e1 100644 --- a/chrome/chromeFiles/content/scholar/note.js +++ b/chrome/chromeFiles/content/scholar/note.js @@ -5,6 +5,7 @@ */ var noteEditor; +var notifierUnregisterID; function onLoad() { @@ -42,12 +43,22 @@ function onLoad() if(collectionID && collectionID != '' && collectionID != 'undefined') noteEditor.collection = Scholar.Collections.get(collectionID); } + + notifierUnregisterID = Scholar.Notifier.registerItemTree(NotifyCallback); } function onUnload() { if(noteEditor && noteEditor.value) noteEditor.save(); + + Scholar.Notifier.unregisterItemTree(notifierUnregisterID); +} + +var NotifyCallback = { + notify: function(){ + noteEditor.id('links').id('tags').reload(); + } } addEventListener("load", function(e) { onLoad(e); }, false); diff --git a/chrome/chromeFiles/content/scholar/note.xul b/chrome/chromeFiles/content/scholar/note.xul index 62b075c5dc..31c255f4dd 100644 --- a/chrome/chromeFiles/content/scholar/note.xul +++ b/chrome/chromeFiles/content/scholar/note.xul @@ -15,6 +15,7 @@ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">