이벤트

2015. 1. 7. 17:56 나홀로스터디/JS For Web Dev

 

13장 이벤트

이 포스팅은 "프론트엔드 개발자를 위한 자바스크립트(2013 인사이트, 한선용 옮김)"에서 발췌 요약한 것입니다.
 
- 이벤트 흐름에 대한 이해
- 이벤트 핸들러 다루기
- 여러 가지 타입의 이벤트


자바스크립트와 HTML 의 상호작용은 문서나 브라우저 창에서 특정 순간에 일어난 일을 가리키는 '이벤트'에 의해 처리된다. 이벤트는 '리스너'(핸들러라고 부르기도 한다)로 추적하며 리스너는 이벤트가 일어날 때만 실행된다. 전통적인 소프트웨어 공학에서는 이 모델을 옵저버 패턴이라 부르고 이 패턴은 자바스크립트에서 정의하는 페이지 행동과 HTML 및 CSS에서 정의하는 페이지 외형 사이에 '느슨한'  연결을 형성한다.
주요 브라우저가 DOM 레벨2 이벤트를 구현하긴 했지만 명세도 이벤트 타입을 전부 다루지는 못했다.

 

 


13.1 이벤트 흐름
'이벤트 흐름'은 페이지에서 이벤트가 전달되는 순서를 설명하는데 흥미롭게도 인터넷 익스플로러와 넷스케이프 개발팀은 이벤트 흐름에서 서로 정반대 순서를 채택했다. 인터넷 익스플로러는 이벤트 버블링을 지원했고 넷스케이프 커뮤니케이터는 이벤트 캡처링을 지원했다.


13.1.1 이벤트 버블링
인터넷 익스플로러의 이벤트 흐름은 '이벤트 버블링'이라 부르는데 이벤트가 가장 명시적인 요소, 즉 이벤트 흐름상 문서 트리에서 가장 깊이 위치한 요소에서 시작해 거슬러 흐르는 모양이 마치 거품이 올라가는 것 같았기 때문이다.  페이지에서 <div> 요소를 클릭하면 click 이벤트가 <div>, <body>, <html>, document 순으로 발생한다.  최신 브라우저는 모두 이벤트 버블링을 지원하지만 인터넷 익스플로러 5.5 및 이전버전에서는 <html> 요소를 건너뛰고 <body> 요소에서 document로 바로 올라갔다. 인터넷 익스플로러 9와 파이어폭스, 크롬, 사파리에서는 이벤트가 window 객체까지 올라간다.


13.1.2 이벤트 캡처링
넷스케이프 커뮤니케이터 팀은 이벤트 캡처링이란 이벤트 흐름을 만들었는데 이것은 최상위 노드에서 처음으로 이벤트가 발생하여 가장 명시적인 노드에서 마지막으로 발생한다. 이벤트 캡처링은  원래 넷스케이프 커뮤니케이터에서만 사용했지만 현재는 인터넷 익스플로러9, 사파리, 크롬, 오페라, 파이어폭스에서 지원한다. 오래된  브라우저에서는 이벤트 캡처링을 지원하지 않으므로 이벤트 버블링을 주로 쓰고 이벤트 캡처링은 특별한 상황에서만 쓰길 권한다.


13.1.3 DOM 이벤트 흐름
DOM 레벨2 이벤트에서 정의한 이벤트 흐름에는 이벤트 캡처링 단계, 타깃 단계, 이벤트 버블링 단계 세 가지가 있다. 이벤트 캡처링이 처음 발생하므로 필요하다면 이 단계에서 이벤트를 가로챈다. 다음에는 실제 타깃이 이벤트를 받는다. 마지막으로 이벤트 버블링이 발생하며 이벤트에 반응할 수 있는 마지막 기회다. 

※ 인터넷 익스플로러 9와 오페라, 파이어폭스, 크롬, 사파리는 모두 DOM 이벤트 모델을 지원하며 인터넷 익스플로러8 및 이전 버전에서는 지원하지 않는다.

 

 

 

 

13.2 이벤트 핸들러
이벤트란 사용자 또는 브라우저가 취하는 특정 동작이다. 이벤트에는 click이나 load, mouseover 같은 이름이 있다. 이벤트에 응답하여 호출되는 함수를 '이벤트 핸들러'('이벤트 리스너')라고 부른다. 이벤트 핸들러 이름은 'on'으로 시작한다.


13.2.1 HTML 이벤트 핸들러
각 요소가 지원하는 이벤트는 이벤트 핸들러 이름을 HTML 속성에 사용하여 할당할 수 있다. 속성 값은 실행할 자바스크립트 코어여야 한다.

<input type="button" value="Click Me" onclick="alert('Clicked')" />

 

자바스크립트 코드도 속성 값이므로 앰퍼센트(&)나 큰 따옴표, <,> 같은 HTML문법 문자를 이스케이프 없이 쓸수는 없다.
이벤트 핸들러는 외부 파일에 정의할수도 있고 실행하는 코드는 전역 스코프에서 실행된다. 이런 방식으로 할당한 이벤트 핸들러에는 몇 가지 특성이 있다. 먼저 attribute 값을 감싸는 함수가 생성된다. 이 함수에는 event 라는 특별한 로컬 변수가 있고 이 변수는 이벤트 객체다. 함수 내부에서 this값은 이벤트 타깃 요소와 일치한다. 

<input type="button" value="Click Me" onclick="alert(event.type)" />
<input type="button" value="Click Me" onclick="alert(thistype)" />

동적으로 생성되는 함수에서 스코프 체인이 확장되는데 함수 내부에서는 document와 해당 요소의 멤버에 마치 로컬 변수처럼 접근할 수 있다. 이 스코프 체인 확장은 with를 통해 이루어진다.  

function(){
  with(document){
    with(this){
    // 속성 값
    }
  }
}

HTML에 이벤트 핸들러를 할당하는 방법에는 몇가지 단점이 있다.
첫번째는 타이밍의 문제다. 이벤트 핸들러 코드가 준비되기 전에 HTML 요소가 화면에 나타나고 사용자가 이를 조작할 가능성이 있다.
두번째는 식별자 해석에 사용하는 규칙이 자바스크립트 엔진에 따라 미묘하게 달라 이벤트 핸들러 함수의 스코프 체인 확장 결과가 브라우저마다 다르다는 것이다.
마지막 단점은 이벤트 핸들러를 HTML에서 할당하면 HTML과 자바스크립트가 너무 단단히 묶여 이벤트 핸들러를 바꿀때 HTML과 자바스크립트 코드를 모두 바꿔야 할 수도 있다.


