본문 바로가기
Web Development/JSTL

[JSP / JSTL / Javascript] 내용 전달 기능 구현을 위한 contenteditable 속성 이용해보기(feat. textarea)

by 감자맹고우 2023. 1. 4.
728x90
반응형

프로젝트를 진행하면서 기획에 새로운 기능이 추가되었다. 바로, 내용 전달 기능...

화면 기획서에 전달 버튼 하나를 추가했을 뿐이라고 오늘도 기획자는 간단하게만 생각한다.

기획을 할 때 뒷단의 동작을 전혀 고려하지 않는데 이것이 참으로 사람을 힘들게 한다...

 

아무튼, 계속 질문을 던져서 정의된 동작은 Outlook이나 메일에서의 내용 공유 전달이었다.

그래서 기존 문서의 작성자, 수신자, 내용, 댓글까지 전부 신규 문서의 내용으로 복사가 되도록 기능을 구현하기로 했다.

 

대충 이런 기능이다.

 

문제는 textarea...

메일도 아니고, 단순 내용 작성이라 에디터를 사용하지 않기로 했다. 그렇기 때문에 퍼블리싱 작업도 textarea로 간단하게 처리되어 왔다.

그러나 textarea는 큰 문제가 있었는데...

 

일단 첫 번째로, 태그 안에 다른 태그를 허용하지 않는다. 이것은 값을 받아와 출력하는데 문제가 발생할 수 있다. 다행히도 JSTL의 c태그 등은 사용 가능한 것을 확인했다.

두번째로는, '<textarea>내용</textarea>' 에서, 내용에 들어가는 부분은 모두 값으로 처리된다. 그 말은 즉, 정렬을 위해 띄어쓰기를 하거나 탭을 사용해도 모두 내용으로 처리된다는 뜻이다. 그렇기 때문에, JSTL로 값을 받아온다고 해도 개행을 하지 못하고 모두 붙여서 써야하기 때문에 가독성이 매우 떨어진다.

 

어떻게 해결할 수 있을까?

 

 

[ 해결 방법 ]

 

역시 가장 쉽게 떠올릴 수 있는 방법은, 선례를 찾아보는 것이었다.

네이버와 여러 메일 시스템에서 어떻게 처리했나 개발자 도구를 통해 살펴보았고,

결국 contenteditable이라는 속성을 발견했다.

 

contenteditable 속성은 해당 HTML 요소를 편집이 가능하도록 해주는 기능이다.

body, div 등에 적용할 수 있으며, 'contenteditable="true"' 설정을 해주면 커서와 text를 쓸 수 있는 것을 확인할 수 있다.

이를 통해서 editor를 커스텀으로 만드는 것도 가능하며, 구글링을 하면 구현 예제들을 쉽게 찾을 수 있다.

 

물론 나는 거기까지는 필요하지 않기 때문에 기본 기능만 사용하기로 했다.

일단, contenteditable이 동작하는 구조를 살펴보자.

 

< 1-1. 작성 화면 >

 

< 1-2. HTML >

 

테스트로 작성한 후, HTML 구조를 보니 contenteditable="true" 로 선언된 요소에서 텍스트를 작성하면 div가 신규 생성이 되고, 개행은 br태그로 처리되는 것을 알 수 있었다.

조금 더 테스트해보니, 선언된 요소 안에 작성되어있는 태그의 영향을 받는 것 같았다. p태그가 먼저 작성되어 있었다면 p태그가 생성되는 것처럼 말이다.

그러나 그런 경우에 기존 요소를 Backspace로 지우고 입력하는 경우 div가 생성되는 것을 알 수 있었다.

심지어 요소안에 아무 것도 없는 공백상태에서 Backspace를 추가로 입력하고 텍스트를 작성하면 첫 줄은 어떤 태그도 감싸지 않는 것을 확인할 수 있었다. 두 번째 줄부터는 다시 div 태그가 생성된다.

심지어 Ctrl + B 단축키도 먹혀서, 폰트에 bold가 b태그로 적용되기도 한다.

 

반응형

 

< 입력(Javascript) >

 

이처럼 경우의 수가 많지만, 규칙을 발견할 수는 있었다.

- div 태그가 줄의 처음과 끝을 나타낸다.
- 첫 줄은 예외처리가 필요하다.
- 나머지 태그는 필요 없다.

 

