diff options
| author | ivarlovlie <git@ivarlovlie.no> | 2020-11-15 23:47:23 +0100 |
|---|---|---|
| committer | ivarlovlie <git@ivarlovlie.no> | 2020-11-15 23:47:23 +0100 |
| commit | a4bf3451bbfc6292f8d33d5241d693251d5a0d01 (patch) | |
| tree | 1d136c532889acd3f9be3e419c5f35f5f7280491 /grid/src | |
| parent | baa57ebf7f927d3d97eb4f538ed185d5d91fb731 (diff) | |
| download | web-components-a4bf3451bbfc6292f8d33d5241d693251d5a0d01.tar.xz web-components-a4bf3451bbfc6292f8d33d5241d693251d5a0d01.zip | |
add wip grid component
use cdn for bootstrap
remove bloat from toaster
Diffstat (limited to 'grid/src')
| -rw-r--r-- | grid/src/grid.ts | 244 |
1 files changed, 244 insertions, 0 deletions
diff --git a/grid/src/grid.ts b/grid/src/grid.ts new file mode 100644 index 0000000..5fcf252 --- /dev/null +++ b/grid/src/grid.ts @@ -0,0 +1,244 @@ +export default class Grid { + private readonly canRender: boolean; + private readonly defaultOptions: GridConfiguration = { + id: "id", + debug: true, + columns: [], + data: [], + pageSize: 0, + }; + private configuration: GridConfiguration; + private domElement: Element; + private currentPage: number = 0; + + constructor(props: GridProps) { + this.setOptions(props); + this.validateOptions(); + this.canRender = true; + } + + private setOptions(props: GridProps = this.defaultOptions): void { + this.configuration = {} as GridConfiguration; + this.configuration.columns = []; + for (const column of props.columns) { + if (isGridColumn(column)) { + this.configuration.columns.push(column); + } else { + this.log("column is not of type GridColumn: " + JSON.stringify(column)); + } + } + this.configuration.data = props.data ?? this.defaultOptions.data; + this.configuration.id = props.id ?? this.defaultOptions.id; + this.configuration.debug = props.debug ?? this.defaultOptions.debug; + this.configuration.pageSize = props.pageSize ?? this.defaultOptions.pageSize; + } + + private validateOptions(): void { + if (this.configuration.data.length === undefined) { + throw new GridError("props.data.length is undefined"); + } + if (this.configuration.columns.length === undefined || this.configuration.columns.length <= 0) { + throw new GridError("props.columns.length is undefined or <= 0"); + } + } + + private navigate(pageNumber) { + const maxPage = Math.ceil(this.configuration.data.length / this.configuration.pageSize - 1); + if (this.configuration.pageSize <= 0 || pageNumber < 0 || pageNumber === this.currentPage || pageNumber > maxPage) return; + this.log("Navigating to page: " + pageNumber); + const skipCount = this.configuration.pageSize * pageNumber; + const endIndex = + this.configuration.data.length < skipCount + this.configuration.pageSize + ? this.configuration.data.length + : skipCount + this.configuration.pageSize; + this.renderBody(this.configuration.data.slice(skipCount, endIndex)); + this.currentPage = pageNumber; + } + + private renderPaginator() { + if (this.configuration.pageSize === 0) return; + const nav = document.createElement("nav"); + nav.className = "float-right user-select-none"; + const ul = document.createElement("ul"); + ul.className = "pagination"; + const previousItem = document.createElement("li"); + previousItem.className = "page-item"; + previousItem.onclick = () => this.navigate(this.currentPage - 1); + const previousLink = document.createElement("span"); + previousLink.style.cursor = "pointer"; + previousLink.className = "page-link"; + previousLink.innerText = "Forrige"; + previousItem.appendChild(previousLink); + ul.appendChild(previousItem); + + for (let i = 0; i < this.configuration.data.length / this.configuration.pageSize; i++) { + const item = document.createElement("li"); + item.className = "page-item"; + item.onclick = () => this.navigate(i); + const link = document.createElement("span"); + link.className = "page-link"; + link.style.cursor = "pointer"; + link.innerText = (i + 1).toString(); + item.appendChild(link); + ul.appendChild(item); + } + + const nextItem = document.createElement("li"); + nextItem.className = "page-item"; + nextItem.onclick = () => this.navigate(this.currentPage + 1); + const nextLink = document.createElement("span"); + nextLink.style.cursor = "pointer"; + nextLink.className = "page-link"; + nextLink.innerText = "Neste"; + nextItem.appendChild(nextLink); + + ul.appendChild(nextItem); + nav.appendChild(ul); + this.domElement.appendChild(nav); + } + + private renderWrapper(): void { + const wrapper = document.createElement("table"); + const thead = document.createElement("thead"); + const tbody = document.createElement("tbody"); + wrapper.className = "table"; + wrapper.appendChild(thead); + wrapper.appendChild(tbody); + this.domElement.appendChild(wrapper); + } + + private renderHead(): void { + const wrapper = this.domElement.querySelector("table thead"); + wrapper.innerHTML = ""; + const row = document.createElement("tr"); + for (const col of this.configuration.columns) { + const th = document.createElement("th"); + th.innerText = this.asString(col.columnName); + row.appendChild(th); + } + wrapper.appendChild(row); + } + + private renderBody(data: Array<any> = null): void { + const wrapper = this.domElement.querySelector("table tbody"); + wrapper.innerHTML = ""; + let items = data ?? this.configuration.data; + if (this.configuration.pageSize !== 0) { + items = items.slice(0, this.configuration.pageSize); + } + for (const item of items) { + const row = document.createElement("tr"); + row.dataset.id = item[this.configuration.id]; + for (const val of this.configuration.columns) { + const cell = document.createElement("td"); + if (typeof val.dataId === "string" && val.dataId.length > 0) { + val.cellValue = item[val.dataId]; + } + + if (typeof val.cellValue === "string" || typeof val.cellValue === "number") { + cell.innerText = val.cellValue; + } else if (val.cellValue instanceof Element) { + cell.appendChild(val.cellValue); + } else if (val.cellValue instanceof Function) { + const computed = val.cellValue(item); + if (computed instanceof Element) cell.appendChild(computed); + else if (typeof computed === "string" || typeof computed === "number") cell.innerText = computed as string; + } + row.appendChild(cell); + } + wrapper.appendChild(row); + } + } + + private asString(val: string | Function, callbackData: any = undefined) { + if (val instanceof Function) { + return val(callbackData); + } else { + return val; + } + } + + private static id(): string { + return Math.random().toString(36).substring(2) + Date.now().toString(36); + } + + private log(message: any): void { + if (!this.configuration.debug) return; + console.log("Grid Debug: " + message); + } + + public removeByID(id: string): void { + const itemIndex = this.configuration.data.findIndex((c) => c[this.configuration.id] === id); + if (itemIndex !== -1) { + delete this.configuration.data[itemIndex]; + this.domElement.querySelector(`tr[data-id="${id}"]`).remove(); + } else { + this.log("Grid does not contain id: " + id); + } + } + + public refreshData() { + this.renderBody(); + } + + public render(el: Element): void { + if (this.canRender) { + this.log("Grid starting render", true); + this.domElement = el; + this.renderWrapper(); + this.renderHead(); + this.renderBody(); + this.renderPaginator(); + this.log("Grid was rendered", true); + } else { + throw new GridError("render is not allowed due to invalid props"); + } + } +} + +class GridError extends Error { + constructor(message: string) { + super(message); + this.name = "GridError"; + } +} + +function isArrayOfGridColumn(array: Array<GridColumn> | Array<string>): array is Array<GridColumn> { + try { + // @ts-ignore + return !(array.map((element) => "name" in element).indexOf(false) !== -1); + } catch (e) { + return false; + } +} + +function isGridColumn(val: GridColumn | string): val is GridColumn { + try { + // @ts-ignore + return "columnName" in val && ("cellValue" in val || "dataId" in val); + } catch (e) { + return false; + } +} + +interface GridProps { + id?: string; + debug?: boolean; + data: Array<any>; + columns: Array<GridColumn>; + pageSize?: number; +} + +interface GridConfiguration { + id: string; + debug: boolean; + data: Array<any>; + columns: Array<GridColumn>; + pageSize: number; +} + +interface GridColumn { + dataId?: string; + columnName: string | Function; + cellValue?: string | Function | Element; +} |
