From b7196aea72ccea2a26ad9c7cac1fac0da80b6d26 Mon Sep 17 00:00:00 2001 From: ivarlovlie Date: Mon, 16 Nov 2020 22:31:47 +0100 Subject: add search --- grid/TODO | 2 - grid/index.js | 7 ++- grid/package-lock.json | 11 ++++ grid/package.json | 25 +++++---- grid/src/grid.ts | 143 +++++++++++++++++++++++++++++++++++-------------- 5 files changed, 134 insertions(+), 54 deletions(-) (limited to 'grid') diff --git a/grid/TODO b/grid/TODO index 8d65727..ae59887 100644 --- a/grid/TODO +++ b/grid/TODO @@ -1,3 +1 @@ - sorting -- search -- column min width diff --git a/grid/index.js b/grid/index.js index 2eec36d..ca61d83 100644 --- a/grid/index.js +++ b/grid/index.js @@ -14,12 +14,15 @@ const grid = new Grid({ { id: "10", firstName: "Sebastian", lastName: "" }, { id: "11", firstName: "Heidi", lastName: "" }, { id: "12", firstName: "Frank", lastName: "" }, - { id: "13", firstName: "Ivar", lastName: "Løvlie" }, + { id: "13", firstName: "Ivar", lastName: "ehhe" }, { id: "14", firstName: "Sebastian", lastName: "" }, { id: "15", firstName: "Heidi", lastName: "" }, { id: "16", firstName: "123123", lastName: "" }, ], pageSize: 5, + search: { + dataIds: ["firstName", "lastName"], + }, columns: [ { dataId: "id", @@ -27,10 +30,12 @@ const grid = new Grid({ }, { dataId: "firstName", + minWidth: "350px", columnName: "Fornavn", }, { cellValue: (row) => row.lastName, + minWidth: "150px", columnName: "Etternavn", }, ], diff --git a/grid/package-lock.json b/grid/package-lock.json index 36a0359..d1c6a7d 100644 --- a/grid/package-lock.json +++ b/grid/package-lock.json @@ -2368,6 +2368,12 @@ "nullthrows": "^1.1.1" } }, + "@types/js-search": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@types/js-search/-/js-search-1.4.0.tgz", + "integrity": "sha1-8tSvoXak/HsX+0ahWThHiH+h+3s=", + "dev": true + }, "@types/q": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.4.tgz", @@ -5119,6 +5125,11 @@ "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==", "dev": true }, + "js-search": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/js-search/-/js-search-2.0.0.tgz", + "integrity": "sha512-lJ8KzjlwcelIWuAdKyzsXv45W6OIwRpayzc7XmU8mzgWadg5UVOKVmnq/tXudddEB9Ceic3tVaGu6QOK/eebhg==" + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", diff --git a/grid/package.json b/grid/package.json index 7c9e8da..b339f43 100644 --- a/grid/package.json +++ b/grid/package.json @@ -1,13 +1,16 @@ { - "name": "grid", - "version": "0.0.1", - "author": { - "email": "ivar@ivarlovlie.no", - "name": "Ivar Løvlie", - "url": "https://ivarlovlie.no" - }, - "devDependencies": { - "@babel/core": "^7.12.3", - "parcel": "next" - } + "name": "grid", + "version": "0.0.1", + "author": { + "email": "ivar@ivarlovlie.no", + "name": "Ivar Løvlie", + "url": "https://ivarlovlie.no" + }, + "devDependencies": { + "@babel/core": "^7.12.3", + "parcel": "next" + }, + "dependencies": { + "js-search": "^2.0.0" + } } diff --git a/grid/src/grid.ts b/grid/src/grid.ts index 5fcf252..0953268 100644 --- a/grid/src/grid.ts +++ b/grid/src/grid.ts @@ -1,3 +1,36 @@ +import * as JsSearch from "js-search"; + +interface GridProps { + id?: string; + debug?: boolean; + data: Array; + columns: Array; + pageSize?: number; + search?: SearchConfiguration; +} + +interface GridConfiguration { + id: string; + debug: boolean; + data: Array; + columns: Array; + pageSize: number; + search: SearchConfiguration; +} + +interface SearchConfiguration { + dataIds: Array; +} + +interface GridColumn { + dataId?: string; + columnName: string | Function; + cellValue?: string | Function | Element; + width?: string; + maxWidth?: string; + minWidth?: string; +} + export default class Grid { private readonly canRender: boolean; private readonly defaultOptions: GridConfiguration = { @@ -6,10 +39,13 @@ export default class Grid { columns: [], data: [], pageSize: 0, + search: { + dataIds: [], + }, }; private configuration: GridConfiguration; private domElement: Element; - private currentPage: number = 0; + public currentPage: number = 0; constructor(props: GridProps) { this.setOptions(props); @@ -31,6 +67,7 @@ export default class Grid { this.configuration.id = props.id ?? this.defaultOptions.id; this.configuration.debug = props.debug ?? this.defaultOptions.debug; this.configuration.pageSize = props.pageSize ?? this.defaultOptions.pageSize; + this.configuration.search = props.search ?? this.defaultOptions.search; } private validateOptions(): void { @@ -42,20 +79,15 @@ export default class Grid { } } - 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 renderCurrentPageIndicator(): void { + if (this.configuration.pageSize <= 0) return; + this.domElement.querySelectorAll(".pagination .page-item").forEach((el) => { + if (el.getAttribute("data-page-number") == this.currentPage.toString()) el.classList.add("active"); + else el.classList.remove("active"); + }); } - private renderPaginator() { + private renderPaginator(): void { if (this.configuration.pageSize === 0) return; const nav = document.createElement("nav"); nav.className = "float-right user-select-none"; @@ -74,6 +106,7 @@ export default class Grid { for (let i = 0; i < this.configuration.data.length / this.configuration.pageSize; i++) { const item = document.createElement("li"); item.className = "page-item"; + item.dataset.pageNumber = i.toString(); item.onclick = () => this.navigate(i); const link = document.createElement("span"); link.className = "page-link"; @@ -119,7 +152,7 @@ export default class Grid { wrapper.appendChild(row); } - private renderBody(data: Array = null): void { + private renderBody(data: Array = null): void { const wrapper = this.domElement.querySelector("table tbody"); wrapper.innerHTML = ""; let items = data ?? this.configuration.data; @@ -131,6 +164,9 @@ export default class Grid { row.dataset.id = item[this.configuration.id]; for (const val of this.configuration.columns) { const cell = document.createElement("td"); + if (val.width) cell.style.width = val.width; + if (val.maxWidth) cell.style.maxWidth = val.maxWidth; + if (val.minWidth) cell.style.minWidth = val.minWidth; if (typeof val.dataId === "string" && val.dataId.length > 0) { val.cellValue = item[val.dataId]; } @@ -167,6 +203,50 @@ export default class Grid { console.log("Grid Debug: " + message); } + private renderSearch() { + if (this.configuration.search.dataIds.length < 1) return; + const wrapper = document.createElement("div"); + const searchInput = document.createElement("input"); + searchInput.type = "text"; + searchInput.className = "form-control"; + searchInput.placeholder = "Søk"; + searchInput.oninput = () => this.search(searchInput.value); + wrapper.appendChild(searchInput); + this.domElement.appendChild(wrapper); + } + + private searchIndex: JsSearch.Search; + + private populateSearchIndex() { + if (this.configuration.search.dataIds.length < 1) return; + this.searchIndex = new JsSearch.Search("id"); + this.searchIndex.indexStrategy = new JsSearch.ExactWordIndexStrategy(); + this.configuration.search.dataIds.forEach((id) => this.searchIndex.addIndex(id)); + this.searchIndex.addDocuments(this.configuration.data); + console.log(this.searchIndex); + } + + public search(query: string): void { + if (this.configuration.search.dataIds.length < 1) return; + let result = this.searchIndex.search(query); + if (result.length === 0) result = this.configuration.data; + this.renderBody(result); + } + + public navigate(pageNumber): void { + 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; + this.renderCurrentPageIndicator(); + } + public removeByID(id: string): void { const itemIndex = this.configuration.data.findIndex((c) => c[this.configuration.id] === id); if (itemIndex !== -1) { @@ -177,19 +257,24 @@ export default class Grid { } } - public refreshData() { - this.renderBody(); + public refreshData(data?: Array) { + this.renderPaginator(); + this.navigate(0); + this.renderBody(data); } public render(el: Element): void { if (this.canRender) { - this.log("Grid starting render", true); + this.log("Grid starting render"); this.domElement = el; + this.renderSearch(); + this.populateSearchIndex(); this.renderWrapper(); this.renderHead(); this.renderBody(); this.renderPaginator(); - this.log("Grid was rendered", true); + this.renderCurrentPageIndicator(); + this.log("Grid was rendered"); } else { throw new GridError("render is not allowed due to invalid props"); } @@ -220,25 +305,3 @@ function isGridColumn(val: GridColumn | string): val is GridColumn { return false; } } - -interface GridProps { - id?: string; - debug?: boolean; - data: Array; - columns: Array; - pageSize?: number; -} - -interface GridConfiguration { - id: string; - debug: boolean; - data: Array; - columns: Array; - pageSize: number; -} - -interface GridColumn { - dataId?: string; - columnName: string | Function; - cellValue?: string | Function | Element; -} -- cgit v1.3