13.2.2 DOM 레벨0 이벤트 핸들러
자바스크립트에서 이벤트 핸들러를 할당하는 전통적인 방법은 이벤트 핸들러 프로퍼티에 함수를 할당하는 방법이다. 자바스크립트에서 이벤트 핸들러를 할당하려면 우선 객체에 대한 참조를 얻어야 한다. window와 document를 포함해 모든 요소에는 이벤트 핸들러 프로퍼티가 있으며 onclick처럼 일반적으로 소문자이다. 이 프로퍼티를 다음과 같이 함수로 설정하면 이벤트 핸들러가 할당된다.  

var btn = document.getElementById("myBtn");
btn.onclick = function(){
  alert(this.id);
}

이런 방식으로 추가한 이벤트 핸들러는 이벤트 흐름에서 버블링 단계에 실행되도록 의도한 것이다. DOM 레벨 0 접근법으로 할당한 이벤트 핸들러를 제거할 때는 해당 이벤트 핸들러 프로퍼티를 null로 설정한다.

※ 이벤트 핸들러를 HTML에서 할당하면 onclick 프로퍼티의 값은 HTML 속성에서 설정한 코드가 담긴 함수이다. 따라서 요소의 이벤트 핸들러 프로퍼티를 null로 설정하면 HTML에서 할당한 이벤트 핸들러도 제거된다.

 


13.2.3 DOM 레벨 2 이벤트 핸들러
DOM 레벨 2 이벤트에서는 이벤트 핸들러 할당과 제거를 담당하는 메서드로 addEventListener()removeEventListener()를 정의했다. 이 메서드는 모든 DOM 노드에 존재하며 매개변수로 이벤트 이름, 이벤트 핸들러 함수, 이벤트 핸들러 캡쳐 단계에서 호출할지(true), 버블링 단계에서 호출할지(false) 나타내는 불리언 값 세 가지를 받는다.

btn.addEventListener("click", function(){
  alert(this.id);
}, false);

DOM 레벨 2 메서드의 주요 장점은 이벤트 핸들러를 '추가'하므로 이벤트 핸들러가 여러 개 있을 수 있다는 점이다.  addEventListener()로 추가한 이벤트 핸들러는 핸들러를 추가할 때와 같은 매개변수를 넘기면서 removeEventListener()를 호출해야 제거할 수 있다.

※ DOM 레벨 2 이벤트 핸들러를 지원하는 브라우저는 인터넷 익스플로러 9, 파이어폭스, 사파리, 크롬, 오페라이다.

 

 

13.2.4 인터넷 익스플로러 이벤트 핸들러
인터넷 익스플로러는 DOM 표준과 비슷한 attachEvent(), detachEvent() 두 메서드를 구현했다. 두 메서드는 핸들러 이름이벤트 핸들러 함수 두 가지를 매개변수로 받는다. 인터넷 익스플로러 8 및 이전 버전은 이벤트 버블링만 지원하므로 attachEvent()로 추가한 이벤트 핸들러는 버블링 단계에서만 실행된다.  

btn.attachEvent("onclick", function(){
  alert("Clicked");
})

DOM 레벨 0 접근법에서 이벤트 핸들러의 this는 요소이지만 attachEvent()로 등록한 이벤트 핸들러는 전역 컨텍스트에서 실행되므로 this는 window이다. attachEvent()도 두번 호출해서 두가지 이벤트 핸들러를 추가할 수 있는데 추가한 순서와 반대의 순서로 실행된다. attachEvent()로 추가한 이벤트 핸들러를 제거할 때는 detachEvent()에 같은 매개변수를 넘겨 호출한다.

※ 인터넷 익스플로러 스타일의 이벤트 핸들러는 인터넷 익스플로러와 오페라에서 지원한다.

 


13.2.5 크로스 브라우저 이벤트 핸들러
많은 개발자들이 브라우저 이벤트 처리 차이를 메우기 위해 자바스크립트 라이브러리를 쓰거나 커스텀 코드를 작성하는데 이벤트 처리 코드가 가능한 한 많은 브라우저에서 동작하게 하려면 버블링 단계에서만 동작하도록 해야 한다.
먼저 addHandler()라는 메서드를 생성한다. 이 메서드는 DOM 레벨 접근법과 DOM 레벨 2 접근법, 인터넷 익스플로러 접근법 중 가능한 방법을 써서 이벤트 핸들러를 추가하며 EventUtil라는 객체에 등록된다. addHandler() 메서드는 매개변수요소, 이벤트 이름, 이벤트 핸들러 함수 세 가지를 받는다.
이벤트 핸들러를 제거하는 removeEventListener()도 같은 매개변수를 받는데 다른 방법을 쓸수 없을 때는 DOM 레벨 0 접근법을 사용한다.

 

 


13.3 event 객체
DOM과 관련된 이벤트가 발생하면 관련 정보는 모두 event라는 객체에 저장된다. 이 객체에는 이벤트가 발생한 요소, 이벤트 타입 같은 기본 정보는 물론 이벤트와 관련된 다른 데이터도 저장된다.


13.3.1 DOM event 객체
DOM 표준을 준수하는 브라우저에서 이벤트 핸들러에 전달되는 매개변수는 event 객체 하나뿐이다. HTML 속성을 통해 이벤트 핸들러를 할당했을 때는 event라는 변수가 event 객체 구실을 한다.
이벤트 핸들러 내부에서 this 객체는 항상 currentTarget의 값과 일치하며 target에는 이벤트의 실제 타깃만 포함된다. 이벤트 핸들러가 타깃에 직접 할당되었다면 this와 currnetTarget, target은 모두 같다.

preventDefault() 메서드는 이벤트의 기본 동작을 취소한다. preventDefault()로 이벤트를 취소하려면 해당 이벤트의 cancelabe 프로퍼티가 true여야 한다.

stopPropagation() 메서드는 이벤트 흐름을 즉시 멈춰서 이벤트 캡처링이나 버블링을 모두 취소한다. 이벤트 핸들러를 직접 추가하고 stopPropagation()을 호출해서 document.body에 등록된 이벤트 핸들러가 동작하지 않게 막을 수 있다.

