====== Zotero JavaScript API ======
Whereas Zotero's [[dev:web_api|Server API]] allows read and write access to online Zotero libraries, it is also possible to access the local Zotero client through the JavaScript API. (It is also possible to [[dev/client_coding/direct_sqlite_database_access|directly access the local SQLite database]], but that approach is much more fragile.)
Note that the (mostly user-contributed) documentation of the JavaScript API is not comprehensive. If you use the JavaScript API in ways beyond what's described here, please consider expanding this wiki page.
===== Helpful Resources =====
For this section, you’ll likely find the following helpful:
* A basic understanding of how to create a Firefox extension
* [[http://developer.mozilla.org|The Mozilla Developer Center]] (including [[http://developer.mozilla.org/en/docs/Building_an_Extension|Building an Extension]])
* [[https://developer.mozilla.org/en/Setting_up_extension_development_environment|Setting up an extension development environment]]
* A decent grasp of object-oriented JavaScript
* [[http://javascript.crockford.com/private.html|public, private and privileged methods and members]]; object literal notation; closures
* [[http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference|MDC Core JavaScript 1.5 Reference]] (also note [[http://developer.mozilla.org/en/docs/New_in_JavaScript_1.6|New in JavaScript 1.6]] and [[http://developer.mozilla.org/en/docs/New_in_JavaScript_1.7|New in JavaScript 1.7]])
===== An Easy Debugging Environment =====
Various Firefox extensions create servers within Firefox that expose the internal Firefox environment, including Zotero internals. These can be used as shells for easy experimentation. These include:
* [[https://addons.mozilla.org/en-US/firefox/addon/execute-js/|ExecuteJS]]
* [[http://davidkellogg.com/wiki/Main_Page|Plain Old Webserver]] (POW) - [[http://github.com/singingfish/zotero-browser|Zotero Browser]] is an example of getting POW to interact with Zotero to create rich annotated bibliographies.
* [[http://wiki.github.com/bard/mozrepl/|MozRepl]] - interactive shell, available by telnetting to localhost on port 4242 (by default). Linux and Mac OS X users can use the built in telnet client. Windows users should use [[http://www.chiark.greenend.org.uk/~sgtatham/putty/|Putty]]. There is also a Firefox plugin to provide MozRepl access [[http://ubik.cc/2009/09/mozrepl-in-a-panel.html|directly inside Firefox]].
* **Potential MozRepl Problems**: Longer scripts can time out non-deterministically. A solution to this is to either send the code to the terminal application in smaller sized chunks, or to keep pasting the code in until it works. Use an external library (like CPAN's MozRepl module) for finer grained control. Syntax errors can cause silent failure. Avoid this by avoiding excessively long blocks of procedural code - factor everything into smaller functions wherever possible.
Perl programmers should be aware of the [[http://search.cpan.org/perldoc?MozRepl|MozRepl]] CPAN module, and the closely related [[http://search.cpan.org/perldoc?MozRepl::RemoteObject|MozRepl::RemoteObject]] which allows you to access JavaScript objects from inside Perl programs.
===== The Zotero Object ====
Zotero exposes an object-oriented JavaScript API that can be used to access and modify Zotero data. Within Zotero itself, most SQL statements are limited to the data layer, with all other elements (including the entire UI) using the data API to access the data. Through the base Zotero XPCOM service, this functionality is available to all privileged code in Firefox, including other loaded extensions.
==== XPCOM ====
A common problem in developing Mozilla extensions is figuring out how to pass data between different windows, which have their own scopes and do not by default share the same variables and code. While there are various mechanisms to get around this, the [[http://developer.mozilla.org/en/docs/Working_with_windows_in_chrome_code#Using_an_XPCOM_singleton_component|recommended method]], and the method used by Zotero, is to use an XPCOM singleton component to store common data, which can then be accessed from any window that imports the component. (XPCOM is the //cross-platform component object model//---the framework---that forms the basis for the Mozilla application environment.)
The base Zotero service is an XPCOM component written in JavaScript. While normally XPCOM components implement various predefined interfaces themselves, in Zotero the data layer and most of the core functionality are stored within a JavaScript object that is then stuffed into the special ''wrappedJSObject'' property of the component. The component itself does not define any XPCOM interfaces. (This is vaguely a hack, but it addresses our needs sufficiently and is an approach used by several other extensions.) Chrome overlays and windows in Zotero import the core object via the script include.js, which calls ''getService()'' on the component and assigns the wrapped object to the variable ''Zotero''. Zotero methods can then be called from anywhere within the window’s scope simply by calling, for example, ''var item = Zotero.Items.get(1)''.
Access to the Zotero service is not limited to Zotero itself, however. As a standard XPCOM component, the Zotero service---and, specifically, the wrapped JavaScript object---can be accessed from anywhere within privileged code, including other loaded extensions.
To access the data API in your own extension, you will need access to the core Zotero JavaScript object. If your extension operates within the main browser overlay, you already have access to the ''Zotero'' object and don’t need to take further steps. Otherwise, you can import the Zotero object into other windows either by including the script %%chrome://zotero/content/include.js%% within a XUL file (recommended) or by manually calling ''getService()'' directly on the Zotero XPCOM service and assigning the wrapped JS object to a variable.
%%chrome://zotero/content/include.js:%%
var Zotero = Components.classes["@zotero.org/Zotero;1"]
.getService(Components.interfaces.nsISupports)
.wrappedJSObject;
===== Notification System =====
Zotero has a built-in notification system that allows other privileged code to be notified when a change is made via the data layer---for example, when an item is added to the library. Within Zotero itself, this is used mostly to update the UI when items change, but external extensions can use the system to perform additional operations when specific events occur---say, to sync item metadata with a web-based tool.
Available events:
* add (c, s, i, t, ci, it)
* modify (c, s, i, t)
* delete (c, s, i, t)
* remove (ci, it)
* move (c, for changing collection parent)
c = collection, s = search (saved search), i = item, t = tag, ci = collection-item, it = item-tag
See the Zotero [[dev/Sample Plugin]] for a demonstration.
===== API Methods =====
//The Zotero JavaScript API is under-documented, and at present requires a lot of looking around in the source code. The most useful parts of the source code are in chrome/content/zotero/xpcom and xpcom/data, and the chrome/content/zotero (particularly zoteroPane.js and fileInterface.js).//
Once you have access to the core Zotero object, you can use the objects and methods provided by the Zotero JavaScript API.
Zotero uses a combination of instantiatable objects (e.g. ''Zotero.Item = function(){...}'') and singletons (''Zotero.Items = **new** function(){...}''). The most important example of the former is ''Zotero.Item'', which represents a single item in the database. Singletons are used mostly to group related static methods into a single namespace.
==== Adding items and modifying data ====
A typical operation might include a call to ''Items.get()'' to retrieve an ''Item'' instance, calls to ''Item'' methods on the retrieved object to modify data, and finally a ''save()'' to save the modified data to the database.
var creator = new Zotero.Creator;
creator.firstName = 'William';
creator.lastName = 'Shakespeare';
creator.save();
var item = new Zotero.Item;
item.setType(Zotero.ItemTypes.getID('book'));
item.setField('title', 'Much Ado About Nothing');
item.setCreator(0, creator, 'author');
var itemID = item.save();
// Fetch saved items with Items.get(itemID)
var item = Zotero.Items.get(itemID);
alert(item.getField('title')); // "Much Ado About Nothing"
alert(item.getCreator(0)); // {'firstName'=>'William', 'lastName'=>'Shakespeare',
// 'creatorTypeID'=>1, 'fieldMode'=>0}
item.setField('place', 'England');
item.setField('date', 1599);
item.save(); // update database with new data
==== Create new Zotero object ====
This is the first thing that you need to do when interacting with Zotero's
internals. The code to do so is:
var Zotero = Components.classes["@zotero.org/Zotero;1"].getService(Components.interfaces.nsISupports).wrappedJSObject;
==== Get the Zotero Pane to interact with the Zotero GUI ====
var ZoteroPane = Components.classes["@mozilla.org/appshell/window-mediator;1"] .getService(Components.interfaces.nsIWindowMediator).getMostRecentWindow("navigator:browser").ZoteroPane;
Then grab the currently selected items from the zotero pane:
//get first selected item
var selected_items = ZoteroPane.getSelectedItems();
var item = selected_items[0];
// proced if selected item is neither a collection nor a note
if ( ! item.isCollection() & ! item.isNote()) {
if (item.isAttachment()) {
// find out about attachment
}
if (item.isRegularItem()) {
// we could grab attachments:
// var att_ids = item.getAttachments(false);
// if (att_ids.length>1) exit(); // bailout
// item_att=Zotero.Items.get(att_ids[0]);
}
alert(item.id);
}
==== Setup a Zotero search ====
If you are focused on data access, then the first thing you will want to do
will be to retrieve items from your Zotero. Creating an in-memory search is a
good start.
var search = new z.Search();
==== Search for items containing a specific tag ====
Starting with the code from "Setup a Zotero search" we then use the following
code to retrieve items with a particular tag:
search.addCondition('tag', 'is', 'tag name here');
==== Zotero Collection Operations ====
=== Get the collection tree and display as a series of nested ordered lists ===
This code was developed in the Firefox extension Plain Old Webserver server side JavaScript. Note that it's a recursive function. With a bit of jQuery the nested ordered list can be easily transformed into a tree widget.
var Zotero = Components.classes["@zotero.org/Zotero;1"] .getService(Components.interfaces.nsISupports).wrappedJSObject;
var render_collection = function(coll) {
if (!coll) {
coll = null;
}
var collections = Zotero.getCollections(coll);
document.writeln("");
for (c in collections) {
document.writeln('- ' + '' + collections[c].name + '
');
if (collections[c].hasChildCollections) {
var childCol = render_collection(collections[c].id);
}
}
document.writeln("
");
}
render_collection();
=== Get the items for a particular collection ===
var Zotero = Components.classes["@zotero.org/Zotero;1"] .getService(Components.interfaces.nsISupports).wrappedJSObject;
var collectionid = pow_server.GET.id; // or some other way of finding the collectionID here
var collection = z.Collections.get(collectionid);
var items = collection.getChildItems();
// or you can obtain an array of itemIDs instead:
var itemids = collection.getChildItems(true);
==== Zotero Search Basics ====
=== Set up the search ===
var s = new Zotero.Search();
s.addCondition('joinMode', 'any'); // joinMode defaults to 'all' as per the
// advanced search GUI
To add the other conditions available in the advanced search GUI, use the following:
s.addCondition('recursive', 'true'); // equivalent of "Search subfolders" checked
s.addCondition('noChildren', 'true'); // "Only show top level children
s.addCondition('includeParentsAndChildren', 'true'); "Include parent and child ..."
There are also some "hidden" search conditions around line 1735 of chrome/content/zotero/xpcom/search.js in the Zotero source code (although some should only be used internally). [TODO remove mention of source code and enumerate all conditions]
One example is:
s.addCondition('deleted', 'true'); // Include deleted items
=== Search by collection ===
To search for a collection or a saved search you need to know the ID:
s.addCondition('collectionID', 'is', collectionID);
s.addCondition('savedSearchID', 'is', savedSearchID);
=== Search by tag ===
To search by tag, you use the tag text:
var tagname = 'something';
search.addCondition('tag', 'is', tagname);
=== Search by other fields ===
The complete list of other fields available to search on is on the [[dev/client_coding/javascript_api/search fields]] page.
==== Executing the search ====
Once the search conditions have been set up, then it's time to execute the
results:
var results = search.search();
This returns the item ids in the search as an array [I could be wrong ... ].
The next thing to do is to get the Zotero items for the array of IDs:
var items = z.Items.get(results);
===== Managing citations and bibliographies =====
TODO: this is pretty sparse. the rtfscan code is a good place to look for some guidance.
==== Getting a bibliography for an array of items: ====
Here we use Zotero's Quick Copy functions to get a bibliography in the style
specified in Zotero's preferences.
First we start with a list of as in the previous entry.
var qc = z.QuickCopy;
var biblio = qc.getContentFromItems(new Array(item),
z.Prefs.get("export.quickCopy.setting"));
var biblio_html_format = cite.html;
var biblio_txt = cite.text;
==== Get a list of available styles ====
var styles = zotero.Styles.getVisible();
var style_info = [];
for each ( var s in styles) {
style_info.push( { "id" : s.styleID, "name" : s.title } );
}
JSON.stringify(style_info); // e.g. to return json from the mozrepl
TODO: get citations. change the style. get stuff in other formats,
especially RTF
==== Get information about an item. ====
TODO: need to list all the possible fields here, and what kind of entry they
belong to.
To get an item's abstract, we get the 'abstractNote' field from the Zotero item:
var abstract = item.getField('abstractNote');
==== Get child notes for an item ====
To get the child notes for an item, we use the following code:
var notes = item.getNotes();
This returns an array of notes. Each note is in HTML format. To get each note
in turn we just iterate through the array:
for (var j=0;j
==== Get an item's related items ====
This technique works for anything that can have related items attached within
the Zotero database. This includes items and notes.
var related_items = item.relatedItemsBidirectional
This returns a list of items just like in the search examples.
==== Get an Item's Attachments ===
Here's some example code to get the full text of HTML and PDF items in storage and puts the data in an array:
var item = 'some item' ; // some Zotero Item obtained previously
var fulltext = new Array;
if (item.isRegularItem()) { // not an attachment already
var attachments = selected_items[item].getAttachments(false);
for (a in attachments) {
var a_item = Zotero.Items.get(attachments[a]);
if (a_item.attachmentMIMEType == 'application/pdf'
|| a_item.attachmentMIMEType == 'text/html') {
fulltext.push(a_item.attachmentText);
}
}
}
==== To Do ===
* Select a Zotero saved search
* Combining search terms
* Complete list of search operators
* Complete list of search fields - with description of what the more obscure fields mean - e.g. abstractNote for abstract, and how do we search the fulltext archive?
* fulltext for an item
* Get stored attachments for an item
===== Batch Editing =====
The JavaScript API can provide a powerful way to script changes to your Zotero library. The common case of search-and-replace is accomplished easily using a basic script.
First, install the [[https://addons.mozilla.org/en-US/firefox/addon/execute-js/|Execute JS]] Firefox extension to interact with Zotero via JavaScript. (Advanced users can also use the Firefox Browser Console or Scratchpad in Browser mode after enable chrome/add-on debugging in the Web Console settings.) Back up your database first, and temporarily disable auto-sync in the Sync pane of the Zotero preferences.
In Execute JS, switch the target window to an open browser window, paste the relevant code into the "JS-Code to execute" box, make any necessary changes, and click "Execute".
==== Example: Item Field Changes ====
Edit the first three lines as necessary:
var fieldName = "publicationTitle";
var oldValue = "Foo";
var newValue = "Foo2";
var fieldID = Zotero.ItemFields.getID(fieldName);
var s = new Zotero.Search;
s.addCondition(fieldName, 'is', oldValue);
var ids = s.search();
if (ids) {
for(var i in ids) {
var item = Zotero.Items.get(ids[i]);
var mappedFieldID = Zotero.ItemFields.getFieldIDFromTypeAndBase(item.itemTypeID, fieldName);
item.setField(mappedFieldID ? mappedFieldID : fieldID, newValue);
item.save();
}
alert(ids.length + " items updated");
}
else {
alert("No items found");
}
The list of field names to use can be retrieved via the server API: https://api.zotero.org/itemFields?pprint=1.
==== Example: Delete All Automatic Tags ====
var tagType = 1; // automatic
Zotero.Tags.erase(Object.keys(Zotero.Tags.getAll([tagType])))
==== Example: Delete Tags By Name ====
var tags = ["foo", "bar", "baz"];
var ids = [];
var allTags = Zotero.Tags.search();
tags = tags.map(tag => tag.toLowerCase());
for (var id in allTags) {
if (tags.indexOf(allTags[id].name.toLowerCase()) != -1) {
ids.push(id);
}
}
Zotero.Tags.erase(ids);
==== Example: Delete Tags By Part of Name ====
var tags = ["foo", "bar", "baz"];
var ids = [];
tags.forEach(function (tag) {
ids = ids.concat(Object.keys(Zotero.Tags.search(tag)));
});
Zotero.Tags.erase(ids);