zotero/chrome/content/zotero/bindings/itembox.xml
2015-05-19 16:25:02 -04:00

2514 lines
77 KiB
XML

<?xml version="1.0"?>
<!--
***** BEGIN LICENSE BLOCK *****
Copyright © 2009 Center for History and New Media
George Mason University, Fairfax, Virginia, USA
http://zotero.org
This file is part of Zotero.
Zotero is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Zotero is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
***** END LICENSE BLOCK *****
-->
<!DOCTYPE bindings SYSTEM "chrome://zotero/locale/zotero.dtd">
<!-- <!DOCTYPE bindings SYSTEM "chrome://zotero/locale/itembox.dtd"> -->
<bindings xmlns="http://www.mozilla.org/xbl"
xmlns:xbl="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<binding id="item-box">
<resources>
<stylesheet src="chrome://zotero/skin/bindings/itembox.css"/>
<stylesheet src="chrome://zotero-platform/content/itembox.css"/>
</resources>
<implementation>
<!--
Public properties
-->
<field name="clickable">false</field>
<field name="editable">false</field>
<field name="saveOnEdit">false</field>
<field name="showTypeMenu">false</field>
<field name="hideEmptyFields">false</field>
<field name="clickByRow">false</field> <!-- Click entire row rather than just field value -->
<field name="clickByItem">false</field>
<field name="clickHandler"/>
<field name="blurHandler"/>
<field name="eventHandlers">[]</field>
<field name="_initialVisibleCreators">10</field>
<field name="_displayAllCreators"/>
<!-- Modes are predefined settings groups for particular tasks -->
<field name="_mode">"view"</field>
<property name="mode" onget="return this._mode;">
<setter>
<![CDATA[
this.clickable = false;
this.editable = false;
this.saveOnEdit = false;
this.showTypeMenu = false;
this.hideEmptyFields = false;
this.clickByRow = false;
this.clickByItem = false;
switch (val) {
case 'view':
break;
case 'edit':
this.clickable = true;
this.editable = true;
this.saveOnEdit = true
this.showTypeMenu = true;
this.clickHandler = this.showEditor;
this.blurHandler = this.hideEditor;
break;
case 'merge':
this.clickByItem = true;
break;
case 'mergeedit':
this.clickable = true;
this.editable = true;
this.saveOnEdit = false;
this.showTypeMenu = true;
this.clickHandler = this.showEditor;
this.blurHandler = this.hideEditor;
break;
case 'fieldmerge':
this.hideEmptyFields = true;
this._fieldAlternatives = {};
break;
default:
throw ("Invalid mode '" + val + "' in itembox.xml");
}
this._mode = val;
document.getAnonymousNodes(this)[0].setAttribute('mode', val);
]]>
</setter>
</property>
<field name="_item"/>
<property name="item" onget="return this._item;">
<setter>
<![CDATA[
if (!(val instanceof Zotero.Item)) {
throw ("<zoteroitembox>.item must be a Zotero.Item");
}
// When changing items, reset truncation of creator list
if (!this._item || val.id != this._item.id) {
this._displayAllCreators = false;
}
this._item = val;
this.refresh();
]]>
</setter>
</property>
<!-- .ref is an alias for .item -->
<property name="ref"
onget="return this._item;"
onset="this.item = val; this.refresh();">
</property>
<!--
An array of field names that should be shown
even if they're empty and hideEmptyFields is set
-->
<field name="_visibleFields">[]</field>
<property name="visibleFields">
<setter>
<![CDATA[
if (val.constructor.name != 'Array') {
throw ('visibleFields must be an array in <itembox>.visibleFields');
}
this._visibleFields = val;
]]>
</setter>
</property>
<!--
An array of field names that should be hidden
-->
<field name="_hiddenFields">[]</field>
<property name="hiddenFields">
<setter>
<![CDATA[
if (val.constructor.name != 'Array') {
throw ('hiddenFields must be an array in <itembox>.visibleFields');
}
this._hiddenFields = val;
]]>
</setter>
</property>
<!--
An array of field names that should be clickable
even if this.clickable is false
-->
<field name="_clickableFields">[]</field>
<property name="clickableFields">
<setter>
<![CDATA[
if (val.constructor.name != 'Array') {
throw ('clickableFields must be an array in <itembox>.clickableFields');
}
this._clickableFields = val;
]]>
</setter>
</property>
<!--
An array of field names that should be editable
even if this.editable is false
-->
<field name="_editableFields">[]</field>
<property name="editableFields">
<setter>
<![CDATA[
if (val.constructor.name != 'Array') {
throw ('editableFields must be an array in <itembox>.editableFields');
}
this._editableFields = val;
]]>
</setter>
</property>
<!--
An object of alternative values for keyed fields
-->
<field name="_fieldAlternatives">{}</field>
<property name="fieldAlternatives">
<setter>
<![CDATA[
if (val.constructor.name != 'Object') {
throw ('fieldAlternatives must be an Object in <itembox>.fieldAlternatives');
}
if (this.mode != 'fieldmerge') {
throw ('fieldAlternatives is valid only in fieldmerge mode in <itembox>.fieldAlternatives');
}
this._fieldAlternatives = val;
]]>
</setter>
</property>
<!--
An array of field names in the order they should appear
in the list; empty spaces can be created with null
-->
<field name="_fieldOrder">[]</field>
<property name="fieldOrder">
<setter>
<![CDATA[
if (val.constructor.name != 'Array') {
throw ('fieldOrder must be an array in <itembox>.fieldOrder');
}
this._fieldOrder = val;
]]>
</setter>
</property>
<property name="itemTypeMenu" onget="return this._id('item-type-menu')"/>
<!-- Private properties -->
<property name="_dynamicFields" onget="return this._id('dynamic-fields')"/>
<property name="_creatorTypeMenu" onget="return this._id('creator-type-menu')"/>
<field name="_selectField"/>
<field name="_beforeRow"/>
<field name="_addCreatorRow"/>
<field name="_creatorCount"/>
<field name="_lastTabIndex"/>
<field name="_tabDirection"/>
<field name="_tabIndexMinCreators" readonly="true">10</field>
<field name="_tabIndexMaxCreators">0</field>
<field name="_tabIndexMinFields" readonly="true">1000</field>
<field name="_tabIndexMaxFields">0</field>
<property name="_defaultFirstName"
onget="return '(' + Zotero.getString('pane.item.defaultFirstName') + ')'"/>
<property name="_defaultLastName"
onget="return '(' + Zotero.getString('pane.item.defaultLastName') + ')'"/>
<property name="_defaultFullName"
onget="return '(' + Zotero.getString('pane.item.defaultFullName') + ')'"/>
<method name="refresh">
<body>
<![CDATA[
Zotero.debug('Refreshing item box');
if (!this.item) {
Zotero.debug('No item to refresh', 2);
return;
}
if (this.clickByItem) {
var itemBox = document.getAnonymousNodes(this)[0];
itemBox.setAttribute('onclick',
'document.getBindingParent(this).clickHandler(this)');
}
// Item type menu
if (this.showTypeMenu) {
// Build item type menu if it hasn't been built yet
if (this.itemTypeMenu.itemCount == 0) {
this.buildItemTypeMenu();
}
else {
this.updateItemTypeMenuSelection();
}
this.itemTypeMenu.parentNode.hidden = false;
}
else {
this.itemTypeMenu.parentNode.hidden = true;
}
//
// Clear and rebuild metadata fields
//
while (this._dynamicFields.childNodes.length > 1) {
this._dynamicFields.removeChild(this._dynamicFields.lastChild);
}
var fieldNames = [];
// Manual field order
if (this._fieldOrder.length) {
for each(var field in this._fieldOrder) {
fieldNames.push(field);
}
}
// Get field order from database
else {
if (!this.showTypeMenu) {
fieldNames.push("itemType");
}
var fields = Zotero.ItemFields.getItemTypeFields(this.item.getField("itemTypeID"));
for (var i=0; i<fields.length; i++) {
fieldNames.push(Zotero.ItemFields.getName(fields[i]));
}
fieldNames.push("dateAdded", "dateModified");
}
for (var i=0; i<fieldNames.length; i++) {
var fieldName = fieldNames[i];
var val = '';
if (fieldName) {
var fieldID = Zotero.ItemFields.getID(fieldName);
if (fieldID && !Zotero.ItemFields.isValidForType(fieldID, this.item.itemTypeID)) {
fieldName = null;
}
}
if (fieldName) {
if (this._hiddenFields.indexOf(fieldName) != -1) {
continue;
}
// createValueElement() adds the itemTypeID as an attribute
// and converts it to a localized string for display
if (fieldName == 'itemType') {
val = this.item.getField('itemTypeID');
}
else {
val = this.item.getField(fieldName);
}
if (!val && this.hideEmptyFields
&& this._visibleFields.indexOf(fieldName) == -1
&& (this.mode != 'fieldmerge' || typeof this._fieldAlternatives[fieldName] == 'undefined')) {
continue;
}
var fieldIsClickable = this._fieldIsClickable(fieldName);
// Start tabindex at 1001 after creators
var tabindex = fieldIsClickable
? (i>0 ? this._tabIndexMinFields + i : 1) : 0;
this._tabIndexMaxFields = Math.max(this._tabIndexMaxFields, tabindex);
if (fieldIsClickable
&& !Zotero.Items.isPrimaryField(fieldName)
&& (Zotero.ItemFields.isFieldOfBase(Zotero.ItemFields.getID(fieldName), 'date')
// TEMP - filingDate
|| fieldName == 'filingDate')
// TEMP - NSF
&& fieldName != 'dateSent') {
this.addDateRow(fieldNames[i], this.item.getField(fieldName, true), tabindex);
continue;
}
}
var valueElement = this.createValueElement(
val, fieldName, tabindex
);
var label = document.createElement("label");
label.setAttribute('fieldname', fieldName);
var prefix = '';
// Add '(...)' before 'Abstract:' for collapsed abstracts
if (fieldName == 'abstractNote') {
if (val && !Zotero.Prefs.get('lastAbstractExpand')) {
prefix = '(...) ';
}
}
if (fieldName) {
label.setAttribute("value", prefix +
Zotero.ItemFields.getLocalizedString(this.item.itemTypeID, fieldName) + ":");
}
// TEMP - NSF (homepage)
if ((fieldName == 'url' || fieldName == 'homepage') && val) {
label.setAttribute("isButton", true);
// TODO: make getFieldValue non-private and use below instead
label.setAttribute("onclick", "ZoteroPane_Local.loadURI(this.nextSibling.firstChild ? this.nextSibling.firstChild.nodeValue : this.nextSibling.value, event)");
label.setAttribute("tooltiptext", Zotero.getString('locate.online.tooltip'));
}
else if (fieldName == 'DOI' && val && typeof val == 'string') {
// Pull out DOI, in case there's a prefix
var doi = Zotero.Utilities.cleanDOI(val);
if (doi) {
doi = "http://dx.doi.org/" + encodeURIComponent(doi);
label.setAttribute("isButton", true);
label.setAttribute("onclick", "ZoteroPane_Local.loadURI('" + doi + "', event)");
label.setAttribute("tooltiptext", Zotero.getString('locate.online.tooltip'));
valueElement.setAttribute('contextmenu', 'zotero-doi-menu');
var openURLMenuItem = document.getElementById('zotero-doi-menu-view-online');
openURLMenuItem.setAttribute("oncommand", "ZoteroPane_Local.loadURI('" + doi + "', event)");
var copyMenuItem = document.getElementById('zotero-doi-menu-copy');
copyMenuItem.setAttribute("oncommand", "Zotero.Utilities.Internal.copyTextToClipboard('" + doi + "')");
}
}
else if (fieldName == 'abstractNote') {
label.setAttribute("onclick",
"if (this.nextSibling.inputField) { this.nextSibling.inputField.blur(); } "
+ "else { document.getBindingParent(this).toggleAbstractExpand(this); }");
}
else {
label.setAttribute("onclick",
"if (this.nextSibling.inputField) { this.nextSibling.inputField.blur(); }");
}
var row = this.addDynamicRow(label, valueElement);
if (fieldName && this._selectField == fieldName) {
this.showEditor(valueElement);
}
// In field merge mode, add a button to switch field versions
else if (this.mode == 'fieldmerge' && typeof this._fieldAlternatives[fieldName] != 'undefined') {
var button = document.createElement("toolbarbutton");
button.className = 'zotero-field-version-button';
button.setAttribute('image', 'chrome://zotero/skin/treesource-duplicates.png');
button.setAttribute('type', 'menu');
var popup = button.appendChild(document.createElement("menupopup"));
for each(var v in this._fieldAlternatives[fieldName]) {
var menuitem = document.createElement("menuitem");
var sv = Zotero.Utilities.ellipsize(v, 60);
menuitem.setAttribute('label', sv);
if (v != sv) {
menuitem.setAttribute('tooltiptext', v);
}
menuitem.setAttribute('fieldName', fieldName);
menuitem.setAttribute('originalValue', v);
menuitem.setAttribute(
'oncommand',
"var binding = document.getBindingParent(this); "
+ "var item = binding.item; "
+ "item.setField(this.getAttribute('fieldName'), this.getAttribute('originalValue')); "
+ "var row = Zotero.getAncestorByTagName(this, 'row'); "
+ "binding.refresh();"
);
popup.appendChild(menuitem);
}
row.appendChild(button);
}
}
this._selectField = false;
//
// Creators
//
// Creator type menu
if (this.editable) {
while (this._creatorTypeMenu.hasChildNodes()) {
this._creatorTypeMenu.removeChild(this._creatorTypeMenu.firstChild);
}
var creatorTypes = Zotero.CreatorTypes.getTypesForItemType(this.item.itemTypeID);
var localized = {};
for (var i=0; i<creatorTypes.length; i++) {
localized[creatorTypes[i]['name']]
= Zotero.getString('creatorTypes.' + creatorTypes[i]['name']);
}
for (var i in localized) {
var menuitem = document.createElement("menuitem");
menuitem.setAttribute("label", localized[i]);
menuitem.setAttribute("typeid", Zotero.CreatorTypes.getID(i));
this._creatorTypeMenu.appendChild(menuitem);
}
var moveSep = document.createElement("menuseparator");
var moveUp = document.createElement("menuitem");
var moveDown = document.createElement("menuitem");
moveSep.id = "zotero-creator-move-sep";
moveUp.id = "zotero-creator-move-up";
moveDown.id = "zotero-creator-move-down";
moveUp.className = "zotero-creator-move";
moveDown.className = "zotero-creator-move";
moveUp.setAttribute("label", Zotero.getString('pane.item.creator.moveUp'));
moveDown.setAttribute("label", Zotero.getString('pane.item.creator.moveDown'));
this._creatorTypeMenu.appendChild(moveSep);
this._creatorTypeMenu.appendChild(moveUp);
this._creatorTypeMenu.appendChild(moveDown);
}
// Creator rows
// Place, in order of preference, after title, after type,
// or at beginning
var titleFieldID = Zotero.ItemFields.getFieldIDFromTypeAndBase(this.item.itemTypeID, 'title');
var field = this._dynamicFields.getElementsByAttribute('fieldname', Zotero.ItemFields.getName(titleFieldID)).item(0);
if (!field) {
var field = this._dynamicFields.getElementsByAttribute('fieldname', 'itemType').item(0);
}
if (field) {
this._beforeRow = field.parentNode.nextSibling;
}
else {
this._beforeRow = this._dynamicFields.firstChild;
}
this._creatorCount = 0;
var num = this.item.numCreators();
if (num > 0) {
// Limit number of creators display
var max = Math.min(num, this._initialVisibleCreators);
// If fewer than five more, just display
if (num < max + 5 || this._displayAllCreators) {
max = num;
}
for (var i = 0; i < max; i++) {
this.addCreatorRow(this.item.getCreator(i).ref,
this.item.getCreator(i).creatorTypeID);
// Display "+" button on all but last row
if (i == max - 2) {
this.disableCreatorAddButtons();
}
}
// Additional creators not displayed
if (num > max) {
this.addMoreCreatorsRow(num - max);
this.disableCreatorAddButtons();
}
else {
// If we didn't start with creators truncated,
// don't truncate for as long as we're viewing
// this item, so that added creators aren't
// immediately hidden
this._displayAllCreators = true;
if (this._addCreatorRow) {
this.addCreatorRow(false, this.item.getCreator(max-1).creatorTypeID, true);
this._addCreatorRow = false;
this.disableCreatorAddButtons();
}
}
}
else if (this.editable && Zotero.CreatorTypes.itemTypeHasCreators(this.item.itemTypeID)) {
// Add default row
this.addCreatorRow(false, false, true, true);
this.disableCreatorAddButtons();
}
// Move to next or previous field if (shift-)tab was pressed
if (this._lastTabIndex && this._tabDirection)
{
this._focusNextField(this._dynamicFields, this._lastTabIndex, this._tabDirection == -1);
}
]]>
</body>
</method>
<method name="buildItemTypeMenu">
<body>
<![CDATA[
if (!this.item) {
return;
}
this.itemTypeMenu.removeAllItems();
var t = Zotero.ItemTypes.getTypes();
// Sort by localized name
var itemTypes = [];
for (var i=0; i<t.length; i++) {
itemTypes.push({
id: t[i].id,
name: t[i].name,
localized: Zotero.ItemTypes.getLocalizedString(t[i].id)
});
}
var collation = Zotero.getLocaleCollation();
itemTypes.sort(function(a, b) {
return collation.compareString(1, a.localized, b.localized);
});
for (var i=0; i<itemTypes.length; i++) {
var name = itemTypes[i].name;
if (name != 'attachment' && name != 'note') {
this.itemTypeMenu.appendItem(itemTypes[i].localized, itemTypes[i].id);
}
}
this.updateItemTypeMenuSelection();
]]>
</body>
</method>
<method name="updateItemTypeMenuSelection">
<body>
<![CDATA[
var listitems = this.itemTypeMenu.firstChild.childNodes;
for (var i=0, len=listitems.length; i < len; i++) {
if (listitems[i].getAttribute('value') == this.item.itemTypeID) {
this.itemTypeMenu.selectedIndex = i;
}
}
]]>
</body>
</method>
<method name="addDynamicRow">
<parameter name="label"/>
<parameter name="value"/>
<parameter name="beforeElement"/>
<body>
<![CDATA[
var row = document.createElement("row");
// Add click event to row
if (this._rowIsClickable(value.getAttribute('fieldname'))) {
row.className = 'zotero-clicky';
row.addEventListener('click', function (event) {
document.getBindingParent(this).clickHandler(this);
}, false);
}
row.appendChild(label);
row.appendChild(value);
if (beforeElement) {
this._dynamicFields.insertBefore(row, this._beforeRow);
}
else {
this._dynamicFields.appendChild(row);
}
return row;
]]>
</body>
</method>
<method name="addCreatorRow">
<parameter name="creator"/>
<parameter name="creatorTypeID"/>
<parameter name="unsaved"/>
<parameter name="defaultRow"/>
<body>
<![CDATA[
// getCreatorFields(), switchCreatorMode() and handleCreatorAutoCompleteSelect()
// may need need to be adjusted if this DOM structure changes
if (!creator) {
creator = {
firstName: '',
lastName: '',
fieldMode: Zotero.Prefs.get('lastCreatorFieldMode')
};
}
if (creator.fieldMode == 1) {
var firstName = '';
var lastName = creator.lastName ? creator.lastName : this._defaultFullName;
}
else {
var firstName = creator.firstName ? creator.firstName : this._defaultFirstName;
var lastName = creator.lastName ? creator.lastName : this._defaultLastName;
}
// Use the first entry in the drop-down for the default type if none specified
var typeID = creatorTypeID ?
creatorTypeID : this._creatorTypeMenu.childNodes[0].getAttribute('typeid');
var typeBox = document.createElement("hbox");
typeBox.setAttribute("typeid", typeID);
typeBox.setAttribute("popup", "creator-type-menu");
typeBox.setAttribute("fieldname", 'creator-' + this._creatorCount + '-typeID');
if (this.editable) {
typeBox.className = 'creator-type-label zotero-clicky';
var img = document.createElement('image');
typeBox.appendChild(img);
}
else {
typeBox.className = 'creator-type-label';
}
var label = document.createElement("label");
label.setAttribute('value',
Zotero.getString('creatorTypes.'+Zotero.CreatorTypes.getName(typeID)) + ":")
typeBox.appendChild(label);
var hbox = document.createElement("hbox");
hbox.className = 'creator-type-value';
// Name
var firstlast = document.createElement("hbox");
firstlast.className = 'creator-name-box';
firstlast.setAttribute("flex","1");
var tabindex = this._tabIndexMinCreators + (this._creatorCount * 2);
var fieldName = 'creator-' + this._creatorCount + '-lastName';
var lastNameLabel = firstlast.appendChild(
this.createValueElement(
lastName,
fieldName,
tabindex
)
);
// Comma
var comma = document.createElement('label');
comma.setAttribute('value', ',');
comma.className = 'comma';
firstlast.appendChild(comma);
var fieldName = 'creator-' + this._creatorCount + '-firstName';
firstlast.appendChild(
this.createValueElement(
firstName,
fieldName,
tabindex + 1
)
);
if (creator.fieldMode) {
firstlast.lastChild.setAttribute('hidden', true);
}
if (this.editable) {
firstlast.setAttribute('contextmenu', 'zotero-creator-transform-menu');
}
this._tabIndexMaxCreators = Math.max(this._tabIndexMaxCreators, tabindex);
hbox.appendChild(firstlast);
// Single/double field toggle
var toggleButton = document.createElement('label');
toggleButton.setAttribute('fieldname',
'creator-' + this._creatorCount + '-fieldMode');
toggleButton.className = 'zotero-field-toggle zotero-clicky';
hbox.appendChild(toggleButton);
// Minus (-) button
var removeButton = document.createElement('label');
removeButton.setAttribute("value","-");
removeButton.setAttribute("class","zotero-clicky zotero-clicky-minus");
// If default first row, don't let user remove it
if (defaultRow) {
this.disableButton(removeButton);
}
else {
removeButton.setAttribute("onclick",
"document.getBindingParent(this).removeCreator("
+ this._creatorCount
+ ", this.parentNode.parentNode)");
}
hbox.appendChild(removeButton);
// Plus (+) button
var addButton = document.createElement('label');
addButton.setAttribute("value","+");
addButton.setAttribute("class", "zotero-clicky zotero-clicky-plus");
// If row isn't saved, don't let user add more
if (unsaved) {
this.disableButton(addButton);
}
else {
this._enablePlusButton(addButton, typeID, creator.fieldMode);
}
hbox.appendChild(addButton);
this._creatorCount++;
if (!this.editable) {
toggleButton.hidden = true;
removeButton.hidden = true;
addButton.hidden = true;
}
this.addDynamicRow(typeBox, hbox, true);
// Set single/double field toggle mode
if (creator.fieldMode) {
this.switchCreatorMode(hbox.parentNode, 1, true);
}
else {
this.switchCreatorMode(hbox.parentNode, 0, true);
}
// Focus new rows
if (unsaved && !defaultRow){
lastNameLabel.click();
}
]]>
</body>
</method>
<method name="addMoreCreatorsRow">
<parameter name="num"/>
<body>
<![CDATA[
var box = document.createElement('box');
var label = document.createElement('label');
label.id = 'more-creators-label';
label.setAttribute('value', Zotero.getString('general.numMore', num));
label.setAttribute('onclick',
"var binding = document.getBindingParent(this); "
+ "binding._displayAllCreators = true; "
+ "binding.refresh()"
);
this.addDynamicRow(box, label, true);
]]>
</body>
</method>
<method name="addDateRow">
<parameter name="field"/>
<parameter name="value"/>
<parameter name="tabindex"/>
<body>
<![CDATA[
var label = document.createElement("label");
label.setAttribute("value", Zotero.ItemFields.getLocalizedString(this.item.itemTypeID, field) + ':');
label.setAttribute("fieldname", field);
label.setAttribute("onclick", "this.nextSibling.firstChild.blur()");
var elem = this.createValueElement(
Zotero.Date.multipartToStr(value),
field,
tabindex
);
elem.setAttribute('flex', 1);
// y-m-d status indicator
var ymd = document.createElement('label');
ymd.id = 'zotero-date-field-status';
ymd.setAttribute(
'value',
Zotero.Date.strToDate(Zotero.Date.multipartToStr(value))
.order.split('').join(' ')
);
var hbox = document.createElement('hbox');
hbox.setAttribute('flex', 1);
hbox.className = "date-box";
hbox.appendChild(elem);
hbox.appendChild(ymd);
this.addDynamicRow(label, hbox);
]]>
</body>
</method>
<method name="switchCreatorMode">
<parameter name="row"/>
<parameter name="fieldMode"/>
<parameter name="initial"/>
<parameter name="updatePref"/>
<body>
<![CDATA[
// Change if button position changes
// row->hbox->label->label->toolbarbutton
var button = row.lastChild.lastChild.previousSibling.previousSibling;
var hbox = button.previousSibling;
var lastName = hbox.firstChild;
var comma = hbox.firstChild.nextSibling;
var firstName = hbox.lastChild;
// Switch to single-field mode
if (fieldMode == 1) {
button.style.backgroundImage = 'url("chrome://zotero/skin/textfield-dual.png")';
button.setAttribute('tooltiptext', Zotero.getString('pane.item.switchFieldMode.two'));
lastName.setAttribute('fieldMode', '1');
button.setAttribute('onclick', "document.getBindingParent(this).switchCreatorMode(Zotero.getAncestorByTagName(this, 'row'), 0, false, true)");
lastName.setAttribute('flex', '1');
delete lastName.style.width;
delete lastName.style.maxWidth;
// Remove firstname field from tabindex
var tab = parseInt(firstName.getAttribute('ztabindex'));
firstName.setAttribute('ztabindex', -1);
if (this._tabIndexMaxCreators == tab) {
this._tabIndexMaxCreators--;
}
// Hide first name field and prepend to last name field
firstName.setAttribute('hidden', true);
comma.setAttribute('hidden', true);
if (!initial) {
var first = this._getFieldValue(firstName);
if (first && first != this._defaultFirstName) {
var last = this._getFieldValue(lastName);
this._setFieldValue(lastName, first + ' ' + last);
}
}
if (this._getFieldValue(lastName) == this._defaultLastName) {
this._setFieldValue(lastName, this._defaultFullName);
}
}
// Switch to two-field mode
else {
button.style.backgroundImage = 'url("chrome://zotero/skin/textfield-single.png")';
button.setAttribute('tooltiptext', Zotero.getString('pane.item.switchFieldMode.one'));
lastName.setAttribute('fieldMode', '0');
button.setAttribute('onclick', "document.getBindingParent(this).switchCreatorMode(Zotero.getAncestorByTagName(this, 'row'), 1, false, true)");
lastName.setAttribute('flex', '0');
// appropriately truncate lastName
// get item box width
var computedStyle = window.getComputedStyle(this, null);
var boxWidth = computedStyle.getPropertyValue('width');
// get field label width
var computedStyle = window.getComputedStyle(row.firstChild, null);
var leftHboxWidth = computedStyle.getPropertyValue('width');
// get last name width
computedStyle = window.getComputedStyle(lastName, null);
var lastNameWidth = computedStyle.getPropertyValue('width');
if(boxWidth.substr(-2) === 'px'
&& leftHboxWidth.substr(-2) === 'px'
&& lastNameWidth.substr(-2) === "px") {
// compute a maximum width
boxWidth = parseInt(boxWidth);
leftHboxWidth = parseInt(leftHboxWidth);
lastNameWidth = parseInt(lastNameWidth);
var maxWidth = boxWidth-leftHboxWidth-140;
if(lastNameWidth > maxWidth) {
lastName.style.width = maxWidth+"px";
lastName.style.maxWidth = maxWidth+"px";
} else {
delete lastName.style.width;
delete lastName.style.maxWidth;
}
}
// Add firstname field to tabindex
var tab = parseInt(lastName.getAttribute('ztabindex'));
firstName.setAttribute('ztabindex', tab + 1);
if (this._tabIndexMaxCreators == tab)
{
this._tabIndexMaxCreators++;
}
if (!initial) {
// Move all but last word to first name field and show it
var last = this._getFieldValue(lastName);
if (last && last != this._defaultFullName) {
var lastNameRE = /(.*?)[ ]*([^ ]+[ ]*)$/;
var parts = lastNameRE.exec(last);
if (parts[2] && parts[2] != last)
{
this._setFieldValue(lastName, parts[2]);
this._setFieldValue(firstName, parts[1]);
}
}
}
if (!this._getFieldValue(firstName)) {
this._setFieldValue(firstName, this._defaultFirstName);
}
if (this._getFieldValue(lastName) == this._defaultFullName) {
this._setFieldValue(lastName, this._defaultLastName);
}
firstName.setAttribute('hidden', false);
comma.setAttribute('hidden', false);
}
// Save the last-used field mode
if (updatePref) {
Zotero.debug("Switching lastCreatorFieldMode to " + fieldMode);
Zotero.Prefs.set('lastCreatorFieldMode', fieldMode);
}
if (!initial)
{
var index = button.getAttribute('fieldname').split('-')[1];
var fields = this.getCreatorFields(row);
fields.fieldMode = fieldMode;
this.modifyCreator(index, fields);
}
]]>
</body>
</method>
<method name="scrollToTop">
<body>
<![CDATA[
// DEBUG: Valid nsIScrollBoxObject but methods return errors
return;
var sbo = document.getAnonymousNodes(this)[0].boxObject;
sbo.QueryInterface(Components.interfaces.nsIScrollBoxObject);
sbo.scrollTo(0,0);
]]>
</body>
</method>
<method name="ensureElementIsVisible">
<parameter name="elem"/>
<body>
<![CDATA[
var sbo = document.getAnonymousNodes(this)[0].boxObject;
if (Zotero.platformMajorVersion < 36) {
sbo.QueryInterface(Components.interfaces.nsIScrollBoxObject);
}
sbo.ensureElementIsVisible(elem);
]]>
</body>
</method>
<method name="changeTypeTo">
<parameter name="itemTypeID"/>
<parameter name="menu"/>
<body>
<![CDATA[
if (itemTypeID == this.item.itemTypeID) {
return true;
}
var fieldsToDelete = this.item.getFieldsNotInType(itemTypeID, true);
// Special cases handled below
var bookTypeID = Zotero.ItemTypes.getID('book');
var bookSectionTypeID = Zotero.ItemTypes.getID('bookSection');
// Add warning for shortTitle when moving from book to bookSection
// when title will be transferred
if (this.item.itemTypeID == bookTypeID && itemTypeID == bookSectionTypeID) {
var titleFieldID = Zotero.ItemFields.getID('title');
var shortTitleFieldID = Zotero.ItemFields.getID('shortTitle');
if (this.item.getField(titleFieldID) && this.item.getField(shortTitleFieldID)) {
if (!fieldsToDelete) {
fieldsToDelete = [];
}
fieldsToDelete.push(shortTitleFieldID);
}
}
// Generate list of localized field names for display in pop-up
if (fieldsToDelete) {
// Ignore warning for bookTitle when going from bookSection to book
// if there's not also a title, since the book title is transferred
// to title automatically in Zotero.Item.setType()
if (this.item.itemTypeID == bookSectionTypeID && itemTypeID == bookTypeID) {
var titleFieldID = Zotero.ItemFields.getID('title');
var bookTitleFieldID = Zotero.ItemFields.getID('bookTitle');
var shortTitleFieldID = Zotero.ItemFields.getID('shortTitle');
if (this.item.getField(bookTitleFieldID) && !this.item.getField(titleFieldID)) {
var index = fieldsToDelete.indexOf(bookTitleFieldID);
fieldsToDelete.splice(index, 1);
// But warn for short title, which will be removed
if (this.item.getField(shortTitleFieldID)) {
fieldsToDelete.push(shortTitleFieldID);
}
}
}
var fieldNames = "";
for (var i=0; i<fieldsToDelete.length; i++) {
fieldNames += "\n - " +
Zotero.ItemFields.getLocalizedString(this.item.itemTypeID, fieldsToDelete[i]);
}
var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
.getService(Components.interfaces.nsIPromptService);
}
if (!fieldsToDelete || fieldsToDelete.length == 0 ||
promptService.confirm(null,
Zotero.getString('pane.item.changeType.title'),
Zotero.getString('pane.item.changeType.text') + "\n" + fieldNames)) {
this.item.setType(itemTypeID);
if (this.saveOnEdit) {
this.item.save();
}
else {
this.refresh();
}
if (this.eventHandlers['itemtypechange'] && this.eventHandlers['itemtypechange'].length) {
var self = this;
this.eventHandlers['itemtypechange'].forEach(function (f) f.bind(self)());
}
return true;
}
// Revert the menu (which changes before the pop-up)
if (menu) {
menu.value = this.item.itemTypeID;
}
return false;
]]>
</body>
</method>
<method name="toggleAbstractExpand">
<parameter name="label"/>
<body>
<![CDATA[
var cur = Zotero.Prefs.get('lastAbstractExpand');
Zotero.Prefs.set('lastAbstractExpand', !cur);
var ab = label.nextSibling;
var valueText = this.item.getField('abstractNote');
var tabindex = ab.getAttribute('ztabindex');
var elem = this.createValueElement(
valueText,
'abstractNote',
tabindex
);
ab.parentNode.replaceChild(elem, ab);
var text = Zotero.ItemFields.getLocalizedString(this.item.itemTypeID, 'abstractNote') + ':';
// Add '(...)' before "Abstract:" for collapsed abstracts
if (valueText && cur) {
text = '(...) ' + text;
}
label.setAttribute('value', text);
]]>
</body>
</method>
<method name="disableButton">
<parameter name="button"/>
<body>
<![CDATA[
button.setAttribute('disabled', true);
button.setAttribute('onclick', false);
]]>
</body>
</method>
<method name="_enablePlusButton">
<parameter name="button"/>
<parameter name="creatorTypeID"/>
<parameter name="fieldMode"/>
<body>
<![CDATA[
button.setAttribute('disabled', false);
button.setAttribute("onclick",
"var parent = document.getBindingParent(this); "
+ "parent.disableButton(this); "
+ "var creator = new Zotero.Creator; "
+ "creator.fieldMode = " + (fieldMode ? fieldMode : 0) + "; "
+ "parent.addCreatorRow("
+ "creator, "
+ (creatorTypeID ? creatorTypeID : 'false') + ", true"
+ ");"
);
]]>
</body>
</method>
<method name="disableCreatorAddButtons">
<body>
<![CDATA[
// Disable the "+" button on all creator rows
var elems = this._dynamicFields.getElementsByAttribute('value', '+');
for (var i = 0, len = elems.length; i < len; i++) {
this.disableButton(elems[i]);
}
]]>
</body>
</method>
<method name="createValueElement">
<parameter name="valueText"/>
<parameter name="fieldName"/>
<parameter name="tabindex"/>
<body>
<![CDATA[
valueText = valueText + '';
if (fieldName) {
var fieldID = Zotero.ItemFields.getID(fieldName);
}
// If an abstract, check last expand state
var abstractAsVbox = fieldName == 'abstractNote' && Zotero.Prefs.get('lastAbstractExpand');
// Use a vbox for multiline fields (but Abstract only if it's expanded)
var useVbox = (fieldName != 'abstractNote' || abstractAsVbox)
&& Zotero.ItemFields.isMultiline(fieldName);
if (useVbox) {
var valueElement = document.createElement("vbox");
}
else {
var valueElement = document.createElement("label");
}
valueElement.setAttribute('fieldname', fieldName);
valueElement.setAttribute('flex', 1);
if (this._fieldIsClickable(fieldName)) {
valueElement.setAttribute('ztabindex', tabindex);
valueElement.addEventListener('click', function (event) {
/* Skip right-click on Windows */
if (event.button) {
return;
}
document.getBindingParent(this).clickHandler(this);
}, false);
valueElement.className = 'zotero-clicky';
}
switch (fieldName) {
case 'itemType':
valueElement.setAttribute('itemTypeID', valueText);
valueText = Zotero.ItemTypes.getLocalizedString(valueText);
break;
// Convert dates from UTC
case 'dateAdded':
case 'dateModified':
case 'accessDate':
// TEMP - NSF
case 'dateSent':
case 'dateDue':
case 'accepted':
if (valueText) {
var date = Zotero.Date.sqlToDate(valueText, true);
if (date) {
// If no time, interpret as local, not UTC
if (Zotero.Date.isSQLDate(valueText)) {
date = Zotero.Date.sqlToDate(valueText);
valueText = date.toLocaleDateString();
}
else {
valueText = date.toLocaleString();
}
}
else {
valueText = '';
}
}
break;
}
if (fieldID) {
// Display the SQL date as a tooltip for date fields
// TEMP - filingDate
if (Zotero.ItemFields.isFieldOfBase(fieldID, 'date') || fieldName == 'filingDate') {
valueElement.setAttribute('tooltiptext',
Zotero.Date.multipartToSQL(this.item.getField(fieldName, true)));
}
// Display a context menu for certain fields
if (this.editable && (fieldName == 'seriesTitle' || fieldName == 'shortTitle' ||
Zotero.ItemFields.isFieldOfBase(fieldID, 'title') ||
Zotero.ItemFields.isFieldOfBase(fieldID, 'publicationTitle'))) {
valueElement.setAttribute('contextmenu', 'zotero-field-transform-menu');
}
}
if (fieldName && fieldName.indexOf('firstName') != -1) {
valueElement.setAttribute('flex', '1');
}
var firstSpace = valueText.indexOf(" ");
// To support newlines in Abstract and Extra fields, use multiple
// <description> elements inside a vbox
if (useVbox) {
var lines = valueText.split("\n");
for (var i = 0; i < lines.length; i++) {
var descriptionNode = document.createElement("description");
// Add non-breaking space to empty lines to prevent them from collapsing.
// (Just using CSS min-height results in overflow in some cases.)
if (lines[i] === "") {
lines[i] = "\u00a0";
}
var linetext = document.createTextNode(lines[i]);
descriptionNode.appendChild(linetext);
valueElement.appendChild(descriptionNode);
}
}
// 29 == arbitrary length at which to chop uninterrupted text
else if ((firstSpace == -1 && valueText.length > 29 ) || firstSpace > 29
|| (fieldName &&
(fieldName.substr(0, 7) == 'creator') || fieldName == 'abstractNote')) {
if (fieldName == 'abstractNote') {
valueText = valueText.replace(/[\t\n]/g, ' ');
}
valueElement.setAttribute('crop', 'end');
valueElement.setAttribute('value',valueText);
}
else {
// Wrap to multiple lines
valueElement.appendChild(document.createTextNode(valueText));
}
return valueElement;
]]>
</body>
</method>
<method name="removeCreator">
<parameter name="index"/>
<parameter name="labelToDelete"/>
<body>
<![CDATA[
// If unsaved row, just remove element
if (!this.item.hasCreatorAt(index)) {
labelToDelete.parentNode.removeChild(labelToDelete);
// Enable the "+" button on the previous row
var elems = this._dynamicFields.getElementsByAttribute('value', '+');
var button = elems[elems.length-1];
var creatorFields = this.getCreatorFields(Zotero.getAncestorByTagName(button, 'row'));
this._enablePlusButton(button, creatorFields.creatorTypeID, creatorFields.fieldMode);
this._creatorCount--;
return;
}
this.item.removeCreator(index);
this.item.save();
]]>
</body>
</method>
<method name="showEditor">
<parameter name="elem"/>
<body>
<![CDATA[
// Blur any active fields
if (this._dynamicFields) {
this._dynamicFields.focus();
}
Zotero.debug('Showing editor');
var fieldName = elem.getAttribute('fieldname');
var tabindex = elem.getAttribute('ztabindex');
var [field, creatorIndex, creatorField] = fieldName.split('-');
if (field == 'creator') {
var c = this.item.getCreator(creatorIndex);
var value = c ? c.ref[creatorField] : '';
var itemID = this.item.id;
}
else {
var value = this.item.getField(fieldName);
var itemID = this.item.id;
// Access date needs to be converted from UTC
if (value != '') {
switch (fieldName) {
case 'accessDate':
// TEMP - NSF
case 'dateSent':
case 'dateDue':
case 'accepted':
// If no time, interpret as local, not UTC
if (Zotero.Date.isSQLDate(value)) {
var localDate = Zotero.Date.sqlToDate(value);
}
else {
var localDate = Zotero.Date.sqlToDate(value, true);
}
var value = Zotero.Date.dateToSQL(localDate);
// Don't show time in editor
value = value.replace(' 00:00:00', '');
break;
}
}
}
var t = document.createElement("textbox");
t.setAttribute('value', value);
t.setAttribute('fieldname', fieldName);
t.setAttribute('ztabindex', tabindex);
t.setAttribute('flex', '1');
if (creatorField=='lastName') {
t.setAttribute('fieldMode', elem.getAttribute('fieldMode'));
t.setAttribute('newlines','pasteintact');
}
if (Zotero.ItemFields.isMultiline(fieldName) || Zotero.ItemFields.isLong(fieldName)) {
t.setAttribute('multiline', true);
t.setAttribute('rows', 8);
}
else {
// Add auto-complete for certain fields
if (Zotero.ItemFields.isAutocompleteField(fieldName)
|| fieldName == 'creator') {
t.setAttribute('type', 'autocomplete');
t.setAttribute('autocompletesearch', 'zotero');
let params = {
fieldName: fieldName,
libraryID: this.item.libraryID
};
if (field == 'creator') {
params.fieldMode = parseInt(elem.getAttribute('fieldMode'));
// Include itemID and creatorTypeID so the autocomplete can
// avoid showing results for creators already set on the item
let row = Zotero.getAncestorByTagName(elem, 'row');
let creatorTypeID = parseInt(
row.getElementsByClassName('creator-type-label')[0]
.getAttribute('typeid')
);
if (itemID) {
params.itemID = itemID;
params.creatorTypeID = creatorTypeID;
}
t.setAttribute('ontextentered',
'document.getBindingParent(this).handleCreatorAutoCompleteSelect(this)');
};
t.setAttribute(
'autocompletesearchparam', JSON.stringify(params)
);
t.setAttribute('completeselectedindex', true);
}
}
var box = elem.parentNode;
box.replaceChild(t, elem);
// Prevent error when clicking between a changed field
// and another -- there's probably a better way
if (!t.select) {
return;
}
t.select();
// Leave text field open when window loses focus
var ignoreBlur = function () {
this.ignoreBlur = true;
}.bind(this);
var unignoreBlur = function () {
this.ignoreBlur = false;
}.bind(this);
addEventListener("deactivate", ignoreBlur);
addEventListener("activate", unignoreBlur);
t.addEventListener('blur', function () {
var self = document.getBindingParent(this);
if (self.ignoreBlur) return;
removeEventListener("deactivate", ignoreBlur);
removeEventListener("activate", unignoreBlur);
self.blurHandler(this);
});
t.setAttribute('onkeypress', "return document.getBindingParent(this).handleKeyPress(event)");
this._tabDirection = false;
this._lastTabIndex = tabindex;
return t;
]]>
</body>
</method>
<!--
Save a multiple-field selection for the creator autocomplete
(e.g. "Shakespeare, William")
-->
<method name="handleCreatorAutoCompleteSelect">
<parameter name="textbox"/>
<body>
<![CDATA[
var comment = false;
var controller = textbox.controller;
if (!controller.matchCount) return;
for (var i=0; i<controller.matchCount; i++)
{
if (controller.getValueAt(i) == textbox.value)
{
comment = controller.getCommentAt(i);
break;
}
}
var [creatorID, numFields] = comment.split('-');
// If result uses two fields, save both
if (numFields==2)
{
// Manually clear autocomplete controller's reference to
// textbox to prevent error next time around
textbox.mController.input = null;
var [field, creatorIndex, creatorField] =
textbox.getAttribute('fieldname').split('-');
// Stay focused
this._lastTabIndex = parseInt(textbox.getAttribute('ztabindex')) - 1;
this._tabDirection = 1;
var creator = Zotero.Creators.get(creatorID);
var otherField = creatorField == 'lastName' ? 'firstName' : 'lastName';
// Update this textbox
textbox.setAttribute('value', creator[creatorField]);
textbox.value = creator[creatorField];
// Update the other label
if (otherField=='firstName'){
var label = textbox.nextSibling.nextSibling;
}
else if (otherField=='lastName'){
var label = textbox.previousSibling.previousSibling;
}
//this._setFieldValue(label, creator[otherField]);
if (label.firstChild){
label.firstChild.nodeValue = creator[otherField];
}
else {
label.value = creator[otherField];
}
var row = Zotero.getAncestorByTagName(textbox, 'row');
var fields = this.getCreatorFields(row);
fields[creatorField] = creator[creatorField];
fields[otherField] = creator[otherField];
this.modifyCreator(creatorIndex, fields);
}
// Otherwise let the autocomplete popup handle matters
]]>
</body>
</method>
<method name="handleKeyPress">
<parameter name="event"/>
<body>
<![CDATA[
var target = event.target;
var focused = document.commandDispatcher.focusedElement;
switch (event.keyCode)
{
case event.DOM_VK_RETURN:
var fieldname = target.getAttribute('fieldname');
// Use shift-enter as the save action for the larger fields
if (Zotero.ItemFields.isMultiline(fieldname) && !event.shiftKey) {
break;
}
// Prevent blur on containing textbox
// DEBUG: what happens if this isn't present?
event.preventDefault();
// Shift-enter adds new creator row
if (fieldname.indexOf('creator-') == 0 && event.shiftKey) {
// Value hasn't changed
if (target.getAttribute('value') == target.value) {
Zotero.debug("Value hasn't changed");
// If + button is disabled, just focus next creator row
if (Zotero.getAncestorByTagName(target, 'row').lastChild.lastChild.disabled) {
this._focusNextField(this._dynamicFields, this._lastTabIndex, false);
}
else {
var creatorFields = this.getCreatorFields(Zotero.getAncestorByTagName(target, 'row'));
this.addCreatorRow(false, creatorFields.creatorTypeID, true);
}
}
// Value has changed
else {
this._tabDirection = 1;
this._addCreatorRow = true;
focused.blur();
}
return false;
}
focused.blur();
// Return focus to items pane
var tree = document.getElementById('zotero-items-tree');
if (tree) {
tree.focus();
}
return false;
case event.DOM_VK_ESCAPE:
// Reset field to original value
target.value = target.getAttribute('value');
focused.blur();
// Return focus to items pane
var tree = document.getElementById('zotero-items-tree');
if (tree) {
tree.focus();
}
return false;
case event.DOM_VK_TAB:
this._tabDirection = event.shiftKey ? -1 : 1;
// Blur the old manually -- not sure why this is necessary,
// but it prevents an immediate blur() on the next tag
focused.blur();
return false;
}
return true;
]]>
</body>
</method>
<method name="itemTypeMenuTab">
<parameter name="event"/>
<body>
<![CDATA[
if (!event.shiftKey) {
this.focusFirstField();
event.preventDefault();
}
// Shift-tab
else {
this._tabDirection = false;
}
]]>
</body>
</method>
<method name="hideEditor">
<parameter name="textbox"/>
<body>
<![CDATA[
try {
Zotero.debug('Hiding editor');
/*
var textbox = Zotero.getAncestorByTagName(t, 'textbox');
if (!textbox){
Zotero.debug('Textbox not found in hideEditor');
return;
}
*/
// TODO: get rid of this?
var saveChanges = this.saveOnEdit;
// Prevent autocomplete breakage in Firefox 3
if (textbox.mController) {
textbox.mController.input = null;
}
var fieldName = textbox.getAttribute('fieldname');
var tabindex = textbox.getAttribute('ztabindex');
//var value = t.value;
var value = textbox.value;
var elem;
var [field, creatorIndex, creatorField] = fieldName.split('-');
// Creator fields
if (field == 'creator') {
var row = Zotero.getAncestorByTagName(textbox, 'row');
var otherFields = this.getCreatorFields(row);
otherFields[creatorField] = value;
var lastName = otherFields.lastName.trim();
//Handle \n\r and \n delimited entries
var rawNameArray = lastName.split(/\r\n?|\n/);
if (rawNameArray.length > 1) {
//Save tab direction and add creator flags since they are reset in the
//process of adding multiple authors
var tabDirectionBuffer = this._tabDirection;
var addCreatorRowBuffer = this._addCreatorRow;
var tabIndexBuffer = this._lastTabIndex;
this._tabDirection = false;
this._addCreatorRow = false;
//Filter out bad names
var nameArray = [tempName for each(tempName in rawNameArray) if(tempName)];
//If not adding names at the end of the creator list, make new creator
//entries and then shift down existing creators.
var initNumCreators = this.item.numCreators();
var creatorsToShift = initNumCreators - creatorIndex;
if (creatorsToShift > 0) {
//Add extra creators
for (var i=0;i<nameArray.length;i++) {
this.modifyCreator(i+initNumCreators,otherFields);
}
//Shift existing creators
for (var i=initNumCreators-1; i>=creatorIndex; i--) {
var shiftedCreator = this.item.getCreator(i);
this.item.setCreator(nameArray.length+i,shiftedCreator.ref,shiftedCreator.creatorTypeID);
}
}
//Add the creators in lastNameArray one at a time
for each(var tempName in nameArray) {
// Check for tab to determine creator name format
otherFields.fieldMode = (tempName.indexOf('\t') == -1) ? 1 : 0;
if (otherFields.fieldMode == 0) {
otherFields.lastName=tempName.split('\t')[0];
otherFields.firstName=tempName.split('\t')[1];
}
else {
otherFields.lastName=tempName;
otherFields.firstName='';
}
this.modifyCreator(creatorIndex,otherFields);
creatorIndex++;
}
this._tabDirection = tabDirectionBuffer;
this._addCreatorRow = (creatorsToShift==0) ? addCreatorRowBuffer : false;
if (this._tabDirection == 1) {
this._lastTabIndex = parseInt(tabIndexBuffer,10) + 2*(nameArray.length-1);
if (otherFields.fieldMode == 0) {
this._lastTabIndex++;
}
}
}
else {
this.modifyCreator(creatorIndex, otherFields);
}
var val = this.item.getCreator(creatorIndex);
val = val ? val.ref[creatorField] : null;
if (!val) {
// Reset to '(first)'/'(last)'/'(name)'
if (creatorField == 'lastName') {
val = otherFields.fieldMode
? this._defaultFullName : this._defaultLastName;
}
else if (creatorField == 'firstName') {
val = this._defaultFirstName;
}
}
elem = this.createValueElement(
val,
fieldName,
tabindex
);
}
// Fields
else {
// Access date needs to be parsed and converted to UTC
if (value != '') {
switch (fieldName) {
case 'accessDate':
// If just date, don't convert to UTC
if (Zotero.Date.isSQLDate(value)) {
var localDate = Zotero.Date.sqlToDate(value);
value = Zotero.Date.dateToSQL(localDate).replace(' 00:00:00', '');
}
else if (Zotero.Date.isSQLDateTime(value)) {
var localDate = Zotero.Date.sqlToDate(value);
value = Zotero.Date.dateToSQL(localDate, true);
}
else {
var d = Zotero.Date.strToDate(value);
value = null;
if (d.year && d.month != undefined && d.day) {
d = new Date(d.year, d.month, d.day);
value = Zotero.Date.dateToSQL(d).replace(' 00:00:00', '');
}
}
break;
// TEMP - NSF
case 'dateSent':
case 'dateDue':
case 'accepted':
if (Zotero.Date.isSQLDate(value)) {
var localDate = Zotero.Date.sqlToDate(value);
value = Zotero.Date.dateToSQL(localDate).replace(' 00:00:00', '');
}
else {
var d = Zotero.Date.strToDate(value);
value = null;
if (d.year && d.month != undefined && d.day) {
d = new Date(d.year, d.month, d.day);
value = Zotero.Date.dateToSQL(d).replace(' 00:00:00', '');
}
}
break;
default:
// TODO: generalize to all date rows/fields
if (Zotero.ItemFields.isFieldOfBase(fieldName, 'date')) {
// Parse 'yesterday'/'today'/'tomorrow' and convert to dates,
// since it doesn't make sense for those to be actual metadata values
var lc = value.toLowerCase();
if (lc == 'yesterday' || lc == Zotero.getString('date.yesterday')) {
value = Zotero.Date.dateToSQL(new Date(new Date().getTime() - 86400000)).substr(0, 10);
}
else if (lc == 'today' || lc == Zotero.getString('date.today')) {
value = Zotero.Date.dateToSQL(new Date()).substr(0, 10);
}
else if (lc == 'tomorrow' || lc == Zotero.getString('date.tomorrow')) {
value = Zotero.Date.dateToSQL(new Date(new Date().getTime() + 86400000)).substr(0, 10);
}
}
}
}
this._modifyField(fieldName, value, this.saveOnEdit);
elem = this.createValueElement(
this.item.getField(fieldName),
fieldName,
tabindex
);
}
var box = textbox.parentNode;
box.replaceChild(elem,textbox);
if(field === 'creator') {
// Reset creator mode settings here so that flex attribute gets reset
this.switchCreatorMode(row, (otherFields.fieldMode ? 1 : 0), true);
if(Zotero.ItemTypes.getName(this.item.itemTypeID) === "bookSection") {
var creatorTypeLabels = document.getAnonymousNodes(this)[0].getElementsByClassName("creator-type-label");
document.getElementById("zotero-author-guidance").show(creatorTypeLabels[creatorTypeLabels.length-1]);
}
}
if (this._tabDirection) {
var focusBox = this._dynamicFields;
this._focusNextField(focusBox, this._lastTabIndex, this._tabDirection == -1);
}
}
// Thrown errors don't seem to show up within XBL without explicit logging
catch (e) {
Zotero.debug(e);
Components.utils.reportError(e);
throw (e);
}
]]>
</body>
</method>
<method name="_rowIsClickable">
<parameter name="fieldName"/>
<body>
<![CDATA[
return this.clickByRow &&
(this.clickable ||
this._clickableFields.indexOf(fieldName) != -1);
]]>
</body>
</method>
<method name="_fieldIsClickable">
<parameter name="fieldName"/>
<body>
<![CDATA[
return !this.clickByRow &&
((this.clickable && !Zotero.Items.isPrimaryField(fieldName))
|| this._clickableFields.indexOf(fieldName) != -1);
]]>
</body>
</method>
<method name="_modifyField">
<parameter name="field"/>
<parameter name="value"/>
<parameter name="save"/>
<body>
<![CDATA[
this.item.setField(field,value);
if (save) {
this.item.save();
}
]]>
</body>
</method>
<method name="_getFieldValue">
<parameter name="label"/>
<body>
<![CDATA[
return label.firstChild
? label.firstChild.nodeValue : label.value;
]]>
</body>
</method>
<method name="_setFieldValue">
<parameter name="label"/>
<parameter name="value"/>
<body>
<![CDATA[
if (label.firstChild) {
label.firstChild.nodeValue = value;
}
else {
label.value = value;
}
]]>
</body>
</method>
<!-- TODO: work with textboxes too -->
<method name="textTransform">
<parameter name="label"/>
<parameter name="mode"/>
<body>
<![CDATA[
var val = this._getFieldValue(label);
switch (mode) {
case 'title':
var newVal = Zotero.Utilities.capitalizeTitle(val.toLowerCase(), true);
break;
case 'sentence':
// capitalize the first letter, including after beginning punctuation
// capitalize after ?, ! and remove space(s) before those as well as colon analogous to capitalizeTitle function
// also deal with initial punctuation here - open quotes and Spanish beginning punctuation marks
newVal = val.toLowerCase().replace(/\s*:/, ":");
newVal = newVal.replace(/(([\?!]\s*|^)([\'\"¡¿“‘„«\s]+)?[^\s])/g, function (x) {
return x.replace(/\s+/m, " ").toUpperCase();});
break;
default:
throw ("Invalid transform mode '" + mode + "' in zoteroitembox.textTransform()");
}
this._setFieldValue(label, newVal);
this._modifyField(label.getAttribute('fieldname'), newVal, this.saveOnEdit);
]]>
</body>
</method>
<method name="getCreatorFields">
<parameter name="row"/>
<body>
<![CDATA[
var typeID = row.getElementsByClassName('creator-type-label')[0].getAttribute('typeid');
var label1 = row.getElementsByClassName('creator-name-box')[0].firstChild;
var label2 = label1.parentNode.lastChild;
var fields = {
lastName: label1.firstChild ? label1.firstChild.nodeValue
: label1.value,
firstName: label2.firstChild ? label2.firstChild.nodeValue
: label2.value,
fieldMode: label1.getAttribute('fieldMode')
? parseInt(label1.getAttribute('fieldMode')) : 0,
creatorTypeID: parseInt(typeID),
};
// Ignore '(first)'
if (fields.fieldMode == 1 || fields.firstName == this._defaultFirstName) {
fields.firstName = '';
}
// Ignore '(last)' or '(name)'
if (fields.lastName == this._defaultFullName
|| fields.lastName == this._defaultLastName) {
fields.lastName = '';
}
return fields;
]]>
</body>
</method>
<method name="modifyCreator">
<parameter name="index"/>
<parameter name="fields"/>
<parameter name="changeGlobally"/>
<body>
<![CDATA[
try {
var libraryID = this.item.libraryID;
var firstName = fields.firstName;
var lastName = fields.lastName;
//var shortName = fields.shortName;
var fieldMode = fields.fieldMode;
var creatorTypeID = fields.creatorTypeID;
var oldCreator = this.item.getCreator(index);
// Don't save empty creators
if (!firstName && !lastName){
if (!oldCreator) {
return;
}
this.item.removeCreator(index);
this.item.save();
return;
}
Zotero.DB.beginTransaction();
var newCreator = new Zotero.Creator;
newCreator.libraryID = libraryID;
newCreator.setFields(fields);
var newLinkedCreators = [];
var creatorDataID = Zotero.Creators.getDataID(fields);
if (creatorDataID) {
newLinkedCreators = Zotero.Creators.getCreatorsWithData(creatorDataID, libraryID);
}
if (oldCreator) {
if (oldCreator.ref.equals(newCreator) || (oldCreator.ref.libraryID != newCreator.libraryID)) {
if (oldCreator.creatorTypeID == creatorTypeID) {
Zotero.debug("Creator " + oldCreator.ref.id + " hasn't changed");
}
// Just change creatorTypeID
else {
this.item.setCreator(index, oldCreator.ref, creatorTypeID);
if (this.saveOnEdit) {
this.item.save();
}
}
Zotero.DB.commitTransaction();
return;
}
oldCreator = oldCreator.ref;
}
var creator;
var creatorID;
if (oldCreator) {
var numLinkedItems = oldCreator.countLinkedItems();
// Creator is linked only to the current item
if (numLinkedItems == 1) {
if (newLinkedCreators.length) {
// Use the first creator found with this data
// TODO: support choosing among options
creatorID = newLinkedCreators[0];
creator = Zotero.Creators.get(creatorID);
}
else {
oldCreator.setFields(fields);
//creatorID = oldCreator.save();
creator = oldCreator;
}
}
// Creator is linked to multiple items with changeGlobally off
else if (!changeGlobally) {
if (newLinkedCreators.length) {
// Use the first creator found with this data
// TODO: support choosing among options
creatorID = newLinkedCreators[0];
creator = Zotero.Creators.get(creatorID);
}
else {
//creatorID = newCreator.save();
creator = newCreator;
}
}
// Creator is linked to multiple items with changeGlobally on
else {
throw ('changeGlobally unimplemented');
if (newLinkedCreators.length) {
// Use the first creator found with this data
// TODO: support choosing among options
creatorID = newLinkedCreators[0];
// TODO: switch all linked items to this creator
}
else {
creatorID = newCreator.save();
// TODO: switch all linked items to new creatorID
}
}
}
// No existing creator
else {
if (newLinkedCreators.length) {
creatorID = newLinkedCreators[0];
creator = Zotero.Creators.get(creatorID);
}
else {
//creatorID = newCreator.save();
creator = newCreator;
}
}
this.item.setCreator(index, creator, creatorTypeID);
if (this.saveOnEdit) {
try {
this.item.save();
}
catch (e) {
// DEBUG: Errors aren't being logged in Fx3.1b4pre without this
Zotero.debug(e);
Components.utils.reportError(e);
throw (e);
}
}
Zotero.DB.commitTransaction();
}
catch (e) {
Zotero.debug(e);
Components.utils.reportError(e);
throw (e);
}
]]>
</body>
</method>
<method name="swapNames">
<body><![CDATA[
var row = Zotero.getAncestorByTagName(document.popupNode, 'row');
var typeBox = row.getElementsByAttribute('popup', 'creator-type-menu')[0];
var creatorIndex = parseInt(typeBox.getAttribute('fieldname').split('-')[1]);
var fields = this.getCreatorFields(row);
var lastName = fields.lastName;
var firstName = fields.firstName;
fields.lastName = firstName;
fields.firstName = lastName;
this.modifyCreator(creatorIndex, fields);
this.item.save();
]]></body>
</method>
<method name="moveCreator">
<parameter name="index"/>
<parameter name="moveUp"/>
<body>
<![CDATA[
if (index == 0 && moveUp) {
Zotero.debug("Can't move up creator 0");
return;
}
else if (index + 1 == this.item.numCreators() && !moveUp) {
Zotero.debug("Can't move down last creator");
return;
}
var newIndex = moveUp ? index - 1 : index + 1;
var creator = this.item.getCreator(index);
var swapCreator = this.item.getCreator(newIndex);
this.item.setCreator(newIndex, creator.ref, creator.creatorTypeID);
this.item.setCreator(index, swapCreator.ref, swapCreator.creatorTypeID);
if (this.saveOnEdit) {
this.item.save();
}
]]>
</body>
</method>
<method name="_updateAutoCompleteParams">
<parameter name="row"/>
<parameter name="changedParams"/>
<body>
<![CDATA[
var textboxes = row.getElementsByTagName('textbox');
if (textboxes.length) {
var t = textboxes[0];
var params = JSON.parse(t.getAttribute('autocompletesearchparam'));
for (var param in changedParams) {
params[param] = changedParams[param];
}
t.setAttribute('autocompletesearchparam', JSON.stringify(params));
}
]]>
</body>
</method>
<!--
/*
function modifyCreatorByID(index, creatorID, creatorTypeID) {
throw ('Unimplemented');
var oldCreator = _itemBeingEdited.getCreator(index);
if (creator) {
oldCreator = creator.ref;
var oldCreatorID = oldCreator.creatorID;
}
Zotero.debug("Old creatorID is " + oldCreatorID);
_itemBeingEdited.setCreator(index, firstName, lastName, typeID, fieldMode);
_itemBeingEdited.save();
}
*/
-->
<method name="focusFirstField">
<body>
<![CDATA[
this._focusNextField(this._dynamicFields, 0, false);
]]>
</body>
</method>
<!--
Advance the field focus forward or backward
Note: We're basically replicating the built-in tabindex functionality,
which doesn't work well with the weird label/textbox stuff we're doing.
(The textbox being tabbed away from is deleted before the blur()
completes, so it doesn't know where it's supposed to go next.)
-->
<method name="_focusNextField">
<parameter name="box"/>
<parameter name="tabindex"/>
<parameter name="back"/>
<body>
<![CDATA[
tabindex = parseInt(tabindex);
if (back)
{
switch (tabindex)
{
case 1:
//Zotero.debug('At beginning');
document.getElementById('item-type-menu').focus();
return false;
case this._tabIndexMinCreators:
var nextIndex = 1; // Title field
break;
case this._tabIndexMinFields:
// No creators
if (this._tabIndexMaxCreators == 0) {
var nextIndex = 1; // Title field
}
else {
var nextIndex = this._tabIndexMaxCreators;
}
break;
default:
var nextIndex = tabindex - 1;
}
}
else
{
switch (tabindex)
{
case 1:
var nextIndex = this._tabIndexMinCreators;
break;
case this._tabIndexMaxCreators:
var nextIndex = this._tabIndexMinFields;
break;
case this._tabIndexMaxFields:
//Zotero.debug('At end');
return false;
default:
var nextIndex = tabindex + 1;
}
}
Zotero.debug('Looking for tabindex ' + nextIndex, 4);
var next = box.getElementsByAttribute('ztabindex', nextIndex);
if (!next[0])
{
//Zotero.debug("Next field not found");
return this._focusNextField(box, nextIndex, back);
}
next[0].click();
// DEBUG: next[0] is always equal to the target element,
// but for some reason it's necessary to scroll to the next
// element when moving forward for the target element to
// be fully in view
if (!back && next[0].parentNode.nextSibling) {
var visElem = next[0].parentNode.nextSibling;
}
else {
var visElem = next[0];
}
this.ensureElementIsVisible(visElem);
return true;
]]>
</body>
</method>
<method name="blurOpenField">
<body>
<![CDATA[
var textboxes = document.getAnonymousNodes(this)[0].getElementsByTagName('textbox');
if (textboxes && textboxes.length) {
textboxes[0].inputField.blur();
}
]]>
</body>
</method>
<!--
Available handlers:
- 'itemtypechange'
Note: 'this' in the function will be bound to the item box.
-->
<method name="addHandler">
<parameter name="eventName"/>
<parameter name="func"/>
<body>
<![CDATA[
if (!this.eventHandlers[eventName]) {
this.eventHandlers[eventName] = [];
}
this.eventHandlers[eventName].push(func);
]]>
</body>
</method>
<method name="removeHandler">
<parameter name="eventName"/>
<parameter name="func"/>
<body>
<![CDATA[
if (!this.eventHandlers[eventName]) {
return;
}
var pos = this.eventHandlers[eventName].indexOf(func);
if (pos != -1) {
this.eventHandlers[eventName].splice(pos, 1);
}
]]>
</body>
</method>
<method name="_id">
<parameter name="id"/>
<body>
<![CDATA[
return document.getAnonymousNodes(this)[0].getElementsByAttribute('id', id)[0];
]]>
</body>
</method>
</implementation>
<content>
<scrollbox id="item-box" flex="1" orient="vertical"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<popupset>
<menupopup id="creator-type-menu" position="after_start"
onpopupshowing="var typeBox = document.popupNode.localName == 'hbox' ? document.popupNode : document.popupNode.parentNode;
var index = parseInt(typeBox.getAttribute('fieldname').split('-')[1]);
var item = document.getBindingParent(this).item;
var exists = item.hasCreatorAt(index);
var moreCreators = item.numCreators() > index + 1;
var hideMoveUp = !exists || index == 0;
var hideMoveDown = !exists || !moreCreators;
var hideMoveSep = hideMoveUp &amp;&amp; hideMoveDown;
document.getElementById('zotero-creator-move-sep').setAttribute('hidden', hideMoveSep);
document.getElementById('zotero-creator-move-up').setAttribute('hidden', hideMoveUp);
document.getElementById('zotero-creator-move-down').setAttribute('hidden', hideMoveDown);"
oncommand="var typeBox = document.popupNode.localName == 'hbox' ? document.popupNode : document.popupNode.parentNode;
var index = parseInt(typeBox.getAttribute('fieldname').split('-')[1]);
var itemBox = document.getBindingParent(this);
if (event.explicitOriginalTarget.className == 'zotero-creator-move') {
var up = event.explicitOriginalTarget.id == 'zotero-creator-move-up';
itemBox.moveCreator(index, up);
return;
}
var typeID = event.explicitOriginalTarget.getAttribute('typeid');
var row = typeBox.parentNode;
var fields = itemBox.getCreatorFields(row);
fields.creatorTypeID = typeID;
typeBox.getElementsByTagName('label')[0].setAttribute(
'value',
Zotero.getString(
'creatorTypes.' + Zotero.CreatorTypes.getName(typeID)
) + ':'
);
typeBox.setAttribute('typeid', typeID);
/* If a creator textbox is already open, we need to
change its autocomplete parameters so that it
completes on a creator with a different creator type */
var changedParams = {
creatorTypeID: typeID
};
itemBox._updateAutoCompleteParams(row, changedParams);
itemBox.modifyCreator(index, fields);"/>
<menupopup id="zotero-field-transform-menu">
<menu label="&zotero.item.textTransform;">
<menupopup>
<menuitem label="&zotero.item.textTransform.titlecase;" class="menuitem-non-iconic"
oncommand="document.getBindingParent(this).textTransform(document.popupNode, 'title')"/>
<menuitem label="&zotero.item.textTransform.sentencecase;" class="menuitem-non-iconic"
oncommand="document.getBindingParent(this).textTransform(document.popupNode, 'sentence')"/>
</menupopup>
</menu>
</menupopup>
<menupopup id="zotero-creator-transform-menu"
onpopupshowing="var row = Zotero.getAncestorByTagName(document.popupNode, 'row');
var typeBox = row.getElementsByAttribute('popup', 'creator-type-menu')[0];
var index = parseInt(typeBox.getAttribute('fieldname').split('-')[1]);
var item = document.getBindingParent(this).item;
var exists = item.hasCreatorAt(index);
if (exists) {
var fieldMode = item.getCreator(index).ref.fieldMode;
}
var hideTransforms = !exists || !!fieldMode;
return !hideTransforms;">
<menuitem label="&zotero.item.creatorTransform.nameSwap;"
oncommand="document.getBindingParent(this).swapNames();"/>
</menupopup>
<menupopup id="zotero-doi-menu">
<menuitem id="zotero-doi-menu-view-online" label="&zotero.item.viewOnline;"/>
<menuitem id="zotero-doi-menu-copy" label="&zotero.item.copyAsURL;"/>
</menupopup>
<zoteroguidancepanel id="zotero-author-guidance" about="authorMenu" position="after_end" x="-25"/>
</popupset>
<grid flex="1">
<columns>
<column/>
<column flex="1"/>
</columns>
<rows id="dynamic-fields" flex="1">
<row class="zotero-item-first-row">
<label value="&zotero.items.itemType;:"/>
<menulist class="zotero-clicky" id="item-type-menu" oncommand="document.getBindingParent(this).changeTypeTo(this.value, this)" flex="1"
onfocus="document.getBindingParent(this).ensureElementIsVisible(this)"
onkeypress="if (event.keyCode == event.DOM_VK_TAB) { document.getBindingParent(this).itemTypeMenuTab(event); }">
<menupopup/>
</menulist>
</row>
</rows>
</grid>
</scrollbox>
</content>
</binding>
</bindings>