https://greed-yb.tistory.com/275
이전글에 기능에 연관되어 있어서 참고 바란다
완성 화면
html
<style>
/* 선택된 권한 dataTable 행의 배경색 */
tr.selected {
background-color: #f1f1f1;
}
</style>
<div class="col-sm-8">
<!-- DataTales Example -->
<div class="card shadow mb-4">
<div class="card-header py-3" style="text-align: left">
<div class="row">
<div class="col-6">
<h5 class="m-0 font-weight-bold text-dark">메뉴</h5>
</div>
<div class="col-6">
<button id="menuSave" class="btn btn-info float-right"
type="button" onclick="menuSave()">저장
</button>
</div>
</div>
</div>
<div class="card-body">
<div class="form-group">
<table class="table asm table-bordered" id="treeMenuTable" style="width:100%">
<tbody style="border: none">
<tr>
<td>
<input onclick='selectAllcheck(this)' type='checkbox' name='selectall' id='selectAll'> 전체
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<td> </td> 안에 있는
<input onclick='selectAllcheck(this)' type='checkbox' name='selectall' id='selectAll'> 전체
코드는 DB에 있는 데이터를 가져오기 전에 일부로 미리 생성해 놓은 건데
상황에 맞게 추가 및 지우고 사용하면 되겠다
javascript
/**
* 메뉴 체크 변수
*/
let menu; // 체크 된 메뉴 확인 용 변수
let menuArr; // 체크 된 메뉴를 담기 위한 변수
let roleId; // 선택된 권한의 id 변수
// 권한 정보 DataTable에 넣기
function RoleInit(data){
role_dataTable = $("#role_dataTable").DataTable({
data: data, // 가져온 데이터
"paging": true, // 페이징
"lengthMenu": [[5,10, 50, 100, -1], [5,10, 50, 100, "All"] ], // 목록 개수
"pageLength": 5, // 보여지는 기본 개수
"searching": true, // 검색 기능
"info": true, // 정보 표시
// "scrollX": false, // 가로 스크롤(true , false)
// "scrollY": false, // 새로 스크롤(false , px 단위 : 200 )
"ordering": false, // 정렬 기능
// "order": [0 , "ASC"], // 정렬 기준
"language": { // 언어 설정
decimal: "",
emptyTable: "검색된 데이터가 없습니다.",
info: "Showing _START_ to _END_ of _TOTAL_ entries",
infoEmpty: "Showing 0 to 0 of 0 entries",
infoFiltered: "(filtered from _MAX_ total entries)",
infoPostFix: "",
thousands: ",",
lengthMenu: " _MENU_ ",
loadingRecords: "Loading...",
processing: "Processing...",
search: "Search",
zeroRecords: "항목이 존재하지 않습니다",
paginate: {
"first": "처음",
"last": "마지막",
"next": "다음",
"previous": "이전"
},
aria: {
sortAscending: ": activate to sort column ascending",
sortDescending: ": activate to sort column descending"
}
},
"columnDefs": [
],
"columns":[
{
data: "roleId",
render: function (data, type, row){
return data;
}
},
{
data: "roleName",
render: function (data, type, row){
return '<input class="form-control form-control-user" id="roleName" value=' + data + '>';
}
},
{
data: null,
render: function (data, type, row){
return '<a name="save" class="btn btn-success btn-circle mx-1 my-1"><i class="fas fa-save"> </i></a>' +
'<a name="del" class="btn btn-danger btn-circle"><i class="fas fas fa-trash"> </i></a>';
}
}
]
});
// 버튼 제어
$("#role_dataTable tbody").on("click", "a", function (){
let tr = $(this).closest("tr");
let row = role_dataTable.row(tr);
let aName = $(this).attr('name');
if(aName == 'save'){
saveRole(tr);
}else if(aName == 'del'){
deleteRole(row);
}
});
/* 선택된 tr 행 색상 변경 및 권한에 해당하는 메뉴 정보 가져오기*/
$('#role_dataTable tbody').on('click', 'tr', function () {
let tr = $(this).closest('tr');
let row = role_dataTable.row(tr);
if (tr.hasClass('selected')) {
tr.removeClass('selected');
roleId = null;
} else {
role_dataTable.$('tr.selected').removeClass('selected');//테이블에서 선택된 것 삭제
tr.addClass('selected');
//선택 ajax 호출
roleId = row.data().roleId;
selectMenuList(roleId);
}
});
}
/**
* 메뉴 리스트 가져오기
*/
function menuList(){
$.ajax({
url : "/menuList",
type : "GET",
success : function (data) {
if(data != null){
for (let i = 0; i < data.length; i++) {
if(data[i].parentId == 0){
$("#treeMenuTable").append("<tr data-tt-id=\"" + data[i].id + "\" data-tt-parent-id=\"" + data[i].parentId + "\"><td style='border: 0;' id=\"" + data[i].id + "\"><span class='fas fa-plus plusIcon' style='display:none; border: 0;'></span><span class='fas fa-minus plusIcon'></span>" + " " + "<input onclick='checkMenu(this)' type='checkbox' name='checkMenu' id=\"" + data[i].id + "\">" + " " + data[i].name + "</td></tr>");
}else{
$("#" + data[i].parentId).append("<tr data-tt-id=\"" + data[i].id + "\" data-tt-parent-id=\"" + data[i].parentId + "\"><td style='width:100%; border: 0;'>" + " " + "<input onclick='checkMenu(this)' type='checkbox' name='checkMenu' id=\"" + data[i].id + "\">" + " " + data[i].name + "</td></tr>");
}
}
}
},
error(e){
console.log("error : "+ e);
}
});
}
/**
* 테이블의 + - 아이콘 클릭시 제어
*/
$("#treeMenuTable").on("click", ".plusIcon", function () {
var obj = $(this);
if (obj.hasClass("fa-plus")) {
obj.hide();
obj.next().show();
obj.parent().children().children().show();
} else {
obj.hide();
obj.prev().show();
obj.parent().children().children().hide()
}
});
/**
* 체크 박스 클릭 시 부모/자식 체크 박스 여부
* @param checkbox
*/
function checkMenu(checkbox) {
menu = document.getElementsByName("checkMenu")
menuArr = [];
// 전체 체크 되어 있는 상태에서 자식데이터의 체크박스 해제시 전체 체크박스 해제
const selectall = document.querySelector('input[name="selectall"]');
if (checkbox != null && checkbox.checked === false) {
selectall.checked = false;
}
// 체크시 자식데이터가 있으면 전체 체크
let childMenu = $("#" + checkbox.id).find("tr").find("td").find("input");
if (childMenu.length > 0 && checkbox.checked == true) {
for (let i = 0; i < childMenu.length; i++) {
childMenu[i].checked = true;
}
} else if (childMenu.length > 0 && checkbox.checked == false) {
for (let i = 0; i < childMenu.length; i++) {
childMenu[i].checked = false;
}
}
// 체크시 부모가 있다면 부모 체크
// 자식 노드이면서 부모 노드가 false 인 경우
if(checkbox.parentNode.parentNode.dataset.ttParentId == checkbox.parentNode.parentNode.parentNode.id && checkbox.parentNode.parentNode.parentNode.childNodes[3].checked == false){
checkbox.parentNode.parentNode.parentNode.childNodes[3].checked = true;
}
// 자식 노드이면서 부모 노드가 true 일때 자식노드가 모두 체크 해제되면 부모노드도 체크 해제하기
else if(checkbox.parentNode.parentNode.dataset.ttParentId == checkbox.parentNode.parentNode.parentNode.id && checkbox.parentNode.parentNode.parentNode.childNodes[3].checked == true){
let childCheck = $("#" + checkbox.parentNode.parentNode.parentNode.id).find("tr").find("td").find("input");
if (childCheck.length > 0){
let countCheck = 0;
for (let i = 0; i < childCheck.length; i++) {
if(childCheck[i].checked == false){
countCheck++;
}
}
if (countCheck == childCheck.length){
checkbox.parentNode.parentNode.parentNode.childNodes[3].checked = false;
}
}
}
// 체크되어 있는 데이터
for (let i = 0; i < menu.length; i++) {
if (menu[i].checked == true) {
menuArr.push(menu[i].id);
}
}
}
/**
* 체크박스 모두 선택/해제
* @param selectAll
*/
function selectAllcheck(selectAll) {
const checkboxes = document.getElementsByName('checkMenu');
checkboxes.forEach((checkbox) => {
checkbox.checked = selectAll.checked
})
menu = document.getElementsByName("checkMenu")
menuArr = [];
// 체크되어 있는 데이터
for (let i = 0; i < menu.length; i++) {
if (menu[i].checked == true) {
menuArr.push(menu[i].id);
}
}
}
/**
* 선택한 권한의 메뉴 정보 가져오기
* @param id
*/
function selectMenuList(id) {
$.ajax({
type: 'GET',
url: '/selectMenuList',
data: {id: id},
dataType: 'json',
success: function (data) {
let inputCount = $("#treeMenuTable").find("input").length;
menuArr = [];
for (let i = 0; i < inputCount; i++) {
$("#treeMenuTable").find("input")[i].checked = false;
}
if(data != ''){
for (let i = 0; i < data.length; i++) {
for (let j = 0; j < inputCount; j++) {
if ($("#treeMenuTable").find("input")[j].id == data[i].id) {
$("#treeMenuTable").find("input")[j].checked = true;
menuArr.push(data[i].id);
}
}
}
}
},error(e){
}
})
}
// 선택된 메뉴 저장하기
function menuSave() {
if (roleId == null || roleId == '') {
alert("권한을 선택해주세요.");
}else if(menuArr == null || menuArr == ''){
alert("메뉴를 선택해주세요.");
}else{
$.ajax({
type: 'POST',
url: '/saveMenuList',
data: {"roleId": roleId , "menuArr" : menuArr},
success: function (data) {
if(data == "success") {
alert("저장 완료 하였습니다.");
}else {
alert("저장 오류가 발생하였습니다.");
}
},error(e){
alert("오류가 발생하였습니다.");
}
})
}
}
$(document).ready(function() {
// 권한 정보 가져오기
roleInfo();
// 메뉴 정보 가져오기
menuList();
});
function menuList() 으로 페이지 시작 시 메뉴 데이터를 가져온다
ajax 로 가져온 데이터를 html 에 treeMenuTable table 태그에 append 해주는 방식이다
append 하는 tr 태그에 data 속성을 이용하여 메뉴의 id 및 해당 메뉴의 부모 id 값을 넣어서 가지고 있는다
controller
import com.example.practice.service.role.RoleService;
import com.example.practice.vo.RoleVo;
import com.example.practice.vo.menu.MenuVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.List;
@RestController
public class RoleController {
@Autowired
private RoleService roleService;
/**
* 메뉴 정보 가져오기
* @return
* @throws Exception
*/
@GetMapping("/menuList")
public List<MenuVo> menuList() throws Exception{
List<MenuVo> vo = roleService.menuList();
return vo;
}
/**
* 체크된 권한의 메뉴 정보 가져오기
* @param id
* @return
* @throws Exception
*/
@GetMapping("/selectMenuList")
public List<MenuVo> selectMenuList(@RequestParam String id) throws Exception{
List<MenuVo> vo = roleService.selectMenuList(id);
return vo;
}
/**
* 선택한 권한에 메뉴 넣기
* @param roleId
* @param menuArr
* @return
*/
@PostMapping("/saveMenuList")
public String saveMenuList(@RequestParam String roleId , @RequestParam(value = "menuArr[]") ArrayList<String> menuArr){
System.err.println("roleId : " + roleId);
System.err.println("menuArr : " + menuArr.toString());
try {
roleService.deleteMenuList(roleId);
for (int i = 0; i < menuArr.size(); i++) {
roleService.insertMenuList(roleId, menuArr.get(i));
}
return "success";
}catch (Exception e){
e.printStackTrace();
return "fail";
}
}
}
vo
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class MenuVo {
private Long id;
private Long parentId;
private String name;
private Long ord;
private String icon;
}
service
import com.example.practice.vo.RoleVo;
import com.example.practice.vo.menu.MenuVo;
import java.util.List;
public interface RoleService {
/**
* 권한 정보 가져오기
* @return
* @throws Exception
*/
List<RoleVo> roleInfo() throws Exception;
/**
* 권한 정보 추가하기
* @param vo
* @throws Exception
*/
void roleAdd(RoleVo vo) throws Exception;
/**
* 권한 정보 수정하기
* @param vo
* @throws Exception
*/
void roleUpdate(RoleVo vo) throws Exception;
/**
* 권한 정보 삭제하기
* @param roleId
* @throws Exception
*/
void roleDelete(String roleId) throws Exception;
/**
* 메뉴 정보 가져오기
* @return
* @throws Exception
*/
List<MenuVo> menuList() throws Exception;
/**
* 선택한 권한의 메뉴 정보 가져오기
* @param id
* @return
* @throws Exception
*/
List<MenuVo> selectMenuList(String id) throws Exception;
/**
* 권한에 연결되어있는 메뉴 삭제
* @param roleId
* @throws Exception
*/
void deleteMenuList(String roleId) throws Exception;
/**
* 선택한 권한에 메뉴 넣기
* @param roleId
* @param menuId
*/
void insertMenuList(String roleId , String menuId);
}
mapper.class
import com.example.practice.vo.RoleVo;
import com.example.practice.vo.menu.MenuVo;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface RoleMapper {
/**
* 권한 정보 가져오기
* @return
* @throws Exception
*/
List<RoleVo> roleInfo() throws Exception;
/**
* 권한 정보 추가하기
* @param vo
* @throws Exception
*/
void roleAdd(RoleVo vo) throws Exception;
/**
* 권한 정보 수정하기
* @param vo
* @throws Exception
*/
void roleUpdate(RoleVo vo) throws Exception;
/**
* 권한 정보 삭제하기
* @param roleId
* @throws Exception
*/
void roleDelete(String roleId) throws Exception;
/**
* 메뉴 정보 가져오기
* @return
* @throws Exception
*/
List<MenuVo> menuList() throws Exception;
/**
* 선택한 권한의 메뉴 정보 가져오기
* @param id
* @return
* @throws Exception
*/
List<MenuVo> selectMenuList(String id) throws Exception;
/**
* 권한에 연결되어있는 메뉴 삭제
* @param roleId
* @throws Exception
*/
void deleteMenuList(String roleId) throws Exception;
/**
* 선택한 권한에 메뉴 넣기
* @param roleId
* @param menuId
*/
void insertMenuList(String roleId , String menuId);
}
serviceImpl
import com.example.practice.mapper.role.RoleMapper;
import com.example.practice.service.role.RoleService;
import com.example.practice.vo.RoleVo;
import com.example.practice.vo.menu.MenuVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class RoleServiceImpl implements RoleService {
@Autowired
private RoleMapper roleMapper;
@Override
public List<RoleVo> roleInfo() throws Exception {
return roleMapper.roleInfo();
}
@Override
public void roleAdd(RoleVo vo) throws Exception {
roleMapper.roleAdd(vo);
}
@Override
public void roleUpdate(RoleVo vo) throws Exception {
roleMapper.roleUpdate(vo);
}
@Override
public void roleDelete(String roleId) throws Exception {
roleMapper.roleDelete(roleId);
}
@Override
public List<MenuVo> menuList() throws Exception {
return roleMapper.menuList();
}
@Override
public List<MenuVo> selectMenuList(String id) throws Exception {
return roleMapper.selectMenuList(id);
}
@Override
public void deleteMenuList(String roleId) throws Exception {
roleMapper.deleteMenuList(roleId);
}
@Override
public void insertMenuList(String roleId, String menuId) {
roleMapper.insertMenuList(roleId, menuId);
}
}
mapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.practice.mapper.role.RoleMapper">
<!-- 메뉴 정보 가져오기 -->
<select id="menuList" resultType="com.example.practice.vo.menu.MenuVo">
SELECT
ID,
PARENT_ID,
NAME,
ORD,
ICON
FROM MENU
START WITH PARENT_ID = 0
CONNECT BY PRIOR ID = PARENT_ID
ORDER SIBLINGS BY ORD
</select>
<!-- 선택된 권한의 메뉴 정보를 가져온다 -->
<select id="selectMenuList" resultType="com.example.practice.vo.menu.MenuVo">
SELECT
M.ID AS ID,
M.PARENT_ID,
M.NAME,
M.ORD,
M.ICON
FROM MENU M INNER JOIN MENU_LINK ML
ON M.ID = ML.MENU_ID
WHERE ML.ROLE_ID = #{id}
START WITH (M.PARENT_ID = 0 AND ML.ROLE_ID = #{id})
CONNECT BY PRIOR M.ID = M.PARENT_ID
ORDER SIBLINGS BY M.ORD
</select>
<!-- 권한에 연결되어있는 메뉴 삭제 -->
<delete id="deleteMenuList">
DELETE FROM MENU_LINK WHERE ROLE_ID = #{roleId}
</delete>
<!-- 선택한 권한에 메뉴 넣기 -->
<insert id="insertMenuList">
INSERT INTO MENU_LINK (
ID,
ROLE_ID,
MENU_ID
)VALUES(
(SELECT NVL(MAX(ID) , 0) + 1 AS ID FROM MENU_LINK),
#{roleId},
#{menuId}
)
</insert>
</mapper>
구현 내용
1. 해당 글은 Bootstrap 을 이용하고 있다
2. 메뉴를 계층형으로 출력하려고 한다
3. 권한에 따라 메뉴를 설정하려고 한다
4. 부모 메뉴의 체크박스 체크 및 해제에 따라 전체 선택 및 해제를 구현하려고 한다
DB
https://greed-yb.tistory.com/276
이전글 상태에서 작업하였다
-- Menu 테이블의 Id 와 Role 테이블의 Id를 가지고 있는 테이블
CREATE TABLE MENU_LINK(
ID NUMBER PRIMARY KEY,
ROLE_ID NUMBER,
MENU_ID NUMBER
)
TEST
1. 권한 dataTable 선택
권한 선택 시 DB에 있는 데이터가
제대로 불러와지고 체크까지 되는 것을 확인하였다
전체 CheckBox 선택/해제에 따라 메뉴의 CheckBox 가 동작되며,
부모의 CheckBox 선택/해제에 따라 자식 메뉴의 CheckBox 도 같이 동작한다
자식 메뉴의 CheckBox 의 여부에 따라 부모의 CheckBox 도 같이 동작한다
내용이 많이 길어져서 TEST 구현 부분은 건너 뛰려고 한다
뭔가 너무 하드코딩같은 느낌이긴한데...DOM 구조 공부는 엄청 된 것 같다
'개발 > Spring' 카테고리의 다른 글
[SpringBoot] WebSocket 채팅방 구현(2) - WebSocketConfig (0) | 2024.08.17 |
---|---|
[SpringBoot] WebSocket 채팅방 구현(1) - 구현 화면 (0) | 2024.08.17 |
[SpringBoot] DataTable 이용하여 추가,수정,삭제 구현하기 (0) | 2024.07.26 |
[SpringBoot] FullCalendar을 이용한 캘린더 구현하기 (2) | 2024.07.23 |
[SpringBoot] Autocomplete을 이용한 자동완성기능 구현하기 (2) | 2024.07.22 |
댓글