eventPhase 프로퍼티는 현재 이벤트가 어느 단계에 있는지를 나타낸다. eventPhase는 이벤트 핸들러가 캡쳐단계에서 호출되었으면 1, 타깃에서 호출되었으면2, 버블링 단계에서 호출되었으면 3이다. "타깃"이 버블링 단계에 포함되긴 하지만 eventPhase는 항상 2이고 eventPhase가 2이면 this와 target, currentTarget은 항상 일치한다.

※ event 객체는 이벤트 핸들러가 아직 실행 중일 때만 존재하며 이벤트 핸들러가 실행을 마치면 event 객체는 파과된다.

 


13.3.2 인터넷 익스플로러의 event 객체
DOM 표준의 event 객체와 달리 인터넷 익스플로러의 event 객체는 이벤트 핸들러를 어떻게 할당했느냐에 따라 다르게 접근한다. 이벤트 핸들러를 DOM 레벨 0 접근법으로 할당하면 event 객체는 오로지 window 객체의 프로퍼티로만 존재하고 이벤트 핸들러를 attachEvent()로 할당하면 event 객체는 함수의 유일한 매개변수로 전달된다.

btn.onclick = function(){
  var event = window.event;
  alert(event.type); // "click"
}

btn.attachEvent("onclick", function(event)){
  alert(event.type); //"click"
}

HTML 속성에서 이벤트 핸들러를 할당하면 DOM 모델과 마찬가지로 event 객체는 event 변수에 저장된다.

<input type="button" value="Click Me" onclick = "alert(event.type)" /> 

이벤트 핸들러의 스코프는 할당 방식에 따라 다르므로 this 역시 항상 이벤트 타깃과 같지는 않는다. 항상 event.srcElement를 사용하는 것이 좋다. (event.srcElement === this)

returnValue 프로퍼티는 DOM 표준의 preventDefault() 메서드와 마찬가지로 주어진 이벤트의 기본 동작을 취소한다. 기본 동작을 취소하려면 returnValue를 false로 설정한다. (event.returnValue = false)

cancelBubblle 프로퍼티는 DOM 표준의 stopPropagation() 메서드와 마찬가지로 이벤트 버블링을 취소한다. IE8 및 이전 버전에서는 캡처링 단계를 지원하지 않으므로 버블링 단계만 취소된다. 버튼의 onclick 이벤트 핸들러에서 cancelBubblle을 true로 설정하면 이벤트가 document.body 이벤트 핸들러까지 버블링되어 올라가지 않는다.


13.3.3 크로스 브라우저 이벤트 객체
DOM 표준과 인터넷 익스플로러 event 객체가 서로 다르긴 하지만 익스플로러의 event 객체에 들어있는 정보와 기능은 모두 DOM 객체에 들어있으며 단지 형태가 다를 뿐이다.

 

 


13.4 이벤트 타입
DOM 레벨 3 이벤트는 이벤트를 다음과 같이 분류한다.

- 사용자 인터페이스(UI) 이벤트는 일반적인 브라우저 이벤트이며 BOM과의 상호 작용이 포함될 수 있다.
- 포커스 이벤트는 요소가 포커스를 얻거나 잃었을 때 발생한다.
- 마우스 이벤트는 마우스로 어떤 동작을 취할 때 발생한다.
- 휠 이벤트는 마우스 휠이나 이와 비슷한 장치를 사용할 때 발생한다.
- 텍스트 이벤트는 문서에 텍스트를 입력할 때 발생한다.
- 키보드 이벤트는 키보드로 어떤 동작을 취할 때 발생한다.
- 구성 이벤트는 입력 방법 에디터를 통해 문자를 입력할 때 발생한다.
- 변경 이벤트는 DOM 구조가 바뀔 때 발생한다.

이 외에 전용이벤트는 일반적으로 명세의 요건보다는 개발자의 필요에 따라 만들어지므로 브라우저에 따라 다르게 구현되며 인터넷 익스클로러 9를 포함해 주요 브라우저는 모두 DOM 레벨 2 이벤트를 지원한다.


13.4.1 UI 이벤트
UI 이벤트는 사용자와 직접 관련이 있지는 않다.

- DOMActivate - 사용자가 마우스나 키보드로 요소를 활성화했을 때 발생한다. click이나 keydown보다 범용적이나 DOM 레벨 3 이벤트에서 폐기되었다.
- load - 페이지를 완전히 불러왔을 때 window 에서, 모든 프레임을 완전히 불러왔을  때 프레임셋에서, 이미지나 객체를 완전히 불러 왔을때 <img> 요소나 <object>요소에서 발생한다.
- unload - 페이지를 완전히 종료했을 때 window에서, 모든 프레임을 완전히 종료했을 때 프레임셋에서, 객체를 완전히 종료햇을때 <object> 요소에서 발생한다.
- abort - <object> 요소의 콘텐츠를 완전히 내려받기 전에 사용자가 취소했을 때 해당 요소에서 발생한다.
- error -  자바스크립트 에러가 발생했을 때 window에서, 이미지를 불러올 수 없을때 해당 <img> 요소에서, <object>요소에서. <object> 요소 콘텐츠를 불러올 수 없을 때 해당 요소에서 프레임을 불러올 수 없을 때 해당 프레임셋에서 발생한다.
- select - 사용자가 텍스트 박스<input>이나 <textarea>에서 글자를 선택할 때 발생한다.
- resize - window나 프레임의 크기를 바꿀 때 발생한다.
- scroll - 사용자가 스크롤바 있는 요소를 스클롤할 때 발생한다. <body> 요소에는 페이지 전체에 대한 스크롤바가 있다.

DOMActivate를 제외하면 이들 이벤트는 DOM 레벨 2 이벤트의 HTML 이벤트 그룹에 속한다.(DOMActivate는 아직 DOM 레벨 2의 UI 이벤트에 속한다.)

 

load 이벤트
window 객체의 load 이벤트는 이미지나 자바스크립트 파일, CSS 파일 같은 외부 자원을 포함해 전체 페이지를 완전히 불러왔을 때 발생한다. onload 이벤트 핸들러는 두 가지 방법으로 정의할 수 있는데 첫 번째 방법은 자바스크립트를 이용하는 방법이고 다른 방법은 <body> 요소에 onload 속성을 추가하는 것이다. 가능한 한 자바스크립트 접근법을 사용하길 권한다.

