From 3383001c3a779a5517de97f6a30ac7662ce10b05 Mon Sep 17 00:00:00 2001 From: MattMo Date: Mon, 17 Apr 2023 12:09:21 -0700 Subject: [PATCH] Added more documentation. Fixed a few bugs and improved the code. Added a new pager template that can construct a pager used for pagination. --- ignite-html.js | 5 + ignite-template.js | 320 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 319 insertions(+), 6 deletions(-) diff --git a/ignite-html.js b/ignite-html.js index 9f330de..85af996 100644 --- a/ignite-html.js +++ b/ignite-html.js @@ -4,6 +4,11 @@ * @ignore */ class IgniteProperty { + /** + * + * @param {Any} val Starting value of this property. + * @param {{ onChange, onPush, onPop, onShift, onUnshift, onSplice }} options + */ constructor(val, options = null) { this.onChangeCallbacks = []; this.onPushCallbacks = []; diff --git a/ignite-template.js b/ignite-template.js index 4ef422c..91c9300 100644 --- a/ignite-template.js +++ b/ignite-template.js @@ -139,7 +139,7 @@ class IgniteTemplate { /** * Adds a html element attribute to this template to be added once this template is constructed. * @param {String} name The name of the attribute to add - * @param {String|IgniteProperty|Function} value The value of the attribute to set, can be anything. If Property is passed it will auto update. + * @param {String|IgniteProperty|Function|Array} value The value of the attribute to set, can be anything. If Property is passed it will auto update. * @param {Function} converter Optional function that can convert the value if needed. * @returns {IgniteTemplate} This ignite template so function calls can be chained. */ @@ -2629,6 +2629,13 @@ class slot extends IgniteTemplate { * on the items, page size, current page. */ class pagination extends IgniteTemplate { + /** + * Constructs a new pagination with all the settings needed. + * @param {Array|IgniteProperty} list The list of items to paginate. + * @param {Number|IgniteProperty} pageSize The size of each page. + * @param {Number|IgniteProperty} currentPage The current page to display. + * @param {Function(index)} forEach The render function foreach item in the list. + */ constructor(list, pageSize, currentPage, forEach) { super(); @@ -2733,7 +2740,7 @@ class pagination extends IgniteTemplate { //Init our pages to put elements into. var pages = 0; if (this.list) { - pages = parseInt((this.list.length / this.pageSize)) + (this.list.length % this.pageSize > 0 ? 1 : 0); + pages = Math.ceil(this.list.length / this.pageSize); } this.pages = []; @@ -2919,6 +2926,300 @@ class pagination extends IgniteTemplate { } } +/** + * A pager is a template that converts pagination information into elements + * to form a page selection. + */ +class pager extends IgniteTemplate { + /** @type {Number|Array|IgniteProperty} */ + items; + + /** @type {Number|IgniteProperty} */ + pageSize; + + /** @type {Number|IgniteProperty} */ + currentPage; + + /** @type {Number|IgniteProperty} */ + pageRange; + + /** @type {Number} */ + pageCount; + + /** @type {Array} */ + pages; + + /** @type {Function(pageIndex, current, dots)} */ + renderCallback; + + /** + * Constructs a new pager with all the settings needed. + * @param {Number|Array|IgniteProperty} items The items to construct pages for. + * @param {Number|IgniteProperty} pageSize The number of items to show per page. + * @param {Number|IgniteProperty} currentPage The current page being shown. + * @param {Number|IgniteProperty} pageRange The number of pages that can be selected at a time. 3 is typically chosen. + * @param {Function(pageIndex, current, dots)} renderCallback Render function for a page, pageIndex is the index of the page, current is whether or not this is the current page, dots is whether this render is for dots. + */ + constructor(items, pageSize, currentPage, pageRange, renderCallback) { + super(); + + if (items instanceof IgniteProperty) { + this._callbacks.push(items.attachOnChange((oldValue, newValue) => this.onItemsChanged(oldValue, newValue))); + this._callbacks.push(items.attachOnPush((list, items) => this.onItemsPush(list, items))); + this._callbacks.push(items.attachOnUnshift((list, items) => this.onItemsUnshift(list, items))); + this._callbacks.push(items.attachOnPop(list => this.onItemsPop(list))); + this._callbacks.push(items.attachOnShift(list => this.onItemsShift(list))); + this._callbacks.push(items.attachOnSplice((list, start, deleteCount, items) => this.onItemsSplice(list, start, deleteCount, items))); + + this.items = items.value; + } else { + this.items = items; + } + + if (pageSize instanceof IgniteProperty) { + this._callbacks.push(pageSize.attachOnChange((oldValue, newValue) => { + this.pageSize = newValue; + + this.recalculate(); + + this.construct(null); + })); + + this.pageSize = pageSize.value; + } else { + this.pageSize = pageSize; + } + + if (currentPage instanceof IgniteProperty) { + this._callbacks.push(currentPage.attachOnChange((oldValue, newValue) => { + this.currentPage = newValue; + + this.recalculate(); + + this.construct(null); + })); + + this.currentPage = currentPage.value; + } else { + this.currentPage = currentPage; + } + + if (pageRange instanceof IgniteProperty) { + this._callbacks.push(pageRange.attachOnChange((oldValue, newValue) => { + this.pageRange = newValue; + + this.recalculate(); + + this.construct(null); + })); + + this.pageRange = pageRange.value; + } else { + this.pageRange = pageRange; + } + + this.renderCallback = renderCallback; + + this.tagName = "pager placeholder"; + + this.pages = []; + + //Calculate initially before render. + this.recalculate(); + } + + construct(parent, sibling) { + //Don't construct if we have no parent, no sibling and no element. + if (!parent && !sibling && !this.element) { + return; + } + + if (!this.element) { + this.element = window.document.createTextNode(""); //Use a textnode as our placeholder + + if (sibling) { + sibling.parentElement.insertBefore(this.element, sibling); + } else { + parent.appendChild(this.element); + } + } else { + parent = this.element.parentElement; + } + + //If we already have page created, destroy them. + if (this.pages.length > 0) { + for (var i = 0; i < this.pages.length; i++) { + this.pages[i].deconstruct(); + } + this.pages = []; + } + + //Construct the pages + if (this.pageCount > 0) { + //Construct the first page + var firstPage = this.renderCallback(0, this.currentPage == 0, false); + firstPage.construct(parent, this.element); + this.pages.push(firstPage); + + //If the number of pages is less than or equal to the page range, just render all the numbers inbetween first/last. + if (this.pageCount <= this.pageRange) { + for (var i = 1; i < this.pageCount - 1; i++) { + var page = this.renderCallback(i, i == this.currentPage, false); + page.construct(parent, this.element); + this.pages.push(page); + } + } else { + var leftSiblingIndex = Math.max(this.currentPage - (this.pageRange - 1), 1); + + var rightSiblingIndex = Math.min(this.currentPage + (this.pageRange - 1) + ((this.pageRange - 1) - (this.currentPage - leftSiblingIndex)), this.pageCount - 2); + + var shouldShowLeftDots = leftSiblingIndex > 2; + + var shouldShowRightDots = rightSiblingIndex < this.pageCount - 2; + + if (!shouldShowLeftDots && shouldShowRightDots) { + for (var i = leftSiblingIndex; i <= rightSiblingIndex; i++) { + var page = this.renderCallback(i, i == this.currentPage, false); + page.construct(parent, this.element); + this.pages.push(page); + } + + var page = this.renderCallback(rightSiblingIndex + 1, false, true); + page.construct(parent, this.element); + this.pages.push(page); + } else if (shouldShowLeftDots && !shouldShowRightDots) { + var page = this.renderCallback(leftSiblingIndex - 1, false, true); + page.construct(parent, this.element); + this.pages.push(page); + + for (var i = leftSiblingIndex; i <= rightSiblingIndex; i++) { + var page = this.renderCallback(i, i == this.currentPage, false); + page.construct(parent, this.element); + this.pages.push(page); + } + } else if (shouldShowLeftDots && shouldShowRightDots) { + var page = this.renderCallback(leftSiblingIndex - 1, false, true); + page.construct(parent, this.element); + this.pages.push(page); + + for (var i = leftSiblingIndex; i <= rightSiblingIndex; i++) { + var page = this.renderCallback(i, i == this.currentPage, false); + page.construct(parent, this.element); + this.pages.push(page); + } + + var page = this.renderCallback(rightSiblingIndex + 1, false, true); + page.construct(parent, this.element); + this.pages.push(page); + } + } + + //Construct the last page if we have more than 1 page. + if (this.pageCount > 1) { + var lastPage = this.renderCallback(this.pageCount - 1, this.currentPage == this.pageCount - 1, false); + lastPage.construct(parent, this.element); + this.pages.push(lastPage); + } + } + } + + recalculate() { + if (this.items == null) { + this.pageCount = 0; + } else if (this.items instanceof Array) { + this.pageCount = Math.ceil(this.items.length / this.pageSize); + } else { + this.pageCount = Math.ceil(this.items / this.pageSize); + } + } + + onItemsChanged(oldValue, newValue) { + this.items = newValue; + + this.recalculate(); + + IgniteRendering.enter(); + + try { + this.construct(null); + } catch (error) { + console.error("An error occurred during Pager.onItemsChanged:", error); + } + + IgniteRendering.leave(); + } + + onItemsPush(list, items) { + this.recalculate(); + + IgniteRendering.enter(); + + try { + this.construct(null); + } catch (error) { + console.error("An error occurred during Pager.onItemsPush:", error); + } + + IgniteRendering.leave(); + } + + onItemsUnshift(list, items) { + this.recalculate(); + + IgniteRendering.enter(); + + try { + this.construct(null); + } catch (error) { + console.error("An error occurred during Pager.onItemsUnshift:", error); + } + + IgniteRendering.leave(); + } + + onItemsPop(list) { + this.recalculate(); + + IgniteRendering.enter(); + + try { + this.construct(null); + } catch (error) { + console.error("An error occurred during Pager.onItemsPop:", error); + } + + IgniteRendering.leave(); + } + + onItemsShift(list) { + this.recalculate(); + + IgniteRendering.enter(); + + try { + this.construct(null); + } catch (error) { + console.error("An error occurred during Pager.onItemsShift:", error); + } + + IgniteRendering.leave(); + } + + onItemsSplice(list, start, deleteCount, items) { + this.recalculate(); + + IgniteRendering.enter(); + + try { + this.construct(null); + } catch (error) { + console.error("An error occurred during Pager.onItemsSplice:", error); + } + + IgniteRendering.leave(); + } +} + /** * An ignite template that can construct a population of items * based on a count. @@ -2926,15 +3227,21 @@ class pagination extends IgniteTemplate { class population extends IgniteTemplate { /** * Creates a new population with the number of items in it, a converter if needed, and a foreach function. - * @param {Number|IgniteProperty} count The number of items in this population. - * @param {Function} forEach A function to generate items in the population. - * @param {Function?} converter A converter to be used to convert the count if needed. + * @param {Number|IgniteProperty|Array} count The number of items in this population. + * @param {Function(index, count)} forEach A function to generate items in the population. + * @param {Function} converter A converter to be used to convert the count if needed. */ constructor(count, forEach, converter = null) { super(); if (count instanceof IgniteProperty) { this._callbacks.push(count.attachOnChange((oldValue, newValue) => this.onCountChange(converter != null ? converter(newValue) : newValue))); + this._callbacks.push(count.attachOnPush((list, items) => this.onCountChange(converter != null ? converter(list) : list))); + this._callbacks.push(count.attachOnUnshift((list, items) => this.onCountChange(converter != null ? converter(list) : list))); + this._callbacks.push(count.attachOnPop(list => this.onCountChange(converter != null ? converter(list) : list))); + this._callbacks.push(count.attachOnShift(list => this.onCountChange(converter != null ? converter(list) : list))); + this._callbacks.push(count.attachOnSplice((list, start, deleteCount, items) => this.onCountChange(converter != null ? converter(list) : list))); + this.count = count.value; } else if (Array.isArray(count) && count.length > 0 && count[0] instanceof IgniteProperty) { //There must be a converter for this to work correctly @@ -2990,7 +3297,7 @@ class population extends IgniteTemplate { //Construct all the elements for this population. for (var i = 0; i < this.count; i++) { - var template = this.forEach(i); + var template = this.forEach(i, this.count); if (this.elements.length > 0) { template.construct(this.element.parentElement, this.elements[this.elements.length - 1].nextSibling); @@ -3064,6 +3371,7 @@ export { script, slot, pagination, + pager, population, table, tr,