jb-grid 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. package/README.md +138 -11
  2. package/package.json +5 -8
  3. package/react/README.md +207 -202
  4. package/react/dist/Components/module-declaration.d.ts +1 -1
  5. package/react/dist/Content.d.ts +1 -2
  6. package/react/dist/Footer.d.ts +1 -2
  7. package/react/dist/Header.d.ts +3 -16
  8. package/react/dist/JBGrid.cjs.js +3631 -1
  9. package/react/dist/JBGrid.cjs.js.map +1 -1
  10. package/react/dist/JBGrid.d.ts +288 -26
  11. package/react/dist/JBGrid.js +3624 -1
  12. package/react/dist/JBGrid.js.map +1 -1
  13. package/react/dist/JBGrid.umd.js +3641 -1
  14. package/react/dist/JBGrid.umd.js.map +1 -1
  15. package/react/dist/JBGridData.d.ts +1 -5
  16. package/react/dist/JBGridViewModel.d.ts +28 -25
  17. package/react/dist/types.d.ts +17 -49
  18. package/react/lib/Components/Cell.tsx +17 -15
  19. package/react/lib/Components/ColumnHeader.tsx +44 -0
  20. package/react/lib/Components/ExpandToggle.tsx +14 -10
  21. package/react/lib/Components/FullscreenIcon.tsx +28 -0
  22. package/react/lib/Components/JBLoading.tsx +5 -4
  23. package/react/lib/Components/Pagination.tsx +1 -1
  24. package/react/lib/Components/PaginationInfo.tsx +71 -0
  25. package/react/lib/Components/RefreshIcon.tsx +11 -0
  26. package/react/lib/Components/Row.tsx +17 -15
  27. package/react/lib/Components/TableHeader.tsx +21 -0
  28. package/react/lib/Components/blob-loading.css +27 -23
  29. package/react/lib/Components/cell.css +7 -7
  30. package/react/lib/Components/content-error/ContentError.tsx +19 -16
  31. package/react/lib/Components/content-error/content-error.css +5 -11
  32. package/react/lib/Components/module-declaration.ts +61 -13
  33. package/react/lib/Content.tsx +23 -47
  34. package/react/lib/Footer.tsx +52 -79
  35. package/react/lib/Header.tsx +11 -24
  36. package/react/lib/JBGrid.tsx +129 -70
  37. package/react/lib/JBGridViewModel.ts +189 -452
  38. package/react/lib/footer.css +25 -55
  39. package/react/lib/header.css +20 -20
  40. package/react/lib/jb-grid.css +69 -55
  41. package/react/lib/types.ts +32 -129
  42. package/react/lib/variables.css +6 -0
  43. package/react/package.json +14 -10
  44. package/react/tsconfig.json +5 -4
  45. package/web-component/dist/index.cjs.js +559 -1
  46. package/web-component/dist/index.cjs.js.br +0 -0
  47. package/web-component/dist/index.cjs.js.gz +0 -0
  48. package/web-component/dist/index.cjs.js.map +1 -1
  49. package/web-component/dist/index.d.ts +161 -4
  50. package/web-component/dist/index.d.ts.map +1 -1
  51. package/web-component/dist/index.js +561 -1
  52. package/web-component/dist/index.js.br +0 -0
  53. package/web-component/dist/index.js.gz +0 -0
  54. package/web-component/dist/index.js.map +1 -1
  55. package/web-component/dist/index.umd.js +567 -1
  56. package/web-component/dist/index.umd.js.br +0 -0
  57. package/web-component/dist/index.umd.js.gz +0 -0
  58. package/web-component/dist/index.umd.js.map +1 -1
  59. package/web-component/dist/row/row.d.ts.map +1 -1
  60. package/web-component/dist/row/types.d.ts +3 -5
  61. package/web-component/dist/row/types.d.ts.map +1 -1
  62. package/web-component/dist/row/utils.d.ts +1 -2
  63. package/web-component/dist/row/utils.d.ts.map +1 -1
  64. package/web-component/dist/table-header/render.d.ts +2 -0
  65. package/web-component/dist/table-header/render.d.ts.map +1 -0
  66. package/web-component/dist/table-header/table-header.d.ts +9 -0
  67. package/web-component/dist/table-header/table-header.d.ts.map +1 -0
  68. package/web-component/dist/table-header/types.d.ts +4 -0
  69. package/web-component/dist/table-header/types.d.ts.map +1 -0
  70. package/web-component/dist/types.d.ts +6 -0
  71. package/web-component/dist/types.d.ts.map +1 -0
  72. package/web-component/dist/utils.d.ts +8 -0
  73. package/web-component/dist/utils.d.ts.map +1 -0
  74. package/web-component/lib/cell/cell.ts +10 -5
  75. package/web-component/lib/cell/style.css +2 -1
  76. package/web-component/lib/column-header/column-header.ts +117 -0
  77. package/web-component/lib/column-header/render.ts +14 -0
  78. package/web-component/lib/column-header/style.css +60 -0
  79. package/web-component/lib/column-header/types.ts +10 -0
  80. package/web-component/lib/fullscreen-icon/fullscreen-icon.ts +72 -0
  81. package/web-component/lib/fullscreen-icon/render.ts +23 -0
  82. package/web-component/lib/fullscreen-icon/style.css +44 -0
  83. package/web-component/lib/fullscreen-icon/types.ts +1 -0
  84. package/web-component/lib/i18n.ts +38 -0
  85. package/web-component/lib/index.ts +9 -4
  86. package/web-component/lib/pagination/README.md +4 -0
  87. package/web-component/lib/pagination/pagination.ts +25 -15
  88. package/web-component/lib/pagination/render.ts +13 -10
  89. package/web-component/lib/pagination/style.css +43 -31
  90. package/web-component/lib/pagination/types.ts +1 -1
  91. package/web-component/lib/pagination/variables.css +10 -0
  92. package/web-component/lib/pagination-info/pagination-info.ts +191 -0
  93. package/web-component/lib/pagination-info/render.ts +16 -0
  94. package/web-component/lib/pagination-info/style.css +47 -0
  95. package/web-component/lib/pagination-info/types.ts +12 -0
  96. package/web-component/lib/refresh-icon/refresh-icon.ts +79 -0
  97. package/web-component/lib/refresh-icon/render.ts +10 -0
  98. package/web-component/lib/refresh-icon/style.css +21 -0
  99. package/web-component/lib/row/render.ts +3 -3
  100. package/web-component/lib/row/row.ts +29 -15
  101. package/web-component/lib/row/style.css +30 -30
  102. package/web-component/lib/row/types.ts +7 -8
  103. package/web-component/lib/row/utils.ts +1 -40
  104. package/web-component/lib/row/variables.css +13 -0
  105. package/web-component/lib/table-header/render.ts +9 -0
  106. package/web-component/lib/table-header/style.css +29 -0
  107. package/web-component/lib/table-header/table-header.ts +50 -0
  108. package/web-component/lib/table-header/types.ts +4 -0
  109. package/web-component/lib/toggle/expand-toggle.ts +17 -13
  110. package/web-component/lib/toggle/render.ts +18 -13
  111. package/web-component/lib/toggle/style.css +18 -5
  112. package/web-component/lib/types.ts +6 -0
  113. package/web-component/lib/utils.ts +46 -0
  114. package/react/lib/JBGridBridgeExample.js +0 -90
  115. package/react/lib/JBGridData.ts +0 -51
  116. package/react/lib/global.d.ts +0 -15
  117. package/web-component/lib/global.d.ts +0 -15
  118. package/web-component/tsconfig.json +0 -17
