MenuForm.html 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517
  1. <!DOCTYPE html>
  2. <html lang="ko"
  3. xmlns:th="http://www.thymeleaf.org">
  4. <!--
  5. *******************************************************************************
  6. * @source : MenuForm.html
  7. * @desc : 메뉴관리 Page
  8. *============================================================================
  9. * STYLE24
  10. * Copyright(C) 2020 TSIT, All rights reserved.
  11. *============================================================================
  12. * VER DATE AUTHOR DESCRIPTION
  13. * === =========== ========== =============================================
  14. * 1.0 2020.10.08 gagamel 최초 작성
  15. *******************************************************************************
  16. -->
  17. <div id="main">
  18. <!-- 메인타이틀 영역 -->
  19. <div class="main-title">
  20. </div>
  21. <!-- //메인타이틀 영역 -->
  22. <!-- 메뉴 설명 -->
  23. <div class="infoBox menu-desc">
  24. </div>
  25. <!-- //메뉴 설명 -->
  26. <!-- 검색조건 영역 -->
  27. <div class="panelStyle">
  28. <!-- Search -->
  29. <form id="searchForm" name="searchForm" action="#" th:action="@{'/system/menu/list'}" onsubmit="$('#btnSearch').trigger('click'); return false;">
  30. <table class="frmStyle" aria-describedby="검색조건">
  31. <colgroup>
  32. <col style="width:10%;"/>
  33. <col style="width:90%;"/>
  34. </colgroup>
  35. <tr>
  36. <th>상위메뉴</th>
  37. <td>
  38. <select name="pmenuId" class="w150">
  39. <option value="">[전체]</option>
  40. <option th:if="${topMenuList}" th:each="oneData, status : ${topMenuList}" th:value="${oneData.cd}" th:text="${'[' + oneData.cd + '] ' + oneData.cdNm}"></option>
  41. </select>
  42. </td>
  43. </tr>
  44. </table>
  45. <ul class="panelBar">
  46. <li class="center">
  47. <button type="button" class="btn btn-success btn-lg" id="btnSearch">조회</button>
  48. </li>
  49. </ul>
  50. </form>
  51. </div>
  52. <!-- //검색조건 영역 -->
  53. <!-- TABS AREA -->
  54. <div class="tabs">
  55. <ul class="tabsNav">
  56. <li class="on"><a href="#tab-1">목록</a></li>
  57. <li><a href="#tab-2">신규</a></li>
  58. <li class="off"><a href="#tab-3">상세</a></li>
  59. </ul>
  60. <ul class="tabsCont">
  61. <li class="tab on" id="tab-1">
  62. <div class="panelStyle">
  63. <!-- 버튼 배치 영역 -->
  64. <ul class="panelBar">
  65. <li class="right">
  66. <button type="button" class="btn btn-danger btn-lg btnDelete">사용안함</button>
  67. </li>
  68. </ul>
  69. <!-- //버튼 배치 영역 -->
  70. <div id="gridList" style="width: 100%; height: 570px;" class="ag-theme-balham"></div>
  71. </div>
  72. </li>
  73. <!-- 신규 -->
  74. <li class="tab" id="tab-2">
  75. <div class="panelStyle">
  76. <form id="registerForm" name="registerForm" action="#" th:action="@{'/system/menu/save'}">
  77. <table class="frmStyle" aria-describedby="신규">
  78. <colgroup>
  79. <col style="width:10%"/>
  80. <col style="width:40%;"/>
  81. <col style="width:10%;"/>
  82. <col/>
  83. </colgroup>
  84. <tr>
  85. <th>메뉴ID<i class="required" title="필수" aria-hidden="true"></i></th>
  86. <td>
  87. <input type="text" name="menuId" class="w150" placeholder="" maxlength="20" required="required" data-valid-type="alphaNumeric" data-valid-name="메뉴ID" onkeyup="$(this).val($(this).val().toUpperCase());"/>
  88. <button type="button" class="btn btn-default btn-sm" id="menuIdDupCheck">중복체크</button>
  89. </td>
  90. <th>메뉴명<i class="required" title="필수" aria-hidden="true"></i></th>
  91. <td>
  92. <input type="text" name="menuNm" placeholder="" maxlength="50" required="required" data-valid-name="메뉴명"/>
  93. </td>
  94. </tr>
  95. <tr>
  96. <th>메뉴구분<i class="required" title="필수" aria-hidden="true"></i></th>
  97. <td>
  98. <label class="rdoBtn"><input type="radio" name="menuGb" value="M" checked="checked"/>메뉴</label>
  99. <label class="rdoBtn"><input type="radio" name="menuGb" value="P"/>프로그램</label>
  100. </td>
  101. <th>상위메뉴</th>
  102. <td>
  103. <select name="pmenuId">
  104. <option value="">[선택]</option>
  105. <option th:if="${allMenuList}" th:each="oneData, status : ${allMenuList}" th:value="${oneData.cd}" th:text="${oneData.cdNm}"></option>
  106. </select>
  107. </td>
  108. </tr>
  109. <tr class="menu-role">
  110. <th>메뉴권한</th>
  111. <td colspan="3">
  112. <!-- <select class="chosen-select" name="roleCd" multiple="multiple" data-placeholder="[선택]" data-valid-name="메뉴권한">
  113. <option th:if="${roleList}" th:each="oneData, status : ${roleList}" th:value="${oneData.cd}" th:text="${'[' + oneData.cd + '] ' + oneData.cdNm}"></option>
  114. </select> -->
  115. <div class="mSelectWrap">
  116. <select name="roleCd" multiple="multiple" tabindex="-1" data-valid-name="메뉴권한">
  117. <option th:if="${roleList}" th:each="oneData, status : ${roleList}" th:attr="data-index=${status.index + 1}" th:value="${oneData.cd}" th:text="${'[' + oneData.cd + '] ' + oneData.cdNm}"></option>
  118. </select>
  119. <div class="mSelectBox">
  120. <ul class="mSelected">
  121. <li class="off" th:if="${roleList}" th:each="oneData, status : ${roleList}" th:attr="data-index=${status.index + 1}" th:utext="${'[' + oneData.cd + '] ' + oneData.cdNm + '&lt;a&gt;닫기&lt;/a&gt;'}"></li>
  122. <li class="srchFld"><input type="text" autocomplete="off"/></li>
  123. </ul>
  124. <div class="mSelecting">
  125. <ul>
  126. <li th:if="${roleList}" th:each="oneData, status : ${roleList}" th:attr="data-index=${status.index + 1}" th:text="${'[' + oneData.cd + '] ' + oneData.cdNm}"></li>
  127. </ul>
  128. </div>
  129. </div>
  130. </div>
  131. <input type="hidden" name="roleCds"/>
  132. </td>
  133. </tr>
  134. <tr>
  135. <th>메뉴URL</th>
  136. <td colspan="3">
  137. <input type="text" name="menuUrl" placeholder="" maxlength="100"/>
  138. </td>
  139. </tr>
  140. <tr>
  141. <th>메뉴설명</th>
  142. <td colspan="3">
  143. <textarea class="textareaR4" name="menuDesc"></textarea>
  144. </td>
  145. </tr>
  146. <tr>
  147. <th>표시순서<i class="required" title="필수" aria-hidden="true"></i></th>
  148. <td>
  149. <input type="text" class="w100 text-right" name="dispOrd" placeholder="" maxlength="4" required="required" data-valid-type="numeric" data-valid-name="표시순서" />
  150. </td>
  151. <th>사용여부</th>
  152. <td>
  153. <input type="hidden" name="useYn"/>
  154. <label class="chkBox"><input type="checkbox" name="chkUseYn" value="Y" checked="checked" disabled="disabled"/>사용</label>
  155. </td>
  156. </tr>
  157. </table>
  158. </form>
  159. <!-- 버튼 배치 영역 -->
  160. <ul class="panelBar">
  161. <li class="right">
  162. <input type="button" value="초기화" class="btn btn-gray btn-lg" onclick="$('#registerForm')[0].reset();"/>
  163. <input type="button" value="저장" class="btn btn-base btn-lg" onclick="fnSave('#registerForm');"/>
  164. </li>
  165. </ul>
  166. <!-- //버튼 배치 영역 -->
  167. </div>
  168. </li>
  169. <!-- //신규 -->
  170. <!-- 상세 -->
  171. <li class="tab" id="tab-3">
  172. <div class="panelStyle">
  173. <form id="detailForm" name="detailForm" action="#" th:action="@{'/system/menu/save'}">
  174. <table class="frmStyle" aria-describedby="상세">
  175. <colgroup>
  176. <col style="width:10%"/>
  177. <col style="width:40%;"/>
  178. <col style="width:10%;"/>
  179. <col/>
  180. </colgroup>
  181. <tr>
  182. <th>메뉴ID</th>
  183. <td>
  184. <input type="text" name="menuId" placeholder="" maxlength="20" readonly="readonly"/>
  185. </td>
  186. <th>메뉴명<i class="required" title="필수" aria-hidden="true"></i></th>
  187. <td>
  188. <input type="text" name="menuNm" placeholder="" maxlength="50" required="required" data-valid-name="메뉴명"/>
  189. </td>
  190. </tr>
  191. <tr>
  192. <th>메뉴구분<i class="required" title="필수" aria-hidden="true"></i></th>
  193. <td>
  194. <label class="rdoBtn"><input type="radio" name="menuGb" value="M"/>메뉴</label>
  195. <label class="rdoBtn"><input type="radio" name="menuGb" value="P"/>프로그램</label>
  196. </td>
  197. <th>상위메뉴</th>
  198. <td>
  199. <select name="pmenuId">
  200. <option value="">[선택]</option>
  201. <option th:if="${allMenuList}" th:each="oneData, status : ${allMenuList}" th:value="${oneData.cd}" th:text="${oneData.cdNm}"></option>
  202. </select>
  203. </td>
  204. </tr>
  205. <tr class="menu-role">
  206. <th>메뉴권한</th>
  207. <td colspan="3">
  208. <!-- <select class="chosen-select" name="roleCd" multiple="multiple" data-placeholder="[선택]" data-valid-name="메뉴권한">
  209. <option th:if="${roleList}" th:each="oneData, status : ${roleList}" th:value="${oneData.cd}" th:text="${'[' + oneData.cd + '] ' + oneData.cdNm}"></option>
  210. </select> -->
  211. <div class="mSelectWrap">
  212. <select name="roleCd" multiple="multiple" tabindex="-1" data-valid-name="메뉴권한">
  213. <option th:if="${roleList}" th:each="oneData, status : ${roleList}" th:attr="data-index=${status.index + 1}" th:value="${oneData.cd}" th:text="${'[' + oneData.cd + '] ' + oneData.cdNm}"></option>
  214. </select>
  215. <div class="mSelectBox">
  216. <ul class="mSelected">
  217. <li class="off" th:if="${roleList}" th:each="oneData, status : ${roleList}" th:attr="data-index=${status.index + 1}" th:utext="${'[' + oneData.cd + '] ' + oneData.cdNm + '&lt;a&gt;닫기&lt;/a&gt;'}"></li>
  218. <li class="srchFld"><input type="text" autocomplete="off"/></li>
  219. </ul>
  220. <div class="mSelecting">
  221. <ul>
  222. <li th:if="${roleList}" th:each="oneData, status : ${roleList}" th:attr="data-index=${status.index + 1}" th:text="${'[' + oneData.cd + '] ' + oneData.cdNm}"></li>
  223. </ul>
  224. </div>
  225. </div>
  226. </div>
  227. <input type="hidden" name="roleCds"/>
  228. </td>
  229. </tr>
  230. <tr>
  231. <th>메뉴URL</th>
  232. <td colspan="3">
  233. <input type="text" name="menuUrl" placeholder="" maxlength="100"/>
  234. </td>
  235. </tr>
  236. <tr>
  237. <th>메뉴설명</th>
  238. <td colspan="3">
  239. <textarea class="textareaR4" name="menuDesc"></textarea>
  240. </td>
  241. </tr>
  242. <tr>
  243. <th>표시순서<i class="required" title="필수" aria-hidden="true"></i></th>
  244. <td>
  245. <input type="text" class="w100 text-right" name="dispOrd" placeholder="" maxlength="4" required="required" data-valid-type="numeric" data-valid-name="표시순서" />
  246. </td>
  247. <th>사용여부<i class="required" title="필수" aria-hidden="true"></i></th>
  248. <td>
  249. <input type="hidden" name="useYn"/>
  250. <label class="chkBox"><input type="checkbox" name="chkUseYn" value="Y"/>사용</label>
  251. </td>
  252. </tr>
  253. </table>
  254. </form>
  255. <!-- 버튼 배치 영역 -->
  256. <ul class="panelBar">
  257. <li class="right">
  258. <!-- <input type="button" value="초기화" class="btn btn-gray btn-lg" onclick="$('#detailForm')[0].reset();"/> -->
  259. <input type="button" value="저장" class="btn btn-base btn-lg" onclick="fnSave('#detailForm');"/>
  260. </li>
  261. </ul>
  262. <!-- //버튼 배치 영역 -->
  263. </div>
  264. </li>
  265. <!-- //상세 -->
  266. </ul>
  267. <!-- //TAB CONTENT -->
  268. </div>
  269. <!-- //TABS AREA -->
  270. </div>
  271. <script th:inline="javascript">
  272. /*<![CDATA[*/
  273. var roleList = [[${roleList}]];
  274. // specify the columns
  275. var columnDefs = [
  276. {
  277. headerName: "메뉴명", field: "menuNm", width: 200,
  278. cellRenderer: function(params) {
  279. return '<a href="javascript:void(0);">' + params.value + '</a>';
  280. }
  281. },
  282. {
  283. headerName: "메뉴구분", field: "menuGb", width: 100, cellClass: 'text-center',
  284. cellRenderer: function(params) { return params.value == 'M' ? '메뉴' : '프로그램'; }
  285. },
  286. {headerName: "메뉴권한코드", field: "roleCds", hide: true},
  287. {headerName: "메뉴권한", field: "roleNms", width: 400, cellClass: 'text-center'},
  288. {headerName: "메뉴URL", field: "menuUrl", width: 300},
  289. {headerName: "메뉴설명", field: "menuDesc", hide: true},
  290. {headerName: "표시순서", field: "dispOrd", width: 100, cellClass: 'text-right'},
  291. {headerName: "사용여부", field: "useYn", width: 100, cellClass: 'text-center'}
  292. ];
  293. // Get GridOptions
  294. var gridOptions = gagaAgGrid.getGridOptions(columnDefs);
  295. // Add on options
  296. gridOptions.suppressRowClickSelection = true;
  297. gridOptions.rowSelection = 'multiple';
  298. //gridOptions.groupSelectsChildren = true; // Tree Data mode에서는 작동 안 함
  299. gridOptions.treeData = true; // enable Tree Data mode
  300. gridOptions.groupDefaultExpanded = -1; // expand all groups by default
  301. gridOptions.getDataPath = function(data) { // just return the hierarchy, no conversion required
  302. return data.treePath.split("/");
  303. };
  304. gridOptions.autoGroupColumnDef = {
  305. headerName: "메뉴ID", field: "menuId", width: 200,
  306. cellRendererParams: {
  307. suppressCount: true, // 하위의 항목 건수 표시 안 함
  308. checkbox: true
  309. }
  310. };
  311. // Cell Click
  312. gridOptions.onCellClicked = function(event) {
  313. if (event.colDef.field != 'menuNm')
  314. return;
  315. fnBindDetail(event.data);
  316. fnUnEscapeHtml(); //XSS변환
  317. }
  318. // 조회
  319. $('#btnSearch').on('click', function() {
  320. var actionUrl = $('#searchForm').prop('action') + '?' + $('#searchForm').serialize();
  321. // Fetch data
  322. gagaAgGrid.fetch(actionUrl, gridOptions);
  323. });
  324. // 목록 > row 클릭 시
  325. var fnBindDetail = function(rowData) {
  326. $('#detailForm input[name=menuId]').val(rowData.menuId);
  327. $('#detailForm input[name=menuNm]').val(rowData.menuNm);
  328. $("#detailForm input:radio[name=menuGb]").parents('td').find('label').removeClass('checked');
  329. if (rowData.menuGb == 'M') {
  330. $("#detailForm input:radio[name=menuGb]").eq(0).prop('checked', true);
  331. $("#detailForm input:radio[name=menuGb]").eq(0).parent().addClass('checked');
  332. $('#detailForm').find('.menu-role').hide();
  333. } else {
  334. $("#detailForm input:radio[name=menuGb]").eq(1).prop('checked', true);
  335. $("#detailForm input:radio[name=menuGb]").eq(1).parent().addClass('checked');
  336. $('#detailForm').find('.menu-role').show();
  337. }
  338. $('#detailForm select[name=pmenuId]').val(rowData.pmenuId);
  339. // 다중Select에 권한 표시
  340. cfnShowMultiSelectedValue($('#detailForm select[name=roleCd]'), rowData.roleCds);
  341. if (!gagajf.isNull(rowData.menuDesc)) {
  342. $('#detailForm textarea[name=menuDesc]').val(rowData.menuDesc.replaceAll('&lt;','<').replaceAll('&gt;','>'));
  343. } else {
  344. $('#detailForm textarea[name=menuDesc]').val('');
  345. }
  346. $('#detailForm input[name=menuUrl]').val(rowData.menuUrl);
  347. $('#detailForm input[name=dispOrd]').val(rowData.dispOrd);
  348. $("#detailForm input[name=useYn]").val(rowData.useYn);
  349. if (rowData.useYn == 'Y') {
  350. $("#detailForm input:checkbox[name=chkUseYn]").prop('checked', true);
  351. $("#detailForm input:checkbox[name=chkUseYn]").parent().addClass('checked');
  352. } else {
  353. $("#detailForm input:checkbox[name=chkUseYn]").prop('checked', false);
  354. $("#detailForm input:checkbox[name=chkUseYn]").parent().removeClass('checked');
  355. }
  356. $('.tabsNav li').eq(2).removeClass('off');
  357. $('.tabsNav li').eq(2).trigger('click');
  358. $('#detailForm input[name=menuNm]').focus();
  359. }
  360. // 메뉴ID 중복 체크
  361. var isUnique = true;
  362. $('#menuIdDupCheck').on('click', function() {
  363. var $e = $('#registerForm input[name=menuId]');
  364. if (gagajf.isNull($e.val())) {
  365. mcxDialog.alertC('메뉴ID를 입력해 주세요.', {
  366. sureBtnText: "확인",
  367. sureBtnClick: function() {
  368. $e.focus();
  369. }
  370. });
  371. return;
  372. }
  373. $.get('/system/menu/id/' + $e.val()
  374. , function(data) {
  375. if (data > 0) {
  376. mcxDialog.alertC('이 ID는 이미 사용중입니다. 다른 ID를 입력해 주세요.', {
  377. sureBtnText: "확인",
  378. sureBtnClick: function() {
  379. isUnique = false;
  380. $e.val('');
  381. $e.focus();
  382. }
  383. });
  384. } else {
  385. mcxDialog.alert('이 ID는 사용 가능합니다.');
  386. isUnique = true;
  387. }
  388. });
  389. });
  390. // 저장 처리
  391. var fnSave = function(formId) {
  392. // 입력 값 체크
  393. if (!gagajf.validation(formId))
  394. return false;
  395. if (formId == '#registerForm') {
  396. if (!isUnique) {
  397. mcxDialog.alert('메뉴ID를 중복체크해 주세요.');
  398. return;
  399. }
  400. }
  401. $(formId + ' input[name=useYn]').val($(formId + ' input:checkbox[name=chkUseYn]').is(":checked") ? 'Y' : 'N');
  402. $(formId + ' input[name=roleCds]').val($(formId + ' select[name=roleCd]').val());
  403. if ($(formId + " input:radio[name=menuGb]").eq(1).is(':checked')) {
  404. if (gagajf.isNull($(formId + ' input[name=menuUrl]').val())) {
  405. mcxDialog.alertC('메뉴URL을 입력해 주세요.', {
  406. sureBtnText: "확인",
  407. sureBtnClick: function() {
  408. $(formId + ' input[name=menuUrl]').focus();
  409. }
  410. });
  411. return;
  412. }
  413. }
  414. mcxDialog.confirm("저장하시겠습니까?", {
  415. cancelBtnText: "취소",
  416. sureBtnText: "확인",
  417. sureBtnClick: function() {
  418. gagajf.ajaxFormSubmit($(formId).prop('action'), formId, fnSaveCallback);
  419. }
  420. });
  421. }
  422. // 등록/수정 처리 후 콜백함수
  423. var fnSaveCallback = function(result) {
  424. // 초기화
  425. $('#registerForm')[0].reset();
  426. // 상세의 다중Select 초기화
  427. var mSelected = '';
  428. var mSelecting = '';
  429. $.each(roleList, function(idx, item) {
  430. mSelected += '<li class="off" data-index="' + (idx + 1) + '">[' + item.cd + '] ' + item.cdNm + '<a>닫기</a></li>\n';
  431. mSelecting += '<li data-index="' + (idx + 1) + '">[' + item.cd + '] ' + item.cdNm + '</li>\n';
  432. });
  433. mSelected += '<li class="srchFld"><input type="text" autocomplete="off"/></li>\n';
  434. $('#detailForm').find('.mSelected').html(mSelected);
  435. $('#detailForm').find('.mSelecting > ul').html(mSelecting);
  436. // 목록 재조회
  437. $('.tabsNav li').eq(0).trigger('click');
  438. $('#btnSearch').trigger('click');
  439. }
  440. // 삭제 버튼 클릭 시
  441. $('.btnDelete').on('click', function() {
  442. var removedData = gagaAgGrid.removeRowData(gridOptions);
  443. if (removedData.length == 0) {
  444. mcxDialog.alert('선택된 행이 없습니다.');
  445. return;
  446. }
  447. mcxDialog.confirm("사용안함으로 처리하시겠습니까?", {
  448. cancelBtnText: "취소",
  449. sureBtnText: "확인",
  450. sureBtnClick: function() {
  451. // delete 대신 update 처리해야 하므로 다음과 같이 useYn 값을 변환
  452. var updatedData = [];
  453. $.each(removedData, function(idx, item) {
  454. item.useYn = 'N';
  455. updatedData.push(item);
  456. });
  457. var jsonData = JSON.stringify(updatedData);
  458. gagajf.ajaxJsonSubmit('/system/menu/list/save', jsonData, fnSaveCallback);
  459. }
  460. });
  461. });
  462. var fnUnEscapeHtml = function(){ //XSS변환
  463. $('#detailForm input[name=menuNm]').val($('#detailForm input[name=menuNm]').val().replaceXSS());
  464. $('#detailForm input[name=menuUrl]').val($('#detailForm input[name=menuUrl]').val().replaceXSS());
  465. };
  466. $(document).ready(function() {
  467. // Create a agGrid
  468. gagaAgGrid.createGrid('gridList', gridOptions);
  469. });
  470. /*]]>*/
  471. </script>
  472. </html>