evui 3.3.36 → 3.3.39

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 (141) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +40 -40
  3. package/dist/evui.common.js +1907 -1832
  4. package/dist/evui.common.js.map +1 -1
  5. package/dist/evui.umd.js +1907 -1832
  6. package/dist/evui.umd.js.map +1 -1
  7. package/dist/evui.umd.min.js +1 -1
  8. package/dist/evui.umd.min.js.map +1 -1
  9. package/dist/img/{EVUI.7f3588fb.svg → EVUI.b82ee81a.svg} +292 -292
  10. package/dist/img/{icon_mysql.7ea26d5d.svg → icon_mysql.1085fdc9.svg} +78 -78
  11. package/dist/img/{icon_oracle.9009b108.svg → icon_oracle.0572d3ee.svg} +13 -13
  12. package/dist/img/{icon_postgresql.f8fffba9.svg → icon_postgresql.ee12bde8.svg} +58 -58
  13. package/package.json +61 -61
  14. package/src/common/emitter.js +20 -20
  15. package/src/common/utils.debounce.js +223 -223
  16. package/src/common/utils.js +134 -134
  17. package/src/common/utils.table.js +78 -78
  18. package/src/common/utils.throttle.js +83 -83
  19. package/src/common/utils.tree.js +18 -18
  20. package/src/components/button/Button.vue +198 -198
  21. package/src/components/button/index.js +7 -7
  22. package/src/components/buttonGroup/ButtonGroup.vue +11 -11
  23. package/src/components/buttonGroup/index.js +7 -7
  24. package/src/components/calendar/Calendar.vue +661 -661
  25. package/src/components/calendar/index.js +7 -7
  26. package/src/components/calendar/uses.js +1272 -1272
  27. package/src/components/chart/Chart.vue +189 -192
  28. package/src/components/chart/chart.core.js +870 -870
  29. package/src/components/chart/element/element.bar.js +524 -524
  30. package/src/components/chart/element/element.bar.time.js +156 -156
  31. package/src/components/chart/element/element.heatmap.js +533 -533
  32. package/src/components/chart/element/element.line.js +339 -339
  33. package/src/components/chart/element/element.pie.js +197 -197
  34. package/src/components/chart/element/element.scatter.js +184 -184
  35. package/src/components/chart/element/element.tip.js +550 -542
  36. package/src/components/chart/helpers/helpers.canvas.js +265 -265
  37. package/src/components/chart/helpers/helpers.constant.js +206 -206
  38. package/src/components/chart/helpers/helpers.util.js +346 -338
  39. package/src/components/chart/index.js +9 -9
  40. package/src/components/chart/model/index.js +4 -4
  41. package/src/components/chart/model/model.series.js +93 -93
  42. package/src/components/chart/model/model.store.js +977 -967
  43. package/src/components/chart/plugins/plugins.interaction.js +769 -769
  44. package/src/components/chart/plugins/plugins.legend.gradient.js +602 -602
  45. package/src/components/chart/plugins/plugins.legend.js +1155 -1151
  46. package/src/components/chart/plugins/plugins.pie.js +254 -254
  47. package/src/components/chart/plugins/plugins.title.js +56 -56
  48. package/src/components/chart/plugins/plugins.tooltip.js +692 -692
  49. package/src/components/chart/scale/scale.js +848 -848
  50. package/src/components/chart/scale/scale.linear.js +38 -38
  51. package/src/components/chart/scale/scale.logarithmic.js +128 -128
  52. package/src/components/chart/scale/scale.step.js +336 -336
  53. package/src/components/chart/scale/scale.time.category.js +277 -277
  54. package/src/components/chart/scale/scale.time.js +48 -48
  55. package/src/components/chart/style/chart.scss +312 -312
  56. package/src/components/chart/uses.js +264 -252
  57. package/src/components/checkbox/Checkbox.vue +200 -200
  58. package/src/components/checkbox/index.js +7 -7
  59. package/src/components/checkboxGroup/CheckboxGroup.vue +44 -44
  60. package/src/components/checkboxGroup/index.js +7 -7
  61. package/src/components/contextMenu/ContextMenu.vue +80 -80
  62. package/src/components/contextMenu/MenuList.vue +149 -149
  63. package/src/components/contextMenu/index.js +7 -7
  64. package/src/components/contextMenu/uses.js +203 -203
  65. package/src/components/datePicker/DatePicker.vue +437 -437
  66. package/src/components/datePicker/index.js +7 -7
  67. package/src/components/datePicker/uses.js +419 -419
  68. package/src/components/grid/Grid.vue +827 -827
  69. package/src/components/grid/grid.filter.window.vue +493 -493
  70. package/src/components/grid/grid.pagination.vue +75 -75
  71. package/src/components/grid/grid.summary.vue +265 -265
  72. package/src/components/grid/grid.toolbar.vue +26 -26
  73. package/src/components/grid/index.js +11 -11
  74. package/src/components/grid/style/grid.scss +263 -263
  75. package/src/components/grid/uses.js +1002 -1007
  76. package/src/components/icon/Icon.vue +49 -49
  77. package/src/components/icon/index.js +8 -8
  78. package/src/components/inputNumber/InputNumber.vue +212 -212
  79. package/src/components/inputNumber/index.js +7 -7
  80. package/src/components/inputNumber/uses.js +217 -217
  81. package/src/components/loading/Loading.vue +125 -125
  82. package/src/components/loading/index.js +7 -7
  83. package/src/components/menu/Menu.vue +68 -68
  84. package/src/components/menu/MenuItem.vue +187 -187
  85. package/src/components/menu/index.js +7 -7
  86. package/src/components/message/Message.vue +223 -223
  87. package/src/components/message/index.js +31 -31
  88. package/src/components/messageBox/MessageBox.vue +358 -358
  89. package/src/components/messageBox/index.js +22 -22
  90. package/src/components/notification/Notification.vue +316 -316
  91. package/src/components/notification/index.js +49 -49
  92. package/src/components/pagination/Pagination.vue +271 -271
  93. package/src/components/pagination/index.js +7 -7
  94. package/src/components/pagination/pageButton.vue +30 -30
  95. package/src/components/progress/Progress.vue +139 -139
  96. package/src/components/progress/index.js +7 -7
  97. package/src/components/radio/Radio.vue +159 -159
  98. package/src/components/radio/index.js +7 -7
  99. package/src/components/radioGroup/RadioGroup.vue +41 -41
  100. package/src/components/radioGroup/index.js +7 -7
  101. package/src/components/scheduler/Scheduler.vue +149 -149
  102. package/src/components/scheduler/index.js +7 -7
  103. package/src/components/scheduler/uses.js +183 -183
  104. package/src/components/select/Select.vue +440 -440
  105. package/src/components/select/index.js +7 -7
  106. package/src/components/select/uses.js +270 -270
  107. package/src/components/slider/Slider.vue +505 -505
  108. package/src/components/slider/index.js +7 -7
  109. package/src/components/slider/uses.js +390 -390
  110. package/src/components/tabPanel/TabPanel.vue +74 -74
  111. package/src/components/tabPanel/index.js +7 -7
  112. package/src/components/tabs/Tabs.vue +517 -517
  113. package/src/components/tabs/index.js +7 -7
  114. package/src/components/textField/TextField.vue +375 -375
  115. package/src/components/textField/index.js +7 -7
  116. package/src/components/timePicker/TimePicker.vue +352 -352
  117. package/src/components/timePicker/index.js +7 -7
  118. package/src/components/toggle/Toggle.vue +115 -115
  119. package/src/components/toggle/index.js +7 -7
  120. package/src/components/tree/Tree.vue +313 -313
  121. package/src/components/tree/TreeNode.vue +293 -293
  122. package/src/components/tree/index.js +7 -7
  123. package/src/components/treeGrid/TreeGrid.vue +758 -758
  124. package/src/components/treeGrid/TreeGridNode.vue +275 -275
  125. package/src/components/treeGrid/index.js +9 -9
  126. package/src/components/treeGrid/style/treeGrid.scss +261 -261
  127. package/src/components/treeGrid/treeGrid.toolbar.vue +26 -26
  128. package/src/components/treeGrid/uses.js +867 -867
  129. package/src/components/window/Window.vue +329 -329
  130. package/src/components/window/index.js +7 -7
  131. package/src/components/window/uses.js +899 -899
  132. package/src/directives/clickoutside.js +90 -90
  133. package/src/main.js +116 -116
  134. package/src/style/components/input.scss +108 -108
  135. package/src/style/functions.scss +3 -3
  136. package/src/style/index.scss +6 -6
  137. package/src/style/lib/fonts/EVUI.svg +292 -292
  138. package/src/style/lib/icon.css +888 -888
  139. package/src/style/mixins.scss +94 -94
  140. package/src/style/themes.scss +67 -67
  141. package/src/style/variables.scss +22 -22
