From 505ce8b99614f07803bab8bdffb658c0acab490f Mon Sep 17 00:00:00 2001 From: kimyell <61657275+kimyell@users.noreply.github.com> Date: Thu, 31 Mar 2022 10:04:58 +0900 Subject: [PATCH] =?UTF-8?q?[#1100]=20Grid=20>=20row=20=EB=8B=A4=EC=A4=91?= =?UTF-8?q?=20=EC=84=A0=ED=83=9D=20(#1105)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: yell --- docs/views/grid/api/grid.md | 4 ++ docs/views/grid/example/Default.vue | 70 +++++++++++++++++++----- src/components/grid/Grid.vue | 51 +++++++++++++----- src/components/grid/uses.js | 83 ++++++++++++++++------------- 4 files changed, 145 insertions(+), 63 deletions(-) diff --git a/docs/views/grid/api/grid.md b/docs/views/grid/api/grid.md index 9a27142d3..3ee41b571 100644 --- a/docs/views/grid/api/grid.md +++ b/docs/views/grid/api/grid.md @@ -43,6 +43,10 @@ | | | use | 체크박스 사용 여부 | Boolean | | | | mode | 단일 및 다중 선택 설정 | 'multi', 'single' | | | | headerCheck | 헤더 체크박스 사용 여부 | Boolean | +| | useSelection | {} | 각 row별 선택 여부 및 단일 선택이나 다중 선택을 설정한다. | | +| | | use | Selection 사용 여부 | Boolean | +| | | multiple | 다중 선택 설정 여부 | Boolean | +| | | limitCount | 선택 가능한 row의 수를 제한 | Number | | | style | {} | 그리드의 스타일을 설정한다. | | | | | stripe | row의 배경색을 Stripe 스타일로 설정한다. | Boolean | | | | border | 그리드의 Border 여부를 설정한다. | 'none', 'rows' | diff --git a/docs/views/grid/example/Default.vue b/docs/views/grid/example/Default.vue index f5dea1d8b..f552c1923 100644 --- a/docs/views/grid/example/Default.vue +++ b/docs/views/grid/example/Default.vue @@ -20,6 +20,11 @@ mode: checkboxModeMV, headerCheck: headerCheckMV, }, + useSelection: { + use: useSelectionMV, + multiple: isSelectionMultiple, + limitCount: limitMV, + }, style: { stripe: stripeMV, border: borderMV, @@ -84,6 +89,30 @@ v-model="stripeMV" /> +
+ + Use Selection + + + + Multiple Selection + + + + Limit Count + + +
@@ -198,17 +227,9 @@ -
@@ -237,6 +258,8 @@ export default { const checkedRowsMV = ref(); const clickedRowMV = ref(); const DbClickedRowsMV = ref(); + const useSelectionMV = ref(true); + const isSelectionMultiple = ref(false); const menuItems = ref([ { text: 'Menu1', @@ -258,6 +281,17 @@ export default { value: 'rows', }, ]); + const limitMV = ref(2); + const limitItems = ref([ + { + name: '2', + value: 2, + }, + { + name: '4', + value: 4, + }, + ]); const columns = ref([ { caption: '', field: 'user-icon', type: 'string' }, { caption: 'Name', field: 'userName', type: 'stringNumber', width: 80 }, @@ -276,16 +310,20 @@ export default { }; const onCheckedRow = () => { let checkedRow = ''; - for (let i = 0; i < checked.value.length; i++) { - checkedRow += JSON.stringify(checked.value[i]); - } + checked.value.forEach((row) => { + checkedRow += JSON.stringify(row); + }); checkedRowsMV.value = checkedRow; }; const onDoubleClickRow = (e) => { DbClickedRowsMV.value = `${e.rowData}`; }; - const onClickRow = (e) => { - clickedRowMV.value = `${e.rowData}`; + const onClickRow = () => { + let clickedRow = ''; + selected.value.forEach((row) => { + clickedRow += JSON.stringify(row); + }); + clickedRowMV.value = clickedRow; }; const getData = (count, startIndex) => { const temp = []; @@ -356,6 +394,10 @@ export default { borderMV, items, pageInfo, + isSelectionMultiple, + useSelectionMV, + limitMV, + limitItems, changeMode, onCheckedRow, onDoubleClickRow, diff --git a/src/components/grid/Grid.vue b/src/components/grid/Grid.vue index 4905f09f0..66de055c0 100644 --- a/src/components/grid/Grid.vue +++ b/src/components/grid/Grid.vue @@ -150,7 +150,7 @@ :data-index="row[0]" :class="{ row: true, - selected: row[2] === selectedRow, + selected: row[3], 'non-border': !!borderStyle && borderStyle !== 'rows', highlight: row[0] === highlightIdx, }" @@ -323,9 +323,10 @@ export default { 'page-change': null, }, setup(props) { - const ROW_INDEX = 0; + // const ROW_INDEX = 0; const ROW_CHECK_INDEX = 1; const ROW_DATA_INDEX = 2; + const ROW_SELECT_INDEX = 3; const { isRenderer, getComponentName, @@ -389,7 +390,6 @@ export default { prevCheckedRow: [], isHeaderChecked: false, checkedRows: props.checked, - checkedIndex: new Set(), useCheckbox: computed(() => (props.option.useCheckbox || {})), }); const scrollInfo = reactive({ @@ -402,8 +402,14 @@ export default { hasVerticalScrollBar: false, }); const selectInfo = reactive({ - useSelect: props.option.useSelect === undefined ? true : props.option.useSelect, selectedRow: props.selected, + useSelect: computed(() => props.option?.useSelection?.use ?? true), + limitCount: computed(() => { + let limit = props.option?.useSelection?.limitCount; + limit = !!limit && limit >= 2 ? limit : 0; + return limit; + }), + multiple: computed(() => props.option?.useSelection?.multiple ?? false), }); const sortInfo = reactive({ isSorting: false, @@ -430,12 +436,17 @@ export default { }); const clearCheckInfo = () => { checkInfo.checkedRows = []; - checkInfo.checkedIndex.clear(); checkInfo.isHeaderChecked = false; stores.store.forEach((row) => { row[ROW_CHECK_INDEX] = false; }); }; + const clearSelectInfo = () => { + selectInfo.selectedRow = []; + stores.store.forEach((row) => { + row[ROW_SELECT_INDEX] = false; + }); + }; const { getPagingData, updatePagingInfo, @@ -466,7 +477,7 @@ export default { const { onRowClick, onRowDblClick, - } = clickEvent(selectInfo); + } = clickEvent({ selectInfo }); const { onCheck, @@ -582,7 +593,6 @@ export default { (checkedList) => { checkInfo.checkedRows = checkedList; checkInfo.isHeaderChecked = false; - checkInfo.checkedIndex.clear(); let store = stores.store; if (pageInfo.isClientPaging) { store = getPagingData(); @@ -590,9 +600,6 @@ export default { if (store.length) { store.forEach((row) => { row[ROW_CHECK_INDEX] = checkedList.includes(row[ROW_DATA_INDEX]); - if (row[ROW_CHECK_INDEX]) { - checkInfo.checkedIndex.add(row[ROW_INDEX]); - } }); checkInfo.isHeaderChecked = checkedList.length === store.length; } @@ -601,8 +608,14 @@ export default { ); watch( () => props.selected, - (value) => { - selectInfo.selectedRow = value; + (selectedList) => { + if (selectInfo.useSelect) { + selectInfo.selectedRow = selectedList; + stores.store.forEach((row) => { + row[ROW_SELECT_INDEX] = selectInfo.selectedRow.includes(row[ROW_DATA_INDEX]); + }); + updateVScroll(); + } }, ); watch( @@ -611,6 +624,18 @@ export default { clearCheckInfo(); }, ); + watch( + () => selectInfo.useSelect, + () => { + clearSelectInfo(); + }, + ); + watch( + () => selectInfo.multiple, + () => { + clearSelectInfo(); + }, + ); watch( () => props.checked.length, (checkedSize) => { @@ -652,6 +677,7 @@ export default { onSearch(value?.value ?? value); if (pageInfo.isClientPaging) { clearCheckInfo(); + clearSelectInfo(); } } }, { immediate: true }, @@ -673,6 +699,7 @@ export default { changePage(beforeVal[0]); if (pageInfo.isClientPaging && currentVal[0] !== beforeVal[0]) { clearCheckInfo(); + clearSelectInfo(); } updateVScroll(); }); diff --git a/src/components/grid/uses.js b/src/components/grid/uses.js index bed781143..e88285426 100644 --- a/src/components/grid/uses.js +++ b/src/components/grid/uses.js @@ -1,10 +1,11 @@ import { getCurrentInstance, nextTick } from 'vue'; -import { isEqual, uniqBy } from 'lodash-es'; +import { uniqBy } from 'lodash-es'; import { numberWithComma } from '@/common/utils'; const ROW_INDEX = 0; const ROW_CHECK_INDEX = 1; const ROW_DATA_INDEX = 2; +const ROW_SELECT_INDEX = 3; export const commonFunctions = () => { const { props } = getCurrentInstance(); @@ -338,7 +339,7 @@ export const resizeEvent = (params) => { export const clickEvent = (params) => { const { emit } = getCurrentInstance(); - const selectInfo = params; + const { selectInfo } = params; const getClickedRowData = (event, row) => { const tagName = event.target.tagName.toLowerCase(); let cellInfo = {}; @@ -361,17 +362,41 @@ export const clickEvent = (params) => { * @param {object} event - 이벤트 객체 * @param {array} row - row 데이터 */ + let timer = null; const onRowClick = (event, row) => { if (event.target && event.target.parentElement && event.target.parentElement.classList.contains('row-checkbox-input')) { return false; } - if (selectInfo.useSelect) { - const rowData = row[ROW_DATA_INDEX]; - selectInfo.selectedRow = rowData; - emit('update:selected', rowData); - emit('click-row', getClickedRowData(event, row)); - } + clearTimeout(timer); + timer = setTimeout(() => { + if (selectInfo.useSelect) { + const rowData = row[ROW_DATA_INDEX]; + if (row[ROW_SELECT_INDEX]) { + row[ROW_SELECT_INDEX] = false; + if (selectInfo.multiple) { + if (event.ctrlKey) { + selectInfo.selectedRow.splice(selectInfo.selectedRow.indexOf(row[ROW_DATA_INDEX]), 1); + } else { + selectInfo.selectedRow = [rowData]; + } + } else { + selectInfo.selectedRow = []; + } + } else { + row[ROW_SELECT_INDEX] = true; + if (event.ctrlKey + && selectInfo.multiple + && (!selectInfo.limitCount || selectInfo.limitCount > selectInfo.selectedRow.length)) { + selectInfo.selectedRow.push(rowData); + } else { + selectInfo.selectedRow = [rowData]; + } + } + emit('update:selected', selectInfo.selectedRow); + emit('click-row', getClickedRowData(event, row)); + } + }, 100); return true; }; /** @@ -381,9 +406,7 @@ export const clickEvent = (params) => { * @param {array} row - row 데이터 */ const onRowDblClick = (event, row) => { - const rowData = row[ROW_DATA_INDEX]; - selectInfo.selectedRow = rowData; - emit('update:selected', rowData); + clearTimeout(timer); emit('dblclick-row', getClickedRowData(event, row)); }; return { onRowClick, onRowDblClick }; @@ -420,12 +443,9 @@ export const checkEvent = (params) => { if (row[ROW_CHECK_INDEX]) { if (checkInfo.useCheckbox.mode === 'single') { checkInfo.checkedRows = [row[ROW_DATA_INDEX]]; - checkInfo.checkedIndex.clear(); } else { checkInfo.checkedRows.push(row[ROW_DATA_INDEX]); } - checkInfo.checkedIndex.add(row[ROW_INDEX]); - let store = stores.store; if (pageInfo.isClientPaging) { store = getPagingData(); @@ -437,10 +457,8 @@ export const checkEvent = (params) => { } else { if (checkInfo.useCheckbox.mode === 'single') { checkInfo.checkedRows = []; - checkInfo.checkedIndex.clear(); } else { checkInfo.checkedRows.splice(checkInfo.checkedRows.indexOf(row[ROW_DATA_INDEX]), 1); - checkInfo.checkedIndex.delete(row[ROW_INDEX]); } checkInfo.isHeaderChecked = false; } @@ -464,12 +482,8 @@ export const checkEvent = (params) => { if (!checkInfo.checkedRows.includes(row[ROW_DATA_INDEX])) { checkInfo.checkedRows.push(row[ROW_DATA_INDEX]); } - if (!checkInfo.checkedIndex.has(row[ROW_INDEX])) { - checkInfo.checkedIndex.add(row[ROW_INDEX]); - } } else { checkInfo.checkedRows.splice(checkInfo.checkedRows.indexOf(row[ROW_DATA_INDEX]), 1); - checkInfo.checkedIndex.delete(row[ROW_INDEX]); } row[ROW_CHECK_INDEX] = isHeaderChecked; }); @@ -888,30 +902,25 @@ export const storeEvent = (params) => { /** * 전달된 데이터를 내부 store 및 속성에 저장한다. * - * @param {array} value - row 데이터 + * @param {array} rows - row 데이터 * @param {boolean} isMakeIndex - 인덱스 생성 유무 */ - const setStore = (value, isMakeIndex = true) => { - const store = []; - let checked; - let selected = false; + const setStore = (rows, isMakeIndex = true) => { if (isMakeIndex) { + const store = []; let hasUnChecked = false; - for (let ix = 0; ix < value.length; ix++) { - checked = props.checked.includes(value[ix]); + rows.forEach((row, idx) => { + const checked = props.checked.includes(row); + let selected = false; + if (selectInfo.useSelect) { + selected = props.selected.includes(row); + } if (!checked) { hasUnChecked = true; } - if (!selected && isEqual(selectInfo.selectedRow, value[ix])) { - selectInfo.selectedRow = value[ix]; - selected = true; - } - store.push([ix, checked, value[ix]]); - } - if (!selected) { - selectInfo.selectedRow = []; - } - checkInfo.isHeaderChecked = value.length > 0 ? !hasUnChecked : false; + store.push([idx, checked, row, selected]); + }); + checkInfo.isHeaderChecked = rows.length > 0 ? !hasUnChecked : false; stores.originStore = store; } if (filterInfo.isFiltering) {