summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--grid/TODO2
-rw-r--r--grid/index.js7
-rw-r--r--grid/package-lock.json11
-rw-r--r--grid/package.json25
-rw-r--r--grid/src/grid.ts143
5 files changed, 134 insertions, 54 deletions
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<Object>;
+ columns: Array<GridColumn>;
+ pageSize?: number;
+ search?: SearchConfiguration;
+}
+
+interface GridConfiguration {
+ id: string;
+ debug: boolean;
+ data: Array<Object>;
+ columns: Array<GridColumn>;
+ pageSize: number;
+ search: SearchConfiguration;
+}
+
+interface SearchConfiguration {
+ dataIds: Array<string>;
+}
+
+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<any> = null): void {
+ private renderBody(data: Array<Object> = 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<Object>) {
+ 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<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;
-}