업무 중
테이블 안에 체크박스 Shift 키 감지(+ Vue.js, TypeScript)
콩유니
2024. 11. 27. 14:12
체크박스
테이블 안에 체크박스 Shift 키 감지
문제 → 회사에서 v-data-table 컴포넌트를 쓰는데 전체 체크박스 이외에는 기능이 없음.
회사에서는 Shift 키를 누른 상태일 경우 클릭을 시작한 지점부터 마지막 클릭 지점까지 영역이
체크가 동기화 되길 원함.
우선 Shift키를 누르고 있는 지에 대한 확인이 필요하고 마지막 체크한 로우의 값,
그리고 체크 된 값들을 넘겨줄 배열을 만들었다.
const isShiftPressed = ref<boolean>(false);
const lastChecked = ref<number | null>(null);
const checkedValues = ref<any[]>([]);
window.addEventListener("keydown", keydownShift);
window.addEventListener("keyup", keyupShift);
- 디자인테이블
내가 쓴 건 vue3 Composition API였지만 기존 디자인 테이블은 테이블의 형태만 있을 뿐
스크립트 코드가 있거나 하지 않고 slot으로 페이지 내에서 모든 걸 넣어주었기 때문에
요소를 찾고 체크박스를 감지할 필요가 있었음.
<script setup lang="ts">
interface DesignTableProps {
data?: any[];
}
const props = withDefaults(defineProps<DesignTableProps>(), {
data: () => [],
});
interface TableRowData {
index: number;
data: HTMLTableRowElement | null;
}
const isShiftPressed = ref<boolean>(false);
const lastChecked = ref<number | null>(null);
const checkedValues = ref<any[]>([]);
const emit = defineEmits(["checkChange"]);
// watch(
// () => props.data,
// (newData, oldData) => {
// // 데이터 변경 시
// },
// { deep: true }
// );
const updateCheckedValues = () => {
checkedValues.value = [];
const tbody = document.querySelector("tbody");
const rows = Array.from(tbody?.querySelectorAll("tr") || []);
rows.filter((row, index) => {
const checkbox = row.querySelector("input[type=checkbox]") as HTMLInputElement;
if (checkbox?.checked) checkedValues.value.push(index);
});
checkedValues.value = Array.from(new Set(checkedValues.value)).sort((a, b) => a - b);
emit("checkChange", checkedValues.value);
};
const handleCheckboxClick = (item: TableRowData) => {
const tbody = document.querySelector("tbody");
const rows = Array.from(tbody?.querySelectorAll("tr") || []);
if (isShiftPressed.value && lastChecked.value !== null) {
const start = Math.min(lastChecked.value, item.index);
const end = Math.max(lastChecked.value, item.index);
const isChecked = (rows[start].querySelector(
"input[type=checkbox]"
) as HTMLInputElement)?.checked;
for (let i = start; i <= end; i++) {
const checkbox = rows[i].querySelector("input[type=checkbox]") as HTMLInputElement;
if (checkbox) {
checkbox.checked = isChecked;
}
}
}
lastChecked.value = item.index;
updateCheckedValues(); // 체크된 값들 업데이트
};
const keydownShift = (e: any) => {
if (e.key === "Shift") isShiftPressed.value = true;
};
const keyupShift = (e: any) => {
if (e.key === "Shift") isShiftPressed.value = false;
};
onMounted(async () => {
window.addEventListener("keydown", keydownShift);
window.addEventListener("keyup", keyupShift);
const tbody = document.querySelector("tbody");
if (tbody) {
tbody.addEventListener("change", (event) => {
const target = event.target as HTMLInputElement;
if (target && target.type === "checkbox") {
const row = target.closest("tr");
const index = Array.from(tbody.children).indexOf(row as HTMLTableRowElement);
const item = { index, data: row };
handleCheckboxClick(item);
}
});
}
});
onBeforeUnmount(() => {
window.removeEventListener("keydown", keydownShift);
window.removeEventListener("keyup", keyupShift);
});
</script>
<template>
<div class="table_wrap">
<table class="tbl">
<caption>
테이블 정보
</caption>
<colgroup>
<slot name="colgroup"></slot>
</colgroup>
<thead>
<slot name="thead"></slot>
</thead>
<tbody>
<slot name="tbody"></slot>
</tbody>
</table>
</div>
</template>
<style scoped></style>
<template>
<DesignTable @checkChange="getCheckedValue">
<table>~~~~~</table>
</DesignTable>
</template>
<script>
//선택된 로우들 담아줄 값
const selectedRow = ref<any[]>([]);
//gridData는 테이블에 뿌려주는 데이터
//TypeScript를 쓸 경우 CHECKED를 type에 추가해주어야 한다.
//기존에 페이지에 체크박스 로직이 구현되어있다면 이걸 굳이 넣진 않아도 됨.
const getCheckedValue = (getCheckValue: number[]) => {
// 초기화 후에 새로 담기
selectedRow.value = [];
gridData.value.map((item, index) => {
if (getCheckValue.includes(index) ){ selectedRow.value.push(item)} ;
});
};
// 전체 thead에 있는 checkbox 관련 변수 및 함수
const checked = ref(false);
const allCheckedChange = () => {
if (!checked.value) {
// 전체 체크 해제
selectedRow.value = [];
for (let i = 0; i < gridData.value.length; i++) {
gridData.value[i].CHECKED = false;
}
} else {
// 전체 체크
selectedRow.value = gridData.value.map((item) => item.RNUM);
for (let i = 0; i < gridData.value.length; i++) {
gridData.value[i].CHECKED = true;
}
}
};
// tbody 체크박스 클릭
const checkedChange = (item: 리스트타입) => {
if (!item.CHECKED) {
item.CHECKED = false;
if (isShiftPressed.value && lastChecked.value !== null) {
const start = Math.min(lastChecked.value, item.RNUM);
const end = Math.max(lastChecked.value, item.RNUM);
for (const item of gridData.value) {
if (item.RNUM >= start && item.RNUM <= end) {
item.CHECKED = false;
}
}
}
} else {
item.CHECKED = true;
if (isShiftPressed.value && lastChecked.value !== null) {
const start = Math.min(lastChecked.value, item.RNUM);
const end = Math.max(lastChecked.value, item.RNUM);
for (const item of gridData.value) {
if (item.RNUM >= start && item.RNUM <= end) {
item.CHECKED = true;
}
}
}
gridData.value.forEach((item) => {
if(item.CHECKED) {
selectedRow.value.push(item);
} else if(!item.CHECKED) {
selectedRow.value = selectedRow.value.filter(
(key) => key.RNUM !== item.RNUM
);
}
})
}
lastChecked.value = item.RNUM;
// 중복제거를 한번 해줘야 한다.
selectedRow.value = Array.from(new Set(selectedRow.value));
};
</script>
- 데이터 테이블
기존 v-data-table을 썼는데 공식 홈페이지를 뒤져도 Shift 키 기능이 되어 있지 않다.
누가 문의를 남긴 흔적이 있는데 댓글에 아직도 그 기능 안 되어있냐고 하는 게 인상적이었음.
<script>
const isShiftPressed = ref<boolean>(false);
const lastCheckedIndex = ref(-1);
const handleCheckboxClick = (index: number, item: any, isChecked: boolean) => {
const rowKeyValue = filteredData.value[index][props.rowKey];
if (isShiftPressed.value && lastCheckedIndex.value !== -1) {
// Shift 클릭 처리
const start = Math.min(index, lastCheckedIndex.value);
const end = Math.max(index, lastCheckedIndex.value);
for (let i = start; i <= end; i++) {
const keyValue = filteredData.value[i][props.rowKey];
if (isChecked && !props.modelValue.includes(keyValue)) {
props.modelValue.push(keyValue);
} else if (!isChecked && props.modelValue.includes(keyValue)) {
props.modelValue.splice(props.modelValue.indexOf(keyValue), 1);
}
}
} else {
// 단일 클릭 처리
if (isChecked && !props.modelValue.includes(rowKeyValue)) {
props.modelValue.push(rowKeyValue);
} else if (!isChecked && props.modelValue.includes(rowKeyValue)) {
props.modelValue.splice(props.modelValue.indexOf(rowKeyValue), 1);
}
}
lastCheckedIndex.value = index;
}
const keydownShift = (e: any) => {
if (e.key === "Shift") isShiftPressed.value = true;
}
const keyupShift = (e: any) => {
if (e.key === "Shift") isShiftPressed.value = false;
}
onMounted(async() => {
addCaption();
// await nextTick();
window.addEventListener("keydown", keydownShift);
window.addEventListener("keyup", keyupShift);
});
onBeforeUnmount(() => {
window.removeEventListener("keydown", keydownShift);
window.removeEventListener("keyup", keyupShift);
});
</script>
<template>
<!-- 커스텀 체크박스 슬롯 -->
<template
#item.data-table-select="{index, item, column}: {index: number, item: any, column: any,}">
<td>
<label for="checkbox-{{ index }}" class="sr-only">{{item[props.rowKey]}}</label>
<CommonCheckbox
:modelValue="checked.includes(item[rowKey])"
:value="item[rowKey]"
id="checkbox-{{ index }}"
@update:modelValue="(value: any) => handleCheckboxClick(index, item, value)"
/>
</td>
</template>
</template>
Tip
Shift 영역 체크박스를 구현 할 때는
- [ ] Shift 키가 눌려진 상태인지
- [ ] 단일 클릭인지 영역 클릭인지
- [ ] 클릭한 값들을 중복 없이 돌려주는지
를 체크해야한다.