※ DOM 레벨 2 이벤트에서는 load 이벤트를 window 가 아니라 document에서 발생하도록 정했다. 하지만 하위 호환성 때문에 모든 브라우저가 load 이벤트를 window에도 구현한다. 문서에 포함된 이미지에는 모두 HTML에서 onload 이벤트 핸들러를 직접 할당할 수 있고 자바스크립트에서 지정할 수도 있다.

※ 인터넷 익스플로러 8 및 이전 버전에서는 load 이벤트가 발생할 때 DOM에 포함되지 않은 이미지에 대해서는  event 객체를 생성하지 않았다. 이는 문서에 추가되지 않은 <img>요소 및 Image 객체에 모두 적용된다. 이 문제는 인터넷 익스플로러 9에서 수정되었다.


unload 이벤트
unload 이벤트는 load 이벤트와 반대로 문서를 완전히 닫을 때 발생한다. unload 이벤트는 일반적으로 다른 페이지로 이동할 때 발생하며 주로 각종 참조를 제거하여 메모리 누수를 방지하는 목적으로 사용한다. load 이벤트와 같이 두 가지 방법으로 할당이 가능한데 첫 번째 방법은 자바스크립트를 이용하는 방법이고 두 번째 방법은 <body> 요소에 이벤트 핸들러 속성을 추가하는 방법이다. unload 이벤트는 모든것이 해제될 때 발생하므로 페이지에 존재하던 객체를 전부 사용할 수 없다.

※ DOM 레벨 2 이벤트에서는 unload 이벤트가 window가 아닌 <body>에서 발생한다고 정의한다. 하지만 하위 호환성 때문에 모든 브라우저는 unload 이벤트를 window에 구현한다.


resize 이벤트
브라우저 창의 사이즈를 바꾸면 resize 이벤트가 발생한다. 이 이벤트는 window에서 발생하므로 이벤트 핸들러를 할당할 때 자바스크립트를 쓸 수도 있고 <body> 요소에 onresize 속성을 추가해도 된다. resize 이벤트는 인터넷 익스플로러와 사파리, 크롬, 오페라에서는 1픽셀이라도 바뀌는 즉시 발행하며 사용자가 브라우저 창 크기를 조절하는 동안 계속 발생하지만 파이어폭스에서는 창 크기 조절을 멈추는 시점에서 발생한다.


scroll 이벤트
쿽스 모드에서는 <body> 요소의 scrollLeft와 scrollTop을 통해 얼마나 스크롤했는지 알 수 있으며 표준 모드에서는 사파리를 제외한 모든 브라우저에서 <html>요소의 scrollLeft와 scrollTop을 통해 얼마나 스크롤했는지 알 수 있다. 문서를 스크롤하는 동안 반복 실행된다.

 


13.4.2 Focus 이벤트
포커스 이벤트는 요소가 포커스를 받거나 잃을때 발생하며 이들 이벤트와 document.hasFocus(), document.activeElement 프로퍼티를 조합하면 사용자가 페이지를 어떻게 움직이는지 알수 있다.

- blur - 요소가 포커스를 잃을 때 발생한다. 이 이벤트는 버블링되지 않으며 모든 브라우저에서 지원된다.
- DOMFocusIn - 요소가 포커스를 받을 때 발생한다. 이 이벤트는 HTML 이벤트 focus의 버블링 버전으로 오페라만 지원한다. DOM 레벨 3 이벤트는 대신 focusin를 권장한다.
- DOMFocusOut -  요소가 포커스를 잃을 때 발생한다. 이 이벤트는 HTML 이벤트 blur의 범용 버전으로 오페라만 지원한다. DOM 레벨 3 이벤트는 focusout을 권장한다.
- focus - 요소가 포커스를 받을 때 발생한다.  이 이벤트는 버블링되지 않으며 모든 브라우저에서 지원된다.
- focusin - 요소가 포커스를 받을 때 발생한다. HTML이벤트 focus의 버블링 버전이며 IE5.5 이상, 사파리 5.1 이상, 오페라 11.5이상, 크롬에서 지원한다.
- focusout - 요소가 포커스를 잃을 때 발생한다. HTML이벤트 blur의 범용 버전이며 IE5.5이상, 사파리 5.1 이상, 오페라 11.5이상, 크롬에서 지원한다.

 

이 그룹의 우선적 이벤트는 focus와 blur이며 자바스크립트 초기부터 지원되었다. 이 두 이벤트의 가장 큰 문제는 버블링 되지 않는것이고 이 때문에 인터넷 익스플로러는 focusin과 focusout을 도입했다. 포커스가 요소에서 다른 요소로 이동할 때는 다음 순서로 이벤트가 발생한다.

1. 포커스를 잃는 요소에서 focusout이 발생한다.
2. 포커스를 받는 요소에서 focusin이 발생한다.
3. 포커스를 잃는 요소에서 blur가 발생한다.
4. 포커스를 잃는 요소에서 DOMFocusOut이 발생한다.
5. 포커스를 받는 요소에서 focus가 발생한다.
6. 포커스를 받는 요소에서 DOMFocusIn이 발생한다.

 

※ focus와 blur는 버블링되지는 않지만 캡처링 단계에서 가로챌 수 있다.  

 


13.4.3 마우스 이벤트와 휠 이벤트
DOM 레벨 3 이벤트에 정의된 마우스 이벤트는 아홉가지이다.

- click - 사용자가 주요 마우스 버튼(일반적으로 왼쪽 버튼)을 클릭하거나 엔터키를 누를 때 발생한다. onclick 이벤트 핸들러가 키보드와 마우스에 모두 반응한다는 점은 접근성 구현에 중요하다.
- dbclick - 사용자가 주요 마우스 버튼을 더블 클릭할 때 발생한다.
- mousedown - 사용자가 마우스 버튼을 누를 때 발생한다.
- mouseenter - 마우스 커서가 요소 밖에서 요소 경계 안으로 처음 이동할 때 발생한다. 이 이벤트는 버블링되지 않으며 커서가 자손 요소 위에 올라갈 때 발생하지 않는다.
- mouseleave - 마우스 커서가 요소 위에 있다가 요소 경계 밖으로 이동할 때 발생한다. 이 이벤트는 버블링되지 않으며 커서가 자손 요소 위에 올라갈 때 발생하지도 않는다.
- mousemove - 마우스 커서가 요소 주변을 이동하는 동안 계속 발생한다.
- mouseout - 마우스 커서가 요소 위에 있다가 다른 요소 위로 이동할 때 발생한다.'다른 요소'는 요소의 경계 밖에 있는 요소일 수도 자식 요소일수도 있다.
- mouseover - 마우스 커서가 요소 바깥에 있다가 경계 아느로 이동할 때 발생한다.
- mouseup - 사용자가 마우스 버튼을 누르고 있다가 놓을 때 발생한다.

 

