본문 바로가기
혼공학습단 11기(完)

[혼공스] 07 - 2 '이벤트 활용' 정리(3)

by jaeheon0520 2024. 2. 6.

 

오늘은 07 - 2장의 내용 정리를 마무리해보자.

 

07 - 2장의 남은 부분은, 여러 가지 글자 입력 양식 이벤트에 관해서 설명하고 있다.

 

기본 이벤트 막기

웹 브라우저는 이미지에서 마우스 오른쪽 버튼을 클릭하면 다음과 같은 컨텍스트 메뉴(context menu)를 출력한다. 이처럼 어떤 이벤트가 발생했을 때 웹 브라우저가 기본적으로 처리해주는 것을 기본 이벤트라고 부른다.

 

컨텍스트 메뉴

 

링크를 클릭했을 때 이동하는 것, 제출 버튼을 눌렀을 때 이동하는 등이 모두 기본 이벤트의 예이다. 이러한 기본 이벤트를 제거할 때는 event 객체의 preventDefault() 메소드를 사용한다.

 

다음 코드는 모든 img 태그의 contextmenu 이벤트가 발생할 때 preventDefault() 메소드를 호출해서 기본 이벤트를 막는 예이다. contextmenu 이벤트는 기본적으로 앞의 그림과 같은 컨텍스트 메뉴를 출력하는데, 이를 막으면 이미지에서 마우스 오른쪽 버튼을 클릭해도 아무 반응이 없다.

 

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Document</title>
    <script>
        document.addEventListener('DOMContentLoaded', () => {
            const imgs = document.querySelectorAll('img')

            imgs.forEach((img) => {
                img.addEventListener('contextmenu', (event) => {
                    event.preventDefault() // 컨텍스트 메뉴를 출력하는 기본 이벤트를 제거한다
                })
            })
        })
    </script>
</head>
<body>
    <img src="http://placekitten.com/300/300" alt="">
</body>
</html>

 

인터넷에서 이미지 불펌 방지 등을 구현할 때 사용하는 코드이므로 기억해두면 유용하게 사용할 수 있다.

 

다른 입력 양식과 조합해서 사용하는 예도 살펴보자.

 

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Document</title>
    <script>
        document.addEventListener('DOMContentLoaded', () => {
            let status = false

            const checkbox = document.querySelector('input')
            checkbox.addEventListener('change', (event) => {
                status = event.currentTarget.checked // checked 속성을 사용한다.
            })

            const link = document.querySelector('a')
            link.addEventListener('click', (event) => {
                if (!status) {
                    event.preventDefault() // status가 false가 아니면 링크의 기본 이벤트를 제거한다
                }
            })
        })
    </script>
</head>
<body>
    <input type="checkbox">
    <span>링크 활성화</span>
    <br>
    <a href="http://hanbit.co.kr">한빛미디어</a>
</body>
</html>

 

체크박스가 체크되어 있는 상태에서만 링크를 클릭했을 때 해당 페이지로 이동한다. 체크가 해제되어 있는 상태에서는 링크를 클릭해도 아무 반응이 없다.

 

실행 결과

 

누적예제: 할 일 목록 만들기

지금까지 배운 것을 종합해서 할 일 목록을 만들어보자.

 

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Document</title>
</head>
<body>
    <h1>할 일 목록</h1>
    <input id="todo">
    <button id="add-button">추가하기</button>
    <div id="todo-List">


    </div>
