Optimistic updates for item tags box

Add/update/remove rows immediately and save after. If there's an error
during saving, reload the pane.
This commit is contained in:
Dan Stillman 2017-07-18 17:09:40 -04:00
parent ef7da3486a
commit df353bdc05

View File

@ -154,9 +154,10 @@
} }
let data = extraData[ids[i]]; let data = extraData[ids[i]];
let tagName = data.tag; let tagName = data.tag;
let tagType = data.type;
if (event == 'add') { if (event == 'add') {
var newTabIndex = this.add(tagName); var newTabIndex = this.add(tagName, tagType);
if (newTabIndex == -1) { if (newTabIndex == -1) {
return; return;
} }
@ -174,7 +175,7 @@
else if (event == 'modify') { else if (event == 'modify') {
let oldTagName = data.old.tag; let oldTagName = data.old.tag;
this.remove(oldTagName); this.remove(oldTagName);
this.add(tagName); this.add(tagName, tagType);
} }
else if (event == 'remove') { else if (event == 'remove') {
var oldTabIndex = this.remove(tagName); var oldTabIndex = this.remove(tagName);
@ -325,15 +326,25 @@
// "-" button // "-" button
if (this.editable) { if (this.editable) {
remove.setAttribute('disabled', false); remove.setAttribute('disabled', false);
var self = this; remove.addEventListener('click', function (event) {
remove.addEventListener('click', function () {
Zotero.spawn(function* () { Zotero.spawn(function* () {
self._lastTabIndex = false; this._lastTabIndex = false;
if (tagData) { if (tagData) {
let item = document.getBindingParent(this).item let item = this.item;
this.remove(tagName);
try {
item.removeTag(tagName); item.removeTag(tagName);
yield item.saveTx() yield item.saveTx()
} }
catch (e) {
this.reload();
throw e;
}
}
// Remove empty textbox row
else {
row.parentNode.removeChild(row);
}
// Return focus to items pane // Return focus to items pane
var tree = document.getElementById('zotero-items-tree'); var tree = document.getElementById('zotero-items-tree');
@ -341,7 +352,7 @@
tree.focus(); tree.focus();
} }
}.bind(this)); }.bind(this));
}); }.bind(this));
} }
]]></body> ]]></body>
</method> </method>
@ -447,7 +458,7 @@
var box = elem.parentNode; var box = elem.parentNode;
box.replaceChild(t, elem); box.replaceChild(t, elem);
t.setAttribute('onblur', "return document.getBindingParent(this).blurHandler(this)"); t.setAttribute('onblur', "return document.getBindingParent(this).blurHandler(event)");
t.setAttribute('onkeypress', "return document.getBindingParent(this).handleKeyPress(event)"); t.setAttribute('onkeypress', "return document.getBindingParent(this).handleKeyPress(event)");
t.setAttribute('onpaste', "return document.getBindingParent(this).handlePaste(event)"); t.setAttribute('onpaste', "return document.getBindingParent(this).handlePaste(event)");
@ -498,11 +509,12 @@
var fieldname = 'tag'; var fieldname = 'tag';
var row = Zotero.getAncestorByTagName(target, 'row'); var row = Zotero.getAncestorByTagName(target, 'row');
let blurOnly = false;
// If non-empty last row, add new row // If non-empty last row, only blur, because the open textbox will
// be cleared in hideEditor() and remain in place
if (row == row.parentNode.lastChild && !empty) { if (row == row.parentNode.lastChild && !empty) {
var focusField = true; blurOnly = true;
this._tabDirection = 1;
} }
// If empty non-last row, refocus current row // If empty non-last row, refocus current row
else if (row != row.parentNode.lastChild && empty) { else if (row != row.parentNode.lastChild && empty) {
@ -514,9 +526,11 @@
this._lastTabIndex = false; this._lastTabIndex = false;
} }
target.onblur = null; yield this.blurHandler(event);
yield this.blurHandler(target);
if (blurOnly) {
return false;
}
if (focusField) { if (focusField) {
this._focusField(); this._focusField();
} }
@ -537,8 +551,7 @@
var tagsbox = Zotero.getAncestorByTagName(focused, 'tagsbox'); var tagsbox = Zotero.getAncestorByTagName(focused, 'tagsbox');
this._lastTabIndex = false; this._lastTabIndex = false;
target.onblur = null; yield this.blurHandler(event);
yield this.blurHandler(target);
if (tagsbox) { if (tagsbox) {
tagsbox.closePopup(); tagsbox.closePopup();
@ -562,8 +575,7 @@
} }
this._tabDirection = event.shiftKey ? -1 : 1; this._tabDirection = event.shiftKey ? -1 : 1;
target.onblur = null; yield this.blurHandler(event);
yield this.blurHandler(target);
this._focusField(); this._focusField();
return false; return false;
} }
@ -633,8 +645,10 @@
<method name="hideEditor"> <method name="hideEditor">
<parameter name="textbox"/> <parameter name="event"/>
<body><![CDATA[ <body><![CDATA[
var textbox = event.target;
return Zotero.spawn(function* () { return Zotero.spawn(function* () {
Zotero.debug('Hiding editor'); Zotero.debug('Hiding editor');
@ -675,15 +689,36 @@
if (value !== "") { if (value !== "") {
if (oldValue !== value) { if (oldValue !== value) {
// The existing textbox will be removed in notify() // The existing textbox will be removed in notify()
this.removeRow(row);
this.add(value);
if (event.type != 'blur') {
this._focusField();
}
try {
this.item.replaceTag(oldValue, value); this.item.replaceTag(oldValue, value);
yield this.item.saveTx(); yield this.item.saveTx();
} }
catch (e) {
this.reload();
throw e;
}
}
} }
// Existing tag cleared // Existing tag cleared
else { else {
try {
this.removeRow(row);
if (event.type != 'blur') {
this._focusField();
}
this.item.removeTag(oldValue); this.item.removeTag(oldValue);
yield this.item.saveTx(); yield this.item.saveTx();
} }
catch (e) {
this.reload();
throw e;
}
}
} }
// Multiple tags // Multiple tags
else if (tags.length > 1) { else if (tags.length > 1) {
@ -714,12 +749,22 @@
} }
// Single tag at end // Single tag at end
else { else {
// Remove the textbox row. The new tag will be added in notify() if (event.type == 'blur') {
// if it doesn't already exist. this.removeRow(row);
row.parentNode.removeChild(row); }
else {
textbox.value = '';
}
this.add(value);
this.item.addTag(value); this.item.addTag(value);
try {
yield this.item.saveTx(); yield this.item.saveTx();
} }
catch (e) {
this.reload();
throw e;
}
}
}.bind(this)); }.bind(this));
]]></body> ]]></body>
</method> </method>
@ -732,7 +777,7 @@
var rows = rowsElement.childNodes; var rows = rowsElement.childNodes;
// Don't add new row if there already is one // Don't add new row if there already is one
if (rows.length > this.count) { if (rows.length && rows[rows.length - 1].querySelector('textbox')) {
return; return;
} }
@ -758,6 +803,7 @@
<method name="add"> <method name="add">
<parameter name="tagName"/> <parameter name="tagName"/>
<parameter name="tagType"/>
<body><![CDATA[ <body><![CDATA[
var rowsElement = this.id('tagRows'); var rowsElement = this.id('tagRows');
var rows = rowsElement.childNodes; var rows = rowsElement.childNodes;
@ -772,7 +818,7 @@
var tagData = { var tagData = {
tag: tagName, tag: tagName,
type: this.item.getTagType(tagName) type: tagType
}; };
if (row) { if (row) {
@ -810,7 +856,9 @@
continue; continue;
} }
if (collation.compareString(1, tagName, labels[i].textContent) > 0) { if (collation.compareString(1, tagName, labels[i].textContent) > 0
// Ignore textbox at end
&& labels[i].tagName != 'textbox') {
labels[i].setAttribute('ztabindex', index); labels[i].setAttribute('ztabindex', index);
continue; continue;
} }
@ -826,6 +874,8 @@
rowsElement.appendChild(row); rowsElement.appendChild(row);
} }
this.updateCount(this.count + 1);
return newTabIndex; return newTabIndex;
]]></body> ]]></body>
</method> </method>
@ -841,16 +891,8 @@
for (var i=0; i<rows.length; i++) { for (var i=0; i<rows.length; i++) {
let value = rows[i].getAttribute('tagName'); let value = rows[i].getAttribute('tagName');
if (value === tagName) { if (value === tagName) {
oldTabIndex = i + 1; oldTabIndex = this.removeRow(rows[i]);
removed = true; break;
rowsElement.removeChild(rows[i]);
i--;
continue;
}
// After the removal, update tab indexes
if (removed) {
var elem = rows[i].getElementsByAttribute('fieldname', 'tag')[0];
elem.setAttribute('ztabindex', i + 1);
} }
} }
return oldTabIndex; return oldTabIndex;
@ -858,6 +900,27 @@
</method> </method>
<!--
Remove the row and update tab indexes
-->
<method name="removeRow">
<parameter name="row"/>
<body><![CDATA[
var origTabIndex = row.getElementsByAttribute('fieldname', 'tag')[0]
.getAttribute('ztabindex');
var origRow = row;
var i = origTabIndex;
while (row = row.nextSibling) {
let elem = row.getElementsByAttribute('fieldname', 'tag')[0];
elem.setAttribute('ztabindex', i++);
}
origRow.parentNode.removeChild(origRow);
this.updateCount(this.count - 1);
return origTabIndex;
]]></body>
</method>
<method name="removeAll"> <method name="removeAll">
<body><![CDATA[ <body><![CDATA[
if (Services.prompt.confirm(null, "", Zotero.getString('pane.item.tags.removeAll'))) { if (Services.prompt.confirm(null, "", Zotero.getString('pane.item.tags.removeAll'))) {
@ -878,11 +941,13 @@
if(typeof count == 'undefined') { if(typeof count == 'undefined') {
var tags = this.item.getTags(); var tags = this.item.getTags();
if(tags) if (tags) {
count = tags.length; count = tags.length;
else }
else {
count = 0; count = 0;
} }
}
var str = 'pane.item.tags.count.'; var str = 'pane.item.tags.count.';
switch (count){ switch (count){
@ -993,6 +1058,16 @@
]]></body> ]]></body>
</method> </method>
<method name="_onAddButtonPress">
<parameter name="event"/>
<body><![CDATA[
return async function () {
await this.blurOpenField();
this.newTag();
}.bind(this)();
]]></body>
</method>
<method name="_onBackgroundContextMenuShowing"> <method name="_onBackgroundContextMenuShowing">
<body><![CDATA[ <body><![CDATA[
@ -1044,14 +1119,20 @@
<method name="blurOpenField"> <method name="blurOpenField">
<parameter name="stayOpen"/>
<body><![CDATA[ <body><![CDATA[
return Zotero.spawn(function* () { return Zotero.spawn(function* () {
this._lastTabIndex = false; this._lastTabIndex = false;
var textboxes = document.getAnonymousNodes(this)[0].getElementsByTagName('textbox'); var textboxes = document.getAnonymousNodes(this)[0].getElementsByTagName('textbox');
if (textboxes && textboxes.length) { if (textboxes && textboxes.length) {
textboxes[0].inputField.onblur = null; yield this.blurHandler({
yield this.blurHandler(textboxes[0].inputField); target: textboxes[0],
// If coming from the Add button, pretend user pressed return
type: stayOpen ? 'keypress' : 'blur',
// DOM_VK_RETURN
keyCode: stayOpen ? 13 : undefined
});
} }
}.bind(this)); }.bind(this));
]]> ]]>
@ -1082,7 +1163,7 @@
<xul:label id="tagsNum"/> <xul:label id="tagsNum"/>
<xul:button id="addButton" label="&zotero.item.add;" <xul:button id="addButton" label="&zotero.item.add;"
onkeypress="return document.getBindingParent(this)._onAddButtonKeypress(event)" onkeypress="return document.getBindingParent(this)._onAddButtonKeypress(event)"
oncommand="document.getBindingParent(this).newTag();"/> oncommand="return document.getBindingParent(this)._onAddButtonPress(event)"/>
</xul:hbox> </xul:hbox>
<xul:grid> <xul:grid>
<xul:columns> <xul:columns>