mouseenter와 mouseleave를 제외하면 모든 마우스 이벤트가 버블링되어 올라가며 모든 마우스 이벤트는 취소할 수 있고 브라우저 기본 동작에 영향을 미친다. 이들 네 가지 마우스 이벤트는 항상 다음 순서대로 발생한다.

mousedown > mouseup > click > mousedown > mouseup > click > dbclick

click과 dbclick은 다른 이벤트에 영향을 받지만 mousedown, mouseup은 다른 이벤트에 영향을 받지 않는다.
마우스 이벤트에는 '휠 이벤트'라는 부속 그룹이 있는데 휠 이벤트 그룹은 mousewheel 이벤트 하나뿐이며 마우스 휠이나 맥 트랙패드처럼 이와 비슷한 장치에서 발생한다.


클라이언트 좌표
마우스 이벤트는 모든 브라우저 뷰포트의 어느 위치에서 발생한다. 이 정보는 event 객체의 clientX, clientY 프로퍼티에 저장되는데 이들 프로퍼티는 이벤트 당시 마우스 커서의 위치를 나타내며 모든 브라우저에서 지원한다.


페이지 좌표
클라이언트 좌표는 뷰포트 기준으로 어디에서 일어났는지 나타내는데 반해 '페이지 좌표'는 페이지 기준이며 event 객체에 pageX, pageY 프로퍼티로 저장된다. 퀵스 모드에서는 document.body, 표준모드에서는 document.documentElement의 scrollLeft/scrollTop 프로퍼티를 이용해서 계산한다.


화면 좌표
전체 화면을 기준으로 한 마우스 위치는 screenX/screenY 프로퍼티에 저장된다.


키보드 수정
'수정 키'는 Shift, Ctrl, Alt, Meta 키이며 이들은 종종 마우스 이벤트의 동작에 영향을 끼친다. DOM 표준에는 이들 키의 상태를 나타내는 shiftKey, ctrlKey, altKey, metaKey 네 가지 프로퍼티가 있다. 각 프로퍼티는 키를 누른 상태일 때 true, 그렇지 않다면 false인 불리언 값이다.

※ 인터넷 익스플로러 8 및 이전 버전은 metaKey 프로퍼티를 지원하지 않는다.


관련요소
mouseover와 mouseout 이벤트는 마우스 커서가 한 요소의 경계 안에서 다른 요소의 경계 안으로 이동할 때 발생한다. mouseover 이벤트에서 이벤트의 우선적 타깃은 커서가 이동하는 목적지 요소이며 '관련요소'는 커서가 출발하는 출발점 요소이다. mouseout의 우선적 타깃은 출발점 요소이며 관련 요소는 목적지 요소이다.
DOM 표준에서는 관련 요소에 관한 정보를 event 객체의 relatedTarget 프로퍼티로 제공하는데 mouseover와 mouseout 이벤트에서만 값을 가지며 다른 이벤트에서는 null이다.


버튼
click 이벤트는 요소에서 마우스 기본 버튼을 클릭하거나 엔터 키를 눌렀을 때만 발생하므로 어떤 버튼인지에 대한 정보는 필요하지 않다. mousedown과 mouseup 이벤트는 event 객체에 어떤 버튼을 누르거나 뗐는지 나타내는 button 프로퍼티가 있다. DOM 표준의 button 프로퍼티에는 세 가지 값이 있는데 0은 마우스 기본 버튼(보통 왼쪽), 1은 마우스 가운데 버튼(보통 스크롤 휠 버튼), 2는 두 번째 마우스 버튼(보통 오른쪽)을 나타낸다.

※ onmouseup 이벤트 핸들러에서 사용할 경우 button 의 값은 지금 막 손을 뗀 버튼의 값이다.


추가적인 이벤트 정보
DOM 레벨 2 이벤트 명세에는 event 객체에서 추가 정보를 제공하는 detail 프로퍼티가 존재한다. detail 초기값은 1이며 클릭할 때마다 늘어난다. mousedown과 mouseup 사이에 마우스를 움직였다면 detail은 0으로 초기화 된다.


mousewheel 이벤트
이 이벤트는 사용자가 마우스 휠을 세로 방향으로 움직일 때 발생한다. mousewheel 이벤트의 event 객체에는 마우스 이벤트에 관한 표준 정보 외에도 wheelDelta 라는 추가 프로퍼티가 있는데 마우스 휠을 앞으로 굴리면 wheelDelta는 120의 배수인 양수이고 뒤로 굴리면 wheelDelta는 120의 배수인 음수이다.


터치 장치 지원

- dbclick 이벤트는 전혀 지원되지 않는다.
- 클릭 가능한 요소를 탭하면 mousemove 이벤트가 발생한다. 탭한 결과로 컨텐츠가 바뀌면 이벤트는 더 이상 발생하지 않는다.
- mousemove 이벤트도 mouseover와 mouseout 이벤트를 발생시킨다.
- mousewheel과 scroll 이벤트는 화면에 두 손가락을 올릴 때 발생하며 손가락을 움직이면 페이지가 스크롤된다.


접근성 문제
웹 애플리케이션이나 웹사이트를 접근성 있게, 특히 스크린 리더 사용자가 쓰기 쉽게 만들려면 마우스 이벤트를 조심해서 사용해야 한다.

- 코드 실행에는 click을 이용한다. 일부는 애플리케이션 코드를 onmousedown에서 실행하면 더 빠르다고 하는데 시각에 문제가 없는 사용자에게는 이 말도 맞지만 스크린 리더 사용자는 mousedown 이벤트를 발생시킬수 없으므로 이 코드는 사용 불가능하다.

- 사용자에게 새 옵션을 제시할 때 onmouseover를 사용해서는 안된다. 새 옵션을 이런식으로 표현해야 한다면 정보를 키보드 단축키에도 할당해야 한다.

- 중요한 동작을 dbclick으로 실행해서는 안된다. 키보드로는 이 이벤트를 발생시킬 수 없다.

 

※ 웹 페이지의 접근성에 대해 더 읽어보려면 http://webaim.orghttp://accessibility.yahoo.com을 참고하길 바란다.

 

 