</body>
<script>
    document.addEventListener('DOMContentLoaded', () => {
        // 문서 객채를 가져옵니다
        const input = document.querySelector('#todo')
        const todoList = document.querySelector('#todo-List')
        const addButton = document.querySelector('#add-button')

        // 변수를 선언한다
        let keyCount = 0 // 이후에 removeTodo() 함수에서 문서 객체를 쉽게 제거하기 위한 용도로 만든 변수

        // 함수를 선언한다.
        const addTodo = () => {
            // 입력 양식에 내용이 없으면 추가하지 않는다
            if (input.value.trim() === '') {
                alert('할 일을 입력해주세요.')
                return
            }

            // 문서 객체를 설정한다
            const item = document.createElement('div')
            const checkbox = document.createElement('input')
            const text = document.createElement('span')
            const button = document.createElement('button')

            // 문서 객체를 식별할 키를 생성한다.
            const key = keyCount // 이후에 removeTodo() 함수에서 문서 객체를 쉽게 제거하기 위한 용도로 만든 변수
            keyCount += 1
           
            // <div data-key="숫자">
            //     <input>
            //     <span></span>
            //     <button></button>
            // </div>
            // 형태를 구성한다.

            item.setAttribute('data-key', key)
            item.appendChild(checkbox)
            item.appendChild(text)
            item.appendChild(button)
            todoList.appendChild(item)

            // checkbox 객체를 조작한다
            checkbox.type = 'checkbox' // <div data-key="checkbox" 형태를 구성한다
            checkbox.addEventListener('change', (event) => { // checkbox를 클릭하면 선을 그어준다
                item.style.textDecoration = event.target.checked ? 'line-through' : ''
            })

            text.textContent = input.value // <span>글자</span> 형태를 구성

            // text 객체를 조작한다
            button.textContent = '제거하기'
            button.addEventListener('click', () => {
                removeTodo(key)
            })

            // 입력 양식의 내용을 비운다
            input.value = ''
        }

        const removeTodo = (key) => {
            // 식별 키로 문서 객체를 제거한다
            const item = document.querySelector(`[data-key="${key}"]`)
            // 위에서 지정한 <div data-key="숫자">를 기반으로 요소를 찾고 제거한다.
            todoList.removeChild(item)
        }

        // 이벤트 연결
        addButton.addEventListener('click', addTodo)
        input.addEventListener('keyup', (event) => {
            // 입력 양식에서 Enter 키를 누르면 addTodo() 함수를 호출한다
            const ENTER = 13
            if (event.keyCode === ENTER) {
                addTodo()
            }
        })
    })
</script>
</html>

 

할 일 목록 만들기

 

코드를 실행하면 할 일 목록 프로그램이 나온다. 할 일을 입력하고 enter키를 누르거나 추가하기 버튼을 클릭하면 아래에 할 일이 추가된다. 체크 박스를 클릭하면 할 일에 취소선이 그어지며, 제거하기 버튼을 클릭하면 할 일이 제거된다.

 

개선 된 글자 수 세기 프로그램

아시아권의 문자는 키보드 이벤트(keydown, keypress, keyup 이벤트)로 원하는 것을 제대로 구현할 수 없는 경우가 많다. 본문에서 살펴보았던 남은 글자 수 세기 프로그램도 꾹 눌렀을 때 글자를 셀 수 없다는 문제가 있었다.

 

실제로 트위터는 다음과 같이 타이머를 사용해서 50밀리초마다 입력 양식 내부의 글자를 확인해서 글자 수를 센다. focus 이벤트와 blur 이벤트를 사용했는데, 이 이벤트는 입력 양식에 초점을 맞춘 경우(활성화 상태)와 초점을 해제한 경우(비활성화 상태)에 발생하는 이벤트이다. 입력 양식에 글자를 입력하려고 선택한 순간부터 타이머를 돌리고, 다른 일을 하기 위해서 입력 양식에서 초점을 해제해면 타이머를 정지하게 만들었다.

 

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Document</title>
    <script>
        document.addEventListener('DOMContentLoaded', () => {
            const textarea = document.querySelector('textarea')
            const h1 = document.querySelector('h1')
            let timerId

            textarea.addEventListener('focus', (event) => {
                timerId = setInterval(() => {
                    const length = textarea.value.length
                    h1.textContent = `글자 수: ${length}`
                }, 50)
            })
            textarea.addEventListener('blur', (event) => {
                clearInterval(timerId)
            })
        })
    </script>
</head>
<body>
    <h1></h1>
    <textarea></textarea>
</body>
</html>

 

글자 수 세기 프로그램

 

07 - 2장의 내용은 이렇게 마무리 하면 될 것 같다.

 

중요한 내용이 굉장히 많이 등장해서 포스팅을 3번에 나눠서 했다.

 

내일은 08 - 1장의 내용을 정리해보자.