@@ -1,867 +1,867 @@
1
- import { getCurrentInstance, nextTick } from 'vue';
2
- import { numberWithComma } from '@/common/utils';
3
-
4
- export const commonFunctions = (params) => {
5
- const { props } = getCurrentInstance();
6
- const { checkInfo } = params;
7
- /**
8
- * 해당 컬럼이 사용자 지정 컬럼인지 확인한다.
9
- *
10
- * @param {object} column - 컬럼 정보
11
- * @returns {boolean} 사용자 지정 컬럼 유무
12
- */
13
- const isRenderer = (column = {}) => !!column?.render?.use;
14
- const getComponentName = (type = '') => {
15
- const setUpperCaseFirstStr = str => str.charAt(0).toUpperCase() + str.slice(1);
16
- const rendererStr = 'Renderer';
17
- let typeStr = '';
18
- if (type.indexOf('_') !== -1) {
19
- const typeStrArray = type.split('_');
20
- for (let ix = 0; ix < typeStrArray.length; ix++) {
21
- typeStr += setUpperCaseFirstStr(typeStrArray[ix]);
22
- }
23
- } else {
24
- typeStr = setUpperCaseFirstStr(type);
25
- }
26
- return typeStr + rendererStr;
27
- };
28
- /**
29
- * 데이터 타입에 따라 변환된 데이터을 반환한다.
30
- *
31
- * @param {object} column - 컬럼 정보
32
- * @param {number|string} value - 데이터
33
- * @returns {number|string} 변환된 데이터
34
- */
35
- const getConvertValue = (column, value) => {
36
- let convertValue = column.type === 'number' || column.type === 'float' ? Number(value) : value;
37
-
38
- if (column.type === 'number') {
39
- convertValue = numberWithComma(value);
40
- convertValue = convertValue === false ? value : convertValue;
41
- } else if (column.type === 'float') {
42
- const floatValue = convertValue.toFixed(column.decimal ?? 3);
43
- convertValue = floatValue.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
44
- }
45
-
46
- return convertValue;
47
- };
48
- /**
49
- * 전달받은 필드명과 일치하는 컬럼 인덱스를 반환한다.
50
- *
51
- * @param {string} field - 컬럼 필드명
52
- * @returns {number} 일치한다면 컬럼 인덱스, 일치하지 않는다면 -1
53
- */
54
- const getColumnIndex = field => props.columns.findIndex(column => column.field === field);
55
- const setPixelUnit = (value) => {
56
- let size = value;
57
- const hasPx = size.toString().indexOf('px') >= 0;
58
- const hasPct = size.toString().indexOf('%') >= 0;
59
- if (!hasPx && !hasPct) {
60
- size = `${size}px`;
61
- }
62
- return size;
63
- };
64
- const checkHeader = (rows) => {
65
- checkInfo.isHeaderChecked = !!rows.length && rows.every(row => row.checked);
66
- const disabledList = rows.filter(row => row._disabled);
67
- if (disabledList.length) {
68
- const checkedList = rows.filter(row => row.checked);
69
- if (disabledList.length + checkedList.length === rows.length) {
70
- checkInfo.isHeaderChecked = true;
71
- }
72
- }
73
- };
74
- return {
75
- isRenderer,
76
- getComponentName,
77
- getConvertValue,
78
- getColumnIndex,
79
- setPixelUnit,
80
- checkHeader,
81
- };
82
- };
83
-
84
- export const scrollEvent = (params) => {
85
- const {
86
- scrollInfo,
87
- stores,
88
- elementInfo,
89
- resizeInfo,
90
- pageInfo,
91
- summaryScroll,
92
- getPagingData,
93
- updatePagingInfo,
94
- } = params;
95
- /**
96
- * 수직 스크롤의 위치 계산 후 적용한다.
97
- */
98
- const updateVScroll = (isScroll) => {
99
- let store = stores.showTreeStore;
100
- if (pageInfo.isClientPaging) {
101
- store = getPagingData();
102
- }
103
- const bodyEl = elementInfo.body;
104
- if (bodyEl) {
105
- const rowHeight = resizeInfo.rowHeight;
106
- const rowCount = bodyEl.clientHeight > rowHeight
107
- ? Math.ceil(bodyEl.clientHeight / rowHeight) : store.length;
108
- const totalScrollHeight = store.length * rowHeight;
109
- let firstVisibleIndex = Math.floor(bodyEl.scrollTop / rowHeight);
110
- if (firstVisibleIndex > store.length - 1) {
111
- firstVisibleIndex = 0;
112
- }
113
-
114
- const lastVisibleIndex = firstVisibleIndex + rowCount + 1;
115
- const firstIndex = Math.max(firstVisibleIndex, 0);
116
- const lastIndex = lastVisibleIndex;
117
- const tableEl = elementInfo.table;
118
-
119
- stores.viewStore = store.slice(firstIndex, lastIndex);
120
- scrollInfo.hasVerticalScrollBar = rowCount < store.length
121
- || bodyEl.clientHeight < tableEl.clientHeight;
122
- scrollInfo.vScrollTopHeight = firstIndex * rowHeight;
123
- scrollInfo.vScrollBottomHeight = totalScrollHeight - (stores.viewStore.length * rowHeight)
124
- - scrollInfo.vScrollTopHeight;
125
- if (isScroll && pageInfo.isInfinite && scrollInfo.vScrollBottomHeight === 0) {
126
- pageInfo.prevPage = pageInfo.currentPage;
127
- pageInfo.currentPage = Math.ceil(lastIndex / pageInfo.perPage) + 1;
128
- pageInfo.startIndex = lastIndex;
129
- updatePagingInfo({ onScrollEnd: true });
130
- }
131
- }
132
- };
133
- /**
134
- * 수평 스크롤의 위치 계산 후 적용한다.
135
- */
136
- const updateHScroll = () => {
137
- const headerEl = elementInfo.header;
138
- const bodyEl = elementInfo.body;
139
- const tableEl = elementInfo.table;
140
-
141
- headerEl.scrollLeft = bodyEl.scrollLeft;
142
- summaryScroll.value = bodyEl.scrollLeft;
143
- scrollInfo.hasHorizontalScrollBar = bodyEl.clientWidth < tableEl.clientWidth;
144
- };
145
- /**
146
- * scroll 이벤트를 처리한다.
147
- */
148
- const onScroll = () => {
149
- const bodyEl = elementInfo.body;
150
- const scrollTop = bodyEl.scrollTop;
151
- const scrollLeft = bodyEl.scrollLeft;
152
- const lastTop = scrollInfo.lastScroll.top;
153
- const lastLeft = scrollInfo.lastScroll.left;
154
- const isHorizontal = !(scrollLeft === lastLeft);
155
- const isVertical = !(scrollTop === lastTop);
156
-
157
- if (isVertical && bodyEl.clientHeight) {
158
- updateVScroll(true);
159
- }
160
-
161
- if (isHorizontal) {
162
- updateHScroll();
163
- }
164
-
165
- scrollInfo.lastScroll.top = scrollTop;
166
- scrollInfo.lastScroll.left = scrollLeft;
167
- };
168
- return { updateVScroll, updateHScroll, onScroll };
169
- };
170
-
171
- export const resizeEvent = (params) => {
172
- const { props } = getCurrentInstance();
173
- const {
174
- resizeInfo,
175
- elementInfo,
176
- checkInfo,
177
- stores,
178
- isRenderer,
179
- updateVScroll,
180
- updateHScroll,
181
- } = params;
182
- /**
183
- * 고정 너비, 스크롤 유무 등에 따른 컬럼 너비를 계산한다.
184
- */
185
- const calculatedColumn = () => {
186
- stores.viewStore = stores.showTreeStore;
187
- const store = stores.viewStore;
188
- let columnWidth = resizeInfo.columnWidth;
189
- if (resizeInfo.columnWidth > 0) {
190
- columnWidth = resizeInfo.columnWidth;
191
- }
192
- columnWidth = resizeInfo.columnWidth;
193
- let remainWidth = 0;
194
- if (resizeInfo.adjust) {
195
- const bodyEl = elementInfo.body;
196
- let elWidth = bodyEl.offsetWidth;
197
- const elHeight = bodyEl.offsetHeight;
198
- const result = stores.orderedColumns.reduce((acc, column) => {
199
- if (column.hide) {
200
- return acc;
201
- }
202
-
203
- if (column.width) {
204
- acc.totalWidth += column.width;
205
- } else {
206
- acc.emptyCount++;
207
- }
208
-
209
- return acc;
210
- }, { totalWidth: 0, emptyCount: 0 });
211
-
212
- if (resizeInfo.rowHeight * store.length > elHeight - resizeInfo.scrollWidth) {
213
- elWidth -= resizeInfo.scrollWidth;
214
- }
215
-
216
- if (checkInfo.useCheckbox.use) {
217
- elWidth -= resizeInfo.minWidth;
218
- }
219
-
220
- columnWidth = elWidth - result.totalWidth;
221
- if (columnWidth > 0) {
222
- remainWidth = columnWidth
223
- - (Math.floor(columnWidth / result.emptyCount) * result.emptyCount);
224
- columnWidth = Math.floor(columnWidth / result.emptyCount);
225
- } else {
226
- columnWidth = resizeInfo.columnWidth;
227
- }
228
-
229
- columnWidth = columnWidth < resizeInfo.minWidth ? resizeInfo.minWidth : columnWidth;
230
- resizeInfo.columnWidth = columnWidth;
231
- }
232
-
233
- stores.orderedColumns.map((column) => {
234
- const item = column;
235
- const minWidth = isRenderer(column) ? resizeInfo.rendererMinWidth : resizeInfo.minWidth;
236
- if (item.width && item.width < minWidth) {
237
- item.width = minWidth;
238
- }
239
- if (!item.width && !item.hide) {
240
- item.width = columnWidth;
241
- }
242
- return item;
243
- });
244
-
245
- if (remainWidth) {
246
- let index = stores.orderedColumns.length - 1;
247
- let lastColumn = stores.orderedColumns[index];
248
- while (lastColumn.hide) {
249
- index -= 1;
250
- lastColumn = stores.orderedColumns[index];
251
- }
252
- lastColumn.width += remainWidth;
253
- }
254
- resizeInfo.isResize = !resizeInfo.isResize;
255
- };
256
- /**
257
- * grid resize 이벤트를 처리한다.
258
- */
259
- const onResize = () => {
260
- nextTick(() => {
261
- if (resizeInfo.adjust) {
262
- stores.orderedColumns.map((column) => {
263
- const item = column;
264
-
265
- if (!props.columns[column.index].width && !item.resized) {
266
- item.width = 0;
267
- }
268
-
269
- return item;
270
- }, this);
271
- }
272
-
273
- calculatedColumn();
274
- if (elementInfo.body?.clientHeight) {
275
- updateVScroll();
276
- }
277
- if (elementInfo.body?.clientWidth) {
278
- updateHScroll();
279
- }
280
- });
281
- };
282
- const onShow = (isVisible) => {
283
- if (isVisible) {
284
- onResize();
285
- }
286
- };
287
- /**
288
- * column resize 이벤트를 처리한다.
289
- *
290
- * @param {number} columnIndex - 컬럼 인덱스
291
- * @param {object} event - 이벤트 객체
292
- */
293
- const onColumnResize = (columnIndex, event) => {
294
- const headerEl = elementInfo.header;
295
- const bodyEl = elementInfo.body;
296
- const headerLeft = headerEl.getBoundingClientRect().left;
297
- const columnEl = headerEl.querySelector(`li[data-index="${columnIndex}"]`);
298
- const minWidth = isRenderer(stores.orderedColumns[columnIndex])
299
- ? resizeInfo.rendererMinWidth : resizeInfo.minWidth;
300
- const columnRect = columnEl.getBoundingClientRect();
301
- const maxRight = bodyEl.getBoundingClientRect().right - headerLeft;
302
- const resizeLineEl = elementInfo.resizeLine;
303
- const minLeft = columnRect.left - headerLeft + minWidth;
304
- const startLeft = columnRect.right - headerLeft;
305
- const startMouseLeft = event.clientX;
306
- const startColumnLeft = columnRect.left - headerLeft;
307
-
308
- resizeLineEl.style.left = `${startLeft}px`;
309
-
310
- resizeInfo.showResizeLine = true;
311
-
312
- const handleMouseMove = (evt) => {
313
- const deltaLeft = evt.clientX - startMouseLeft;
314
- const proxyLeft = startLeft + deltaLeft;
315
- let resizeWidth = Math.max(minLeft, proxyLeft);
316
-
317
- resizeWidth = Math.min(maxRight, resizeWidth);
318
-
319
- resizeLineEl.style.left = `${resizeWidth}px`;
320
- };
321
-
322
- const handleMouseUp = () => {
323
- const destLeft = parseInt(resizeLineEl.style.left, 10);
324
- const changedWidth = destLeft - startColumnLeft;
325
-
326
- if (stores.orderedColumns[columnIndex]) {
327
- stores.orderedColumns[columnIndex].width = changedWidth;
328
- stores.orderedColumns.map((column) => {
329
- const item = column;
330
- item.resized = true;
331
- return item;
332
- });
333
- }
334
-
335
- resizeInfo.showResizeLine = false;
336
- document.removeEventListener('mousemove', handleMouseMove);
337
- onResize();
338
- };
339
-
340
- document.addEventListener('mousemove', handleMouseMove);
341
- document.addEventListener('mouseup', handleMouseUp, { once: true });
342
- };
343
- return { calculatedColumn, onResize, onShow, onColumnResize };
344
- };
345
-
346
- export const clickEvent = (params) => {
347
- const { emit } = getCurrentInstance();
348
- const selectInfo = params;
349
- const getClickedRowData = (event, row) => {
350
- const tagName = event.target.tagName.toLowerCase();
351
- let cellInfo = {};
352
- if (event.target.offsetParent) {
353
- if (tagName === 'td') {
354
- cellInfo = event.target.dataset;
355
- } else {
356
- cellInfo = event.target.closest('td')?.dataset ?? {};
357
- }
358
- }
359
- return {
360
- event,
361
- rowData: row,
362
- rowIndex: row.index,
363
- cellName: cellInfo.name,
364
- cellIndex: cellInfo.index,
365
- };
366
- };
367
- /**
368
- * row click 이벤트를 처리한다.
369
- *
370
- * @param {object} event - 이벤트 객체
371
- * @param {array} row - row 데이터
372
- */
373
- let timer = null;
374
- const onRowClick = (event, row) => {
375
- if (event.target && event.target.parentElement
376
- && event.target.parentElement.classList.contains('row-checkbox-input')) {
377
- return false;
378
- }
379
- clearTimeout(timer);
380
- timer = setTimeout(() => {
381
- if (selectInfo.useSelect) {
382
- if (row.selected) {
383
- row.selected = false;
384
- if (selectInfo.multiple) {
385
- if (event.ctrlKey) {
386
- selectInfo.selectedRow = selectInfo.selectedRow.filter(s => s.index !== row.index);
387
- } else {
388
- selectInfo.selectedRow = [row];
389
- }
390
- } else {
391
- selectInfo.selectedRow = [];
392
- }
393
- } else {
394
- row.selected = true;
395
- if (event.ctrlKey
396
- && selectInfo.multiple
397
- && (!selectInfo.limitCount || selectInfo.limitCount > selectInfo.selectedRow.length)) {
398
- selectInfo.selectedRow.push(row);
399
- } else {
400
- selectInfo.selectedRow = [row];
401
- }
402
- }
403
- emit('update:selected', selectInfo.selectedRow);
404
- emit('click-row', getClickedRowData(event, row));
405
- }
406
- }, 100);
407
- return true;
408
- };
409
- /**
410
- * row dblclick 이벤트를 처리한다.
411
- *
412
- * @param {object} event - 이벤트 객체
413
- * @param {array} row - row 데이터
414
- */
415
- const onRowDblClick = (event, row) => {
416
- clearTimeout(timer);
417
- emit('dblclick-row', getClickedRowData(event, row));
418
- };
419
- return { onRowClick, onRowDblClick };
420
- };
421
-
422
- export const checkEvent = (params) => {
423
- const {
424
- checkInfo,
425
- stores,
426
- checkHeader,
427
- pageInfo,
428
- getPagingData,
429
- } = params;
430
- const { emit } = getCurrentInstance();
431
- /**
432
- * row에 대한 체크 상태를 해제한다.
433
- *
434
- * @param {array} row - row 데이터
435
- */
436
- const unCheckedRow = (row) => {
437
- const index = stores.treeStore.findIndex(
438
- item => item.index === row.index);
439
-
440
- if (index !== -1) {
441
- stores.treeStore[index].checked = row.checked;
442
- }
443
- };
444
- const onCheckChildren = (node) => {
445
- if (node.hasChild) {
446
- node.children.forEach((children) => {
447
- const childNode = children;
448
- if (node.checked && !childNode.checked && !childNode._disabled) {
449
- checkInfo.checkedRows.push(childNode);
450
- }
451
- if (!node.checked) {
452
- checkInfo.checkedRows = checkInfo.checkedRows
453
- .filter(checked => checked.index !== childNode.index);
454
- }
455
- childNode.checked = node.checked && !childNode._disabled;
456
-
457
- if (childNode.hasChild) {
458
- onCheckChildren(childNode);
459
- }
460
- });
461
- }
462
- };
463
- const onCheckParent = (node) => {
464
- const parentNode = node.parent;
465
- if (parentNode) {
466
- const isCheck = parentNode.children.every(n => n.checked);
467
- parentNode.checked = isCheck && !parentNode._disabled;
468
- const disabledList = parentNode.children.filter(n => n._disabled);
469
- if (disabledList.length) {
470
- const checkedList = parentNode.children.filter(n => n.checked);
471
- if (disabledList.length + checkedList.length === parentNode.children.length) {
472
- parentNode.checked = true;
473
- }
474
- }
475
- if (!parentNode.checked) {
476
- checkInfo.checkedRows = checkInfo.checkedRows
477
- .filter(checked => checked.index !== parentNode.index);
478
- } else {
479
- checkInfo.checkedRows.push(parentNode);
480
- }
481
- if (parentNode.parent) {
482
- onCheckParent(parentNode);
483
- }
484
- }
485
- };
486
- /**
487
- * checkbox click 이벤트를 처리한다.
488
- *
489
- * @param {object} event - 이벤트 객체
490
- * @param {array} rowData - row 데이터
491
- */
492
- const onCheck = (event, rowData) => {
493
- let store = stores.store;
494
- if (pageInfo.isClientPaging) {
495
- store = getPagingData();
496
- }
497
- const isSingleMode = () => checkInfo.useCheckbox.mode === 'single';
498
- const unCheckHeader = () => {
499
- if (checkInfo.isHeaderChecked) {
500
- checkInfo.isHeaderChecked = false;
501
- }
502
- };
503
- const onSingleMode = () => {
504
- if (isSingleMode() && checkInfo.checkedRows.length > 0) {
505
- checkInfo.prevCheckedRow.checked = false;
506
- unCheckedRow(checkInfo.prevCheckedRow);
507
- }
508
- };
509
- const addCheckedRow = (row) => {
510
- if (isSingleMode()) {
511
- checkInfo.checkedRows = [row];
512
- return;
513
- }
514
- onCheckChildren(row);
515
- onCheckParent(row);
516
- checkInfo.checkedRows.push(row);
517
- };
518
- const removeCheckedRow = (row) => {
519
- if (isSingleMode()) {
520
- checkInfo.checkedRows = [];
521
- return;
522
- }
523
- checkInfo.checkedRows = checkInfo.checkedRows.filter(it => it.index !== row.index);
524
- };
525
-
526
- onSingleMode();
527
- if (rowData.checked) {
528
- addCheckedRow(rowData);
529
- checkHeader(store);
530
- } else {
531
- unCheckHeader();
532
- removeCheckedRow(rowData);
533
- onCheckChildren(rowData);
534
- onCheckParent(rowData);
535
- }
536
- checkInfo.prevCheckedRow = rowData;
537
- emit('update:checked', checkInfo.checkedRows);
538
- emit('check-row', event, rowData.index, rowData);
539
- };
540
- /**
541
- * all checkbox click 이벤트를 처리한다.
542
- *
543
- * @param {object} event - 이벤트 객체
544
- */
545
- const onCheckAll = (event) => {
546
- const status = checkInfo.isHeaderChecked;
547
- let store = stores.store;
548
- if (pageInfo.isClientPaging) {
549
- store = getPagingData();
550
- }
551
- store.forEach((row) => {
552
- row.checked = status && !row._disabled;
553
- if (row.checked) {
554
- if (!checkInfo.checkedRows.find(checked => checked.index === row.index)) {
555
- checkInfo.checkedRows.push(row);
556
- }
557
- } else {
558
- checkInfo.checkedRows = checkInfo.checkedRows
559
- .filter(checked => checked.index !== row.index);
560
- }
561
- });
562
- emit('update:checked', checkInfo.checkedRows);
563
- emit('check-all', event, checkInfo.checkedRows);
564
- };
565
- return { onCheck, onCheckAll };
566
- };
567
-
568
- export const contextMenuEvent = (params) => {
569
- const { emit } = getCurrentInstance();
570
- const { contextInfo, stores, selectInfo } = params;
571
- /**
572
- * 컨텍스트 메뉴를 설정한다.
573
- *
574
- * @param {boolean} useCustom - 사용자 지정 메뉴 사용 유무
575
- */
576
- const setContextMenu = (useCustom = true) => {
577
- const menuItems = [];
578
-
579
- if (useCustom && contextInfo.customContextMenu.length) {
580
- const row = selectInfo.selectedRow;
581
- const customItems = contextInfo.customContextMenu.map(
582
- (item) => {
583
- const menuItem = item;
584
- if (menuItem.validate) {
585
- menuItem.disabled = !menuItem.validate(menuItem.itemId, row);
586
- }
587
-
588
- return menuItem;
589
- });
590
-
591
- menuItems.push(...customItems);
592
- }
593
- contextInfo.contextMenuItems = menuItems;
594
- };
595
- /**
596
- * 마우스 우클릭 이벤트를 처리한다.
597
- *
598
- * @param {object} event - 이벤트 객체
599
- */
600
- const onContextMenu = (event) => {
601
- const target = event.target;
602
- const rowIndex = target.parentElement.dataset.index;
603
-
604
- if (rowIndex) {
605
- const index = stores.viewStore.findIndex(v => v.index === Number(rowIndex));
606
- const rowData = stores.viewStore[index];
607
- selectInfo.selectedRow = [rowData];
608
- setContextMenu();
609
- emit('update:selected', selectInfo.selectedRow);
610
- } else {
611
- selectInfo.selectedRow = [];
612
- setContextMenu(false);
613
- emit('update:selected', []);
614
- }
615
- };
616
- return { setContextMenu, onContextMenu };
617
- };
618
-
619
- export const treeEvent = (params) => {
620
- const { stores, onResize } = params;
621
- const setTreeNodeStore = () => {
622
- let nodeIndex = 0;
623
- const nodeList = [];
624
-
625
- function getDataObj(nodeObj) {
626
- const newObj = {};
627
- Object.keys(nodeObj).forEach((key) => {
628
- if (key !== 'children') {
629
- newObj[key] = nodeObj[key];
630
- }
631
- });
632
- return newObj;
633
- }
634
-
635
- function setNodeData(nodeInfo) {
636
- const { node, level, isShow, parent } = nodeInfo;
637
- if (node !== null && typeof node === 'object') {
638
- node.index = nodeIndex++;
639
- node.level = level;
640
-
641
- if (!Object.hasOwnProperty.call(node, 'checked')) {
642
- node.checked = false;
643
- }
644
-
645
- if (!Object.hasOwnProperty.call(node, 'selected')) {
646
- node.selected = false;
647
- }
648
-
649
- if (!Object.hasOwnProperty.call(node, 'show')) {
650
- node.show = isShow;
651
- }
652
-
653
- if (!Object.hasOwnProperty.call(node, 'expand')) {
654
- node.expand = true;
655
- }
656
-
657
- if (!Object.hasOwnProperty.call(node, 'isFilter')) {
658
- node.isFilter = false;
659
- }
660
-
661
- if (!Object.hasOwnProperty.call(node, 'data')) {
662
- node.data = getDataObj(node);
663
- }
664
-
665
- nodeList.push(node);
666
-
667
- if (!Object.hasOwnProperty.call(node, 'parent')) {
668
- node.parent = parent;
669
- }
670
- if (node.children) {
671
- node.hasChild = true;
672
- node.children.forEach(child =>
673
- setNodeData({
674
- node: child,
675
- level: level + 1,
676
- isShow: node.show && node.expand,
677
- parent: node,
678
- }),
679
- );
680
- }
681
- }
682
- }
683
- stores.treeRows.forEach((root) => {
684
- setNodeData({
685
- node: root,
686
- level: 0,
687
- isShow: true,
688
- parent: undefined,
689
- });
690
- });
691
- return nodeList;
692
- };
693
- const setExpandNode = (children, isShow, isFilter) => {
694
- children.forEach((nodeObj) => {
695
- const node = nodeObj;
696
- node.show = isFilter && isShow ? node.isFilter : isShow;
697
- if (node.hasChild) {
698
- setExpandNode(node.children, node.show && node.expand, node.isFilter);
699
- }
700
- });
701
- };
702
- const handleExpand = (node) => {
703
- const data = node;
704
- data.expand = !data.expand;
705
- setExpandNode(data.children, data.expand, data.isFilter);
706
- onResize();
707
- };
708
- return { setTreeNodeStore, handleExpand };
709
- };
710
-
711
- export const filterEvent = (params) => {
712
- const {
713
- stores,
714
- filterInfo,
715
- pageInfo,
716
- getConvertValue,
717
- onResize,
718
- checkHeader,
719
- getPagingData,
720
- updatePagingInfo,
721
- } = params;
722
- const makeParentShow = (data) => {
723
- if (!data?.parent) {
724
- return;
725
- }
726
- const { parent } = data;
727
- parent.show = true;
728
- parent.isFilter = true;
729
- makeParentShow(parent);
730
- };
731
- const makeChildShow = (data) => {
732
- if (!data?.children) {
733
- return;
734
- }
735
- const { children } = data;
736
- children.forEach((node) => {
737
- const childNode = node;
738
- if (childNode.parent.show && childNode.parent.expand) {
739
- childNode.show = true;
740
- } else {
741
- childNode.show = false;
742
- }
743
- childNode.isFilter = true;
744
- if (childNode.hasChild) {
745
- makeChildShow(childNode);
746
- }
747
- });
748
- };
749
- let timer = null;
750
- const onSearch = (searchWord) => {
751
- if (timer) {
752
- clearTimeout(timer);
753
- }
754
- timer = setTimeout(() => {
755
- filterInfo.isSearch = false;
756
- filterInfo.searchWord = searchWord;
757
- const store = stores.treeStore;
758
- store.forEach((row) => {
759
- row.show = false;
760
- row.isFilter = false;
761
- });
762
- if (searchWord) {
763
- const filterStores = store.filter((row) => {
764
- let isSameWord = false;
765
- for (let ix = 0; ix < stores.orderedColumns.length; ix++) {
766
- const column = stores.orderedColumns[ix] || {};
767
- let columnValue = row[column.field] ?? null;
768
- column.type = column.type || 'string';
769
- if (columnValue !== null) {
770
- if (!column.hide && (column?.searchable === undefined || column?.searchable)) {
771
- columnValue = getConvertValue(column, columnValue).toString();
772
- isSameWord = columnValue.toLowerCase()
773
- .includes(searchWord.toString().toLowerCase());
774
- if (isSameWord) {
775
- break;
776
- }
777
- }
778
- }
779
- }
780
- return isSameWord;
781
- });
782
- filterStores.forEach((row) => {
783
- row.show = true;
784
- if (row.parent && !row.parent.expand) {
785
- row.show = false;
786
- }
787
- row.isFilter = true;
788
- makeParentShow(row);
789
- makeChildShow(row);
790
- });
791
- filterInfo.isSearch = true;
792
- } else {
793
- store.forEach((row) => {
794
- row.show = true;
795
- row.isFilter = false;
796
- });
797
- store.forEach((row) => {
798
- makeParentShow(row);
799
- makeChildShow(row);
800
- });
801
- }
802
- if (!searchWord && pageInfo.isClientPaging && pageInfo.prevPage) {
803
- pageInfo.currentPage = 1;
804
- stores.pagingStore = getPagingData();
805
- }
806
- updatePagingInfo({ onSearch: true });
807
- checkHeader(stores.store);
808
- onResize();
809
- }, 500);
810
- };
811
- return { onSearch };
812
- };
813
-
814
- export const pagingEvent = (params) => {
815
- const { emit } = getCurrentInstance();
816
- const {
817
- stores,
818
- pageInfo,
819
- filterInfo,
820
- elementInfo,
821
- clearCheckInfo,
822
- } = params;
823
- const getPagingData = () => {
824
- const start = (pageInfo.currentPage - 1) * pageInfo.perPage;
825
- const end = parseInt(start, 10) + parseInt(pageInfo.perPage, 10);
826
- return stores.showTreeStore.slice(start, end);
827
- };
828
- const updatePagingInfo = (eventName) => {
829
- emit('page-change', {
830
- eventName,
831
- pageInfo: {
832
- currentPage: pageInfo.currentPage,
833
- prevPage: pageInfo.prevPage,
834
- startIndex: pageInfo.startIndex,
835
- total: pageInfo.pageTotal,
836
- perPage: pageInfo.perPage,
837
- },
838
- searchInfo: {
839
- searchWord: filterInfo.searchWord,
840
- searchColumns: stores.orderedColumns
841
- .filter(c => !c.hide && (c?.searchable === undefined || c?.searchable))
842
- .map(d => d.field),
843
- },
844
- });
845
- if (pageInfo.isInfinite && (eventName?.onSearch || eventName?.onSort)) {
846
- pageInfo.currentPage = 1;
847
- elementInfo.body.scrollTop = 0;
848
- clearCheckInfo();
849
- }
850
- };
851
- const changePage = (beforeVal) => {
852
- if (pageInfo.isClientPaging) {
853
- pageInfo.prevPage = beforeVal;
854
- if (stores.showTreeStore.length <= pageInfo.perPage) {
855
- stores.pagingStore = stores.showTreeStore;
856
- } else {
857
- const start = (pageInfo.currentPage - 1) * pageInfo.perPage;
858
- const end = parseInt(start, 10) + parseInt(pageInfo.perPage, 10);
859
- stores.pagingStore = stores.showTreeStore.slice(start, end);
860
- elementInfo.body.scrollTop = 0;
861
- pageInfo.startIndex = start;
862
- }
863
- }
864
- updatePagingInfo({ onChangePage: true });
865
- };
866
- return { getPagingData, updatePagingInfo, changePage };
867
- };
1
+ import { getCurrentInstance, nextTick } from 'vue';
2
+ import { numberWithComma } from '@/common/utils';
3
+
4
+ export const commonFunctions = (params) => {
5
+ const { props } = getCurrentInstance();
6
+ const { checkInfo } = params;
7
+ /**
8
+ * 해당 컬럼이 사용자 지정 컬럼인지 확인한다.
9
+ *
10
+ * @param {object} column - 컬럼 정보
11
+ * @returns {boolean} 사용자 지정 컬럼 유무
12
+ */
13
+ const isRenderer = (column = {}) => !!column?.render?.use;
14
+ const getComponentName = (type = '') => {
15
+ const setUpperCaseFirstStr = str => str.charAt(0).toUpperCase() + str.slice(1);
16
+ const rendererStr = 'Renderer';
17
+ let typeStr = '';
18
+ if (type.indexOf('_') !== -1) {
19
+ const typeStrArray = type.split('_');
20
+ for (let ix = 0; ix < typeStrArray.length; ix++) {
21
+ typeStr += setUpperCaseFirstStr(typeStrArray[ix]);
22
+ }
23
+ } else {
24
+ typeStr = setUpperCaseFirstStr(type);
25
+ }
26
+ return typeStr + rendererStr;
27
+ };
28
+ /**
29
+ * 데이터 타입에 따라 변환된 데이터을 반환한다.
30
+ *
31
+ * @param {object} column - 컬럼 정보
32
+ * @param {number|string} value - 데이터
33
+ * @returns {number|string} 변환된 데이터
34
+ */
35
+ const getConvertValue = (column, value) => {
36
+ let convertValue = column.type === 'number' || column.type === 'float' ? Number(value) : value;
37
+
38
+ if (column.type === 'number') {
39
+ convertValue = numberWithComma(value);
40
+ convertValue = convertValue === false ? value : convertValue;
41
+ } else if (column.type === 'float') {
42
+ const floatValue = convertValue.toFixed(column.decimal ?? 3);
43
+ convertValue = floatValue.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
44
+ }
45
+
46
+ return convertValue;
47
+ };
48
+ /**
49
+ * 전달받은 필드명과 일치하는 컬럼 인덱스를 반환한다.
50
+ *
51
+ * @param {string} field - 컬럼 필드명
52
+ * @returns {number} 일치한다면 컬럼 인덱스, 일치하지 않는다면 -1
53
+ */
54
+ const getColumnIndex = field => props.columns.findIndex(column => column.field === field);
55
+ const setPixelUnit = (value) => {
56
+ let size = value;
57
+ const hasPx = size.toString().indexOf('px') >= 0;
58
+ const hasPct = size.toString().indexOf('%') >= 0;
59
+ if (!hasPx && !hasPct) {
60
+ size = `${size}px`;
61
+ }
62
+ return size;
63
+ };
64
+ const checkHeader = (rows) => {
65
+ checkInfo.isHeaderChecked = !!rows.length && rows.every(row => row.checked);
66
+ const disabledList = rows.filter(row => row._disabled);
67
+ if (disabledList.length) {
68
+ const checkedList = rows.filter(row => row.checked);
69
+ if (disabledList.length + checkedList.length === rows.length) {
70
+ checkInfo.isHeaderChecked = true;
71
+ }
72
+ }
73
+ };
74
+ return {
75
+ isRenderer,
76
+ getComponentName,
77
+ getConvertValue,
78
+ getColumnIndex,
79
+ setPixelUnit,
80
+ checkHeader,
81
+ };
82
+ };
83
+
84
+ export const scrollEvent = (params) => {
85
+ const {
86
+ scrollInfo,
87
+ stores,
88
+ elementInfo,
89
+ resizeInfo,
90
+ pageInfo,
91
+ summaryScroll,
92
+ getPagingData,
93
+ updatePagingInfo,
94
+ } = params;
95
+ /**
96
+ * 수직 스크롤의 위치 계산 후 적용한다.
97
+ */
98
+ const updateVScroll = (isScroll) => {
99
+ let store = stores.showTreeStore;
100
+ if (pageInfo.isClientPaging) {
101
+ store = getPagingData();
102
+ }
103
+ const bodyEl = elementInfo.body;
104
+ if (bodyEl) {
105
+ const rowHeight = resizeInfo.rowHeight;
106
+ const rowCount = bodyEl.clientHeight > rowHeight
107
+ ? Math.ceil(bodyEl.clientHeight / rowHeight) : store.length;
108
+ const totalScrollHeight = store.length * rowHeight;
109
+ let firstVisibleIndex = Math.floor(bodyEl.scrollTop / rowHeight);
110
+ if (firstVisibleIndex > store.length - 1) {
111
+ firstVisibleIndex = 0;
112
+ }
113
+
114
+ const lastVisibleIndex = firstVisibleIndex + rowCount + 1;
115
+ const firstIndex = Math.max(firstVisibleIndex, 0);
116
+ const lastIndex = lastVisibleIndex;
117
+ const tableEl = elementInfo.table;
118
+
119
+ stores.viewStore = store.slice(firstIndex, lastIndex);
120
+ scrollInfo.hasVerticalScrollBar = rowCount < store.length
121
+ || bodyEl.clientHeight < tableEl.clientHeight;
122
+ scrollInfo.vScrollTopHeight = firstIndex * rowHeight;
123
+ scrollInfo.vScrollBottomHeight = totalScrollHeight - (stores.viewStore.length * rowHeight)
124
+ - scrollInfo.vScrollTopHeight;
125
+ if (isScroll && pageInfo.isInfinite && scrollInfo.vScrollBottomHeight === 0) {
126
+ pageInfo.prevPage = pageInfo.currentPage;
127
+ pageInfo.currentPage = Math.ceil(lastIndex / pageInfo.perPage) + 1;
128
+ pageInfo.startIndex = lastIndex;
129
+ updatePagingInfo({ onScrollEnd: true });
130
+ }
131
+ }
132
+ };
133
+ /**
134
+ * 수평 스크롤의 위치 계산 후 적용한다.
135
+ */
136
+ const updateHScroll = () => {
137
+ const headerEl = elementInfo.header;
138
+ const bodyEl = elementInfo.body;
139
+ const tableEl = elementInfo.table;
140
+
141
+ headerEl.scrollLeft = bodyEl.scrollLeft;
142
+ summaryScroll.value = bodyEl.scrollLeft;
143
+ scrollInfo.hasHorizontalScrollBar = bodyEl.clientWidth < tableEl.clientWidth;
144
+ };
145
+ /**
146
+ * scroll 이벤트를 처리한다.
147
+ */
148
+ const onScroll = () => {
149
+ const bodyEl = elementInfo.body;
150
+ const scrollTop = bodyEl.scrollTop;
151
+ const scrollLeft = bodyEl.scrollLeft;
152
+ const lastTop = scrollInfo.lastScroll.top;
153
+ const lastLeft = scrollInfo.lastScroll.left;
154
+ const isHorizontal = !(scrollLeft === lastLeft);
155
+ const isVertical = !(scrollTop === lastTop);
156
+
157
+ if (isVertical && bodyEl.clientHeight) {
158
+ updateVScroll(true);
159
+ }
160
+
161
+ if (isHorizontal) {
162
+ updateHScroll();
163
+ }
164
+
165
+ scrollInfo.lastScroll.top = scrollTop;
166
+ scrollInfo.lastScroll.left = scrollLeft;
167
+ };
168
+ return { updateVScroll, updateHScroll, onScroll };
169
+ };
170
+
171
+ export const resizeEvent = (params) => {
172
+ const { props } = getCurrentInstance();
173
+ const {
174
+ resizeInfo,
175
+ elementInfo,
176
+ checkInfo,
177
+ stores,
178
+ isRenderer,
179
+ updateVScroll,
180
+ updateHScroll,
181
+ } = params;
182
+ /**
183
+ * 고정 너비, 스크롤 유무 등에 따른 컬럼 너비를 계산한다.
184
+ */
185
+ const calculatedColumn = () => {
186
+ stores.viewStore = stores.showTreeStore;
187
+ const store = stores.viewStore;
188
+ let columnWidth = resizeInfo.columnWidth;
189
+ if (resizeInfo.columnWidth > 0) {
190
+ columnWidth = resizeInfo.columnWidth;
191
+ }
192
+ columnWidth = resizeInfo.columnWidth;
193
+ let remainWidth = 0;
194
+ if (resizeInfo.adjust) {
195
+ const bodyEl = elementInfo.body;
196
+ let elWidth = bodyEl.offsetWidth;
197
+ const elHeight = bodyEl.offsetHeight;
198
+ const result = stores.orderedColumns.reduce((acc, column) => {
199
+ if (column.hide) {
200
+ return acc;
201
+ }
202
+
203
+ if (column.width) {
204
+ acc.totalWidth += column.width;
205
+ } else {
206
+ acc.emptyCount++;
207
+ }
208
+
209
+ return acc;
210
+ }, { totalWidth: 0, emptyCount: 0 });
211
+
212
+ if (resizeInfo.rowHeight * store.length > elHeight - resizeInfo.scrollWidth) {
213
+ elWidth -= resizeInfo.scrollWidth;
214
+ }
215
+
216
+ if (checkInfo.useCheckbox.use) {
217
+ elWidth -= resizeInfo.minWidth;
218
+ }
219
+
220
+ columnWidth = elWidth - result.totalWidth;
221
+ if (columnWidth > 0) {
222
+ remainWidth = columnWidth
223
+ - (Math.floor(columnWidth / result.emptyCount) * result.emptyCount);
224
+ columnWidth = Math.floor(columnWidth / result.emptyCount);
225
+ } else {
226
+ columnWidth = resizeInfo.columnWidth;
227
+ }
228
+
229
+ columnWidth = columnWidth < resizeInfo.minWidth ? resizeInfo.minWidth : columnWidth;
230
+ resizeInfo.columnWidth = columnWidth;
231
+ }
232
+
233
+ stores.orderedColumns.map((column) => {
234
+ const item = column;
235
+ const minWidth = isRenderer(column) ? resizeInfo.rendererMinWidth : resizeInfo.minWidth;
236
+ if (item.width && item.width < minWidth) {
237
+ item.width = minWidth;
238
+ }
239
+ if (!item.width && !item.hide) {
240
+ item.width = columnWidth;
241
+ }
242
+ return item;
243
+ });
244
+
245
+ if (remainWidth) {
246
+ let index = stores.orderedColumns.length - 1;
247
+ let lastColumn = stores.orderedColumns[index];
248
+ while (lastColumn.hide) {
249
+ index -= 1;
250
+ lastColumn = stores.orderedColumns[index];
251
+ }
252
+ lastColumn.width += remainWidth;
253
+ }
254
+ resizeInfo.isResize = !resizeInfo.isResize;
255
+ };
256
+ /**
257
+ * grid resize 이벤트를 처리한다.
258
+ */
259
+ const onResize = () => {
260
+ nextTick(() => {
261
+ if (resizeInfo.adjust) {
262
+ stores.orderedColumns.map((column) => {
263
+ const item = column;
264
+
265
+ if (!props.columns[column.index].width && !item.resized) {
266
+ item.width = 0;
267
+ }
268
+
269
+ return item;
270
+ }, this);
271
+ }
272
+
273
+ calculatedColumn();
274
+ if (elementInfo.body?.clientHeight) {
275
+ updateVScroll();
276
+ }
277
+ if (elementInfo.body?.clientWidth) {
278
+ updateHScroll();
279
+ }
280
+ });
281
+ };
282
+ const onShow = (isVisible) => {
283
+ if (isVisible) {
284
+ onResize();
285
+ }
286
+ };
287
+ /**
288
+ * column resize 이벤트를 처리한다.
289
+ *
290
+ * @param {number} columnIndex - 컬럼 인덱스
291
+ * @param {object} event - 이벤트 객체
292
+ */
293
+ const onColumnResize = (columnIndex, event) => {
294
+ const headerEl = elementInfo.header;
295
+ const bodyEl = elementInfo.body;
296
+ const headerLeft = headerEl.getBoundingClientRect().left;
297
+ const columnEl = headerEl.querySelector(`li[data-index="${columnIndex}"]`);
298
+ const minWidth = isRenderer(stores.orderedColumns[columnIndex])
299
+ ? resizeInfo.rendererMinWidth : resizeInfo.minWidth;
300
+ const columnRect = columnEl.getBoundingClientRect();
301
+ const maxRight = bodyEl.getBoundingClientRect().right - headerLeft;
302
+ const resizeLineEl = elementInfo.resizeLine;
303
+ const minLeft = columnRect.left - headerLeft + minWidth;
304
+ const startLeft = columnRect.right - headerLeft;
305
+ const startMouseLeft = event.clientX;
306
+ const startColumnLeft = columnRect.left - headerLeft;
307
+
308
+ resizeLineEl.style.left = `${startLeft}px`;
309
+
310
+ resizeInfo.showResizeLine = true;
311
+
312
+ const handleMouseMove = (evt) => {
313
+ const deltaLeft = evt.clientX - startMouseLeft;
314
+ const proxyLeft = startLeft + deltaLeft;
315
+ let resizeWidth = Math.max(minLeft, proxyLeft);
316
+
317
+ resizeWidth = Math.min(maxRight, resizeWidth);
318
+
319
+ resizeLineEl.style.left = `${resizeWidth}px`;
320
+ };
321
+
322
+ const handleMouseUp = () => {
323
+ const destLeft = parseInt(resizeLineEl.style.left, 10);
324
+ const changedWidth = destLeft - startColumnLeft;
325
+
326
+ if (stores.orderedColumns[columnIndex]) {
327
+ stores.orderedColumns[columnIndex].width = changedWidth;
328
+ stores.orderedColumns.map((column) => {
329
+ const item = column;
330
+ item.resized = true;
331
+ return item;
332
+ });
333
+ }
334
+
335
+ resizeInfo.showResizeLine = false;
336
+ document.removeEventListener('mousemove', handleMouseMove);
337
+ onResize();
338
+ };
339
+
340
+ document.addEventListener('mousemove', handleMouseMove);
341
+ document.addEventListener('mouseup', handleMouseUp, { once: true });
342
+ };
343
+ return { calculatedColumn, onResize, onShow, onColumnResize };
344
+ };
345
+
346
+ export const clickEvent = (params) => {
347
+ const { emit } = getCurrentInstance();
348
+ const selectInfo = params;
349
+ const getClickedRowData = (event, row) => {
350
+ const tagName = event.target.tagName.toLowerCase();
351
+ let cellInfo = {};
352
+ if (event.target.offsetParent) {
353
+ if (tagName === 'td') {
354
+ cellInfo = event.target.dataset;
355
+ } else {
356
+ cellInfo = event.target.closest('td')?.dataset ?? {};
357
+ }
358
+ }
359
+ return {
360
+ event,
361
+ rowData: row,
362
+ rowIndex: row.index,
363
+ cellName: cellInfo.name,
364
+ cellIndex: cellInfo.index,
365
+ };
366
+ };
367
+ /**
368
+ * row click 이벤트를 처리한다.
369
+ *
370
+ * @param {object} event - 이벤트 객체
371
+ * @param {array} row - row 데이터
372
+ */
373
+ let timer = null;
374
+ const onRowClick = (event, row) => {
375
+ if (event.target && event.target.parentElement
376
+ && event.target.parentElement.classList.contains('row-checkbox-input')) {
377
+ return false;
378
+ }
379
+ clearTimeout(timer);
380
+ timer = setTimeout(() => {
381
+ if (selectInfo.useSelect) {
382
+ if (row.selected) {
383
+ row.selected = false;
384
+ if (selectInfo.multiple) {
385
+ if (event.ctrlKey) {
386
+ selectInfo.selectedRow = selectInfo.selectedRow.filter(s => s.index !== row.index);
387
+ } else {
388
+ selectInfo.selectedRow = [row];
389
+ }
390
+ } else {
391
+ selectInfo.selectedRow = [];
392
+ }
393
+ } else {
394
+ row.selected = true;
395
+ if (event.ctrlKey
396
+ && selectInfo.multiple
397
+ && (!selectInfo.limitCount || selectInfo.limitCount > selectInfo.selectedRow.length)) {
398
+ selectInfo.selectedRow.push(row);
399
+ } else {
400
+ selectInfo.selectedRow = [row];
401
+ }
402
+ }
403
+ emit('update:selected', selectInfo.selectedRow);
404
+ emit('click-row', getClickedRowData(event, row));
405
+ }
406
+ }, 100);
407
+ return true;
408
+ };
409
+ /**
410
+ * row dblclick 이벤트를 처리한다.
411
+ *
412
+ * @param {object} event - 이벤트 객체
413
+ * @param {array} row - row 데이터
414
+ */
415
+ const onRowDblClick = (event, row) => {
416
+ clearTimeout(timer);
417
+ emit('dblclick-row', getClickedRowData(event, row));
418
+ };
419
+ return { onRowClick, onRowDblClick };
420
+ };
421
+
422
+ export const checkEvent = (params) => {
423
+ const {
424
+ checkInfo,
425
+ stores,
426
+ checkHeader,
427
+ pageInfo,
428
+ getPagingData,
429
+ } = params;
430
+ const { emit } = getCurrentInstance();
431
+ /**
432
+ * row에 대한 체크 상태를 해제한다.
433
+ *
434
+ * @param {array} row - row 데이터
435
+ */
436
+ const unCheckedRow = (row) => {
437
+ const index = stores.treeStore.findIndex(
438
+ item => item.index === row.index);
439
+
440
+ if (index !== -1) {
441
+ stores.treeStore[index].checked = row.checked;
442
+ }
443
+ };
444
+ const onCheckChildren = (node) => {
445
+ if (node.hasChild) {
446
+ node.children.forEach((children) => {
447
+ const childNode = children;
448
+ if (node.checked && !childNode.checked && !childNode._disabled) {
449
+ checkInfo.checkedRows.push(childNode);
450
+ }
451
+ if (!node.checked) {
452
+ checkInfo.checkedRows = checkInfo.checkedRows
453
+ .filter(checked => checked.index !== childNode.index);
454
+ }
455
+ childNode.checked = node.checked && !childNode._disabled;
456
+
457
+ if (childNode.hasChild) {
458
+ onCheckChildren(childNode);
459
+ }
460
+ });
461
+ }
462
+ };
463
+ const onCheckParent = (node) => {
464
+ const parentNode = node.parent;
465
+ if (parentNode) {
466
+ const isCheck = parentNode.children.every(n => n.checked);
467
+ parentNode.checked = isCheck && !parentNode._disabled;
468
+ const disabledList = parentNode.children.filter(n => n._disabled);
469
+ if (disabledList.length) {
470
+ const checkedList = parentNode.children.filter(n => n.checked);
471
+ if (disabledList.length + checkedList.length === parentNode.children.length) {
472
+ parentNode.checked = true;
473
+ }
474
+ }
475
+ if (!parentNode.checked) {
476
+ checkInfo.checkedRows = checkInfo.checkedRows
477
+ .filter(checked => checked.index !== parentNode.index);
478
+ } else {
479
+ checkInfo.checkedRows.push(parentNode);
480
+ }
481
+ if (parentNode.parent) {
482
+ onCheckParent(parentNode);
483
+ }
484
+ }
485
+ };
486
+ /**
487
+ * checkbox click 이벤트를 처리한다.
488
+ *
489
+ * @param {object} event - 이벤트 객체
490
+ * @param {array} rowData - row 데이터
491
+ */
492
+ const onCheck = (event, rowData) => {
493
+ let store = stores.store;
494
+ if (pageInfo.isClientPaging) {
495
+ store = getPagingData();
496
+ }
497
+ const isSingleMode = () => checkInfo.useCheckbox.mode === 'single';
498
+ const unCheckHeader = () => {
499
+ if (checkInfo.isHeaderChecked) {
500
+ checkInfo.isHeaderChecked = false;
501
+ }
502
+ };
503
+ const onSingleMode = () => {
504
+ if (isSingleMode() && checkInfo.checkedRows.length > 0) {
505
+ checkInfo.prevCheckedRow.checked = false;
506
+ unCheckedRow(checkInfo.prevCheckedRow);
507
+ }
508
+ };
509
+ const addCheckedRow = (row) => {
510
+ if (isSingleMode()) {
511
+ checkInfo.checkedRows = [row];
512
+ return;
513
+ }
514
+ onCheckChildren(row);
515
+ onCheckParent(row);
516
+ checkInfo.checkedRows.push(row);
517
+ };
518
+ const removeCheckedRow = (row) => {
519
+ if (isSingleMode()) {
520
+ checkInfo.checkedRows = [];
521
+ return;
522
+ }
523
+ checkInfo.checkedRows = checkInfo.checkedRows.filter(it => it.index !== row.index);
524
+ };
525
+
526
+ onSingleMode();
527
+ if (rowData.checked) {
528
+ addCheckedRow(rowData);
529
+ checkHeader(store);
530
+ } else {
531
+ unCheckHeader();
532
+ removeCheckedRow(rowData);
533
+ onCheckChildren(rowData);
534
+ onCheckParent(rowData);
535
+ }
536
+ checkInfo.prevCheckedRow = rowData;
537
+ emit('update:checked', checkInfo.checkedRows);
538
+ emit('check-row', event, rowData.index, rowData);
539
+ };
540
+ /**
541
+ * all checkbox click 이벤트를 처리한다.
542
+ *
543
+ * @param {object} event - 이벤트 객체
544
+ */
545
+ const onCheckAll = (event) => {
546
+ const status = checkInfo.isHeaderChecked;
547
+ let store = stores.store;
548
+ if (pageInfo.isClientPaging) {
549
+ store = getPagingData();
550
+ }
551
+ store.forEach((row) => {
552
+ row.checked = status && !row._disabled;
553
+ if (row.checked) {
554
+ if (!checkInfo.checkedRows.find(checked => checked.index === row.index)) {
555
+ checkInfo.checkedRows.push(row);
556
+ }
557
+ } else {
558
+ checkInfo.checkedRows = checkInfo.checkedRows
559
+ .filter(checked => checked.index !== row.index);
560
+ }
561
+ });
562
+ emit('update:checked', checkInfo.checkedRows);
563
+ emit('check-all', event, checkInfo.checkedRows);
564
+ };
565
+ return { onCheck, onCheckAll };
566
+ };
567
+
568
+ export const contextMenuEvent = (params) => {
569
+ const { emit } = getCurrentInstance();
570
+ const { contextInfo, stores, selectInfo } = params;
571
+ /**
572
+ * 컨텍스트 메뉴를 설정한다.
573
+ *
574
+ * @param {boolean} useCustom - 사용자 지정 메뉴 사용 유무
575
+ */
576
+ const setContextMenu = (useCustom = true) => {
577
+ const menuItems = [];
578
+
579
+ if (useCustom && contextInfo.customContextMenu.length) {
580
+ const row = selectInfo.selectedRow;
581
+ const customItems = contextInfo.customContextMenu.map(
582
+ (item) => {
583
+ const menuItem = item;
584
+ if (menuItem.validate) {
585
+ menuItem.disabled = !menuItem.validate(menuItem.itemId, row);
586
+ }
587
+
588
+ return menuItem;
589
+ });
590
+
591
+ menuItems.push(...customItems);
592
+ }
593
+ contextInfo.contextMenuItems = menuItems;
594
+ };
595
+ /**
596
+ * 마우스 우클릭 이벤트를 처리한다.
597
+ *
598
+ * @param {object} event - 이벤트 객체
599
+ */
600
+ const onContextMenu = (event) => {
601
+ const target = event.target;
602
+ const rowIndex = target.closest('.row').dataset.index;
603
+
604
+ if (rowIndex) {
605
+ const index = stores.viewStore.findIndex(v => v.index === Number(rowIndex));
606
+ const rowData = stores.viewStore[index];
607
+ selectInfo.selectedRow = [rowData];
608
+ setContextMenu();
609
+ emit('update:selected', selectInfo.selectedRow);
610
+ } else {
611
+ selectInfo.selectedRow = [];
612
+ setContextMenu(false);
613
+ emit('update:selected', []);
614
+ }
615
+ };
616
+ return { setContextMenu, onContextMenu };
617
+ };
618
+
619
+ export const treeEvent = (params) => {
620
+ const { stores, onResize } = params;
621
+ const setTreeNodeStore = () => {
622
+ let nodeIndex = 0;
623
+ const nodeList = [];
624
+
625
+ function getDataObj(nodeObj) {
626
+ const newObj = {};
627
+ Object.keys(nodeObj).forEach((key) => {
628
+ if (key !== 'children') {
629
+ newObj[key] = nodeObj[key];
630
+ }
631
+ });
632
+ return newObj;
633
+ }
634
+
635
+ function setNodeData(nodeInfo) {
636
+ const { node, level, isShow, parent } = nodeInfo;
637
+ if (node !== null && typeof node === 'object') {
638
+ node.index = nodeIndex++;
639
+ node.level = level;
640
+
641
+ if (!Object.hasOwnProperty.call(node, 'checked')) {
642
+ node.checked = false;
643
+ }
644
+
645
+ if (!Object.hasOwnProperty.call(node, 'selected')) {
646
+ node.selected = false;
647
+ }
648
+
649
+ if (!Object.hasOwnProperty.call(node, 'show')) {
650
+ node.show = isShow;
651
+ }
652
+
653
+ if (!Object.hasOwnProperty.call(node, 'expand')) {
654
+ node.expand = true;
655
+ }
656
+
657
+ if (!Object.hasOwnProperty.call(node, 'isFilter')) {
658
+ node.isFilter = false;
659
+ }
660
+
661
+ if (!Object.hasOwnProperty.call(node, 'data')) {
662
+ node.data = getDataObj(node);
663
+ }
664
+
665
+ nodeList.push(node);
666
+
667
+ if (!Object.hasOwnProperty.call(node, 'parent')) {
668
+ node.parent = parent;
669
+ }
670
+ if (node.children) {
671
+ node.hasChild = true;
672
+ node.children.forEach(child =>
673
+ setNodeData({
674
+ node: child,
675
+ level: level + 1,
676
+ isShow: node.show && node.expand,
677
+ parent: node,
678
+ }),
679
+ );
680
+ }
681
+ }
682
+ }
683
+ stores.treeRows.forEach((root) => {
684
+ setNodeData({
685
+ node: root,
686
+ level: 0,
687
+ isShow: true,
688
+ parent: undefined,
689
+ });
690
+ });
691
+ return nodeList;
692
+ };
693
+ const setExpandNode = (children, isShow, isFilter) => {
694
+ children.forEach((nodeObj) => {
695
+ const node = nodeObj;
696
+ node.show = isFilter && isShow ? node.isFilter : isShow;
697
+ if (node.hasChild) {
698
+ setExpandNode(node.children, node.show && node.expand, node.isFilter);
699
+ }
700
+ });
701
+ };
702
+ const handleExpand = (node) => {
703
+ const data = node;
704
+ data.expand = !data.expand;
705
+ setExpandNode(data.children, data.expand, data.isFilter);
706
+ onResize();
707
+ };
708
+ return { setTreeNodeStore, handleExpand };
709
+ };
710
+
711
+ export const filterEvent = (params) => {
712
+ const {
713
+ stores,
714
+ filterInfo,
715
+ pageInfo,
716
+ getConvertValue,
717
+ onResize,
718
+ checkHeader,
719
+ getPagingData,
720
+ updatePagingInfo,
721
+ } = params;
722
+ const makeParentShow = (data) => {
723
+ if (!data?.parent) {
724
+ return;
725
+ }
726
+ const { parent } = data;
727
+ parent.show = true;
728
+ parent.isFilter = true;
729
+ makeParentShow(parent);
730
+ };
731
+ const makeChildShow = (data) => {
732
+ if (!data?.children) {
733
+ return;
734
+ }
735
+ const { children } = data;
736
+ children.forEach((node) => {
737
+ const childNode = node;
738
+ if (childNode.parent.show && childNode.parent.expand) {
739
+ childNode.show = true;
740
+ } else {
741
+ childNode.show = false;
742
+ }
743
+ childNode.isFilter = true;
744
+ if (childNode.hasChild) {
745
+ makeChildShow(childNode);
746
+ }
747
+ });
748
+ };
749
+ let timer = null;
750
+ const onSearch = (searchWord) => {
751
+ if (timer) {
752
+ clearTimeout(timer);
753
+ }
754
+ timer = setTimeout(() => {
755
+ filterInfo.isSearch = false;
756
+ filterInfo.searchWord = searchWord;
757
+ const store = stores.treeStore;
758
+ store.forEach((row) => {
759
+ row.show = false;
760
+ row.isFilter = false;
761
+ });
762
+ if (searchWord) {
763
+ const filterStores = store.filter((row) => {
764
+ let isSameWord = false;
765
+ for (let ix = 0; ix < stores.orderedColumns.length; ix++) {
766
+ const column = stores.orderedColumns[ix] || {};
767
+ let columnValue = row[column.field] ?? null;
768
+ column.type = column.type || 'string';
769
+ if (columnValue !== null) {
770
+ if (!column.hide && (column?.searchable === undefined || column?.searchable)) {
771
+ columnValue = getConvertValue(column, columnValue).toString();
772
+ isSameWord = columnValue.toLowerCase()
773
+ .includes(searchWord.toString().toLowerCase());
774
+ if (isSameWord) {
775
+ break;
776
+ }
777
+ }
778
+ }
779
+ }
780
+ return isSameWord;
781
+ });
782
+ filterStores.forEach((row) => {
783
+ row.show = true;
784
+ if (row.parent && !row.parent.expand) {
785
+ row.show = false;
786
+ }
787
+ row.isFilter = true;
788
+ makeParentShow(row);
789
+ makeChildShow(row);
790
+ });
791
+ filterInfo.isSearch = true;
792
+ } else {
793
+ store.forEach((row) => {
794
+ row.show = true;
795
+ row.isFilter = false;
796
+ });
797
+ store.forEach((row) => {
798
+ makeParentShow(row);
799
+ makeChildShow(row);
800
+ });
801
+ }
802
+ if (!searchWord && pageInfo.isClientPaging && pageInfo.prevPage) {
803
+ pageInfo.currentPage = 1;
804
+ stores.pagingStore = getPagingData();
805
+ }
806
+ updatePagingInfo({ onSearch: true });
807
+ checkHeader(stores.store);
808
+ onResize();
809
+ }, 500);
810
+ };
811
+ return { onSearch };
812
+ };
813
+
814
+ export const pagingEvent = (params) => {
815
+ const { emit } = getCurrentInstance();
816
+ const {
817
+ stores,
818
+ pageInfo,
819
+ filterInfo,
820
+ elementInfo,
821
+ clearCheckInfo,
822
+ } = params;
823
+ const getPagingData = () => {
824
+ const start = (pageInfo.currentPage - 1) * pageInfo.perPage;
825
+ const end = parseInt(start, 10) + parseInt(pageInfo.perPage, 10);
826
+ return stores.showTreeStore.slice(start, end);
827
+ };
828
+ const updatePagingInfo = (eventName) => {
829
+ emit('page-change', {
830
+ eventName,
831
+ pageInfo: {
832
+ currentPage: pageInfo.currentPage,
833
+ prevPage: pageInfo.prevPage,
834
+ startIndex: pageInfo.startIndex,
835
+ total: pageInfo.pageTotal,
836
+ perPage: pageInfo.perPage,
837
+ },
838
+ searchInfo: {
839
+ searchWord: filterInfo.searchWord,
840
+ searchColumns: stores.orderedColumns
841
+ .filter(c => !c.hide && (c?.searchable === undefined || c?.searchable))
842
+ .map(d => d.field),
843
+ },
844
+ });
845
+ if (pageInfo.isInfinite && (eventName?.onSearch || eventName?.onSort)) {
846
+ pageInfo.currentPage = 1;
847
+ elementInfo.body.scrollTop = 0;
848
+ clearCheckInfo();
849
+ }
850
+ };
851
+ const changePage = (beforeVal) => {
852
+ if (pageInfo.isClientPaging) {
853
+ pageInfo.prevPage = beforeVal;
854
+ if (stores.showTreeStore.length <= pageInfo.perPage) {
855
+ stores.pagingStore = stores.showTreeStore;
856
+ } else {
857
+ const start = (pageInfo.currentPage - 1) * pageInfo.perPage;
858
+ const end = parseInt(start, 10) + parseInt(pageInfo.perPage, 10);
859
+ stores.pagingStore = stores.showTreeStore.slice(start, end);
860
+ elementInfo.body.scrollTop = 0;
861
+ pageInfo.startIndex = start;
862
+ }
863
+ }
864
+ updatePagingInfo({ onChangePage: true });
865
+ };
866
+ return { getPagingData, updatePagingInfo, changePage };
867
+ };