ㅁ < 이벤트 Event >
- DOM 객체에서 발생되는 시그널
- 시스템에서 일어나는 사건(Action) 또는 발생(Occurence)를 의미한다.
- 주요 이벤트 발생 방식
(1) 사용자의 액션에 의해서 생성 (클릭, 키보드 입력, 마우스 움직임 등)
(2) API의 이벤트 생성
ㅁ < 이벤트 핸들러 Event Handler >
- 이벤트 발생시 동작시킬 함수
- 이벤트 핸들러(함수) 동작시 이벤트 핸들러의 매개변수로
발생된 이벤트에 대한 각종 정보들이 담겨있는 Event 객체가 인자로 자동 전달된다.
- 이벤트 핸들러(함수)를 작성할 때 매개변수를 두면 이벤트 객체가 전달된다.
매개변수명은 관례상 "event" 또는 "evt"로 한다.
ㅁ 이벤트 모델 Event Model
- 이벤트가 발생했을 때 이벤트 핸들러를 할당하는 방법을 이벤트 모델이라고 한다.
- 종류
(1) 고전이벤트 모델
(2) 인라인 이벤트 모델
(3) 표준 이벤트 모델
ㅁ 고전 이벤트 모델
- DOM 객체의 이벤트 핸들러 속성(property)을 활용하는 방법.
- 이벤트 종류 앞에 "on"키워드를 붙이면 이벤트 핸들러 속성이다.
ex) onclick, onkeydown, onsubmit
- 하나의 이벤트 핸들러 속성에 하나의 이벤트핸들러만 할당 가능하다.
- 이벤트 핸들러 속성에 null 대입시 이벤트 핸들러 제거 가능하다.
(1) 익명함수
DOM 객체.onclick = function() { // 이벤트 핸들러
}
(2) 화살표 함수
DOM 객체.onclick = () => { // 이벤트 핸들러
}
(3) 기명함수
function eventHandler() { // 이벤트 핸들러
}
DOM 객체.onclick = eventHandler; // 미리 선언하고 대입 (괄호 없어야 함)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h2>이벤트 모델(Event Model)</h2>
<h3>고전 이벤트 모델</h3>
<button id="btn1">고전이벤트모델1</button> <!-- 버튼 내에 onclick 속성을 쓰지 않음. -->
<button id="btn2">고전이벤트모델2</button>
<button id="btn3">고전이벤트모델3</button>
<script>
(1) 익명함수
const btn1El = document.getElementById("btn1"); // 전역함수
btn1El.onclick = function() {
console.log('btn1 클릭');
}
btn1El.onclick = function() {
alert("btn1 클릭");
}
// alert에도, console에도 띄우고 싶지만. alert 알림창으로는 뜨는데 콘솔에는 안 뜬다.
// 하나의 이벤트 핸들러 속성에 하나의 이벤트 핸들러만 할당 가능하다.
(2) 화살표 함수
const btn2El = document.getElementById("btn2");
btn2El.onclick = () => {
console.log('btn2 클릭, btn1클릭 이벤트핸들러 제거');
btn1El.onclick = null; // 이러면 첫번째 버튼 눌러도 아무 반응 없음.
}
(3) 기명함수
const btn3El = document.getElementById("btn3");
btn3El.onclick = fnAction; // 이미 존재하는 함수를 대입할 때는 괄호열고 닫으면 안 됨. 함수명만 써야 함. 실행되버림.
function fnAction() {
console.log("fnAction함수 실행");
}
</script>
</body>
</html>
- 이미 존재하는 함수를 대입할 때는 괄호를 열고 닫으면 안 된다. 함수명만 써야 한다.
ㅁ 인라인 이벤트 모델
- HTML 요소 내에 직접 이벤트 핸들러 속성을 작성하고 실행할 함수를 호출
- 이벤트 핸들러 작성시 주로 기명함수를 사용
- 인라인 이벤트 모델 방식은 <button onclick="firstHandler(); secondHandler();">Click me</button>
이렇게 하나의 onclick 속성에 여러 핸들러를 추가할 수 있다.
ㅇ 인라인 이벤트 모델 예시
<button onclick="inline();">버튼</button>
function inline() { }
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h2>이벤트 모델(Event Model)</h2>
<h3>인라인 이벤트 모델</h3>
<button onclick="fnClickedFunc();">인라인이벤트모델</button>
<script>
function fnClickedFunc() {
console.log("인라인 이벤트 모델 방식");
}
</script>
</body>
</html>
ㅁ 표준 이벤트 모델
- 이게 w3c 재단에서 권장사항이다. 그런데 인라인 방식을 많이 쓴다.
그래도 이 방식을 쓰는게 좋음. 근데 간혹가다 지원하지 않는 브라우저가 있다. IE 9 이하에서는 지원 안 함.
- 여기서도 button에 onclick속성 안 쓴다. button에 onclick 속성 쓰는건 무조건 인라인 방식이다.
- W3C에서 공식적으로 지정한 이벤트 모델
- addEventListener() 메소드를 호출하는 방식
- 감지할 이벤트와 해당 이벤트 발생시 실행시킬 이벤트 핸들러 작성
- 하나의 이벤트에 여러개의 이벤트 핸들러 할당 가능
( 고전 이벤트 모델은 하나의 이벤트에 여러 개의 이벤트 핸들러를 할당할 수 없다. )
( 인라인 이벤트 모델은 할당이라고 볼 수 없다. 그냥 속성값으로 실행시키고자 하는 소스코드를 작성하는 방식이다. )
(1) 익명함수
DOM 객체.addEventListener( "click", function( ) )
첫번째 매개변수 : 감지할 이벤트명(on이 붙지 않은 이벤트 종류)
두번째 매개변수 : 그때 실행시킬 이벤트 핸들러
(2) 화살표 함수
DOM 객체.addEventListener( "click", ( ) => {
} )
(3) 기명함수
function eventHandler( ) { }
DOM 객체.addEventListener( "click", eventHandler );
// 미리 선언한 함수를 전달할 때 괄호 열고닫지 않음.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h2>이벤트 모델(Event Model)</h2>
<h3>표준 이벤트 모델</h3>
<button id="btn1">표준이벤트모델1</button>
<button id="btn2">표준이벤트모델2</button>
<button id="btn3">표준이벤트모델3</button>
<script>
// 익명함수
const btn1El = document.getElementById("btn1"); // 전역변수
btn1El.addEventListener("click", function() {
console.log("btn1 클릭");
} )
// 동일한 이벤트에 두 개의 이벤트 핸들러 연결
// 고전 이벤트 모델은 마지막에 연결한 이벤트 핸들러만 작동되었었다.
btn1El.addEventListener("click", function() {
alert("btn1 클릭");
} )
// 화살표 함수
const btn2El = document.getElementById("btn2"); // 전역변수
// 마우스가 버튼에 올라가고 내려갈 때 사실 이벤트가 발생한다.
btn2El.addEventListener("mouseenter", ( ) => {
console.log("btn5 마우스 올라감");
} )
btn2El.addEventListener("mouseout", ( ) => {
console.log("btn5 마우스 빠져나감");
} )
// 기명함수
document.getElementById("btn3").addEventListener("dblclick", fnAction); // 더블클릭. 괄호 없어야 함.
function fnAction() {
console.log("fnAction함수 실행");
}
</script>
</body>
</html>
=================================================================================
ㅁ 알게 모르게 브라우저를 이용하면서 이벤트가 많이 발생한다.
키보드로 뭘 입력하거나, 뭘 누르거나, 마우스를 올리거나 내리거나.
알게 모르게 이벤트 객체들이 항상 만들어지고 있다.
어떤 요소에 어떤 이벤트가 발생했는지에 대한 정보를 이벤트 객체가 가지고 있다.
ㅁ 이벤트 객체
- 이벤트가 발생하는 순간 브라우저에 의해 만들어진다.
- 현재 발생한 이벤트에 대한 상세정보를 가지고 있다. (이벤트 종류, 이벤트 대상, 이벤트 위치 등)
- 이벤트 핸들러의 매개변수로 이벤트 객체가 전달된다.
(이벤트 핸들러를 작성할 때 매개변수를 두면 이벤트 객체가 전달됨)
(이 때 매개변수명은 관례상 "event" 또는 "evt")
ㅇ 주요 속성
(1) type
- 발생된 이벤트의 종류
- 마우스 이벤트 : click, dblclick, mouseover, mouseout, mousemove, mouseup, mousedown, ...
- 폼 요소 이벤트 : submit
- 키보드 이벤트 : keydown, keypress, keyup
...
(2) target
- 이벤트가 발생된 요소객체
ㅁ 현재 이벤트가 발생된 요소에 접근하기
- 화살표 함수, 기명함수(선언적 함수)에서는 this 사용 불가(해당 요소 객체가 아닌 window 객체를 가리켜서)
- 기명함수에서는 window.event.target 이용해서 이벤트 발생된 요소객체 활용.
- 화살표함수에서는 window.event.target, 매개변수로 evt로 이벤트 객체 전달받아 evt.target으로 활용.
- 익명함수에서는 window.event.target, evt.target, this 다 활용 가능.
- window.event.target은 다 가능.
(1) 고전 이벤트 방식 - 익명함수
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h2>현재 이벤트가 발생된 요소에 접근하기</h2>
<button id="origin">고전이벤트방식</button>
<script>
// 고전이벤트방식(익명함수)
document.getElementById("origin").onmouseenter = function(evt) {
console.log(window.event); // window.event : Event객체
console.log(evt); // evt : Event객체
console.log(window.event.target); // window.event.target : 현재 이벤트가 발생된 요소객체 (o)
console.log(evt.target); // evt.target : 현재 이벤트가 발생된 요소객체 (o)
console.log(this); // this : 현재 이벤트가 발생된 요소객체 (o)
}
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h2>현재 이벤트가 발생된 요소에 접근하기</h2>
<button id="origin">고전이벤트방식</button>
<script>
// 고전이벤트방식(익명함수)
document.getElementById("origin").onmouseenter = function(evt) {
console.log(window.event); // window.event : Event객체
console.log(evt); // evt : Event객체
console.log(window.event.target); // window.event.target : 현재 이벤트가 발생된 요소객체 (o)
console.log(evt.target); // evt.target : 현재 이벤트가 발생된 요소객체 (o)
console.log(this); // this : 현재 이벤트가 발생된 요소객체 (o)
// 이벤트가 발생된 요소객체 조작
window.event.target.innerHTML = '마우스올라감';
evt.target.style.fontSize = "32px";
this.style.backgroundColor = "red";
}
</script>
</body>
</html>
- this는 현재 이벤트가 발생한 요소 객체다.
(2) 고전 이벤트 방식 - 화살표 함수
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h2>현재 이벤트가 발생된 요소에 접근하기</h2>
<button id="origin">고전이벤트방식</button>
<script>
// 고전이벤트방식(화살표함수)
document.getElementById("origin").onclick = evt => {
console.log(window.event.target); // window.event.target : 현재 이벤트가 발생된 요소객체 (o)
console.log(evt.target); // evt.target : 현재 이벤트가 발생된 요소객체 (o)
//console.log(this); // this : window 객체 (즉, 사용불가) (x)
window.event.target.innerHTML = "클릭됨";
evt.target.style.border = "2px solid blue";
}
</script>
</body>
</html>
(3) 인라인 이벤트 방식 - 기명함수
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h2>현재 이벤트가 발생된 요소에 접근하기</h2>
<button id="inline" onclick="fnInline();">인라인이벤트방식</button>
<script>
// 인라인이벤트방식(기명함수)
function fnInline(evt) {
console.log(window.event.target); // window.event.target : 현재 이벤트가 발생된 요소객체 (o)
//console.log(evt.target); // evt.target : evt는 undefined (즉, 사용불가) (x)
//console.log(this); // this : window 객체 (즉, 사용불가) (x)
const eventEl = window.event.target;
eventEl.innerHTML = "클릭됨";
eventEl.style.color = "green";
}
</script>
</body>
</html>
(4) 표준 이벤트 방식 - 익명함수
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h2>현재 이벤트가 발생된 요소에 접근하기</h2>
<button id="standard">표준이벤트방식</button>
<script>
// 표준이벤트방식(익명함수)
document.getElementById("standard").addEventListener("mouseenter", function(evt) {
console.log(window.event.target); // window.event.target : 현재 이벤트가 발생된 요소객체 (o)
console.log(evt.target); // evt.target : 현재 이벤트가 발생된 요소객체 (o)
console.log(this); // this : 현재 이벤트가 발생된 요소객체 (o)
})
</script>
</body>
</html>
(5) 표준 이벤트 방식 - 화살표 함수
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h2>현재 이벤트가 발생된 요소에 접근하기</h2>
<button id="standard">표준이벤트방식</button>
<script>
// 표준이벤트방식(화살표함수)
document.getElementById("standard").addEventListener("click", evt => {
console.log(window.event.target); // window.event.target : 현재 이벤트가 발생된 요소객체 (o)
console.log(evt.target); // evt.target : 현재 이벤트가 발생된 요소객체 (o)
//console.log(this); // this : window 객체 (즉, 사용불가) (x)
});
</script>
</body>
</html>
ㅁ 예제 - 버튼 클릭시 해당 버튼의 배경색을 해당 버튼의 innerHTML 값으로 변경
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h2>적용예시</h2>
<!-- 세버튼 클릭시 해당 버튼의 배경색을 해당 버튼의 innerHTML값으로 변경 -->
<button id="red" onclick="fnChangeRed();">red</button>
<button id="green" onclick="fnChangeGreen();">green</button>
<button id="blue" onclick="fnChangeBlue();">blue</button>
<script>
function fnChangeRed(){
document.getElementById("red").style.backgroundColor = "red";
}
function fnChangeGreen(){
document.getElementById("green").style.backgroundColor = "green";
}
function fnChangeBlue(){
document.getElementById("blue").style.backgroundColor = "blue";
}
</script>
</body>
</html>
- 이렇게 함수를 다 따로 만들고 각 버튼 클릭시 이 함수들을 호출시킬 수도 있지만, 하나의 함수로 통일할 수도 있다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h2>적용예시</h2>
<!-- 세버튼 클릭시 해당 버튼의 배경색을 해당 버튼의 innerHTML값으로 변경 -->
<button id="red" onclick="fnChangeBackground();">red</button>
<button id="green" onclick="fnChangeBackground();">green</button>
<button id="blue" onclick="fnChangeBackground();">blue</button>
<button onclick="fnChangeBackground()">pink</button>
<script>
function fnChangeBackground() {
const clickedBtn = window.event.target;
clickedBtn.style.backgroundColor = clickedBtn.innerHTML;
}
</script>
</body>
</html>
- fnChangeBackground 함수 하나를 정의해서 각 버튼 클릭시 동일하게 이 함수를 호출시킬 수 있다.
- 클릭 이벤트가 발생한 요소 객체를 window.event.target으로 가져와서 변수에 기록.
- 각 기능의 함수를 따로 정의해서 따로 호출할 필요 없이, 한번에 정의해서 모든 버튼에 동일한 함수를 실행시켰다.
그때 어떤 버튼 요소가 클릭될 지는 모르겠지만 클릭된 버튼 요소가 필요하다면 그 함수 내에서 window.event.target을 이용해서 가져올 수 있다.
- innerHTML로 그냥 가져와서 따옴표 안붙이고 대입했음.
innerHTML은 HTML 요소의 콘텐츠를 문자열로 반환합니다.
예를 들어, <button id="red">red</button>에서 button 요소의 innerHTML은 "red"입니다.
이 값은 이미 문자열이기 때문에, 자바스크립트에서는 추가적인 따옴표가 필요 없습니다.
==============================================================================
ㅁ 키보드 관련 이벤트
- 사용자가 텍스트상자를 클릭해서 키보드로 무언가를 입력할 때도 이벤트가 발생한다.
- 키보드 관련 이벤트는 텍스트 상자에 사용자가 무언가를 입력할 때 마다 사용자가 입력한 값을 가져와서 검수해야 한다든가 할 때 사용한다.
사용자가 유효한 형식으로 잘 입력하고 있는지 실시간으로 입력된 값을 가져와야하는 때가 있다.
- 종류
(1) keydown : 키가 눌려질 때 발생되는 이벤트 (모든 키 감지)
(2) keypress : 키가 눌려질 때 발생되는 이벤트 (펑션키, 기능키, 한글 제외한 키 감지 - 영문, 숫자키 정도만 감지)
(3) keyup : 키가 떼어질 때 발생되는 이벤트 (모든 키 감지)
ㅁ (1) keydown, (2) keypress
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h2>키보드 관련 이벤트</h2>
<textarea id="content" cols="30" rows="10" style="resize:none" placeholder="내용을 입력해주세요"></textarea>
<script>
document.getElementById("content").onkeydown = function(evt) {
console.log('이벤트발생');
}
</script>
</body>
</html>
- 텍스트상자를 input type=text로 한줄짜리말고, textarea로 여러줄 입력할 수 있는 텍스트 상자를 둬 본다.
- 브라우저에서 textarea 내부를 클릭하면 focus 상태가 된다.
- alt, shift, 펑션키(f10 등)가 눌려질때마다 keydown 이벤트가 발생하여 콘솔창에 '이벤트발생'이 출력된다.
- keydown을 keypress로 바꾸면 영문, 숫자키는 잘 작동되지만 한글키, 기능키, 펑션키 등에는 작동하지 않는다.
- 꾹 누르면 keydown 이벤트가 계속 발생한다. keypress도 누르는 동안 이벤트가 계속 발생한다.
ㅁ (3) keyup
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h2>키보드 관련 이벤트</h2>
<textarea id="content" cols="30" rows="10" style="resize:none" placeholder="내용을 입력해주세요"></textarea>
<script>
document.getElementById("content").onkeyup = function(evt) {
console.log('이벤트발생');
console.log(evt);
}
</script>
</body>
</html>
- keyup은 계속 키보드를 누르고 있어도 계속 발생 안 함. 키보드에서 손을 떼면 그 때 한 번만 이벤트가 발생한다.
- 저 함수가 실행될 때 마다 현재 발생하는 이벤트 정보가 담겨있는 이벤트 객체가 전달이 되어서 매개변수 evt에 담겨있다.
- keyboardEvent 라는 객체가 보이고 펼쳐보면 여러 속성을 가지고 있다.
어떤 타입인지(type), 어느 영역의 이벤트인지 그 요소객체 가리키고 있다(target).
- key 속성은 현재 눌려진 키 값.
keyCode는 사용자가 키보드에서 누르는 키에 대한 코드.
(키코드는 한글은 제대로 나오지 않고 영문 입력해야 한다)
- 텍스트 상자에 입력을 다 끝내고 엔터키를 치는 순간 무언가 수행되게끔 하고 싶을 때,
현재 눌려진 키가 엔터일 경우라는 조건을 종종 쓴다.
그럴때 key, keyCode를 가져와서 비교를 한다.
- Enter 키코드는 13.
document.getElementById("content").onkeyup = function(evt) {
console.log('key속성: ', evt.key);
console.log('keyCode속성: ', evt.keyCode);
}
- 현재 눌려진 키 하나에 대한 정보를 알 수 있다.
- 키를 누를 때 마다 어떤 키가 눌렸는지, 그 키코드 값이 뭔지 Event 객체로부터 알아낼 수 있다.
- 만약 텍스트 상자에 실시간으로 사용자가 입력한 값 전체를 알고 싶다면 text상자의 value값 가져오면 된다.
document.getElementById("content").onkeyup = function(evt) {
console.log('key속성: ', evt.key);
console.log('keyCode속성: ', evt.keyCode);
console.log('입력란에 작성된값: ', evt.target.value);
}
한글 입력하면 key 속성이 Process로 나옴.
- 글자 수도 실시간으로 알 수 있다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h2>키보드 관련 이벤트</h2>
<span id="text_length">0</span>/200<br>
<textarea id="content" cols="30" rows="10" style="resize:none" placeholder="내용 입력"></textarea>
<script>
document.getElementById("content").onkeyup = function(evt) {
console.log('key속성: ', evt.key);
console.log('keyCode속성: ', evt.keyCode);
console.log('입력란에 작성된값: ', evt.target.value);
document.getElementById("text_length").innerHTML = evt.target.value.length;
}
</script>
</body>
</html>
==========================================================================
ㅁ < 이벤트 전파(이벤트 버블링) >
- 복잡하게 요소 작업을 하다보면 요소 안에 요소가 있을 수 있다. (중첩작성)
이때 이 요소들에 이벤트가 여러개 걸려있을 경우 이벤트 버블링 현상이 발생할 수 있다.
- 요소들이 중첩되어있을 경우 내부 요소에 이벤트 핸들러가 동작된 후
상위 요소들의 이벤트 핸들러도 같이 동작되는 현상.
- 최상위 요소에 도달할 때까지 반복적으로 진행됨.
- Event 객체의 stopPropagation() 메소드를 통해 이벤트 전파를 중지시킬 수 있다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h2>이벤트 전파</h2>
<style>
.bubbling { margin: 10px; border: 1px solid gray; }
#child { width: 160px; }
#parent { width: 200px; }
#ancestor { width: 240px; }
</style>
<div id="ancestor" class="bubbling">조상div
<div id="parent" class="bubbling">부모div
<div id="child" class="bubbling">자식div</div>
</div>
</div>
<script>
</script>
</body>
</html>
- 기본세팅.
- 요소가 중첩되어 있다.
이때 자식 요소에도 클릭 이벤트가 있고, 부모 요소에도 클릭 이벤트가 있고, 조상 요소에도 클릭 이벤트가 있으면,
기본적으로 안쪽의 요소를 클릭하는 순간 그 상위 요소들의 이벤트도 같이 작동된다.
이게 이벤트 버블링 효과이다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h2>이벤트 전파</h2>
<style>
.bubbling { margin: 10px; border: 1px solid gray; }
#child { width: 160px; }
#parent { width: 200px; }
#ancestor { width: 240px; }
</style>
<div id="ancestor" class="bubbling">조상div
<div id="parent" class="bubbling">부모div
<div id="child" class="bubbling">자식div</div>
</div>
</div>
<script>
const ancestor = document.getElementById("ancestor");
const parent = document.getElementById("parent");
const child = document.getElementById("child");
// 조상, 부모, 자식 div에 클릭 이벤트 부여 (표준 이벤트 방식으로)
ancestor.addEventListener("click", evt => {
console.log('조상div 이벤트핸들러 작동');
});
parent.addEventListener("click", evt => {
console.log('부모div 이벤트핸들러 작동');
});
child.addEventListener("click", evt => {
console.log('자식div 이벤트핸들러 작동');
});
</script>
</body>
</html>
- 브라우저에서 자식 div 영역을 클릭하면 자식 div의 이벤트핸들러뿐만 아니라 부모, 조상 div의 이벤트핸들러도 동작한다.
안쪽 요소를 클릭할 때 상위 요소(부모, 조상 div)들도 같이 클릭 되기 때문이다..
- 부모 div 영역을 클릭하면 부모 div 이벤트핸들러 뿐만 아니라, 조상 div 이벤트 핸들러도 작동한다.
- 조상 div 영역을 클릭하면 조상 div 이벤트핸들러만 작동한다.
ㅁ 이벤트 전파(이벤트 버블링) 방지
ancestor.addEventListener( "click", evt => { console.log("조상div 이벤트핸들러 작동"); } );
parent.addEventListener( "click", evt => {
console.log("부모div 이벤트핸들러 작동");
evt.stopPropagation(); // 이벤트 전파 막기
} );
child.addEventListener( "click", evt => {
console.log("자식div 이벤트핸들러 작동");
evt.stopPropagation(); // 이벤트 전파 막기
} );
- 이벤트 핸들러 호출시 매개변수로 전달받은 Event 객체의 stopPropagation() 메소드를 사용해 이벤트 전파를 막을 수 있다.
- 이러면 상위 요소의 이벤트 핸들러는 실행 안 됨.
- 모든 이벤트 핸들러마다 Event객체.stopPropagation();를 쓰라는 게 아니라
요소들이 중첩되어 있을 경우 이벤트 버블링 현상이 발생하면 사용해서 제어해라.
- Event객체가 워낙 자주 사용되서 보통 이벤트 핸들러를 쓸 때는 Event 객체를 받아줄 매개변수를 당장 사용하지 않더라도 써두는 것이 좋다. 언제 쓰게 될 지 모름.
이벤트 핸들러를 쓸 때는 Event 객체를 받아줄 매개변수를 항상 써두는 습관을 들이는 것이 좋다.
==============================================================================
ㅁ 디폴트 이벤트
- 기본적으로 이벤트를 가지고 있는 요소들이 몇 있다.
내가 이벤트를 부여하지 않아도 자동으로 이벤트 발생시 작동되는 내용이 정의되어 있는 요소들이 있다.
- 몇몇 DOM 객체는 특정 이벤트를 기본적으로 이미 가지고 있다.
- 주요 디폴트 이벤트
(1) <a> 태그는 click시 href속성으로 이동함
(2) <form> 내의 submit 버튼은 click시 서버측으로 데이터를 전송한다.
- 디폴트 이벤트 막기
Event 객체의 preventDefault() 메소드 호출
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h2>디폴트 이벤트</h2>
<br><br>
<form action="/server/test.do" method="get">
아이디 : <input type="text" name="userId" id="userId"> <br>
비밀번호 : <input type="password" name="userPwd" id="userPwd"> <br>
비밀번호 확인 : <input type="password" id="checkPwd"> <br><br>
<input type="submit" id="submit">
</form>
<script>
</script>
</body>
</html>
- a 태그.
클릭 이벤트를 넣지 않아도 기본적으로 클릭시 href에 작성된 url 주소로 페이지가 전환되는 기능을 가지고 있다.
- submit 태그.
form 안에 있어야 작동함.
form 안에 무언가를 제출하는 <input type="submit">으로 submit 버튼을 둘 수 있다.
얘도 디폴트 이벤트를 가지고 있다.
- action에는 요청할 서버의 주소를 기술한다. 우선은 test.do로 대충 표기한다.
- name 속성으로 key 값을 부여해야 값이 정상적으로 넘어간다.
- 비밀번호 확인은 서버로 값을 넘길 필요는 없다.
클라이언트측에서 일치하는지 비교하는지가 목적이다. 그래서 name은 안 줘도 된다.
- form 내의 submit 버튼은 기본적으로 클릭 이벤트를 가지고 있다.
사용자가 입력한 값들을 서버측으로 전송하면서 요청을 보내는 기능을 가지고 있다. 디폴트 이벤트다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h2>디폴트 이벤트</h2>
<br><br>
<form action="/server/test.do" method="get">
아이디 : <input type="text" name="userId" id="userId"> <br>
비밀번호 : <input type="password" name="userPwd" id="userPwd"> <br>
비밀번호 확인 : <input type="password" id="checkPwd"> <br><br>
<input type="submit" id="submit"> <!-- 선택가능하게끔 id를 준다. id를 주는 이유는 선택하기 위함이다. -->
</form>
<script>
document.getElementById("naver-link").addEventListener("click", function(evt) {
evt.preventDefault();
})
</script>
</body>
</html>
- a 태그는 기본적으로 클릭 이벤트를 가지고 있지만 내가 다시 클릭이벤트를 작성할 수 있다.
클릭이벤트 발생시 실행할 함수 작성 가능.
이벤트 객체를 전달받기 위해 매개변수를 써둔다.
- 이벤트 객체.preventDefault();를 작성해 놓으면 a 요소를 클릭해도 아무일도 일어나지 않는다.
document.getElementById("naver-link").addEventListener("click", function(evt) {
if( !confirm("페이지 이동하시겠습니까?")) {
evt.preventDefault();
}
})
- 이렇게 작성하면 사용자에게 의사를 물어서 페이지 이동을 할지 말지 결정하게할 수 있다.
- 기본이벤트보다 내가 부여한 이벤트가 먼저 실행된다.
confirm("페이지 이동하시겠습니까?") || evt.preventDefault(); // shortCircuit 사용
- shortCircuit으로 ||를 쓰고 false일 때 실행 내용을 작성할 수 있다.
- 확인을 누르면 이동하고 취소를 누르면 이동이 되지 않음.
ㅁ 제출버튼 클릭시에도 기본적으로 사용자가 입력한 값을 서버측으로 전송하면서 요청을 보내는 기본 이벤트가 있다.
- 그런데 무작정 제출되면 안된다.
두 비밀번호가 일치할 때만 기본이벤트가 동작하고 일치하지 않으면 기본이벤트가 동작하지 않도록 한다.
- 입력 형식을 구성했을 때는 사용자가 유효한 형식으로 입력을 했는지 먼저 검수해야 한다.
검수해서 맞다면 제출, 맞지 않다면 제출이 안 되게 막아야 한다.
(유효한 값으로 입력했는지 유효성 체크)
- form 태그 내의 submit 버튼의 기능을 제어한다.
기본 이벤트를 가지고 있기 때문에 어떤 경우에는 기본 이벤트가 수행되지 않도록 막아야 한다.
document.getElementById("submit").addEventListener("click", evt => {
// 두 비밀번호가 일치하지 않을 경우 알람창 출력후 디폴트이벤트 막기
const userPwdInput = document.getElementById("userPwd"); // value 값이 아닌 input 요소 객체 자체
const checkPwdInput = document.getElementById("checkPwd"); // value 값이 아닌 input 요소 객체 자체
if( userPwdInput.value != checkPwdInput.value ) {
alert("비밀번호가 일치하지 않습니다. 다시 입력해주세요");
checkPwdInput.focus();
evt.preventDefault(); // 기본이벤트가 동작하지 않도록 막기
}
})
- id가 submit인 버튼 요소 선택 후, 클릭 이벤트 발생시 동작할 이벤트 핸들러 할당하기.
- alert로 사용자에게 왜 안되는지 알려주는 것이 좋다.
- 보통 이렇게 일치하지 않을 경우 비밀번호 확인 쪽에 포커싱이 되게 해서 다시 입력을 유도한다.
특정 인풋 요소에 포커싱 효과를 주고 싶다면 focus 메소드를 호출하면 된다.
이러면 사용자가 보다 편하게 다시 입력할 수 있게끔 비밀번호 확인 인풋 요소가 바로 선택되어진다.
ㅁ form에서 유효성 체크로 두 개의 비밀번호가 일치하는지만 확인했다.
- 그런데 사실은 아이디도 유효한 형식인지 확인해야 한다.
- 아이디도 유효한 형식이 아닌 경우 제출 자체가 안되게 해야 한다.
비밀번호 검수 전에 원래는 아이디 체크도 해줘야 한다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h2>디폴트 이벤트</h2>
<br><br>
<form action="/server/test.do" method="get">
아이디 : <input type="text" name="userId" id="userId"> <br>
비밀번호 : <input type="password" name="userPwd" id="userPwd"> <br>
비밀번호 확인 : <input type="password" id="checkPwd"> <br><br>
<input type="submit" id="submit">
</form>
<script>
document.getElementById("submit").addEventListener("click", evt => {
// 아이디가 영문 또는 숫자로만 5글자이상 12글자 이하로 이루어져있는지 체크
const userId = document.getElementById("userId").value;
if(userId.length >= 5 && userId.length <= 12){
// 아이디의 글자 수가 맞다면 글자 안의 각각의 글자들이 영문 또는 숫자로만 이루어져 있는지 체크
for(let i=0; i<userId.length; i++){
if(userId.charAt(i) >= 0 && userId.charAt(i) <= 9 // 문자를 뽑아서 숫자인지 체크
|| userId.charAt(i) >= 'a' && userId.charAt(i) <= 'z' // 문자를 뽑아서 소문자인지 체크
|| userId.charAt(i) >= 'A' && userId.charAt(i) <= 'Z' // 문자를 뽑아서 대문자인지 체크
) {
}
else {
alert("아이디가 틀림");
evt.preventDefault();
}
}
}
else {
alert("아이디가 틀림");
evt.preventDefault();
}
})
</script>
</body>
</html>
- value로 접근하는 경우가 빈번할 것 같아서 이번에는 input 요소의 value 값 자체를 변수에 담았다.
- 아이디가 저 패턴을 만족하는지 굉장히 복잡한 구문을 써야 한다.
- 정규 표현식을 사용하면 훨씬 간결하게 작성할 수 있다.
ㅁ 정규 표현식
- 어떤 문자열이 특정 패턴을 가지고 있는지 확인하기 위한 표현식.
- 사용자가 입력한 값(아이디, 비번, 이메일, 전화번호 등)이 특정 패턴을 만족하는지 자주 비교한다.
- 자바스크립트에서만 쓰이는 것이 아니다.
자바와 오라클에도 특정 패턴을 제시할 때 이 정규식을 쓸 수 있다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h2>디폴트 이벤트</h2>
<br><br>
<form action="/server/test.do" method="get">
아이디 : <input type="text" name="userId" id="userId"> <br>
비밀번호 : <input type="password" name="userPwd" id="userPwd"> <br>
비밀번호 확인 : <input type="password" id="checkPwd"> <br><br>
<input type="submit" id="submit">
</form>
<script>
document.getElementById("submit").addEventListener("click", evt => {
// 아이디가 영문 또는 숫자로만 5글자이상 12글자 이하로 이루어져있는지 체크
const userId = document.getElementById("userId").value;
let idRegExp = /^[a-zA-Z0-9]{5,12}$/; // 정규 표현식 (공백 있으면 안 됨)
if( !idRegExp.test(userId) ) { // 사용자가 입력한 값이 저 패턴을 만족하지 않는 경우
alert('아이디를 잘못 입력하셨습니다.');
evt.preventDefault(); // 기본 이벤트 수행 막기
}
})
</script>
</body>
</html>
- 양 옆에 슬래시( / )를 긋고 안 쪽에 내가 앞으로 비교할 패턴을 써둘 수 있다.
- 이때 대괄호 블록에 a-z, A-Z, 0-9 를 써서 허용 가능한 문자들의 목록을 나열했다.
영문이랑 숫자 목록을 나열한 것이다.
- 그리고 이 목록에 해당하는 값이 5글자 이상, 12글자 이하인지를 비교할 정규 표현식이라는 것을 작성했다.
- 이렇게 정규 표현식을 작성하면 사용자가 입력한 값이 이 패턴을 만족하는지를 비교해볼 수 있다.
- 정규 표현식.test(문자열)라는 메소드로 비교할 문자열을 제시할 수 있다.
- 사용자가 입력한 아이디 값이 저 패턴을 만족하면 true, 아니면 false를 반환한다.
- 아이디를 체크하고 비밀번호 체크도 이어서 진행하는데
아이디가 잘못 입력되었다면 비밀번호 체크를 할 필요가 없다.
그래서 return;으로 함수를 강제종료한다.