이번 주에는 텍스트 에디터를 만들어 보는 시간을 가졌습니다. JavaScript API Document.execCommand
를 활용해 보았습니다. 사실, mdn 문서를 확인해 보면, 현재 deprecated 된 상태로, 일부 브라우저에서만 지원하고 있고, 관련 웹 기준에서 이미 제외되었음을 알려주고 있습니다. 하지만, Notion과 같은 텍스트 에디터는 어떻게 만들 수 있는지, 아주 기초적인 구현 방법을 알아보기 위해 위의 API를 사용해 보겠습니다.
UI는 다음과 같이 구현하였습니다.
[기본 상태]
[Italic 버튼을 클릭했을 때]
[하단 Show Html Content 버튼을 클릭했을 때]
해당 기능은 다음과 같습니다.
- 텍스트 작성 영역에 타이핑 후, BG 또는 Bold와 같은 버튼을 클릭하면, 해당 스타일이 적용됩니다.
- 텍스트 작성 영역에 타이핑 후, 'show Html Content'를 클릭하면, 스타일이 적용된 상태의 텍스트를 html markup으로 변환하여 보여줍니다.
- 텍스트 작성 영역에 타이핑 후, 'Show Editor Content'를 클릭하면, 현재 텍스트 작성 영역에 띄워진 html markup을 스타일이 적용된 텍스트로 변환하여 보여줍니다.
명령어, value, 버튼에 담길 text를 담은 배열을 만들어 주었습니다. 일종의 데이터입니다.
3가지의 key를 담은 객체를 배열에 담은 이유는 execCommand 메서드가 이 3가지 인자를 받기 때문입니다.
execCommand(aCommandName, aShowDefaultUI, aValueArgument)
const commandGroup = [{
cmd: 'backColor',
value: '#12b866',
label: 'BG'
},{
cmd: 'bold',
label: 'Bold',
},{
cmd: 'italic',
label: 'Italic'
},{
cmd: 'justifyCenter',
label: 'Center'
},{
cmd: 'justifyLeft',
label: 'Left'
},{
cmd: 'justifyRight',
label: 'Right'
}, {
cmd: 'underline',
label: 'U'
}, {
cmd: 'fontSize',
value: '1-7',
label: 'Size'
},{
cmd: 'selectAll',
label: 'SelectAll'
}]
명령어를 뜻하는 키 cmd는 MDN 공식문서를 확인해 보면, 위의 cmd에 담긴 명령어들을 어떻게 사용할 수 있는지 보여주고 있습니다.
html에서 명령어를 담은 버튼을 정적으로 생성하는 것이 아닌, 자바스크립트를 이용해서 동적으로 생성하였습니다.
const commandObj = {}; // literal object
const makeEditorButtons = () => {
commandGroup.map((command) => {
commandObj[command.cmd] = command;
const element = document.createElement('button');
element.innerText = command.label;
element.addEventListener('click', (e) => {
e.preventDefault();
changeContent(command.cmd);
})
$editorButtons.appendChild(element);
})
}
commandObj[command.cmd]
라는 코드는 cmd, 즉 명령어를 execCommand 메서드 실행 시 인자로 넘겨줘야 하는데, 배열 내 객체 내부의 키로 설정되어 있습니다. 데이터에 접근성을 높이기 위해서, commandGroup 배열에 map 메서드를 사용하여 각 객체 요소마다 cmd라는 키에 접근하고, 해당 키의 value를 새로운 commandObj라는 객체의 key로 설정합니다.
commandObj에 어떤 값이 담겼는 지 확인해보면, 다음과 같습니다.
상단의 버튼을 클릭했을 때, 텍스트 스타일이 변경되도록 하는 메서드입니다.
const changeContent = (commandKey) => {
const command = commandObj[commandKey];
const value = command.value ? prompt('Enter new Value', 'green') : '';
document.execCommand(command.cmd, false, value);
}
이 화살표 함수 표현식에는 execCommand
메서드가 담겨 있습니다.
하단의 2가지 버튼 중 하나를 클릭했을 때, 서로 다른 2가지 모드로 변환시켜주는 메서드입니다.
텍스트 작성 영역의 텍스트의 현재 스타일을 반영한 html 마크업 형태로 보여주는 'show html content' 버튼을 클릭했을 때
html 마크업 형태를 스타일이 적용된 텍스트의 모습으로 변환 시켜주는 'show edit content' 버튼을 클릭했을 때
const changeMode = (e) => {
if(!e.target.matches('.content')) return;
if(e.target.matches('.show-edit-button')){
$editorEdit.innerHTML = $editorHtml.innerText;
$editorEdit.classList.add('show');
$editorHtml.classList.remove('show')
} else {
$editorHtml.innerText = $editorEdit.innerHTML;
$editorHtml.classList.add('show');
$editorEdit.classList.remove('show')
}
}
const init = () => {
makeEditorButtons();
}
window.addEventListener('DOMContentLoaded', () => {
init();
$showContentButtons.addEventListener('click', changeMode);
})
show라는 클래스가 추가되면 editorHtml 또는 editorEdit이라는 클래스를 가진 DOM 요소가 display:block
이 되어 보여집니다.
$editorEdit
과 $editorHtml
DOM 요소들은 div 태그로 구성되어 있는데, 어떻게 textarea와 같이 텍스트를 작성할 수 있을까요?
이유는 다음과 같습니다.
<div class="editor edit show" contenteditable="true"></div>
<div class="editor html" contenteditable="true"></div>
DOM 요소를 하나씩 만든 직후에 바로 이벤트 리스너를 이용해, 콜백 함수를 실행하는 것이 과연 옳은 것인가라는 의문이 들었습니다.
즉, 한 번에 commandObj에 key-value를 추가하고, element라는 변수에 DOM 요소를 담아서 클릭 이벤트에 대한 콜백함수를 실행하고, 명령 버튼들을 담는 $editorButtons
DOM 요소에 해당 element를 담는 로직을 map을 돌면서 한 번씩 실행한다는 점이 효율적인가 의문이 들은 것입니다.
이후에 더 깊게 고민해보면서, 리팩터링이 필요할 경우 수정해보도록 하겠습니다.
이렇게 4주 간의 자바스크립트 스터디를 마무리하였습니다. 계속해서 만들고 싶은 간단한 UI가 있다면, 추가적으로 더 만들어 보고, 이전에 만들었던 다양한 UI들을 업그레이드 해서 공유해보겠습니다.