13.4.4 키보드와 텍스트 이벤트
'키보드 이벤트'는 사용자가 키보드를 조작할 때 발생한다.

- keydown - 사용자가 키를 처음 누를 때 발생하며 누르고 있는 동안 계속 발생한다.
- keypress - 사용자 키를 누른 결과로 문자가 입력되었을 때 처음 발생하며 누르고 있는 동안 계속 발생한다. 이 이벤트는 Esc 키에서도 발생한다. DOM 레벨 3 이벤트에서는 keypress 이벤트를 폐기했으며 대신  textInput 이벤트를 권장한다.
- keyup -  사용자가 키에서 손을 뗄 때 발생한다.

 

모든 요소가 이들 이벤트를 지원하지만 텍스트 박스에 가장 많이 쓰인다. 텍스트 이벤트는 textInput 하나뿐이다. 이 이벤트는 keypress를 확장하여 텍스트 입력이 사용자에게 표시되기 전에 가로챌 의도로 만들어졌고 텍스트가 텍스트 박스에 삽입되기 직전에 발생한다.
사용자가 글자 키를 누르면 keydown, keypress, keyup 순서로 이벤트가 발생하는데 keydown과 keypress는 모두 텍스트 박스에 글자가 나타나기 전에 발생하지만 keyup 이벤트는 텍스트 박스에 글자가 나타난 후에 발생한다. 키를 누르고 있으면 손을 뗄때까지 keyup 이벤트가 발생한다.

※ 키보드 이벤트는 마우스 이벤트와 같은 수정 키를 지원한다. shiftKey, ctrlKey, altKey, metaKey 프로퍼티는 모두 키보드 이벤트에도 존재한다. 인터넷 익스플로러 0 및 이전 버전은 metaKey를 지원하지 않는다.


키코드
keydown과 keyup 이벤트에서 event 객체의 keyCode 프로퍼티에는 각 키에 대응하는 코드가 들어있다. 알파벳과 숫자 키의 keyCode는 해당 키의 소문자 또는 숫자의 ASCII 값이다.


문자코드
keypress 이벤트가 일어났다는 것은 해당 키가 화면에 표시된다는 뜻이다. 모든 브라우저는 글자를 삽입하거나 제거하는 키에서 keypress 이벤트를 발생시키며 기타 키에서는 브라우저에 따라 다르다.
인터넷 익스플로러 9 이상, 파이어폭스, 크롬, 사파리는 keypress 이벤트에서만 event 객체에서 charCode라는 프로퍼티를 지원한다. 이 프로퍼티에는 관련된 키 문자의 ASCII 코드가 들어있는데 일반적으로 0 또는 해당 키의 키코드이다.


DOM 레벨 3에서 바뀐점
DOM 레벨 3 이벤트에서는 키보드 이벤트에서 몇 가지를 바꿨다. charCode 프로퍼티를 폐기하고 keychar 두 가지 프로퍼티를 추가했다. key 프로퍼티는 keyCode를 교체할 의도로 도입했으며 문자열을 포함한다. 문자 키를 누르면 key 값은 해당 문자(예를 들어 "k"나 "M") 이며 문자가 아닌 키를 누르면 키 이름("shift"나 "Down")이다. char 프로퍼티는 문자 키에서는 key와 같으며 문자가 아닌 키에서는 null이다.
DOM 레벨 3 이벤트에서는 location라는 프로퍼티도 추가했는데 이는 키를 어디에서 눌렀는지 나타내는 숫자형 값이다.
event 객체에 추가된 마지막은 getModifierState() 메서드인데 매개변수로 Shift, Control, Alt, Meta 중 하나를 받고 이는 체크할 수정 키를 나타내는 문자열이다. 주어진 키가 눌려 있는 상태라면 true를 반환하고 그렇지 않다면 false를 반환한다.


textInput 이벤트
DOM  레벨 3 이벤트 명세에서는 편집 가능한 영역에 문자가 입력될 때 발생하는 textInput 이라는 이벤트를 추가했다. 이 이벤트는 keypress를 교체할 목적으로 디자인되었지만 차이점 중 하나는 keypress 이벤트가 포커스를 받을 수 있는 요소 전체에서 지원되는데 반해 textInput 이벤트는 편집 가능한 영역에서만 지원된다. textInput 이벤트는 문자에만 관심이 있으므로 event 객체의 data 프로퍼티에는 문자 코드가 아니라 삽입 문자 자체가 저장된다.

 

 

13.4.5 조합 이벤트
'조합 이벤트'는 DOM 레벨 3 이벤트에서 처음 도입되었으며 일반적으로 IME에 사용되는 복잡한 입력을 처리하도록 만들어졌다. IME는 일본어 등 사용자가 물리적인 키보드에 존재하지 않는 문자를 입력하도록 만들어졌다.

- compositionstart - IME의 텍스트 조합 시스템이 열리는 순간 발생하며 곧 문자가 입력될 것을 나타냄
- compositionupdate - 입력 필드에 새 문자가 삽입될 때 발생함
- compositionend - 텍스트 조합 시스템이 닫힐 때 발생하며 일반적인 키보드 입력으로 돌아갈 것을 나타냄

 


13.4.6 matation event
DOM 레벨 2에서는 DOM의 일부가 바뀌었을 때 알리는 변경 이벤트를 제공한다. 변경 이벤트는 특정 언어에 종속되지 않고 XML이나 HTML DOM에서 모두 동작하도록 디자인되었다.

- DOMSubtreeModified - DOM 구조가 어떻게든 바뀌었을 때 발생한다. 이 이벤트는 다른 이벤트가 모두 발생한 후에 발생한다.
- DOMNodeInserted -  노드가 다른 노드의 자식으로 삽입될 때 발생한다.
- DOMNodeRemoved - 노드가 부모 노드로부터 제거될 때 발생한다.

인터넷 익스플로러 8 및 이전 버전은 변경 이벤트를 지원하지 않는다.


노드 제거
removeChild()replaceChild()로 DOM에서 노드를 제거하면 가장 먼저 DOMNodeRemoved 이벤트가 발생한다. 이 이벤트 타깃은 제거된 노드이며 event.relatedNode 프로퍼티에는 부모 노드에 대한 참조가 들어있다.


