일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- JavaScript
- 초보 개발자
- ui인터페이스
- thymeleaf
- 게시판
- jsp
- 상태값 저장 유지
- 여러 종류의 사용자 정의 함수
- IndexedDB
- tomcat
- Eclipse
- open in browser
- MYSQL
- 자바
- git
- resutful api
- chart.js 라이브러리
- vsc
- vscode
- @requstbody
- 실시간 상태값 저장
- 자바빈
- 데이터 시각화
- chart.js
- spring boot
- css
- github
- html
- java
- 자바스크립트
- Today
- Total
수월한 IT
<dialog/>태그의 다양한 활용과 SPA구현 본문
안녕하세요. IT 이곳저곳을 여행하고 있는 IT여행자입니다.
프로그램을 자다 보면 팝업창을 만들어 사용자에게 메시지를 전달하거나 특정 작업을 선택하게 하는 일이 어쩔 수 없이 발생하기도 합니다. 이때 가장 손쉽게 사용할 수 있는 대화 상자는 alert, confirm, prompt 등과 같이 자바스크립트를 사용한 대화 상자들이 있습니다.
그러나 이 대화상자에 UI를 변경하거나 특정 요소를 추가하기란 쉽지 않습니다. 어쩌면 배보다 배꼽이 더 커지는 상황이 벌어지기도 합니다. 따라서 일반적으로 css를 사용하여 디자인된 html 태그들을 마치 대화상자처럼 작성하여 사용합니다.
따라서 이번 여행지는 HTML5에 추가되어 있던 <dialog/> 태그를 사용하여 일반적으로 사용되고 있던 alert, confirm, prompt창을 대체하여 사용해 보는 코스의 여행을 해 보려 합니다.
하지만, <dialog/>태그를 사용할 때 두 가지 고려 사항이 있습니다.
- 접근성 회손문제
- IE에서는 사용 불가
접근성 훼손 문제는 focus() 이벤트를 사용하여 어느 정도 해결할 수 있을 것이고, IE에서의 사용문제는 기나치게 긴 long tail 서비스가 아니라면 대부분의 유저들에게는 해당되지 않을 것 같습니다.
제작 과정의 영상은 https://youtu.be/B1ClKDo0NO0
전체 코드는 https://github.com/hiparkwg/dialog_spa.git
참고해 주시기 바랍니다.
그럼, 여행을 시작해 보도록 하겠습니다.
<dialog/>의 간단 소개
- dialog 태그는 기본적으로 화면에 랜더링 되지 않지만 open 속성을 사용하면 화면에 랜더링 됩니다.
- show() : Modeless 타입으로 팝업창을 만들어 줍니다.
- showModal() : Modal 타입으로 팝업창을 만들어 줍니다.
1코스 (alert, confirm, prompt 대체)
가장 먼저 만들어 볼 것은 이미지가 포함된 alert창 형태의 dialog를 만드는 것입니다.
<style> .alert{ width:350px; border:0px; border-radius: 7px; box-shadow: 3px 3px 9px 3px #aaa; margin-top:100px; } .alert .image{ width:90px; } .alert>div{ display: inline-block; margin-top:10px; } .alert .alertClose{ background-color: blue; color :#fff; margin-top:10px; padding:3px; width:70px; } </style> ... <dialog class="alert"> <image class="image" src="/images/it여행자.png"></image> <div> <span>Dialog로 만든 Alert</span><br/> <button type="button" class="alertClose">확인</button> </div> </dialog>
위와 같이 HTML과 CSS를 만들었다면 <dialog/>를 화면에 Modal 타입으로 팝업 시켜주는 자바스크립트 코드를 만듭니다.
const dialogAlert = document.querySelector(".alert"); dialogAlert.showModal(); //(1) const btnClose = dialogAlert.querySelector(".alertClose"); //(2) btnClose.addEventListener("click", ()=>{ dialogAlert.close();//(3) })
(1) class='alert'으로 지정된 dialog 태그를 가져와 Modal 타입으로 팝업 시킵니다. 만약 show() 메서드를 사용한다면 팝업창이 Modeless 타입으로 화면에 보입니다.
(2) dialog 태그 안에 있는 '확인'버튼을 가져옵니다.
(3) '확인'버튼이 클릭되면 dialog탕을 닫습니다.

두 번째는 confirm창을 dialog 태그를 사용하여 만들어 보겠습니다.
<style> .confirm{ width: 450px; height: 120px; text-align: center; padding:10px; border:0px; border-radius: 8px; box-shadow: 2px 2px 9px 2px #aaa; background-color: #eef; margin-top:5px; } .confirm>.btnZone{ margin-top:45px; text-align: right; } .confirm .btnOK{ background-color: #00f; color : #fff; border:0px; padding:5px 20px; } .confirm .btnYes{ background-color: #000; color:#fff; border:0px; padding:5px 20px; } </style> ... <dialog class="confirm"> <div>구독 좋아요 OK?</div> <div class="btnZone"> <button type="button" class="btnOK">당근</button> <button type="button" class="btnYes">넵!</button> </div> </dialog>
자바스크립트 코드입니다. 대부분의 코드 내용은 alert 형태의 dialog를 만들었을 때와 같습니다.
const dialogConfirm = document.querySelector(".confirm"); dialogConfirm.showModal(); const btnOK = dialogConfirm.querySelector(".btnOK"); btnOK.addEventListener("click", ()=>{ dialogConfirm.close(); })

confirm창의 특성상 대부분 '예' 또는 '아니요'의 버튼 형태이지만 버튼의 모양을 confirm창의 특성에 맞게 자유롭게 바꾸어 볼 수 있겠습니다.
세 번째로는 prompt창인데 대부분 prompt 창을 통해 사용자의 입력값을 받지는 않지만 만약 어쩔 수 없이 입력받으려 할 때도, 암호와 같은 값이라도 받고자 하였을 때 prompt창으로는 입력되는 값이 모두 화면에 표시되기 때문에 그리 좋은 선택은 더더욱 아니게 됩니다. 그러나 dialog를 사용하게 되면 어떤 값이든 자유롭게 입력받아 처리할 수 있습니다.
dialog를 사용하여 사용자 암호를 받는 코드를 만들어 보았습니다.
<style> .prompt{ width:450px; padding:20px; } .prompt .password{ width: 100%; margin:10px 0; font-size:1.3em; padding:2px; } .prompt .btnZone{ text-align: right; } .prompt .btnOK{ background-color: #f00; border-color : #f00; color:#ff0; } </style> ... <dialog class="prompt"> <div>삭제하시려면 암호를 입력하세요.</div> <input type="password" class="password"> <div class="btnZone"> <button type="button" class="btnOK">삭제</button> <button type="button" class="btnCancle">취소</button> </div> </dialog>
자바스크립트 부분입니다.
const dialogPrompt = document.querySelector(".prompt"); dialogPrompt.showModal(); const btnOK = dialogPrompt.querySelector(".btnOK"); btnOK.addEventListener("click", ()=>{ dialogPrompt.close(); })

이렇게 해서 첫 번째 여행 코스에서는 alert, confirm, prompt대신 dialog태그를 사용하여 기능을 확장시키거나 개선된 UI를 사용하여 대화 상자를 만들어 보았습니다.
2코스(dialog를 사용한 SPA)
SPA는 Single Page Application으로 대부분의 CRUD를 하나의 페이지에서 처리하고자 하는 디자인 패턴입니다. 다양한 프레임워크에서 이를 지원하고 있지만 본 여행지에서는 dialog 태그와 자바스크립트만을 사용하여 SPA를 구현해 보도록 하겠습니다.
CRUD의 모든 기능을 SPA로 구현해 보기란 쉽지 않은 분량이기에 본 여행 코스에서는 폼데이터를 컨트롤러에서 전송받아 콘솔창에 출력해 보는 것으로 갈무리하겠습니다. 또한 스프링 프로젝트를 생성하는 과정 또한 생략하겠습니다. 만약 이런 과정까지의 내용이 궁금하시다면 제시된 영상을 참조해 보시기 바랍니다.
1) 먼저 데이터를 입력하는 폼태그를 만들어 보겠습니다.
파일은 메인 html 파일이 아닌 dialog_input.html 파일에 dialog태그로 폼태그들이 만들어져 있다고 가정합니다.
<!-- 파일명 : /html/dialog_input.html --> <dialog id="dialog_input"> <h2>판매 정보 입력</h2> <form name="frmInput" id="frmInput"> <span>판매일자</span> <input name="nal" type="date"><br/> <span>제품코드</span> <input name="code" type="text" value="a001"><br/> <span>제품명</span> <input name="codeName" type="text" readonly value="식기 세척기"><br/> <span>수량</span> <input name="ea" type="text" class="ea" oninput="toChangeFormat(this)" ><br/> <span>단가</span> <input name="price" type="text" class="price" oninput="toChangeFormat(this)"><br/> <span>금액</span> <input name="amt" type="text" class="amt" ><br/> <span></span> <button type="button" id="btnInput">입력</button> <button type="button" id="btnCancle">취소</button> </form> </dialog>
css는 민망함의 수준만을 살짝 넘는 단계로만 작성되었습니다.
#dialog_input span{ display: inline-block; width:80px; text-align: right; padding-right:5px; } #dialog_input h2{ text-align: center; } #dialog_input .ea { text-align: right;} #dialog_input .price { text-align: right;} #dialog_input .amt { text-align: right;}
컨트롤러에서 사용자의 폼데이터를 매개변수로 받을 때 필요한 데이터 클래스입니다.
package kr.jobtc.dialog; @lombok.Data //(1) public class Data { //(2) String nal; String code; String codeName; int ea, price, amt; }
(1) Lombok dependency가 설치되어야 사용할 수 있습니다. 필드에 해당하는 setter, getter를 자동으로 만들어주는 어노테이션입니다.
(2) 각 필드명은 HTML의 폼태그에서 작성된 각 <input/> 태그들의 name값과 동일한 이름으로 사용해야 됩니다. 만약 <input type="text" name="code"/>인 태그라면 Data클래스의 필드명은 String code 이여야 됩니다.
다음으로 spring boot 타입으로 만들어진 프로젝트에서 만들어진 컨트롤러 코드입니다.
package kr.jobtc.dialog; import java.util.HashMap; import java.util.Map; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; @RestController // (1) public class DialogController { @RequestMapping(path="/dialog", method=RequestMethod.POST) //(2) public Map<String, Object> dialog(Data param){ //(3) Map<String, Object> returMap = new HashMap<>(); //(4) System.out.println(param.getNal()); System.out.println(param.getCode()); System.out.println(param.getCodeName()); System.out.println(param.getEa()); System.out.println(param.getPrice()); System.out.println(param.getAmt()); returMap.put("message", "모두 처리되었습니다."); //(5) return returMap; } }
(1) Restful API 사용하게 됩니다. 사용자의 요청을 받아들이고 그 결과를 다시 사용자에게 전달해 주는 기능을 합니다.
(2) 자바스크립트에서 fetch("/dialog")로 요청했을 때 해당 요청을 수신하는 요소가 path="/dialog"이고 요청 유형은 POST임을 나타냅니다.
(3) 사용자가 전송한 <form/> 안에 있는 각 요소가 매개변수로 지정된 Data 클래스에 자동으로 대입됩니다. 이때 각 태그들의 name 속성에 해당하는 값들이 Data 클래스의 필드에 매치되고 해당 필드의 setter가 호출되어 데이터 값이 저장됩니다.
(4) 클라이언트로부터 전달된 값들을 콘솔창에 출력해 봅니다.
(5) 결괏값을 message키에 담아 리턴합니다.
이제 이 여행의 끝 부분으로 자바스크립트 코드를 몇 개의 부분으로 나누어 살펴보도록 하겠습니다.
1) 수량, 단가를 입력하면 천 단위 분리기호로 숫자 표시해 주는 부분입니다. HTML 코드 중 수량, 단가를 입력하는 부분을 보면 <input name="ea" type="text" class="ea" oninput="toChangeFormat(this)" >와 같이 데이터가 입력될 때마다 toChangeFormat(this) 함수가 호출되는 것을 볼 수가 있습니다. 이 함수에서 숫자에 천 단위 분리기호를 넣게 됩니다.
function toChangeFormat(tag){ //(1) let value = tag.value.replace(/,/g, ''); //(2) if( !isNaN(value) && value !==""){ //(3) tag.value = new Intl.NumberFormat().format(value); //(4) } computeAmt(); //(5) }
(1) 입력되고 있는 태그 자신을 this로 넘겨준 것을 tag로 받습니다.
(2) 표시되고 있는 숫자값들에서 ", "를 모두 제거합니다.
(3) 유효한 숫자인지를 묻습니다.
(4) 최근에 입력된 숫자값을 사용하여 다시 천 단위 분리기호를 추가합니다. Intl객체는 국제화와 관련된 객체입니다.
(5) 금액을 계산하는 함수를 호출합니다. ea(수량)이거나 price(단가)가 변경될 경우 toChangeFormat(tag)이 호출되므로 값이 변경될 때마다 금액 계산 함수도 다시 호출됩니다.
2) computeAmt() 함수
수량이나 단가가 변경될때 마다 호출되어 금액을 재계산하여 천 단위 분리기호를 넣은 후 화면에 표시합니다.
function computeAmt(){ let amt = 0; let ea = document.querySelector(".ea").value; //(1) let price = document.querySelector(".price").value; //(1) ea = Number(ea.replace(/,/g, '')); //(2) price = Number(price.replace(/,/g, '')); //(2) amt = ea * price; document.querySelector(".amt").value = new Intl.NumberFormat().format(amt); }
(1) 수량과 단가를 가져옵니다.
(2) 입력된 값을 연산식에 사용하기 위해 천단위 분리기호를 제거합니다.
3) dialog_input.html 파일의 내용을 읽어 현재 document에 추가하고 입력버튼, 취소버튼에 이벤트를 추가합니다.
fetch("/html/dialog_input.html") //(1) .then(resp=> resp.text()) .then(html=>{ let parser = new DOMParser(); //(2) let doc = parser.parseFromString(html, "text/html"); //(3) let dialog = doc.querySelector("#dialog_input"); //(4) document.body.appendChild(dialog); //(5) dialog.showModal(); dialog.querySelector("#btnInput").addEventListener("click", ()=>{ let frm = document.querySelector("#frmInput"); sendData(frm); //(6) document.body.removeChild(dialog); //(7) dialog.close(); //(8) }) dialog.querySelector("#btnCancle").addEventListener("click", ()=>{ document.body.removeChild(dialog); dialog.close(); }) }) .catch( err=>{ console.log("파일 오류") })
(1) dialog가 포함된 외부 파일을 읽습니다.
(2) 읽어 들인 내용은 일반 텍스트이므로 이를 html로 만들기 위한 파서 객체를 생성합니다.
(3) 일반 텍스트를 html 형식으로 바꿔줍니다.
(4) <dialog/> 태그 부분을 가져옵니다.
(5) 가져온 <dialog/> 태그 부분을 현재 문서의 body부분에 추가합니다.추가해 주지 않으면 <dialog/>태그 안에 있는 요소들을 정상적으로 가져다 사용할 수 없습니다.
(6) 입력 버튼이 클릭되었을 때 <form/>을 인자값으로 하여 sendData(frm) 함수를 호출합니다.
(7) 현재 body 부분에 추가되었던 <dialog/>를 제거합니다. 제거해 주지 않으면 반복적으로 <dialog/>가 호출되었을 때 동일한 <dialog/>가 여러 개 존재하게 되어 정상적으로 처리되지 않습니다.
(8) 표시되었던 <dialog/>를 닫습니다.
4) 마지막 자바스크립트 함수입니다. sendData(frm) 함수에서는 <form/> 정보를 FormData 객체로 만들어 "/dialog" 매핑 주소를 사용하여 컨트롤러를 호출하고 데이터를 전송합니다.
function sendData(frm){ frm.ea.value = frm.ea.value.replace(/,/g, ""); //(1) frm.price.value = frm.price.value.replace(/,/g, ""); //(1) frm.amt.value = frm.amt.value.replace(/,/g, ""); //(1) let formData = new FormData(frm); //(2) fetch("/dialog", { //(3) method : "POST", //(4) body : formData //(5) }).then(resp=> resp.json()) //(6) .then(data =>{ //(7) document.querySelector("#result").innerHTML=data.message; //(8) }) }
(1) 숫자정보를 정상적으로 서버로 전송하기 위해 천 단위 분리 기호를 제거합니다.
(2) <form/>의 정보를 FormData객체로 생성합니다. 그러면 전송될 모든 데이터들이 key=value와 같은 형식으로 변경됩니다.
(3) 서버의 매핑주소인 "/dialog"를 호출합니다. 이는 DialogController.java에서 @RequestMapping(path="/dialog")과 연관됩니다.
(4) 전송 타입을 "POST"로 지정합니다. 이도 역시 @RequestMapping(method=RequestMethod.POST)와 연관됩니다.
(5) 서버로 전송될 데이터입니다. 이는 public Map<String, Object> dialog(Data param){ }에서 매개변수 param과 연관됩니다.
(6) 컨트롤러로부터 수신되는 데이터를 JSON구조로 바꿉니다.
(7) JSON구조로 바뀐 데이터가 data에 대입되어 실행됩니다.
(8) 서버로부터 수신된 메시지가 #result영역에 표시됩니다. 이때 data.message는 아래의 컨트롤 부분에서
public Map<String, Object> dialog(Data param){
...
returnMap.put("message", "모두 처리되었습니다.");
return returmMap;
}
Map 컬렉션의 키값인 "message"를 의미합니다.
어떻게 보면 그리 복잡하지도 않고 어렵지도 않은 여행 코스였으나 이것저것을 조금 더해 넣다 보니 조금은 어려워 보였던 여행 코스가 돼버린 것 같습니다. 글 만으로는 표현하기 어려운 점이 많았던 것을 양지해 주시길 바라며 이것으로 이번 여행 코스를 마무리하겠습니다. 글로 표현할 수 없었던 내용들은 아래의 영상을 참조해 주시기 바라며 전체 코드 또한 깃허브를 참조해 주시기 바랍니다.
저는 또 다른 여행지에서 찾아뵙겠습니다. 감사합니다.
제작 과정의 영상은 https://youtu.be/B1ClKDo0NO0
전체 코드는 https://github.com/hiparkwg/dialog_spa.git
참고해 주시기 바랍니다.
'BOOKS > 수월한 자바스크립트' 카테고리의 다른 글
NTSC에서 개발된 계산식을 사용한 바탕색 대비 가독성 있는 폰트색 만들기 (0) | 2025.03.23 |
---|---|
<template/>를 사용한 동적 페이지 만들기 (0) | 2025.03.22 |
localStorage VS indexedDB를 사용한 현재 상태값 실시간 저장하기 (0) | 2025.03.22 |
chart.js를 사용한 데이터 시각화하기 #3/3 (4) | 2024.12.27 |
chart.js를 사용한 데이터 시각화하기 #2/3 (0) | 2024.12.27 |