DB에 HTML 자체를 저장하면 규칙을 고려할 필요가 없어 편리하겠지만, 위험 부담이 있을 것 같아서 HTML 태그를 치환해서 텍스트만 저장하는 방식으로 처리하기로 했다.

 

//Javascript (JQuery 사용)
//<div contenteditable="true" id="cntt"></div>로 가정
const divList = $('#cntt').find('div');
const firstText = $('#cntt').html().split('<div>')[0] + '\n';
let html = firstText.trim() == '' ? '' : firstText;
		
divList.map(i => html += $(divList[i]).html() + '\n');
html = html.replaceAll('<b>', '').replaceAll('</b>', '').replaceAll('&nbsp;', ' ').replaceAll('<br>', '');
		
if(html.trim() == '') {
    alert('내용을 입력해주세요.');
}

 

규칙에 따라 Javascript에서 치환을 해주었다.

먼저 divList에 작성된 div를 담고, 첫번째 줄만 split을 이용해 분리해 따로 처리해주기로 했다.

처리할 때 의도치 않은 공백 발생을 예외처리하기 위해 trim 메소드를 사용해주었다.

 

divList에 담긴 div요소를 각각 한 줄로 보고 map을 통해 개행 처리하였다.

그 후, HTML 태그를 각각 replaceAll을 통해 공백 또는 띄어쓰기로 처리하였다.

하지만, 이 코드는 HTML 코드를 완벽하게 막아주고 있지 않기 때문에 필요에 따라 더 처리해야 할 필요가 있다.

 

반응형

 

< 출력(JSP) >

 

입력과 마찬가지로 출력에 있어서도 고민해야할 부분이 있다.

바로 개행문자의 처리이다.

 

서버에서 testVO의 cntt라는 변수에 내용을 담아서 model로 넘겨준다고 했을 때, JSP에서는 el을 통해 ${testVO.cntt}를 받아올 수 있다. textarea라면 그대로 출력해주면 알아서 개행처리가 된다.

 

그러나 여기서는 div contenteditable 속성을 통해 textarea를 대체하고 있고, text가 아닌 html로 처리하기 때문에 개행처리를 따로 설정해줄 필요가 있다.

방법은 입력과 마찬가지로 개행문자를 br태그로 변경해주면 된다.

하지만 개행문자를 어떻게 인식할 것인가가 문제였다.

처음에는 fn:replace를 통해 해결해보려고 했는데 개행문자 자체가 개행을 처리하기 때문에 replace의 구분자로써 의도대로 잘 동작하지 않았다.

 

 

그래서 구글링해 본 결과, model1 방식으로 Java 코드를 JSP에 넣는 방식을 써서 해결할 수 있었다.

구현한 코드는 아래와 같다.

 

<!-- JSP -->
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>

<div contenteditable="true" id="cntt">
    <% pageContext.setAttribute("newLineChar", "\n"); %>
    <c:forEach var="result" items="${fn:split(testVO.cntt, newLineChar)}">
        <c:choose>
            <c:when test="${fn:trim(result) eq ''}">
                <div><br/></div>
            </c:when>
            <c:otherwise>
                <div><c:out value="${fn:trim(result)}"/></div>
            </c:otherwise>
        </c:choose>
    </c:forEach>
</div>

 

pageContext.setAttribute를 통해 \n 으로 개행문자를 받아올 수 있었고, 이를 첫번째 매개변수를 변수명으로 지정하여 할당할 수 있다. 즉, newLineChar라는 변수에 \n 값을 할당했다고 보면 된다.

이를 통해서, JSTL에 해당 변수를 대입할 수 있고, 위에서는 fn:split에 대입하여 사용하였다. 이렇게 개행문자를 구분자로 하여 testVO.cntt의 내용을 나눌 수 있다.

추가적으로 fn:trim을 통해 공백으로 연속해서 개행된 부분은 br 태그로 개행 처리되도록 설정해주었다.

 

이렇게 contenteditable 속성을 통하여, DB 값을 원하는 형식으로 가져오거나 정렬 등 textarea로 처리하기 힘든 부분을 처리할 수 있다!

 

 

🤞 도움이 되셨기를 바랍니다. 한 번의 클릭과 댓글은 어딘가의 누군가에게 진실로 큰 힘이 됩니다. 🐱‍🏍

 

728x90
반응형

댓글