노드 삽입
appendChild()replaceChild(), insertBefore()로 노드를 DOM에 삽입하면 DOMNodeInserted 이벤트가 가장 먼저 발생한다. 이 이벤트의 타깃은 삽입된 노드이며 event.relatedNode 프로퍼티에는 부모 노드에 대한 참조가 들어 있다. 이 이벤트가 발생하는 시점에서 해당 노드는 이미 부모 노드에 추가된 상태이고 이 이벤트는 버블링되므로 조상 요소 어디에든 위임해 처리할 수 있다.

 


13.4.7 HTML5 이벤트
contextmenu 이벤트
개발자들이 어떤 동작(윈도우에서는 오른쪽 클릭, 맥에서는 Ctrl+클릭)에서 컨텍스트 메뉴를 표시할 것인지 기본 컨텍스트 메뉴와 충돌하지 않게 하려면 어떻게 할 것인지를 해결하기 위해 contextmenu 이벤트가 도입되었다. 이 이벤트는 버블링되므로 document에 이벤트 핸들러 하나만 할당해서 페이지에서 발생하는 contextmenu 이벤트를 모두 처리할 수 있다. contextmenu 이벤트는 마우스 이벤트로 간주되므로 커서 위치와 관련된 프로퍼티를 모두 포함한다. 일반적으로 oncontextmenu 이벤트 핸들러에서 커스텀 컨텍스트 메뉴를 표시하고 onclick 이벤트 핸들러에서 다시 숨긴다. 이 이벤트는 인터넷익스플로러, 파이어폭스, 사파리, 크롬, 오페라11 이상에서 지원된다.


beforeunload 이벤트
이 이벤트는 window에서 발생하며 개발자에게 페이지에서 떠나지 못하게 막을 방법을 제공할 의도로 만들어졌다. 브라우저가 페이지를 언로드하기 직전에 발생하며 페이지를 언로드 하겠다고 확인하지 않으면 계속 머무르게 한다. 이 이벤트는 취소할 수 없는데 취소를 허용한다면 사용자가 페이지를 떠나지 못하게 강제하는 것이나 마찬가지이기 때문이다. 오페라는 버전 11까지 이 이벤트를 지원하지 않는다.


DOMContentLoaded 이벤트
window의 load 이벤트는 페이지를 완전히 불러와야 발생하므로 외부 자원이 많이 포함된 페이지에서는 시간이 걸릴 수 있는데 DOMContentLoaded 이벤트는 DOM 트리가 완전히 구성되는 즉시 발생하므로 load 이벤트보다 먼저 이벤트 핸들러를 등록할 수 있다. DOMContentLoaded 이벤트 핸들러는 document와 window에 모두 등록할 수 있다.(이벤트타깃은 document지만 window로 버블링된다)


redadystatechange 이벤트
이 이벤트의 의도는 문서나 요소를 불러오는 상황에 대한 정보를 제공하고자 하는 것이고 redadystatechange 이벤트를 지원하는 각 객체에는 readyState 프로퍼티가 있으며 그 값은 다음 문자열 중 하나이다.

- uninitialized - 객체가 존재하지만 초기화되지 않음
- loading - 객체에서 데이터를 불러오는 중
- loaded - 객체에서 데이터를 완전히 불러옴
- interactive -  객체를 완전히 불러오지는 못했지만 상호 작용은 가능함
- complete -  객체를 완전히 불러옴

redadystatechange 이벤트는 종종 네 번 이하로 발생하며 readyState 값이 항상 같은 순서로 진행하지는 않는다.

 

 

13.4.8 장치 이벤트
스마트폰과 태블릿 장치가 보급되면서 사용자들은 새로운 방식으로 브라우저를 이용하기 시작했고 이에따라 새 이벤트 그룹이 만들어졌다. 장치 이벤트는 장치를 어떻게 사용하고 있는지에 관한 정보를 제공한다.


orientationchange 이벤트
애플은 모바일 사파리에 orientationchange 이벤트를 도입했는데 이는 사용자가 장치를 가로 모드나 세로 모드로 바꿀 때 발생한다. 모바일 사파리에는 window.orientation 프로퍼티가 있는데 0은 세로모드, 90은 왼쪽 가로 모드(홈 버튼이 오른쪽 위치), -90은 오른쪽 가로 모드(홈 버튼이 왼쪽에 있는)를 각각 나타내고 사용자가 장치 방향을 바꿀 때마다 orientationchange 이벤트가 발생한다.  IOS 장치는 모두 orientationchange 이벤트와 window.orientation 프로퍼티를 지원한다.

※ orientationchange는 window 이벤트로 간주하므로 orientationchange <body> 요소에 속성으로 추가해도 이벤트 핸들러가 할당된다


MozOrientation 이벤트
파이어폭스 3.6은 장치 방향을 감지하는 MozOrientation라는 새 이벤트를 도입했는데 이 이벤트 장치에 부착된 가속도계가 장치 방향이 바뀌었음을 감지할 때 발생한다. event 객체에는 가속도계의 세 가지 데이터를 나타내는 x,y,z 프로퍼티가 있고 각 값은 -1과 1 사이의 숫자이며 서로 다른 축을 나타낸다. MozOrientation 이벤트는 가속도계가 부착된 장치 맥북, 레노보 씽크 패드, 윈도 모바일 및 안드로이드 장치에서 지원된다.


deviceorientation 이벤트
이 이벤트는 장치 방향 이벤트 명세에 정의되어 있으며 가속도계가 부착된 장치에서 관련 있는 동작을 감지했을때 window에서 발생한다. deviceorientation 이벤트의 목적은 장치의 방향을 감지하는 것이지 움직임을 감지하는 것은 아니다. event 객체에는 다섯 가지 프로퍼티(alpha, beta, gamma, absolute, compassCalibrated)가 존재한다.


devicemotion 이벤트
이 이벤트는 장치가 실제로 움직이고 있을 때 발생하고 이벤트가 발생하면 event 객체에는 acceleration, accelerationIncludingGravity, interval, rotationRate 프로퍼티가 추가된다.

 

13.4.9 터치와 제스처 이벤트
터치 이벤트는 손가락을 화면에 댈 때, 화면을 따라 드래그할 때, 화면에서 손을 뗄때 발생한다.

- touchstart - 손가락으로 화면을 터치할 때 발생하며 이미 다른 소노가락을 화면에 대고 있어도 다른 손가락을 대면 발생한다.
- touchmove -  손가락을 화면에서 움직일 때 계속 발생하며 이벤트가 일어나는 동안 preventDefault()를 호출하면 스크롤을 막을수 있다.
- touchend - 손가락을 화면에서 뗄 때 발생한다.
- touchcancel - 시스템에서 터치를 더이상 추적하지 않을 때 발생한다.

 

 

