mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
5b2666a88a
Summary: Choice columns can now add new choices directly from the autocomplete menu. The autocomplete will now highlight the first matching item, even if there are equally ranked alternatives. No changes have been made to how the autocomplete index is created, or how it scores items. For choice and choice list columns, the filter menu will now display values using their configured colors, similar to the rest of the UI. Choice tokens throughout the UI now do a better job of handling text overflow by showing an ellipsis whenever there isn't enough space to show the full text of a choice. Test Plan: Browser tests. Reviewers: cyprien Reviewed By: cyprien Differential Revision: https://phab.getgrist.com/D2904
111 lines
3.9 KiB
JavaScript
111 lines
3.9 KiB
JavaScript
var _ = require('underscore');
|
|
var dispose = require('app/client/lib/dispose');
|
|
var TextEditor = require('app/client/widgets/TextEditor');
|
|
|
|
const {Autocomplete} = require('app/client/lib/autocomplete');
|
|
const {ACIndexImpl, buildHighlightedDom} = require('app/client/lib/ACIndex');
|
|
const {ChoiceItem, cssChoiceList, cssMatchText, cssPlusButton,
|
|
cssPlusIcon} = require('app/client/widgets/ChoiceListEditor');
|
|
const {menuCssClass} = require('app/client/ui2018/menus');
|
|
const {testId} = require('app/client/ui2018/cssVars');
|
|
const {choiceToken, cssChoiceACItem} = require('app/client/widgets/ChoiceToken');
|
|
const {dom} = require('grainjs');
|
|
|
|
/**
|
|
* ChoiceEditor - TextEditor with a dropdown for possible choices.
|
|
*/
|
|
function ChoiceEditor(options) {
|
|
TextEditor.call(this, options);
|
|
|
|
this.choices = options.field.widgetOptionsJson.peek().choices || [];
|
|
this.choiceOptions = options.field.widgetOptionsJson.peek().choiceOptions || {};
|
|
|
|
// Whether to include a button to show a new choice.
|
|
// TODO: Disable when the user cannot change column configuration.
|
|
this.enableAddNew = true;
|
|
}
|
|
|
|
dispose.makeDisposable(ChoiceEditor);
|
|
_.extend(ChoiceEditor.prototype, TextEditor.prototype);
|
|
|
|
ChoiceEditor.prototype.getCellValue = function() {
|
|
const selectedItem = this.autocomplete && this.autocomplete.getSelectedItem();
|
|
return selectedItem ? selectedItem.label : TextEditor.prototype.getCellValue.call(this);
|
|
}
|
|
|
|
ChoiceEditor.prototype.renderACItem = function(item, highlightFunc) {
|
|
const options = this.choiceOptions[item.label];
|
|
|
|
return cssChoiceACItem(
|
|
(item.isNew ?
|
|
[cssChoiceACItem.cls('-new'), cssPlusButton(cssPlusIcon('Plus')), testId('choice-editor-new-item')] :
|
|
[cssChoiceACItem.cls('-with-new', this.showAddNew)]
|
|
),
|
|
choiceToken(
|
|
buildHighlightedDom(item.label, highlightFunc, cssMatchText),
|
|
options || {},
|
|
dom.style('max-width', '100%'),
|
|
testId('choice-editor-item-label')
|
|
),
|
|
testId('choice-editor-item'),
|
|
);
|
|
}
|
|
|
|
ChoiceEditor.prototype.attach = function(cellElem) {
|
|
TextEditor.prototype.attach.call(this, cellElem);
|
|
// Don't create autocomplete if readonly.
|
|
if (this.options.readonly) { return; }
|
|
|
|
const acItems = this.choices.map(c => new ChoiceItem(c, false));
|
|
const acIndex = new ACIndexImpl(acItems);
|
|
const acOptions = {
|
|
popperOptions: {
|
|
placement: 'bottom'
|
|
},
|
|
menuCssClass: `${menuCssClass} ${cssChoiceList.className} test-autocomplete`,
|
|
search: (term) => this.maybeShowAddNew(acIndex.search(term), term),
|
|
renderItem: (item, highlightFunc) => this.renderACItem(item, highlightFunc),
|
|
getItemText: (item) => item.label,
|
|
onClick: () => this.options.commands.fieldEditSave(),
|
|
};
|
|
|
|
this.autocomplete = Autocomplete.create(this, this.textInput, acOptions);
|
|
}
|
|
|
|
/**
|
|
* Updates list of valid choices with any new ones that may have been
|
|
* added from directly inside the editor (via the "add new" item in autocomplete).
|
|
*/
|
|
ChoiceEditor.prototype.prepForSave = async function() {
|
|
const selectedItem = this.autocomplete && this.autocomplete.getSelectedItem();
|
|
if (selectedItem && selectedItem.isNew) {
|
|
const choices = this.options.field.widgetOptionsJson.prop('choices');
|
|
await choices.saveOnly([...choices.peek(), selectedItem.label]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* If the search text does not match anything exactly, adds 'new' item to it.
|
|
*
|
|
* Also see: prepForSave.
|
|
*/
|
|
ChoiceEditor.prototype.maybeShowAddNew = function(result, text) {
|
|
// TODO: This logic is also mostly duplicated in ChoiceListEditor and ReferenceEditor.
|
|
// See if there's anything common we can factor out and re-use.
|
|
this.showAddNew = false;
|
|
const trimmedText = text.trim();
|
|
if (!this.enableAddNew || !trimmedText) { return result; }
|
|
|
|
const addNewItem = new ChoiceItem(trimmedText, false, true);
|
|
if (result.items.find((item) => item.cleanText === addNewItem.cleanText)) {
|
|
return result;
|
|
}
|
|
|
|
result.items.push(addNewItem);
|
|
this.showAddNew = true;
|
|
|
|
return result;
|
|
}
|
|
|
|
module.exports = ChoiceEditor;
|