Refactor itemBox.js create element

This commit is contained in:
windingwind 2024-07-16 15:49:52 +08:00
parent b3aa0fe5ec
commit e4ad1862d1

View file

@ -609,15 +609,15 @@
rowLabel.className = "meta-label"; rowLabel.className = "meta-label";
rowLabel.setAttribute('fieldname', fieldName); rowLabel.setAttribute('fieldname', fieldName);
let valueElement = this.createValueElement( let valueElement = this.createFieldValueElement(
val, fieldName val, fieldName
); );
if (fieldName) { if (fieldName) {
let label = document.createElement('label'); let label = this.createLabelElement({
label.className = 'key'; text: Zotero.ItemFields.getLocalizedString(fieldName),
label.textContent = Zotero.ItemFields.getLocalizedString(fieldName); id: `itembox-field-${fieldName}-label`,
label.setAttribute("id", `itembox-field-${fieldName}-label`); });
rowLabel.appendChild(label); rowLabel.appendChild(label);
valueElement.setAttribute('aria-labelledby', label.id); valueElement.setAttribute('aria-labelledby', label.id);
} }
@ -853,35 +853,6 @@
}); });
this._showCreatorTypeGuidance = false; this._showCreatorTypeGuidance = false;
} }
// On click of the label, toggle the focus of the value field
for (let label of this.querySelectorAll(".meta-label > label")) {
if (!this.editable) {
break;
}
label.addEventListener('mousedown', (event) => {
// Prevent default focus/blur behavior - we implement our own below
event.preventDefault();
});
label.addEventListener('click', (event) => {
event.preventDefault();
let labelWrapper = label.closest(".meta-label");
if (labelWrapper.nextSibling.contains(document.activeElement)) {
document.activeElement.blur();
}
else {
let valueField = labelWrapper.nextSibling.firstChild;
if (valueField.id === "item-type-menu") {
valueField.querySelector("menupopup").openPopup();
return;
}
labelWrapper.nextSibling.firstChild.focus();
}
});
}
this._ensureButtonsFocusable(); this._ensureButtonsFocusable();
this._updateCreatorButtonsStatus(); this._updateCreatorButtonsStatus();
@ -925,89 +896,30 @@
let labelElem = document.createElement("div"); let labelElem = document.createElement("div");
labelElem.classList.add("meta-label"); labelElem.classList.add("meta-label");
let label = document.createElement("label");
let labelID = `itembox-custom-row-${row.rowID}-label`; let labelID = `itembox-custom-row-${row.rowID}-label`;
label.id = labelID; let label = this.createLabelElement({
label.classList.add("key"); id: labelID,
text: row.label.text,
});
label.dataset.l10nId = row.label.l10nID; label.dataset.l10nId = row.label.l10nID;
label.addEventListener('mousedown', (event) => {
// Prevent default focus/blur behavior - we implement our own below
event.preventDefault();
});
label.addEventListener('click', (event) => {
event.preventDefault();
let labelWrapper = label.closest(".meta-label");
if (labelWrapper.nextSibling.contains(document.activeElement)) {
document.activeElement.blur();
}
else {
labelWrapper.nextSibling.firstChild.focus();
}
});
labelElem.appendChild(label); labelElem.appendChild(label);
let dataElem = document.createElement("div"); let dataElem = document.createElement("div");
dataElem.classList.add("meta-data"); dataElem.classList.add("meta-data");
let valueElem = document.createXULElement("editable-text"); let editable = row.editable ?? true;
valueElem.id = `itembox-custom-row-${row.rowID}-value`; if (!this.editable) editable = false;
valueElem.classList.add("value", "custom-row-value"); let valueElem = this.createValueElement({
id: `itembox-custom-row-${row.rowID}-value`,
if (row.multiline) { classList: ["custom-row-value"],
valueElem.setAttribute('multiline', true); isMultiline: row.multiline,
} isLong: !row.nowrap,
else if (row.nowrap) { editable,
// Usual fields occupy all available space and keep info on one line attributes: {
valueElem.setAttribute("nowrap", true); "aria-labelledby": labelID
}
if (typeof row.editable == "boolean") {
let editable = row.editable;
if (!this.editable) editable = false;
valueElem.toggleAttribute("readonly", !editable);
}
valueElem.addEventListener("focus", () => {
if (row.multiline) {
valueElem.setAttribute("min-lines", 6);
} }
}); });
valueElem.addEventListener("blur", () => {
if (row.multiline) {
valueElem.setAttribute("min-lines", 1);
}
let onSetData = Zotero.ItemPaneManager.getInfoRowHook(row.rowID, "onSetData");
if (onSetData) {
try {
onSetData({
rowID: row.rowID,
item: this.item,
tabType: this.tabType,
editable: this.editable,
value: valueElem.value
});
}
catch (e) {
Zotero.logError(e);
}
}
});
valueElem.setAttribute('tight', true);
// Regardless, align the text in the label consistently, following the locale's direction
if (Zotero.rtl) {
valueElem.style.textAlign = 'right';
}
else {
valueElem.style.textAlign = 'left';
}
valueElem.setAttribute("aria-labelledby", labelID);
dataElem.appendChild(valueElem); dataElem.appendChild(valueElem);
@ -1142,10 +1054,10 @@
var labelWrapper = document.createElement('div'); var labelWrapper = document.createElement('div');
labelWrapper.className = "meta-label"; labelWrapper.className = "meta-label";
labelWrapper.setAttribute("fieldname", "itemType"); labelWrapper.setAttribute("fieldname", "itemType");
var label = document.createElement("label"); var label = this.createLabelElement({
label.className = "key"; id: "itembox-field-itemType-label",
label.id = "itembox-field-itemType-label"; text: Zotero.getString("zotero.items.itemType")
label.innerText = Zotero.getString("zotero.items.itemType"); });
labelWrapper.appendChild(label); labelWrapper.appendChild(label);
var rowData = document.createElement('div'); var rowData = document.createElement('div');
rowData.className = "meta-data"; rowData.className = "meta-data";
@ -1270,10 +1182,10 @@
} }
rowLabel.appendChild(labelWrapper); rowLabel.appendChild(labelWrapper);
var label = document.createElement("label"); let label = this.createLabelElement({
label.setAttribute('id', 'creator-type-label-inner'); id: 'creator-type-label-inner',
label.className = 'key'; text: Zotero.getString('creatorTypes.' + Zotero.CreatorTypes.getName(typeID))
label.textContent = Zotero.getString('creatorTypes.' + Zotero.CreatorTypes.getName(typeID)); });
labelWrapper.appendChild(label); labelWrapper.appendChild(label);
var rowData = document.createElement("div"); var rowData = document.createElement("div");
@ -1285,7 +1197,7 @@
var fieldName = 'creator-' + rowIndex + '-lastName'; var fieldName = 'creator-' + rowIndex + '-lastName';
var lastNameElem = firstlast.appendChild( var lastNameElem = firstlast.appendChild(
this.createValueElement( this.createFieldValueElement(
lastName, lastName,
fieldName, fieldName,
) )
@ -1294,7 +1206,7 @@
lastNameElem.placeholder = this._defaultLastName; lastNameElem.placeholder = this._defaultLastName;
fieldName = 'creator-' + rowIndex + '-firstName'; fieldName = 'creator-' + rowIndex + '-firstName';
var firstNameElem = firstlast.appendChild( var firstNameElem = firstlast.appendChild(
this.createValueElement( this.createFieldValueElement(
firstName, firstName,
fieldName, fieldName,
) )
@ -1471,16 +1383,16 @@
var rowLabel = document.createElement("div"); var rowLabel = document.createElement("div");
rowLabel.className = "meta-label"; rowLabel.className = "meta-label";
rowLabel.setAttribute("fieldname", field); rowLabel.setAttribute("fieldname", field);
var label = document.createElement('label'); let label = this.createLabelElement({
label.className = 'key'; text: Zotero.ItemFields.getLocalizedString(field),
label.textContent = Zotero.ItemFields.getLocalizedString(field); id: `itembox-field-${field}-label`
label.setAttribute("id", `itembox-field-${field}-label`); });
rowLabel.appendChild(label); rowLabel.appendChild(label);
var rowData = document.createElement('div'); var rowData = document.createElement('div');
rowData.className = "meta-data date-box"; rowData.className = "meta-data date-box";
var elem = this.createValueElement( var elem = this.createFieldValueElement(
Zotero.Date.multipartToStr(value), Zotero.Date.multipartToStr(value),
field field
); );
@ -1687,18 +1599,46 @@
return openLink; return openLink;
} }
createValueElement(valueText, fieldName) { createLabelElement({ text, id, attributes, classList }) {
valueText += ''; let label = document.createElement('label');
label.classList.add('key', ...classList || []);
if (fieldName) { if (text) label.textContent = text;
var fieldID = Zotero.ItemFields.getID(fieldName); if (id) label.id = id;
if (attributes) {
for (let [key, value] of Object.entries(attributes)) {
label.setAttribute(key, value);
}
} }
// On click of the label, toggle the focus of the value field
let isMultiline = Zotero.ItemFields.isMultiline(fieldName); if (this.editable) {
let isLong = Zotero.ItemFields.isLong(fieldName); label.addEventListener('mousedown', (event) => {
// Prevent default focus/blur behavior - we implement our own below
var valueElement = document.createXULElement("editable-text"); event.preventDefault();
valueElement.className = 'value'; });
label.addEventListener('click', (event) => {
event.preventDefault();
let labelWrapper = label.closest(".meta-label");
if (labelWrapper.nextSibling.contains(document.activeElement)) {
document.activeElement.blur();
}
else {
let valueField = labelWrapper.nextSibling.firstChild;
if (valueField.id === "item-type-menu") {
valueField.querySelector("menupopup").openPopup();
return;
}
labelWrapper.nextSibling.firstChild.focus();
}
});
}
return label;
}
createValueElement({ isMultiline, isLong, editable, text, tooltipText, id, attributes, classList } = {}) {
let valueElement = document.createXULElement("editable-text");
valueElement.classList.add('value', ...classList || []);
if (isMultiline) { if (isMultiline) {
valueElement.setAttribute('multiline', true); valueElement.setAttribute('multiline', true);
} }
@ -1706,9 +1646,7 @@
// Usual fields occupy all available space and keep info on one line // Usual fields occupy all available space and keep info on one line
valueElement.setAttribute("nowrap", true); valueElement.setAttribute("nowrap", true);
} }
if (editable) {
if (this._fieldIsClickable(fieldName)) {
valueElement.addEventListener("focus", e => this.showEditor(e.target)); valueElement.addEventListener("focus", e => this.showEditor(e.target));
valueElement.addEventListener("blur", e => this.hideEditor(e.target)); valueElement.addEventListener("blur", e => this.hideEditor(e.target));
} }
@ -1716,13 +1654,51 @@
valueElement.setAttribute('readonly', true); valueElement.setAttribute('readonly', true);
} }
valueElement.setAttribute('id', `itembox-field-value-${fieldName}`); if (id) valueElement.id = id;
valueElement.setAttribute('fieldname', fieldName); if (tooltipText) valueElement.tooltipText = tooltipText;
if (attributes) {
for (let [key, value] of Object.entries(attributes)) {
valueElement.setAttribute(key, value);
}
}
valueElement.setAttribute('tight', true); valueElement.setAttribute('tight', true);
valueElement.value = text;
if (text) {
valueElement.dir = 'auto';
}
// If not, assume it follows the locale's direction
else {
valueElement.dir = Zotero.dir;
}
// Regardless, align the text in the label consistently, following the locale's direction
if (Zotero.rtl) {
valueElement.style.textAlign = 'right';
}
else {
valueElement.style.textAlign = 'left';
}
return valueElement;
}
createFieldValueElement(valueText, fieldName) {
valueText += '';
if (fieldName) {
var fieldID = Zotero.ItemFields.getID(fieldName);
}
let isMultiline = Zotero.ItemFields.isMultiline(fieldName);
let isLong = Zotero.ItemFields.isLong(fieldName);
let attributes = {
fieldname: fieldName,
};
switch (fieldName) { switch (fieldName) {
case 'itemType': case 'itemType':
valueElement.setAttribute('itemTypeID', valueText); attributes.itemTypeID = valueText;
valueText = Zotero.ItemTypes.getLocalizedString(valueText); valueText = Zotero.ItemTypes.getLocalizedString(valueText);
break; break;
@ -1741,33 +1717,25 @@
break; break;
} }
let tooltipText;
if (fieldID) { if (fieldID) {
// Display the SQL date as a tooltip for date fields // Display the SQL date as a tooltip for date fields
// TEMP - filingDate // TEMP - filingDate
if (Zotero.ItemFields.isFieldOfBase(fieldID, 'date') || fieldName == 'filingDate') { if (Zotero.ItemFields.isFieldOfBase(fieldID, 'date') || fieldName == 'filingDate') {
valueElement.tooltipText = Zotero.Date.multipartToSQL(this.item.getField(fieldName, true)); tooltipText = Zotero.Date.multipartToSQL(this.item.getField(fieldName, true));
} }
} }
valueElement.value = valueText; let valueElement = this.createValueElement({
isMultiline,
isLong,
editable: this._fieldIsClickable(fieldName),
text: valueText,
tooltipText,
id: `itembox-field-value-${fieldName}`,
attributes
});
// Attempt to make bidi things work automatically:
// If we have text to work off of, let the layout engine try to guess the text direction
if (valueText) {
valueElement.dir = 'auto';
}
// If not, assume it follows the locale's direction
else {
valueElement.dir = Zotero.dir;
}
// Regardless, align the text in the label consistently, following the locale's direction
if (Zotero.rtl) {
valueElement.style.textAlign = 'right';
}
else {
valueElement.style.textAlign = 'left';
}
if (!fieldName.startsWith('creator-')) { if (!fieldName.startsWith('creator-')) {
// autocomplete for creator names is added in addCreatorRow // autocomplete for creator names is added in addCreatorRow
this.addAutocompleteToElement(valueElement); this.addAutocompleteToElement(valueElement);
@ -1836,13 +1804,25 @@
} }
async showEditor(elem) { async showEditor(elem) {
Zotero.debug(`Showing editor for ${elem.getAttribute('fieldname')}`); let isCustomRow = elem.classList.contains("custom-row-value");
var fieldName = elem.getAttribute('fieldname'); var fieldName = elem.getAttribute('fieldname');
let isMultiline = Zotero.ItemFields.isMultiline(fieldName);
if (isCustomRow) {
isMultiline = elem.hasAttribute("multiline");
}
// Multiline field will be at least 6 lines // Multiline field will be at least 6 lines
if (Zotero.ItemFields.isMultiline(fieldName)) { if (isMultiline) {
elem.setAttribute("min-lines", 6); elem.setAttribute("min-lines", 6);
} }
if (isCustomRow) {
return;
}
Zotero.debug(`Showing editor for ${fieldName}`);
var [field, creatorIndex, creatorField] = fieldName.split('-'); var [field, creatorIndex, creatorField] = fieldName.split('-');
let value; let value;
if (field == 'creator') { if (field == 'creator') {
@ -2099,20 +2079,54 @@
if (this.ignoreBlur || !textbox) { if (this.ignoreBlur || !textbox) {
return; return;
} }
var fieldName = textbox.getAttribute('fieldname');
let isMultiline = Zotero.ItemFields.isMultiline(fieldName);
let isCustomRow = textbox.classList.contains("custom-row-value");
if (isCustomRow) {
isMultiline = textbox.hasAttribute("multiline");
}
if (isMultiline) {
textbox.setAttribute("min-lines", 1);
}
if (isCustomRow) {
let rowID = textbox.closest(".meta-row").dataset.customRowId;
let onSetData = Zotero.ItemPaneManager.getInfoRowHook(rowID, "onSetData");
if (onSetData) {
try {
onSetData({
rowID,
item: this.item,
tabType: this.tabType,
editable: this.editable,
value: textbox.value
});
}
catch (e) {
Zotero.logError(e);
}
}
return;
}
// Handle cases where creator autocomplete doesn't trigger // Handle cases where creator autocomplete doesn't trigger
// the textentered and change events handled in showEditor // the textentered and change events handled in showEditor
if (textbox.getAttribute('fieldname').startsWith('creator-')) { if (fieldName.startsWith('creator-')) {
this.handleCreatorAutoCompleteSelect(textbox); this.handleCreatorAutoCompleteSelect(textbox);
} }
Zotero.debug(`Hiding editor for ${textbox.getAttribute('fieldname')}`); Zotero.debug(`Hiding editor for ${fieldName}`);
// Prevent autocomplete breakage in Firefox 3 // Prevent autocomplete breakage in Firefox 3
if (textbox.mController) { if (textbox.mController) {
textbox.mController.input = null; textbox.mController.input = null;
} }
var fieldName = textbox.getAttribute('fieldname');
// Multiline fields go back to occupying as much space as needed // Multiline fields go back to occupying as much space as needed
if (Zotero.ItemFields.isMultiline(fieldName)) { if (Zotero.ItemFields.isMultiline(fieldName)) {