Skip to content
Snippets Groups Projects
Verified Commit 4cc9ef4c authored by Volker Schukai's avatar Volker Schukai :alien:
Browse files

fix: optimize slect remote filter function #251

parent 8ae294c4
No related branches found
No related tags found
No related merge requests found
......@@ -15,20 +15,13 @@
</ul>
<main>
<script type="application/json" data-monster-role="translations">
{
"51": "xyz",
"52": "abc",
"53": "def"
}
</script>
<monster-select
data-monster-option-features-lazyload="true"
data-monster-option-mapping-labeltemplate="${name} (${value})"
data-monster-option-mapping-valuetemplate="${id}"
data-monster-option-url="/issue-223.json" >
data-monster-option-filter-mode="remote"
data-monster-option-filter-position="popper"
data-monster-option-features-lazyload="false"
data-monster-option-url="/issue-251.json?q={filter}"
data-monster-option-mapping-labeltemplate="${name}"
data-monster-option-mapping-valuetemplate="${id}">
</monster-select>
</main>
......
const json =
`[
{
"id": 2000,
"name": "Autobahn"
},
{
"id": 2001,
"name": "Bus"
},
{
"id": 2002,
"name": "Autobus"
},
{
"id": 2003,
"name": "Fahrrad"
},
{
"id": 2004,
"name": "Tram"
},
{
"id": 2005,
"name": "Flugzeug"
}
]`;
// check if JSON is valid
JSON.parse(json)
const requestDelay = 10
export default [
{
url: '/issue-251.json',
method: 'get',
rawResponse: async (req, res) => {
res.setHeader('Content-Type', 'application/json')
res.statusCode = 200
const url= new URL(req.url, `http://${req.headers.host}`)
const q = Object.fromEntries(url.searchParams)
if (q && q.q) {
const query = q.q.toLowerCase()
const filtered = JSON.parse(json).filter(item => item.name.toLowerCase().includes(query))
setTimeout(function() {
res.end(JSON.stringify(filtered))
}, requestDelay);
return
}
setTimeout(function() {
res.end(json)
}, requestDelay);
},
}
];
\ No newline at end of file
......@@ -28,6 +28,7 @@ import {
ATTRIBUTE_ROLE,
} from "../../dom/constants.mjs";
import {CustomControl} from "../../dom/customcontrol.mjs";
import {clone} from "../../util/clone.mjs";
import {
assembleMethodSymbol,
getSlottedElements,
......@@ -276,7 +277,9 @@ const FILTER_POSITION_INLINE = "inline";
*
* @example /examples/components/form/select-with-options Select with options
* @example /examples/components/form/select-multiple Multiple selection
* @example /examples/components/form/select-fetch Fetch options
* @example /examples/components/form/select-lazy Lazy load
* @example /examples/components/form/select-remote-filter Remote filter
*
* @copyright schukai GmbH
* @summary A beautiful select control that can make your life easier and also looks good.
......@@ -340,7 +343,8 @@ class Select extends CustomControl {
const result = convertValueToSelection.call(this, value);
setSelection
.call(this, result.selection)
.then(() => {})
.then(() => {
})
.catch((e) => {
addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.message);
});
......@@ -380,13 +384,13 @@ class Select extends CustomControl {
* @property {Object} features List with features
* @property {Boolean} features.clearAll=true Display of a delete button to delete the entire selection
* @property {Boolean} features.clear=true Display of a delete key for deleting the specific selection
* @property {Boolean} features.lazyLoad=false Load options when first opening the dropdown
* @property {Boolean} features.lazyLoad=false Load options when first opening the dropdown. (Hint; lazylLoad is not supported with remote filter)
* @property {Boolean} features.closeOnSelect=false Close the dropdown when an option is selected (since 3.54.0)
* @property {Boolean} features.emptyValueIfNoOptions=false If no options are available, the selection is set to an empty array
* @property {Boolean} features.storeFetchedData=false Store fetched data in the object
* @property {Boolean} features.useStrictValueComparison=true Use strict value comparison for the selection
* @property {Boolean} filter.defaultValue=* Default filter value, if the filter is empty
* @property {Boolean} filter.mode=options Filter mode, values: options, remote, disabled
* @property {Boolean} filter.mode=options Filter mode, values: options, remote, disabled (Hint; lazylLoad is not supported with remote filter)
* @property {Object} templates Template definitions
* @property {string} templates.main Main template
* @property {string} templateMapping Mapping of the template placeholders
......@@ -507,7 +511,13 @@ class Select extends CustomControl {
initControlReferences.call(self);
initEventHandler.call(self);
const lazyLoadFlag = self.getOption("features.lazyLoad");
if (self.getOption("filter.mode") === FILTER_MODE_REMOTE) {
self.getOption("features.lazyLoad", false);
if (lazyLoadFlag === true) {
addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, "lazyLoad is not supported with remote filter");
lazyLoadFlag = false;
}
}
if (self.hasAttribute("value")) {
new Processing(10, () => {
......@@ -539,7 +549,8 @@ class Select extends CustomControl {
lastValue = n;
setSelection
.call(self, n)
.then(() => {})
.then(() => {
})
.catch((e) => {
addAttributeToken(self, ATTRIBUTE_ERRORMESSAGE, `${e}`);
});
......@@ -624,80 +635,7 @@ class Select extends CustomControl {
* @return {Promise}
*/
fetch(url) {
if (url instanceof URL) {
url = url.toString();
}
if (url !== undefined && url !== null) {
url = validateString(url);
} else {
url = this.getOption("url");
if (url === null) {
return Promise.reject(new Error("No url defined"));
}
}
hide.call(this);
return new Promise((resolve, reject) => {
setStatusOrRemoveBadges.call(this, "loading");
new Processing(10, () => {
this.setOption("options", []);
fetchData
.call(this, url)
.then((map) => {
if (
isObject(map) ||
isArray(map) ||
map instanceof Set ||
map instanceof Map
) {
try {
this.importOptions(map);
} catch (e) {
setStatusOrRemoveBadges.call(this, "error");
reject(e);
return;
}
this[lastFetchedDataSymbol] = map;
let result;
const selection = this.getOption("selection");
let newValue = [];
if (selection) {
newValue = selection;
} else if (this.hasAttribute("value")) {
newValue = this.getAttribute("value");
}
result = setSelection.call(this, newValue);
requestAnimationFrame(() => {
checkOptionState.call(this);
setStatusOrRemoveBadges.call(this, "closed");
resolve(result);
});
return;
}
setStatusOrRemoveBadges.call(this, "error");
reject(new Error("invalid response"));
})
.catch((e) => {
setStatusOrRemoveBadges.call(this, "error");
reject(e);
});
})
.run()
.catch((e) => {
setStatusOrRemoveBadges.call(this, "error");
addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.message);
reject(e);
});
});
return fetchIt.call(this, url);
}
/**
......@@ -719,7 +657,9 @@ class Select extends CustomControl {
new Processing(() => {
gatherState.call(this);
focusFilter.call(this);
}).run();
}).run().catch((e) => {
addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, `${e}`);
});
}
/**
......@@ -771,7 +711,8 @@ class Select extends CustomControl {
const map = buildMap(data, selector, labelTemplate, valueTemplate, filter);
const options = [];
const options = []
if (!isIterable(map)) {
throw new Error("map is not iterable");
}
......@@ -863,6 +804,81 @@ function lookupSelection() {
}, 100);
}
function fetchIt(url, controlOptions) {
if (url instanceof URL) {
url = url.toString();
}
if (url !== undefined && url !== null) {
url = validateString(url);
} else {
url = this.getOption("url");
if (url === null) {
return Promise.reject(new Error("No url defined"));
}
}
return new Promise((resolve, reject) => {
setStatusOrRemoveBadges.call(this, "loading");
new Processing(10, () => {
fetchData
.call(this, url)
.then((map) => {
if (
isObject(map) ||
isArray(map) ||
map instanceof Set ||
map instanceof Map
) {
try {
this.importOptions(map);
} catch (e) {
setStatusOrRemoveBadges.call(this, "error");
reject(e);
return;
}
this[lastFetchedDataSymbol] = map;
let result;
const selection = this.getOption("selection");
let newValue = [];
if (selection) {
newValue = selection;
} else if (this.hasAttribute("value")) {
newValue = this.getAttribute("value");
}
result = setSelection.call(this, newValue);
requestAnimationFrame(() => {
checkOptionState.call(this);
setStatusOrRemoveBadges.call(this, "closed");
updatePopper.call(this);
resolve(result);
});
return;
}
setStatusOrRemoveBadges.call(this, "error");
reject(new Error("invalid response"));
})
.catch((e) => {
setStatusOrRemoveBadges.call(this, "error");
reject(e);
});
})
.run()
.catch((e) => {
setStatusOrRemoveBadges.call(this, "error");
addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.message);
reject(e);
});
});
}
/**
* This attribute can be used to pass a URL to this select.
*
......@@ -1162,7 +1178,8 @@ function getDefaultTranslation() {
try {
const doc = getDocumentTranslations();
translation.locale = doc.locale;
} catch (e) {}
} catch (e) {
}
return translation;
}
......@@ -1481,11 +1498,6 @@ function handleFilterKeyboardEvents(event) {
}
}
/**
*
*/
/**
* Method handleFilterKeyEvents is used to handle filter key events.
* Debounce is used to prevent multiple calls.
......@@ -1523,12 +1535,14 @@ function handleFilterKeyEvents() {
* @private
*/
function filterFromRemote() {
if (!(this[inlineFilterElementSymbol] instanceof HTMLElement)) {
if (!(this[inlineFilterElementSymbol] instanceof HTMLElement)&&!(this[popperFilterElementSymbol] instanceof HTMLElement)) {
return;
}
const optionUrl = this.getOption("url");
if (!optionUrl) {
show.call(this);
const url = this.getOption("url");
if (!url) {
addAttributeToken(
this,
ATTRIBUTE_ERRORMESSAGE,
......@@ -1537,22 +1551,35 @@ function filterFromRemote() {
return;
}
let filterValue;
switch (this.getOption("filter.position")) {
case FILTER_POSITION_INLINE:
if (this[inlineFilterElementSymbol] instanceof HTMLElement) {
filterValue = this[inlineFilterElementSymbol].value.toLowerCase();
}
break;
case FILTER_POSITION_POPPER:
default:
if (this[popperFilterElementSymbol] instanceof HTMLInputElement) {
filterValue = this[popperFilterElementSymbol].value.toLowerCase();
}
}
return filterFromRemoteByValue.call(
this,
optionUrl,
this[inlineFilterElementSymbol].value,
url,
filterValue
);
}
/**
* @private
*/
function filterFromRemoteByValue(optionUrl, value) {
return new Processing(() => {
const filterValue = encodeURI(value);
let url = optionUrl;
if (filterValue.length > 0) {
const formatter = new Formatter({ filter: filterValue });
function formatURL(url, value) {
if(value === undefined || value === null|| value === ""){
value = this.getOption("filter.defaultValue");
}
const formatter = new Formatter({filter: encodeURI(value)});
const openMarker = this.getOption("filter.marker.open");
let closeMarker = this.getOption("filter.marker.close");
if (!closeMarker) {
......@@ -1563,21 +1590,34 @@ function filterFromRemoteByValue(optionUrl, value) {
formatter.setMarker(openMarker, closeMarker);
}
url = formatter.format(optionUrl);
return formatter.format(url);
}
this.fetch(url)
/**
* @private
*/
function filterFromRemoteByValue(optionUrl, value) {
return new Processing(() => {
let url = formatURL.call(this, optionUrl, value);
fetchIt.call(this, url, {
disableHiding: true,
})
.then(() => {
checkOptionState.call(this);
show.call(this);
})
.catch((e) => {
throw e;
});
}).run();
}).run().catch((e) => {
throw e;
});
}
/**
*
* @param {Event} event
* @private
*/
......@@ -1767,7 +1807,8 @@ function gatherState() {
setSelection
.call(this, selection)
.then(() => {})
.then(() => {
})
.catch((e) => {
addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, `${e}`);
});
......@@ -1796,7 +1837,8 @@ function clearSelection() {
setSelection
.call(this, [])
.then(() => {})
.then(() => {
})
.catch((e) => {
addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, `${e}`);
});
......@@ -1827,7 +1869,7 @@ function areOptionsAvailableAndInit() {
) {
setStatusOrRemoveBadges.call(this, "empty");
hide.call(this);
// hide.call(this);
let msg = this.getOption("labels.no-options-available");
......@@ -2006,6 +2048,7 @@ function convertSelectionToValue(selection) {
* @throws {Error} no shadow-root is defined
*/
function setSelection(selection) {
if (isString(selection)) {
const result = convertValueToSelection.call(this, selection);
selection = result?.selection;
......@@ -2016,8 +2059,14 @@ function setSelection(selection) {
validateArray(selection);
for (let i = 0; i < selection.length; i++) {
var l = getSelectionLabel.call(this, selection[i].value);
if (l === selection[i].value) {
l = selection[i].label;
}
selection[i] = {
label: getSelectionLabel.call(this, selection[i].value),
label: l,
value: selection[i].value,
};
}
......@@ -2092,9 +2141,7 @@ function fetchData(url) {
delayWatch = true;
});
url = new Formatter({ filter: this.getOption("filter.defaultValue") }).format(
url,
);
url = formatURL.call(this, url);
self[isLoadingSymbol] = true;
const global = getGlobal();
......@@ -2176,8 +2223,10 @@ function show() {
return;
}
const hasPopperFilterFlag = this.getOption("filter.position") === FILTER_POSITION_POPPER && this.getOption("filter.mode") !== FILTER_MODE_DISABLED;
const options = getOptionElements.call(this);
if (options.length === 0) {
if (options.length === 0 && hasPopperFilterFlag === false) {
return;
}
......
......@@ -263,6 +263,8 @@ div[data-monster-role=selection] {
[data-monster-role="no-options"] {
margin: 1.1em 0 0 1.1em;
padding: 0.3em 0.8em;
border-radius: 0.2em;
color: var(--monster-color-warning-4);
background-color: var(--monster-bg-color-warning-4);
}
......
This diff is collapsed.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment