mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
90 lines
3.3 KiB
JavaScript
90 lines
3.3 KiB
JavaScript
|
/* global $ */
|
||
|
var ko = require('knockout');
|
||
|
var kf = require('./koForm');
|
||
|
var dom = require('./dom');
|
||
|
var kd = require('./koDom');
|
||
|
|
||
|
/**
|
||
|
* Creates a multi-select implemented with a draggable list of selected items followed by
|
||
|
* an autocomplete input containing the remaining selectable items.
|
||
|
*
|
||
|
* Items in `selected` list can be arbitrary objects, and get passed to remove()/reorder().
|
||
|
* Items for auto-complete should have 'value' and 'label' properties, and are passed to add().
|
||
|
*
|
||
|
* @param {Function} source(request, response):
|
||
|
* Called with the autocomplete request, containing .term with the search term entered so far.
|
||
|
* The response callback must be called with a list of suggested items (with 'value' and
|
||
|
* 'label' properties). The selected item is passed to add(). The caller should filter out
|
||
|
* items already selected if appropriate.
|
||
|
* @param {koArray} selected:
|
||
|
* KoArray of selected items.
|
||
|
* @param {Function} itemCreateFunc:
|
||
|
* Called as `itemCreateFunc(item)` for each element of the `selected` array. Should return a
|
||
|
* single Node, or null or undefined to omit that node.
|
||
|
* @param {Function} options.add(autoCompleteItem):
|
||
|
* Called to add a new item.
|
||
|
* @param {Function} options.remove(item):
|
||
|
* Called to remove a selected item.
|
||
|
* @param {Function} options.reorder(item, nextItem):
|
||
|
* Optional. Called to move item to just before nextItem (or to the end when nextItem is null).
|
||
|
* If omitted, items are not draggable. The callback must update the 'selected' array to
|
||
|
* match the UI. See koForm.draggableList for more details.
|
||
|
* @param {String} options.hint:
|
||
|
* Optional. Text to display above the input if nothing is selected.
|
||
|
*/
|
||
|
function multiselect(source, selected, itemCreateFunc, options) {
|
||
|
options = options || {};
|
||
|
var noneSelected = ko.computed(() => selected.all().length === 0);
|
||
|
var selector;
|
||
|
var input;
|
||
|
|
||
|
// Calls add on the item, closes the autocomplete and clears the input.
|
||
|
function selectItem(item) {
|
||
|
options.add(item);
|
||
|
$(input).autocomplete("close");
|
||
|
input.value = '';
|
||
|
}
|
||
|
|
||
|
// Searches for the item by label in the source and selects the first match.
|
||
|
function searchItem(searchTerm) {
|
||
|
source({ term: searchTerm }, resp => {
|
||
|
var item = resp.find(respItem => respItem.label === searchTerm);
|
||
|
if (item) { selectItem(item); }
|
||
|
});
|
||
|
}
|
||
|
|
||
|
// Main selector dom with draggable list.
|
||
|
selector = dom('div.multiselect',
|
||
|
dom.autoDispose(noneSelected),
|
||
|
dom('div.multiselect-selected',
|
||
|
kf.draggableList(selected, item => itemCreateFunc(item), {
|
||
|
drag_indicator: Boolean(options.reorder),
|
||
|
removeButton: true,
|
||
|
reorder: options.reorder,
|
||
|
remove: options.remove
|
||
|
}),
|
||
|
kd.toggleClass('multiselect-empty', noneSelected),
|
||
|
kd.maybe(noneSelected, () => dom('div.multiselect-hint', options.hint || ""))
|
||
|
),
|
||
|
input = dom('input.multiselect-input',
|
||
|
dom.on('focus', () => { $(input).autocomplete("search"); }),
|
||
|
dom.on('change', () => { searchItem(input.value); })
|
||
|
)
|
||
|
);
|
||
|
|
||
|
// Set up the auto-complete widget.
|
||
|
$(input).autocomplete({
|
||
|
source: source,
|
||
|
minLength: 0,
|
||
|
delay: 10,
|
||
|
focus: () => false, // Keeps input empty on focus
|
||
|
select: function(event, ui) {
|
||
|
selectItem(ui.item);
|
||
|
return false;
|
||
|
}
|
||
|
});
|
||
|
|
||
|
return selector;
|
||
|
}
|
||
|
module.exports = multiselect;
|