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