@@ -1,452 +1,189 @@
1
- import React, { createContext, useContext, type RefObject } from 'react';
2
- import { observable, extendObservable, makeObservable, action, computed } from 'mobx';
3
- import type { ActionDispatchers, AnyObject, JBGridBridgeClassInterface, JBGridBridgeInterface, JBGridCallbacks, JBGridColumnDef, JBGridConfig, JBGridConfigInterface, JBGridFilter, JBGridI18nConfig, JBGridResponseData, JBGridRowData, JBGridRowDetail, JBGridStyles, SearchbarConfig } from './types.js';
4
- import type { JBSearchbarWebComponent, JBSearchbarValue } from 'jb-searchbar';
5
- import { defaultI18n } from './i18n.js';
6
- import { assign } from 'lodash';
7
- class JBGridViewModel<T extends AnyObject> {
8
- //we write computed style of grid here
9
- styles: JBGridStyles = {
10
- table: {
11
- generalCols: {
12
- gridTemplateColumns: "auto",
13
-
14
- },
15
- fullWidthCol: {
16
- gridColumn: '1 / end'
17
- },
18
- scrollIndent: {
19
- width: 'calc(100% - 17px)'
20
- },
21
- mainRowStyle: {
22
- gridTemplateColumns: "auto",
23
- }
24
- },
25
- searchBar: {
26
- },
27
- contentWrapper: {
28
- }
29
- }
30
- elements = {
31
- refreshIcon: React.createRef<SVGElement>(),
32
- searchbar: React.createRef<JBSearchbarWebComponent>()
33
- }
34
- //the whole component DOM store(referenced) in this variable
35
- JBGridComponentDom: RefObject<HTMLDivElement | null> = React.createRef();
36
- //keep wrapper DOM element for some purpose like wrapper changing in full screen functionality
37
- gridWrapperElement: HTMLElement | null = null;
38
- //when we start fetch new data from server it get true until load data is finished
39
- isLoading = false;
40
- //define bridge to convert grid data to server compatible data and convert server data to grid understandable format
41
- dataBridge: JBGridBridgeInterface;
42
- //add debounce feature to grid gotoPage function
43
- paginationDebounce;
44
- //keep grid searchbar height so on height
45
- isErrorOccurred = false;
46
- filter: JBGridFilter = {
47
- config: null,
48
- value: []
49
- }
50
- callBacks: JBGridCallbacks = {
51
- onFullscreenChange: () => { console.error('you must set onFullscreenChange callback to jb-grid component if you want it to work'); }
52
- }
53
- config: JBGridConfig<T>;
54
- i18n!: JBGridI18nConfig;
55
- constructor(onFullscreenChange: ((isFullScreen: boolean) => void) | undefined, config: JBGridConfigInterface<T>, bridge: JBGridBridgeClassInterface) {
56
- makeObservable(this, {
57
- styles: observable,
58
- isLoading: observable,
59
- isErrorOccurred: observable,
60
- filter: observable,
61
- exitFullScreenGrid: action,
62
- fullScreenGrid: action,
63
- fetchGridData: action,
64
- initFilter: action.bound,
65
- InitSize: action.bound,
66
- onFetchSuccess: action.bound,
67
- mergeObject: action,
68
- sendFirstRequest: action,
69
- refreshBtnClick: action.bound,
70
- setSortColumn: action.bound,
71
- goToPage: action.bound,
72
- openMainHeaderSection: action.bound,
73
- openSearchHeaderSection: action.bound,
74
- onPageSizeChange: action.bound,
75
- InitGrid: action.bound,
76
- refreshData: action.bound,
77
- showErrorPanel: action.bound,
78
- hideErrorPanel: action.bound,
79
- paginationDisplayNumbers: computed
80
- });
81
- if (config == undefined || config == null) {
82
- //when user dont pass config prop
83
- console.error("JBGrid need you to pass config as a prop to it \n and currently its null or undefined");
84
- }
85
- const observableConfig = observable(config);
86
- this.setI18n({});
87
- this.paginationDebounce = this.debounce(this.refreshData, 300);
88
-
89
- //TODO:add trigger function so user can call grid functions outside of grid js file
90
- const actionDispatchers: ActionDispatchers = Object.freeze({
91
- refreshData: () => this.refreshData(),
92
- fullScreenGrid: () => this.fullScreenGrid(),
93
- exitFullScreenGrid: () => this.exitFullScreenGrid()
94
- });
95
- this.config = observableConfig;
96
- this.config.actionDispatchers = actionDispatchers;
97
- if (typeof bridge != 'function') {
98
- //no bridge provided
99
- }
100
- this.dataBridge = new bridge();
101
- if (typeof onFullscreenChange == "function") {
102
- this.callBacks.onFullscreenChange = onFullscreenChange;
103
- }
104
- this.InitGrid();
105
- }
106
- setI18n(newValue: JBGridI18nConfig) {
107
- //loadash assign work from left to right so newValue has top priority
108
- this.i18n = assign({}, defaultI18n, newValue);
109
- }
110
- InitGrid() {
111
- //init grid config on load or change
112
- this.InitSize();
113
-
114
- }
115
- onComponentDidMount(searchbarConfig: SearchbarConfig | null) {
116
- this.sendFirstRequest();
117
- this.initFilter(searchbarConfig);
118
- }
119
- mergeObject(inputConfig: JBGridConfig<any>, defaultConfig: JBGridConfig<any>) {
120
- const addedProperty = {};
121
- for (const prop in defaultConfig) {
122
- if (inputConfig[prop] == undefined || inputConfig[prop] == null) {
123
- addedProperty[prop] = defaultConfig[prop];
124
- }
125
- }
126
- extendObservable(inputConfig, addedProperty);
127
- return inputConfig;
128
- }
129
- sendFirstRequest() {
130
- this.isLoading = true;
131
- this.fetchGridData().then(action(() => {
132
- this.isLoading = false;
133
- this.hideErrorPanel();
134
- })).catch(action((e: any) => {
135
- this.isLoading = false;
136
- this.showErrorPanel();
137
- }));
138
- }
139
- initFilter(searchbarConfig: SearchbarConfig | null) {
140
- if (searchbarConfig && this.elements.searchbar.current) {
141
- this.elements.searchbar.current.filterList = searchbarConfig.columnList;
142
- this.elements.searchbar.current.searchOnChange = searchbarConfig.searchOnChange === true ? searchbarConfig.searchOnChange : false;
143
- this.elements.searchbar.current.addEventListener('search', (e) => {
144
- this.elements.searchbar.current!.isLoading = true;
145
- const target = e.target as JBSearchbarWebComponent;
146
- this.onSearch(target.value).finally(() => {
147
- this.elements.searchbar.current!.isLoading = false;
148
- });
149
- });
150
- //this.elements.searchbar.current.addEventListener('');
151
- this.filter.config = searchbarConfig;
152
-
153
- }
154
- }
155
- InitSize() {
156
- //init table width column
157
- const scrollWidth = this.getScrollbarWidth();
158
- this.styles.table.scrollIndent.width = `calc(100% - ${scrollWidth}px)`;
159
- //config css grid for table layout
160
- let gridTemplateColumns = "";
161
- this.config.table.columns.forEach((item) => {
162
- if (item.width != null || item.width != undefined) {
163
- if (typeof (item.width) == "number") {
164
- gridTemplateColumns += ` ${item.width}px`;
165
- } else {
166
- gridTemplateColumns += ` ${item.width}`;
167
- }
168
-
169
- } else {
170
- gridTemplateColumns += " 1fr";
171
- }
172
- });
173
- this.styles.table.generalCols.gridTemplateColumns = gridTemplateColumns;
174
- this.styles.table.fullWidthCol.gridColumn = `1 / ${this.config.table.columns.length + 1}`;
175
- }
176
- getScrollbarWidth() {
177
- const outer = document.createElement("div");
178
- outer.style.visibility = "hidden";
179
- outer.style.width = "100px";
180
- document.body.appendChild(outer);
181
-
182
- const widthNoScroll = outer.offsetWidth;
183
- // force scrollbars
184
- outer.style.overflow = "scroll";
185
-
186
- // add inner div
187
- const inner = document.createElement("div");
188
- inner.style.width = "100%";
189
- outer.appendChild(inner);
190
-
191
- const widthWithScroll = inner.offsetWidth;
192
-
193
- // remove div's
194
- outer.parentNode?.removeChild(outer);
195
-
196
- return widthNoScroll - widthWithScroll;
197
- }
198
- fetchGridData() {
199
- const fetchGridDataPromise = new Promise((resolve, reject) => {
200
- const requestBody = this.CreateRequestBody();
201
- this.dataBridge.getData(this.config.data.requestParams, requestBody).then(action((data) => {
202
- const bridgeData = this.dataBridge.mapServerResponseDataToGridData(data);
203
- if (bridgeData.pageIndex == this.config.page.index) {
204
- this.config.data.data = [];
205
- //check user don't change page during loading time if he do we wait for latest response
206
- this.standardData(bridgeData.content).then((content: JBGridRowData<T>[]) => {
207
- const data = { ...bridgeData, content };
208
- this.onFetchSuccess(data);
209
- resolve(null);
210
- });
211
- } else {
212
- console.error('jb-grid requested page index is different from response page index it maybe a bridge problem or server data problem');
213
- }
214
- })).catch((err) => {
215
- reject(err);
216
- });
217
- });
218
- return fetchGridDataPromise;
219
- }
220
- onFetchSuccess(data: JBGridResponseData<T>) {
221
- this.config.data.data = data.content;
222
- this.config.data.metaData.startItemIndex = data.startItemIndex;
223
- this.config.data.metaData.endItemIndex = data.endItemIndex;
224
- this.config.data.metaData.totalItemsCount = data.totalItemsCount;
225
- this.config.page.totalPages = data.totalPages;
226
- }
227
- standardData(data: AnyObject[]) {
228
- return new Promise<JBGridRowData<T>[]>((resolve) => {
229
- const items: JBGridRowData<AnyObject>[] = data.map((item) => {
230
- const detail: JBGridRowDetail = {
231
- jbGridDetail: {
232
- isDeleting: false,
233
- isDeleted: false,
234
- isRecovering: false,
235
- isExpanded: false
236
- }
237
- };
238
- const row: JBGridRowData<AnyObject> = Object.assign({}, item, detail);
239
- return row;
240
- });
241
- //in case of user want to modify or add custom field to our observable array
242
- if (typeof this.config.callbacks.onDataStandardizing == "function") {
243
- const response = this.config.callbacks.onDataStandardizing<T>(items);
244
- if (response instanceof Promise) {
245
- response.then((content) => {
246
- resolve(content);
247
- });
248
- } else {
249
- resolve(response);
250
- }
251
- //end of callback block
252
- } else {
253
- resolve(items as JBGridRowData<T>[]);
254
- }
255
-
256
-
257
-
258
- });
259
-
260
- }
261
- CreateRequestBody() {
262
- const sortColumn = this.config.table.columns.find(x => x.sort) || null;
263
- const requestBody = this.dataBridge.createRequestBody(this.config.page, this.filter.value, sortColumn, this.config.data);
264
- return requestBody;
265
- }
266
- goToFirstPage() {
267
- const currentPage = this.config.page.index;
268
- if (currentPage != 1) {
269
- this.goToPage(1);
270
- }
271
-
272
- }
273
- refreshBtnClick() {
274
- const anime = this.playRefreshBtnAnimation();
275
- this.refreshData().then(() => {
276
- this.stopRefreshBtnAnimation(anime);
277
- }).catch((e) => {
278
- console.error('Error while refreshing data', e);
279
- this.stopRefreshBtnAnimation(anime);
280
- });
281
- }
282
- playRefreshBtnAnimation() {
283
- const anime = this.elements.refreshIcon.current!.animate([{ transform: "rotate(0deg)" }, { transform: "rotate(360deg)" }], { id: 'rotate', duration: 400, direction: "reverse", iterations: Infinity });
284
- return anime;
285
- }
286
- stopRefreshBtnAnimation(anime: Animation) {
287
- anime.cancel();
288
- }
289
- goToPage(destinationPageIndex: number) {
290
- return new Promise((resolve, reject) => {
291
- //for navigate in pages you must call this function and every other way is forbidden
292
- this.config.page.index = destinationPageIndex;
293
- this.paginationDebounce()
294
- .then(() => {
295
- resolve(null);
296
- if (this.config.callbacks.onPageIndexChange) {
297
- this.config.callbacks.onPageIndexChange(destinationPageIndex);
298
- }
299
- }).catch((e: any) => {
300
- reject(e);
301
- });
302
- });
303
- }
304
- debounce(func: (...args: any[]) => any, delay: number) {
305
- //create a waiting time for serial function call and execute last function execute request
306
- let inDebounce: ReturnType<typeof setTimeout>;
307
- const debounceInstance = (...inputs: any[]) => {
308
- return new Promise((resolve, reject) => {
309
- const self: JBGridViewModel<T> = this;
310
- const args = inputs;
311
- clearTimeout(inDebounce);
312
- inDebounce = setTimeout(
313
- () => func.apply(self, args)
314
- .then((args: any) => {
315
- resolve(args);
316
- }).catch((e: Error) => { reject(e); })
317
- , delay);
318
- });
319
- };
320
- return debounceInstance;
321
- }
322
- refreshData(): Promise<void> {
323
- const refreshDataPromise = new Promise<void>((resolve, reject) => {
324
- this.isLoading = true;
325
- this.fetchGridData().then(action(() => {
326
- this.isLoading = false;
327
- this.hideErrorPanel();
328
- resolve();
329
- })).catch(action((e) => {
330
- this.isLoading = false;
331
- this.showErrorPanel();
332
- reject(e);
333
- }));
334
- });
335
- //every time we need to change showing data we must call this func
336
- return refreshDataPromise;
337
- }
338
- onSearch(filterList: JBSearchbarValue) {
339
- this.filter.value = filterList;
340
- const onSearchPromise = new Promise((resolve, reject) => {
341
- //reset pagination when filter change
342
- this.goToPage(1).then(() => {
343
- resolve(null);
344
- }).catch((e) => {
345
- reject(e);
346
- });
347
- });
348
- return onSearchPromise;
349
- }
350
- onPageSizeChange(e) {
351
- this.config.page.size = parseInt(e.target.value);
352
- this.goToPage(1);
353
- }
354
- onFullScreenBtnClicked(currentValue: boolean) {
355
- const newValue = !currentValue;
356
- this.callBacks.onFullscreenChange(newValue);
357
- }
358
- onFullscreenChanged(newValue: boolean) {
359
- if (newValue == true) {
360
- this.fullScreenGrid();
361
- } else {
362
- this.exitFullScreenGrid();
363
- }
364
- }
365
- fullScreenGrid() {
366
- //TODO: handle if another grid is open before new request come up
367
- const container = document.createElement('div');
368
- container.classList.add('jb-grid-full-screen-container');
369
- document.body.append(container);
370
- this.JBGridComponentDom;
371
- const child = document.createElement('div');
372
- child.innerHTML = "";
373
- this.gridWrapperElement = this.JBGridComponentDom!.current!.parentElement!;
374
- container.append(this.JBGridComponentDom.current as Node);
375
- //TODO:call on full screen call back
376
- }
377
- exitFullScreenGrid() {
378
- const container = document.querySelector('.jb-grid-full-screen-container') as HTMLDivElement;
379
- if (this.gridWrapperElement) {
380
- //put grid element back to their orginal place
381
- this.gridWrapperElement.append(this.JBGridComponentDom.current!);
382
- //remove added temp fullscreen container
383
- }
384
- container[0].remove();
385
- }
386
- setSortColumn(column: JBGridColumnDef) {
387
- if (column.sortable) {
388
- if (column.sort) {
389
- //if we just change sort order
390
- column.sort = column.sort.toUpperCase() == "ASC" ? "DESC" : "ASC";
391
- } else {
392
- //we user chnge sort column
393
- const prevColumnSort = this.config.table.columns.find(x => x.sort);
394
- if (prevColumnSort) {
395
- prevColumnSort.sort = undefined;
396
- }
397
- column.sort = "ASC";
398
- }
399
- this.refreshData();
400
- }
401
- }
402
- changePageNumberToInput() {
403
- //when user click on page number
404
- //TODO: change page Input method to text input
405
- const pageNumber: string | null = prompt(this.i18n.messages?.EnterPageNumberMessage, this.config.page.totalPages.toString());
406
- if (pageNumber && Number(pageNumber) > 0 && Number(pageNumber) < this.config.page.totalPages) {
407
- this.goToPage(Number(pageNumber));
408
- }
409
- }
410
- openSearchHeaderSection() {
411
- this.elements.searchbar.current?.focus();
412
- this.config.states.headerSection = "SEARCH";
413
- }
414
- openMainHeaderSection() {
415
- this.config.states.headerSection = "MAIN";
416
- }
417
- showErrorPanel() {
418
- //when we couldn't connect to server or get error from server for our request we show error panel to user
419
- this.isErrorOccurred = true;
420
- }
421
- hideErrorPanel() {
422
- this.isErrorOccurred = false;
423
- }
424
- get paginationDisplayNumbers() {
425
- return {
426
- currentPage: this.toPersianNumber(this.config.page.index),
427
- nextPage: this.config.page.index + 1 <= this.config.page.totalPages ? this.toPersianNumber(this.config.page.index + 1) : "",
428
- next2Page: this.config.page.index + 2 <= this.config.page.totalPages ? this.toPersianNumber(this.config.page.index + 2) : "",
429
- prevPage: this.config.page.index - 1 > 0 ? this.toPersianNumber(this.config.page.index - 1) : "",
430
- prev2Page: this.config.page.index - 2 > 0 ? this.toPersianNumber(this.config.page.index - 2) : "",
431
- totalItemsCount: this.toPersianNumber(this.config.data.metaData.totalItemsCount),
432
- startItemIndex:this.toPersianNumber(this.config.data.metaData.startItemIndex),
433
- endItemIndex:this.toPersianNumber(this.config.data.metaData.endItemIndex),
434
- pageSizes:[this.toPersianNumber(20),this.toPersianNumber(30),this.toPersianNumber(50),this.toPersianNumber(100)]
435
- };
436
- }
437
- toPersianNumber(input: string | number) {
438
- if (this.i18n.showPersianNumber) {
439
- const inputString = input.toString();
440
- const correctedString = inputString.replace(/[0-9]/g, function (word) {
441
- return String.fromCharCode(1776 + Number(word));
442
- });
443
- return correctedString;
444
- }
445
- return input;
446
- }
447
-
448
- }
449
-
450
- export default JBGridViewModel;
451
- export const JBGridContext = createContext<JBGridViewModel<AnyObject> | null>(null);
452
- export const useJBGridVM = () => useContext(JBGridContext);
1
+ import React, { createContext, useContext, type RefObject } from 'react';
2
+ import type { JBGridCallbacks, JBGridI18nConfig, JBGridPaginationMeta } from './types.js';
3
+ import type { JBRefreshIconWebComponent } from './Components/module-declaration.js';
4
+ import { defaultI18n } from './i18n.js';
5
+ import { assign } from 'lodash';
6
+
7
+ type StateChangeCallback = (() => void) | undefined;
8
+ type HeaderSection = "MAIN" | "SEARCH";
9
+
10
+ export type JBGridState = {
11
+ pageIndex: number,
12
+ pageSize: number,
13
+ totalPages: number,
14
+ metaData: JBGridPaginationMeta
15
+ }
16
+
17
+ class JBGridViewModel {
18
+ #onStateChange: StateChangeCallback;
19
+ headerSection: HeaderSection = "MAIN";
20
+ elements = {
21
+ refreshIcon: React.createRef<JBRefreshIconWebComponent>()
22
+ }
23
+ JBGridComponentDom: RefObject<HTMLDivElement | null> = React.createRef();
24
+ gridWrapperElement: HTMLElement | null = null;
25
+ callBacks: JBGridCallbacks = {}
26
+ page = {
27
+ index: 1,
28
+ size: 20,
29
+ totalPages: 1
30
+ }
31
+ metaData: JBGridPaginationMeta = {
32
+ startItemIndex: 0,
33
+ endItemIndex: 0,
34
+ totalItemsCount: 0
35
+ }
36
+ i18n!: JBGridI18nConfig;
37
+
38
+ constructor(callBacks: JBGridCallbacks | undefined, onStateChange?: () => void) {
39
+ this.#onStateChange = onStateChange;
40
+ this.bindMethods();
41
+ this.setI18n({}, false);
42
+ this.callBacks = callBacks ?? {};
43
+ }
44
+
45
+ bindMethods() {
46
+ this.exitFullScreenGrid = this.exitFullScreenGrid.bind(this);
47
+ this.fullScreenGrid = this.fullScreenGrid.bind(this);
48
+ this.refreshBtnClick = this.refreshBtnClick.bind(this);
49
+ this.goToPage = this.goToPage.bind(this);
50
+ this.openMainHeaderSection = this.openMainHeaderSection.bind(this);
51
+ this.openSearchHeaderSection = this.openSearchHeaderSection.bind(this);
52
+ this.onPageSizeChange = this.onPageSizeChange.bind(this);
53
+ this.setPageSize = this.setPageSize.bind(this);
54
+ }
55
+
56
+ setStateChangeCallback(callback: StateChangeCallback) {
57
+ this.#onStateChange = callback;
58
+ }
59
+
60
+ setCallbacks(callBacks: JBGridCallbacks | undefined) {
61
+ this.callBacks = callBacks ?? {};
62
+ }
63
+
64
+ setGridState(state: JBGridState) {
65
+ this.page = {
66
+ index: state.pageIndex,
67
+ size: state.pageSize,
68
+ totalPages: state.totalPages
69
+ };
70
+ this.metaData = state.metaData;
71
+ }
72
+
73
+ notifyStateChange() {
74
+ this.#onStateChange?.();
75
+ }
76
+
77
+ setI18n(newValue: JBGridI18nConfig, notifyStateChange = true) {
78
+ this.i18n = assign({}, defaultI18n, newValue);
79
+ if (notifyStateChange) {
80
+ this.notifyStateChange();
81
+ }
82
+ }
83
+
84
+ goToFirstPage() {
85
+ if (this.page.index != 1) {
86
+ this.goToPage(1);
87
+ }
88
+ }
89
+
90
+ refreshBtnClick() {
91
+ this.elements.refreshIcon.current?.play();
92
+ Promise.resolve(this.callBacks.onRefresh?.()).finally(() => {
93
+ this.elements.refreshIcon.current?.stop();
94
+ });
95
+ }
96
+
97
+ goToPage(destinationPageIndex: number) {
98
+ this.callBacks.onPageIndexChange?.(destinationPageIndex);
99
+ }
100
+
101
+ onPageSizeChange(e: React.ChangeEvent<HTMLSelectElement>) {
102
+ const newPageSize = parseInt(e.target.value);
103
+ this.setPageSize(newPageSize);
104
+ }
105
+
106
+ setPageSize(newPageSize: number) {
107
+ this.callBacks.onPageSizeChange?.(newPageSize);
108
+ }
109
+
110
+ onFullScreenBtnClicked(currentValue: boolean) {
111
+ const newValue = !currentValue;
112
+ if (newValue) {
113
+ this.callBacks.onFullscreen?.();
114
+ } else {
115
+ this.callBacks.onExitFullscreen?.();
116
+ }
117
+ this.callBacks.onFullscreenChange?.(newValue);
118
+ }
119
+
120
+ onFullscreenChanged(newValue: boolean) {
121
+ if (newValue == true) {
122
+ this.fullScreenGrid();
123
+ } else {
124
+ this.exitFullScreenGrid();
125
+ }
126
+ }
127
+
128
+ fullScreenGrid() {
129
+ const container = document.createElement('div');
130
+ container.classList.add('jb-grid-full-screen-container');
131
+ document.body.append(container);
132
+ this.gridWrapperElement = this.JBGridComponentDom!.current!.parentElement!;
133
+ container.append(this.JBGridComponentDom.current as Node);
134
+ }
135
+
136
+ exitFullScreenGrid() {
137
+ const container = document.querySelector('.jb-grid-full-screen-container') as HTMLDivElement;
138
+ if (this.gridWrapperElement) {
139
+ this.gridWrapperElement.append(this.JBGridComponentDom.current!);
140
+ }
141
+ container?.remove();
142
+ }
143
+
144
+ changePageNumberToInput() {
145
+ const pageNumber: string | null = prompt(this.i18n.messages?.EnterPageNumberMessage, this.page.totalPages.toString());
146
+ if (pageNumber && Number(pageNumber) > 0 && Number(pageNumber) < this.page.totalPages) {
147
+ this.goToPage(Number(pageNumber));
148
+ }
149
+ }
150
+
151
+ openSearchHeaderSection() {
152
+ this.headerSection = "SEARCH";
153
+ this.notifyStateChange();
154
+ }
155
+
156
+ openMainHeaderSection() {
157
+ this.headerSection = "MAIN";
158
+ this.notifyStateChange();
159
+ }
160
+
161
+ get paginationDisplayNumbers() {
162
+ return {
163
+ currentPage: this.toPersianNumber(this.page.index),
164
+ nextPage: this.page.index + 1 <= this.page.totalPages ? this.toPersianNumber(this.page.index + 1) : "",
165
+ next2Page: this.page.index + 2 <= this.page.totalPages ? this.toPersianNumber(this.page.index + 2) : "",
166
+ prevPage: this.page.index - 1 > 0 ? this.toPersianNumber(this.page.index - 1) : "",
167
+ prev2Page: this.page.index - 2 > 0 ? this.toPersianNumber(this.page.index - 2) : "",
168
+ totalItemsCount: this.toPersianNumber(this.metaData.totalItemsCount),
169
+ startItemIndex: this.toPersianNumber(this.metaData.startItemIndex),
170
+ endItemIndex: this.toPersianNumber(this.metaData.endItemIndex),
171
+ pageSizes: [this.toPersianNumber(20), this.toPersianNumber(30), this.toPersianNumber(50), this.toPersianNumber(100)]
172
+ };
173
+ }
174
+
175
+ toPersianNumber(input: string | number) {
176
+ if (this.i18n.showPersianNumber) {
177
+ const inputString = input.toString();
178
+ const correctedString = inputString.replace(/[0-9]/g, function (word) {
179
+ return String.fromCharCode(1776 + Number(word));
180
+ });
181
+ return correctedString;
182
+ }
183
+ return input;
184
+ }
185
+ }
186
+
187
+ export default JBGridViewModel;
188
+ export const JBGridContext = createContext<JBGridViewModel | null>(null);
189
+ export const useJBGridVM = () => useContext(JBGridContext);