ModeratorElection/frontend/generated/jar-resources/comboBoxConnector.js
2024-06-06 17:45:46 +02:00

284 lines
12 KiB
JavaScript

import { Debouncer } from '@polymer/polymer/lib/utils/debounce.js';
import { timeOut } from '@polymer/polymer/lib/utils/async.js';
import { ComboBoxPlaceholder } from '@vaadin/combo-box/src/vaadin-combo-box-placeholder.js';
(function () {
const tryCatchWrapper = function (callback) {
return window.Vaadin.Flow.tryCatchWrapper(callback, 'Vaadin Combo Box');
};
window.Vaadin.Flow.comboBoxConnector = {
initLazy: (comboBox) =>
tryCatchWrapper(function (comboBox) {
// Check whether the connector was already initialized for the ComboBox
if (comboBox.$connector) {
return;
}
comboBox.$connector = {};
// holds pageIndex -> callback pairs of subsequent indexes (current active range)
const pageCallbacks = {};
let cache = {};
let lastFilter = '';
const placeHolder = new window.Vaadin.ComboBoxPlaceholder();
const serverFacade = (() => {
// Private variables
let lastFilterSentToServer = '';
let dataCommunicatorResetNeeded = false;
// Public methods
const needsDataCommunicatorReset = () => (dataCommunicatorResetNeeded = true);
const getLastFilterSentToServer = () => lastFilterSentToServer;
const requestData = (startIndex, endIndex, params) => {
const count = endIndex - startIndex;
const filter = params.filter;
comboBox.$server.setRequestedRange(startIndex, count, filter);
lastFilterSentToServer = filter;
if (dataCommunicatorResetNeeded) {
comboBox.$server.resetDataCommunicator();
dataCommunicatorResetNeeded = false;
}
};
return {
needsDataCommunicatorReset,
getLastFilterSentToServer,
requestData
};
})();
const clearPageCallbacks = (pages = Object.keys(pageCallbacks)) => {
// Flush and empty the existing requests
pages.forEach((page) => {
pageCallbacks[page]([], comboBox.size);
delete pageCallbacks[page];
// Empty the comboBox's internal cache without invoking observers by filling
// the filteredItems array with placeholders (comboBox will request for data when it
// encounters a placeholder)
const pageStart = parseInt(page) * comboBox.pageSize;
const pageEnd = pageStart + comboBox.pageSize;
const end = Math.min(pageEnd, comboBox.filteredItems.length);
for (let i = pageStart; i < end; i++) {
comboBox.filteredItems[i] = placeHolder;
}
});
};
comboBox.dataProvider = function (params, callback) {
if (params.pageSize != comboBox.pageSize) {
throw 'Invalid pageSize';
}
if (comboBox._clientSideFilter) {
// For clientside filter we first make sure we have all data which we also
// filter based on comboBox.filter. While later we only filter clientside data.
if (cache[0]) {
performClientSideFilter(cache[0], params.filter, callback);
return;
} else {
// If client side filter is enabled then we need to first ask all data
// and filter it on client side, otherwise next time when user will
// input another filter, eg. continue to type, the local cache will be only
// what was received for the first filter, which may not be the whole
// data from server (keep in mind that client side filter is enabled only
// when the items count does not exceed one page).
params.filter = '';
}
}
const filterChanged = params.filter !== lastFilter;
if (filterChanged) {
cache = {};
lastFilter = params.filter;
this._filterDebouncer = Debouncer.debounce(this._filterDebouncer, timeOut.after(500), () => {
if (serverFacade.getLastFilterSentToServer() === params.filter) {
// Fixes the case when the filter changes
// to something else and back to the original value
// within debounce timeout, and the
// DataCommunicator thinks it doesn't need to send data
serverFacade.needsDataCommunicatorReset();
}
if (params.filter !== lastFilter) {
throw new Error("Expected params.filter to be '" + lastFilter + "' but was '" + params.filter + "'");
}
// Remove the debouncer before clearing page callbacks.
// This makes sure that they are executed.
this._filterDebouncer = undefined;
// Call the method again after debounce.
clearPageCallbacks();
comboBox.dataProvider(params, callback);
});
return;
}
// Postpone the execution of new callbacks if there is an active debouncer.
// They will be executed when the page callbacks are cleared within the debouncer.
if (this._filterDebouncer) {
pageCallbacks[params.page] = callback;
return;
}
if (cache[params.page]) {
// This may happen after skipping pages by scrolling fast
commitPage(params.page, callback);
} else {
pageCallbacks[params.page] = callback;
const maxRangeCount = Math.max(params.pageSize * 2, 500); // Max item count in active range
const activePages = Object.keys(pageCallbacks).map((page) => parseInt(page));
const rangeMin = Math.min(...activePages);
const rangeMax = Math.max(...activePages);
if (activePages.length * params.pageSize > maxRangeCount) {
if (params.page === rangeMin) {
clearPageCallbacks([String(rangeMax)]);
} else {
clearPageCallbacks([String(rangeMin)]);
}
comboBox.dataProvider(params, callback);
} else if (rangeMax - rangeMin + 1 !== activePages.length) {
// Wasn't a sequential page index, clear the cache so combo-box will request for new pages
clearPageCallbacks();
} else {
// The requested page was sequential, extend the requested range
const startIndex = params.pageSize * rangeMin;
const endIndex = params.pageSize * (rangeMax + 1);
serverFacade.requestData(startIndex, endIndex, params);
}
}
};
comboBox.$connector.clear = tryCatchWrapper((start, length) => {
const firstPageToClear = Math.floor(start / comboBox.pageSize);
const numberOfPagesToClear = Math.ceil(length / comboBox.pageSize);
for (let i = firstPageToClear; i < firstPageToClear + numberOfPagesToClear; i++) {
delete cache[i];
}
});
comboBox.$connector.filter = tryCatchWrapper(function (item, filter) {
filter = filter ? filter.toString().toLowerCase() : '';
return comboBox._getItemLabel(item, comboBox.itemLabelPath).toString().toLowerCase().indexOf(filter) > -1;
});
comboBox.$connector.set = tryCatchWrapper(function (index, items, filter) {
if (filter != serverFacade.getLastFilterSentToServer()) {
return;
}
if (index % comboBox.pageSize != 0) {
throw 'Got new data to index ' + index + ' which is not aligned with the page size of ' + comboBox.pageSize;
}
if (index === 0 && items.length === 0 && pageCallbacks[0]) {
// Makes sure that the dataProvider callback is called even when server
// returns empty data set (no items match the filter).
cache[0] = [];
return;
}
const firstPageToSet = index / comboBox.pageSize;
const updatedPageCount = Math.ceil(items.length / comboBox.pageSize);
for (let i = 0; i < updatedPageCount; i++) {
let page = firstPageToSet + i;
let slice = items.slice(i * comboBox.pageSize, (i + 1) * comboBox.pageSize);
cache[page] = slice;
}
});
comboBox.$connector.updateData = tryCatchWrapper(function (items) {
const itemsMap = new Map(items.map((item) => [item.key, item]));
comboBox.filteredItems = comboBox.filteredItems.map((item) => {
return itemsMap.get(item.key) || item;
});
});
comboBox.$connector.updateSize = tryCatchWrapper(function (newSize) {
if (!comboBox._clientSideFilter) {
// FIXME: It may be that this size set is unnecessary, since when
// providing data to combobox via callback we may use data's size.
// However, if this size reflect the whole data size, including
// data not fetched yet into client side, and combobox expect it
// to be set as such, the at least, we don't need it in case the
// filter is clientSide only, since it'll increase the height of
// the popup at only at first user filter to this size, while the
// filtered items count are less.
comboBox.size = newSize;
}
});
comboBox.$connector.reset = tryCatchWrapper(function () {
clearPageCallbacks();
cache = {};
comboBox.clearCache();
});
comboBox.$connector.confirm = tryCatchWrapper(function (id, filter) {
if (filter != serverFacade.getLastFilterSentToServer()) {
return;
}
// We're done applying changes from this batch, resolve pending
// callbacks
let activePages = Object.getOwnPropertyNames(pageCallbacks);
for (let i = 0; i < activePages.length; i++) {
let page = activePages[i];
if (cache[page]) {
commitPage(page, pageCallbacks[page]);
}
}
// Let server know we're done
comboBox.$server.confirmUpdate(id);
});
const commitPage = tryCatchWrapper(function (page, callback) {
let data = cache[page];
if (comboBox._clientSideFilter) {
performClientSideFilter(data, comboBox.filter, callback);
} else {
// Remove the data if server-side filtering, but keep it for client-side
// filtering
delete cache[page];
// FIXME: It may be that we ought to provide data.length instead of
// comboBox.size and remove updateSize function.
callback(data, comboBox.size);
}
});
// Perform filter on client side (here) using the items from specified page
// and submitting the filtered items to specified callback.
// The filter used is the one from combobox, not the lastFilter stored since
// that may not reflect user's input.
const performClientSideFilter = tryCatchWrapper(function (page, filter, callback) {
let filteredItems = page;
if (filter) {
filteredItems = page.filter((item) => comboBox.$connector.filter(item, filter));
}
callback(filteredItems, filteredItems.length);
});
// Prevent setting the custom value as the 'value'-prop automatically
comboBox.addEventListener(
'custom-value-set',
tryCatchWrapper((e) => e.preventDefault())
);
})(comboBox)
};
})();
window.Vaadin.ComboBoxPlaceholder = ComboBoxPlaceholder;