UNPKG

wj-elements

Version:

WebJET Elements is a modern set of user interface tools harnessing the power of web components designed to simplify web application development.

386 lines (385 loc) 16.2 kB
var __defProp = Object.defineProperty; var __typeError = (msg) => { throw TypeError(msg); }; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg); var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj)); var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value); var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value); var _drawnItems, _loadedItems, _response, _infiniteScrollTemplate, _abortController, _signal, _loading; import WJElement from "./wje-element.js"; import { WjElementUtils } from "./element-utils.js"; import { event } from "./event.js"; const styles = "/*\n[ Wj infinite Scroll ]\n*/\n\n:host {\n overflow-x: auto;\n width: var(--wje-infinite-scroll-width);\n height: var(--wje-infinite-scroll-height);\n display: block;\n}\n\n.native {\n /*position: relative;*/\n}\n\n.loading {\n position: sticky;\n display: none;\n justify-content: center;\n align-items: center;\n width: 100%;\n height: 100%;\n top: 0;\n left: 0;\n z-index: 9999;\n background-color: var(--wje-infinite-scroll-loading-bg);\n &.show {\n display: flex;\n }\n}\n\n[name='ending'] {\n display: none;\n margin-top: 1rem;\n text-align: center;\n}\n\n[name='ending'].show {\n display: block;\n}\n"; class InfiniteScroll extends WJElement { /** * Creates an instance of InfiniteScroll. */ constructor() { super(); __privateAdd(this, _drawnItems); __privateAdd(this, _loadedItems); __privateAdd(this, _response); __privateAdd(this, _infiniteScrollTemplate); __privateAdd(this, _abortController); __privateAdd(this, _signal); __privateAdd(this, _loading); __publicField(this, "className", "InfiniteScroll"); /** * Attaches a scroll event listener to the current object. * The `scrollEvent` function binds the `onScroll` method to the 'scroll' event * of the current object. This enables handling of scroll events for * specific functionality such as updating UI elements, loading content dynamically, * or tracking user interaction with scrollable content. */ __publicField(this, "scrollEvent", () => { this.addEventListener("scroll", this.onScroll); }); /** * A function that removes the scroll event listener from the current context. * This function is used to unbind the `onScroll` event listener * from the `scroll` event of the current object. It ensures that * the scroll event no longer triggers the `onScroll` handler. * @function */ __publicField(this, "unScrollEvent", () => { this.removeEventListener("scroll", this.onScroll); }); /** * A scroll event handler function that checks the scroll position and triggers loading additional content * when the user scrolls near the bottom of the page. * Properties accessed: * - `scrollTop`: The number of pixels that the content of an element is scrolled vertically. * - `scrollHeight`: The total height of the element's content. * - `clientHeight`: The inner height of the element in pixels, including padding but excluding borders and scrollbars. * Conditions: * - Determines if the scroll position is within 300 pixels of the bottom of the element. * - Verifies that the current page number is less than or equal to the total number of pages. * - Checks if the current page is already in the loading state. * Actions: * - Increments the current page number when the conditions are met. * - Initiates loading for the next page by calling the `loadPages` function. * @param {Event} e The scroll event object. */ __publicField(this, "onScroll", (e) => { const { scrollTop, scrollHeight, clientHeight } = e.target; if (scrollTop + clientHeight >= scrollHeight - 300 && this.currentPage <= this.totalPages && this.isLoading.includes(this.currentPage)) { this.currentPage++; __privateSet(this, _loading, this.loadPages(this.currentPage)); } }); __publicField(this, "compareFunction", (i, item) => i.id === item.id); /** * Converts a data item into an HTML element based on a template. * This function takes a data item, interpolates it into a predefined template, * parses the resulting HTML string, and returns the first child element of the parsed HTML content. * @param {object} item The data object to interpolate into the HTML template. * @returns {Element} The first child element generated from the interpolated HTML string. */ __publicField(this, "dataToHtml", (item) => { let interpolateItem = this.interpolate(this.infiniteScrollTemplate, item); let doc = this.parser.parseFromString(interpolateItem, "text/html"); let element = doc.activeElement.firstElementChild; return element; }); /** * A custom implementation of the forEach method designed to iterate over an array of data, * transform each item into an HTML element, and append the element to a specified placement object. * Additionally, it adds an event listener to each generated element for handling click events. * @param {Array} data An array of items to process. Each item is transformed into an HTML element * and appended to the placement object specified in the context of `this`. */ __publicField(this, "customForeach", (data) => { data.forEach((item) => { let element = this.dataToHtml(item); let symbol = Symbol("infinite-scroll-item"); element[symbol] = item; item[symbol] = element; event.addListener(element, "click", "wje-infinite-scroll:click-item", null); this.placementObj.insertAdjacentElement("beforeend", element); }); }); /** * Interpolates a string template with values from the provided parameters object. * The template contains placeholders in the format `{{key}}` or `{{key.subkey}}`, * which are replaced with the corresponding values from the `params` object. * Placeholders support dot notation for accessing nested properties within the `params` object. * @param {string} template The string template containing placeholders to be replaced. * @param {object} params The object containing key-value pairs used for substitution in the template. * @returns {string} A string with all placeholders replaced by their respective values from the `params` object. */ __publicField(this, "interpolate", (template, params) => { let keys = template.match(/\{{.*?\}}/g); if (keys) { for (let key of keys) { let cleanKey = key.replace("{{", "").replace("}}", ""); let val = ""; cleanKey.split(".").forEach((k) => { val = val === "" ? params[k] : val[k]; }); template = template.replace(key, val); } } return template; }); this.totalPages = 0; this.isLoading = []; __privateSet(this, _response, {}); this.iterate = null; __privateSet(this, _infiniteScrollTemplate, null); __privateSet(this, _abortController, new AbortController()); __privateSet(this, _signal, __privateGet(this, _abortController).signal); __privateSet(this, _drawnItems, []); __privateSet(this, _loadedItems, []); } /** * Dependencies of the InfiniteScroll component. * @param value */ set infiniteScrollTemplate(value) { __privateSet(this, _infiniteScrollTemplate, value); } /** * Getter for the infiniteScrollTemplate property. * @returns {null} */ get infiniteScrollTemplate() { return __privateGet(this, _infiniteScrollTemplate); } /** * Dependencies of the InfiniteScroll component. * @param value */ set response(value) { __privateSet(this, _response, value); } /** * Getter for the response property. * @returns {*|{}} */ get response() { return __privateGet(this, _response); } /** * Dependencies of the InfiniteScroll component. * @param value */ set objectName(value) { this.setAttribute("object-name", value); } get objectName() { return this.getAttribute("object-name") ?? "data"; } /** * Returns the CSS styles for the component. * @static * @returns {CSSStyleSheet} */ static get cssStyleSheet() { return styles; } /** * Returns the list of attributes to observe for changes. * @static * @returns {Array<string>} */ static get observedAttributes() { return []; } /** * Sets up the attributes for the component. */ setupAttributes() { this.isShadowRoot = "open"; } /** * Prepares the component for updates before it is drawn. * This method handles the removal of templates for iteration, adjusts the height styling of the component, * and manages abort signals for loading operations. * @returns {void} No return value. */ beforeDraw() { var _a, _b, _c; __privateSet(this, _loadedItems, []); __privateSet(this, _drawnItems, []); this.iterate = this.querySelector("[iterate]"); if (this.iterate) { if (this.iterate.nodeName !== "TEMPLATE") { console.error("The iterate attribute must be a template element"); this.infiniteScrollTemplate = (_a = this.iterate) == null ? void 0 : _a.outerHTML; } else { this.infiniteScrollTemplate = (_b = this.iterate) == null ? void 0 : _b.innerHTML; } (_c = this.iterate) == null ? void 0 : _c.remove(); } this.setAttribute("style", "height: " + this.height); if (__privateGet(this, _signal)) { __privateGet(this, _abortController).abort(); __privateSet(this, _abortController, new AbortController()); __privateSet(this, _signal, __privateGet(this, _abortController).signal); } } /** * Creates and returns a document fragment containing the structure for an infinite scroll component. * The structure includes native elements, slots for customization, and optional loading content. * @returns {DocumentFragment} The document fragment containing the component's DOM structure. */ draw() { let fragment = document.createDocumentFragment(); let native = document.createElement("div"); native.classList.add("native"); native.setAttribute("part", "native-infinite-scroll"); let slot = document.createElement("slot"); let ending = document.createElement("slot"); ending.setAttribute("name", "ending"); if (WjElementUtils.hasSlot(this, "loader")) { let loading = document.createElement("div"); loading.classList.add("loading"); let loader = document.createElement("slot"); loader.setAttribute("name", "loader"); loading.appendChild(loader); this.loadingEl = loading; fragment.appendChild(loading); } native.appendChild(slot); native.appendChild(ending); fragment.appendChild(native); this.endingEl = ending; return fragment; } /** * Called after the component has been drawn. */ async afterDraw() { this.queryParams = this.queryParams || ""; this.size = +this.size || 10; this.currentPage = 0; this.scrollEvent(); __privateSet(this, _loading, this.loadPages(this.currentPage)); await __privateGet(this, _loading); } /** * Fetches the pages from the server. * @param {number} page The page number. * @returns {Promise<object>} The response from the server. */ async getPages(page) { let hasParams = this.url.includes("?"); const response = await fetch( `${this.url}${hasParams ? "&" : "?"}page=${page}&size=${this.size}${this == null ? void 0 : this.queryParams}`, { signal: __privateGet(this, _signal) } ); if (!response.ok) { throw new Error(`An error occurred: ${response.status}`); } return await response.json(); } /** * Hides the loader. */ hideLoader() { var _a; (_a = this == null ? void 0 : this.loadingEl) == null ? void 0 : _a.classList.remove("show"); } /** * Displays the loader element by adding the 'show' class to its class list. * This method is useful for indicating a loading or processing state in the UI. * @returns {void} No return value. */ showLoader() { var _a; (_a = this == null ? void 0 : this.loadingEl) == null ? void 0 : _a.classList.add("show"); } /** * Checks if there are more pages to load. * @param {number} page The page number. * @returns {boolean} Whether there are more pages to load. */ hasMorePages(page) { return this.totalPages === 0 || page < this.totalPages; } /** * Loads the pages. * @param {number} page The page number. */ async loadPages(page) { this.showLoader(); try { if (this.hasMorePages(page)) { let response; this.parser = new DOMParser(); if (typeof this.setCustomData === "function") { response = await this.setCustomData(page, __privateGet(this, _signal)); } else { response = await this.getPages(page); } this.totalPages = response == null ? void 0 : response.totalPages; this.currentPage = page; this.placementObj = this; if (this.hasAttribute("placement")) this.placementObj = this.querySelector(this.placement); event.dispatchCustomEvent(this, "wje-infinite-scroll:load", response); this.response = response; __privateSet(this, _loadedItems, this.objectName ? response[this.objectName] : response); const notDrawnItems = __privateGet(this, _loadedItems).filter( (item) => !__privateGet(this, _drawnItems).some(this.compareFunction.bind(this, item)) ); this.customForeach(notDrawnItems); __privateGet(this, _drawnItems).push(...notDrawnItems); this.isLoading.push(page); } else { event.dispatchCustomEvent(this, "wje-infinite-scroll:complete"); this.endingEl.classList.add("show"); } } catch (error) { console.log(error); } finally { this.hideLoader(); } } addItem(item, place = "beforeend") { let element = this.dataToHtml(item); let symbol = Symbol("infinite-scroll-item"); element[symbol] = item; item[symbol] = element; this.placementObj.insertAdjacentElement(place, element); __privateGet(this, _drawnItems).push(item); if (__privateGet(this, _drawnItems).length > this.size * this.currentPage) { this.totalPages += 1; } } removeItem(item) { let drawnItem = __privateGet(this, _drawnItems).find(this.compareFunction.bind(this, item)); if (!drawnItem) { console.error("Item not found"); return; } let symbol = Object.getOwnPropertySymbols(drawnItem).at(0); let element = drawnItem[symbol]; if (!element) { console.error("Element not found"); return; } element == null ? void 0 : element.remove(); __privateSet(this, _drawnItems, __privateGet(this, _drawnItems).filter((i) => i !== item)); if (__privateGet(this, _drawnItems).length < this.size * this.currentPage) { this.isLoading = this.isLoading.filter((i) => i !== this.currentPage); this.currentPage--; } } } _drawnItems = new WeakMap(); _loadedItems = new WeakMap(); _response = new WeakMap(); _infiniteScrollTemplate = new WeakMap(); _abortController = new WeakMap(); _signal = new WeakMap(); _loading = new WeakMap(); InfiniteScroll.define("wje-infinite-scroll", InfiniteScroll); export { InfiniteScroll as default }; //# sourceMappingURL=wje-infinite-scroll.js.map