각 이벤트는 버블링 되어 올라가며 취소 가능하다. 터치 이벤트는 DOM 명세의 일부는 아니나 명세와 호환되도록 구현되어 터치이벤트의 event 객체에는 마우스 이벤트와 공통인 bubbles, cancelable, view, clientX, clientY, screenX, screenY, derail, altKey, ctrlKey, meraKey 프로퍼티가 들어있으며 이 외에도 터치를 추적하는 touches, targetTouches, changedTouches 프로퍼티가 들어있다. 요소에 탭 했을때 이벤트가 일어나는 순서는 touchstart, mouseover, mousemove, mousedown, mouseup, click, touchend 이다.


제스처 이벤트
'제스처'란 두 손가락으로 화면을 터치한 채 표시된 부분의 크기를 바꾸거나 회전하는 동작을 말한다.

- gesturestart - 한 손가락을 화면에 얹은 채 다른 손가락으로 화면을 터치할 때 발생한다.
- gesturechange - 화면에서 두 손가락 중 하나의 위치가 바뀔 때 발생한다.
- gestureend - 두 손가락 중 하나를 화면에서 뗄 때 발생한다.

 

이들 이벤트는 두 손가락으로 이벤트 대상을 터치할 때만 발생한다. event 객체에 추가 되는 프로퍼티는 rotation과 scale이다.

※ 터치 이벤트도 rotation과 scale 프로퍼티를 반환하긴 하지만 이 값은 두 손가락이 화면에 닿아 있을 때에만 사용한다. 일반적으로 터치 이벤트의 복잡한 상호작용을 모두 추적하기보다는 제스처 이벤트를 쓰는 편이다.

 

 

 

13.5 메모리와 성능
최신 웹 애플리케이션에서 상호작용을 담당하는 것이 핸들러이긴 하지만 자바스크립트에서는 페이지에 존재하는 이벤트 핸들러의 개수가 페이지에 직접적으로 영향을 미친다. 그 첫번째 이유는 각 함수가 메모리를 점유하는 객체이기 때문이고 두 번째는 이벤트 핸들러를 많이 할당하려면 DOM 접근도 많아지며 전체 페이지의 응답성을 떨어 뜨리기 때문이다.


13.5.1 이벤트 위임
"이벤트 핸들러 개수" 문제의 해결책은 '이벤트 위임'이라는 방법이다. 이벤트 위임은 이벤트 버블링의 장점을 활용하여 이벤트 핸들러를 하나만 할당해서 해당 타입의 이벤트를 모두 처리하는 테크닉이다.  이벤트 위임을 적용하기에 알맞는 이벤트는 click, mousedown, mouseup, keydown, keyup, keypress가 있다.


13.5.2 이벤트 핸들러 제거
이벤트 핸들러를 요소에 할당하면 브라우저 코드와 자바스크립트 코드가 연결되는데 이런 연결이 많을수록 페이지가 느려진다. 이 문제를 해결하는 방법은 이벤트 위임을 통해 연결 개수를 제한하는 것이고 다른 방법은 더이상 사용하지 않은 잔류 핸들러를 제거하는 것이다. 이 문제는 요소에서 이벤트 핸들러를 제거하지 않은 채 요소만 문서에서 제거하는 경우 DOM 표준 메서드인 removeChild()나 replaceChild()에서도 간혹 일어나긴 하지만 innerHTML로 페이지 일부를 교체할 때 자주 일어난다.
페이지를 떠나기 전 제거하지 않은 이벤트 핸들러는 메모리에 계속 남게 되므로 페이지를 떠나기 전에 onunload 이벤트 핸들러를 써서 이벤트 핸들러를 모두 제거하는 편이 좋다.

※ onunload 이벤트 핸들러를 사용하면 페이지는 결코 bfcache에 저장되지 않음을 염두해야 한다. bfcache를 이용하는 편이 낫다고 판단한다면 onunload로 이벤트 핸들러를 제거하는 작업은 인터넷 익스플로러에서만 하는 방법도 있다.

 

 

 

13.6 이벤트 시뮬레이션

13.6.1 DOM 이벤트 시뮬레이션
document에서 createEvent() 메서드를 호출하여 언제든 event 객체를 생성할 수 있다. 이 메서드는 생성할 이벤트 타입을 나타내는 문자열을 매개변수로 받았다. event 객체를 생성하면 이벤트에 관한 정보를 이용해 초기화해야 하는데 각 타입의 event 객체에는 적절한 데이터를 넘겨 초기화하는 메서드가 존재한다. 메서드 이름은 createEvent()에 넘긴 매개변수마다 다르다
이벤트 시뮬레이션의 마지막 단계는 이벤트를 발생시키는 것이고 이벤트 발생은 dispatchEvent() 메서드를 통하는데 이벤트를 지원하는 DOM 노드에는 모두 이 메서드가 존재한다. dispatchEvent() 메서드는 발생시킬 이벤트를 표현하는 event 객체를 매개변수로 받는다.


13.6.2 인터넷 익스플로의 이벤트 시뮬레이션
익스플로러8 및 이전 버전의 이벤트 시뮬레이션도 event 객체를 생성하고 적절한 정보를 할당한 후 해당 객체를 이용해 이벤트를 발생시킨다. document의 createEventObject() 메서드로 event 객체를 생성하는데 매개변수를 받지 않으며 범용 event 객체를 반환한다. 반환받은 객체에 필요한 프로퍼티를 반드시 직접 할당해야 하는데 모두 할당했으면 마지막으로 타깃에서 fireEvent()를 호출한다. 이 메서드는 매개변수로 이벤트 핸들러 이름과 event 객체 두 가지를 받는데 secElement와 type 프로퍼티는 자동으로 할당되지만 다른 프로퍼티는 반드시 직접 할당해야 한다.

 

 

 

'나홀로스터디 > JS For Web Dev' 카테고리의 다른 글

20장 JSON  (0) 2015.03.24
14장 폼스크립트  (0) 2015.03.18
DOM 확장  (0) 2014.12.18
DOM  (0) 2014.12.08
클라이언트 감지  (0) 2014.11.20
Copyright © HuckleberryM All Rights Reserved | JB All In One Designed by CMSFactory.NET