UI Laboratory

UI 개발을 위한 레퍼런스

1.12 DOM 요소 교체하기
replaceWith()메서드를 사용할 경우 교체할 DOM 집합을 선택할 수 있다. DOM에 추가되는 새로운 DOM 구조는 replaceWith() 메서드에 지정된 문자열 매개변수이다.
ex) 페이지에 있는 <button> 요소들중 클릭된 <button> 요소를 <div> 요소로 바꾸려면?
1.13 DOM 요소 복제하기
DOM요소의 복사는 clone() 메서드이며 결과는 원래 선택된 DOM 요소가 아닌 그 DOM 구조의 복사본이며, 그에 대해서 메서드 체인을 사용할 수 있다.
복제 메서드는 DOM내부에서 DOM의 일부를 이동하려는 경우에 대단히 유용하다. 또한 그 복제된 DOM 요소에 첨부된 이벤트가 있다면 그 또한 복제한다.
이렇게 하려면 clone()메서드에 true를 전달하면 된다.
ex) 목록에서 li요소와 li요소에 첨부된 이벤트를 복제하여 아이디가 #b인 새로운 목록에 삽입한 후 복제된 요소들(이벤트 포함)을 제거해 버리려면?
1.17 전역적인 충돌없이 $ 별칭 사용하기
전역적인 충돌에 대한 걱정 없이 단축 표현인 $ 별칭을 사용할 수 있다.
해결방법은 익명의 자기호출 함수를 만드는 것이다. 즉, 익명 함수에 jQuery 개체를 전달하면서 jQuery 개체에 대한 매개변수 포인터로서 $를 사용하는 것이다.
예를 들면, 모든 jQuery 코드는 다음과 같은 자기호출 함수로 캡슐화될 수 있다.
					(function($){ //내부에서 $ 매개변수를 사용하는 함수를 생성

						//충돌에 대한 걱정 없이 내부 영역에서 $를 사용할 수 있다.

					})(jQuery); //익명 함수를 호출하고 jQuery 개체를 전달한다.
				
익명 자기호출 함수 안에 있는 코드는 자체 개별 범위 내에서 실행된다.
그래서 함수 안에 있는 모든 것들(변수 등)이 전역 범위에 있는 다른 자바스크립트 코드와 결코 충돌되지 않고 안심히 사용할 수 있다.
2.11 컨텍스트 매개변수 사용하기
셀렉터에 컨텍스트를 지정하면 보다 최적화되어 가독성과 처리속도에 도움이 된다.
컨텍스트란 jQuery가 셀렉터 식에 의해 매치되는 요소를 찾을 장소를 말하며 jQuery가 그 컨텍스트 안에 있는 요소에 대해 검색을 할 것이다.
					jQuery('form').bind('submit', function(){
						var allInputs = jQuery('input', this);
						// Now you would do something with 'allInputs'
					});
				
위 구문은 폼이 전송되기에 앞서 폼 안에 있는 모든 input 필드를 선택하고 있다.
이때 this가 두번째 매개변수로 전달되고 있으며 처리기내에서 this는 폼요소를 의미한다.
이를 컨텍스트로 지정한다면 jQuery는 그 폼안에 존재하는 input 요소들만을 반환할 것이다.
3.3 선택된 jQuery 개체를 원래의 DOM 개체로 변환하기
jQuery는 모든 매치된 jQuery개체들을 DOM개체의 배열로 변환해주는 메서드인 get()메서드를 제공하고 있다.
$.get(1);와 같이 인덱스값을 매개변수로 지정하면, 결과집합에서 지정된 인덱스에 해당하는 요소를 DOM 개체로 반환한다.
그러나 관례적인 측면을 고려할 경우 가장 좋은 방법은 $("div")[1]과 같이 []표기법을 사용하는 것이다.
ex) 만일 어떤 목록이 있는데 그를 역순으로 보여주고자 한다면?
get()메서드는 배열을 반환하기 때문에, 그 배열을 역순으로 정렬하도록 기본 내장된 자바스크립트 메서드 reverse()를 사용하여 역순 정렬을 한 다음 그 목록을 재출력하면 된다.
단순히 배열을 얻기위한 목적이라면 get()보다는 .toArray()메서드가 더 적합하다.
3.4 선택집합에서 항목의 인덱스 얻기
기본 메서드인 index()는 jQuery 집합에서 찾고자 하는 DOM요소의 인덱스를 얻을 수 있게 하며 $("div").index(this);에서 this가 찾고자 하는 DOM요소이다.
ex) 페이지상에서 광범위하게 선택된 요소들에 대해 이벤트를 바인딩하는 경우, 바인드된 이벤트가 '개별적으로' 동작하게 만들기 위해 선택된 집합에서 클릭된 항목의 인덱스를 알고 싶다면?
ex) 루프 안에서 변수 test에 저장된 컬렉션과 일치하는 <div>인지를 확인한후 만약 일치한다면 일치되는 컬렉션에 대해 어떠한 사용자 작업을 수행하려면?
					var test = $("div.test");
					$("div").each(function(i){
					  if($(this).index(test)>=0){
						//작업수행
					  }else{
						//그 밖의 작업수행
					  }
					});
				
만일 index()메서드가 전달된 대상을 찾지 못한다면 -1을 반환한다.
3.5 기존 배열로부터 고유한 배열 만들기
$.map() 메서드는 이미 가지고 있는 배열을 또 다른 배열로 "매핑"하는 jQuery의 유틸리티 메서드이다.
jQuery.map( array, callback(elementOfArray, indexInArray) )
첫번째 매개변수로 매핑할 배열을 지정하며 두번째 매개변수로 콜백함수를 지정한다.
콜백함수는 각 배열의 요소와 배열인덱스를 매개변수로 가지며 이 함수는 변환을 수행하고 새로운 배열에 저장될 새로운 값을 반환한다.
					$.map([1,2,3], function(n,i) {return n+i;} );

					// [1,3,5]를 출력한다.
				
ex) 회원 목록이 있고 그 목록에서 처음 세명을 별도의 문장으로 페이지의 맨 마지막에 출력하고 싶다면?
만약 null값이 반환된다면 새로운 배열에 아무런 값도 저장되지 않을 것이며, 해당 항목을 제거하게 된다.
3.7 jQuery가 다른 라이브러리와 충돌하지 않도록 설정하기
1. jQuery.noConflict() 사용하기
jQuery.noConflict()를 호출하는 경우 $변수를 가장 먼저 구현하고 있는 라이브러리에게 그에 대한 제어권을 양보한다.
일단 $변수를 양보하고 나면 jQuery에 접근하기 위해서는 반드시 jQuery 변수를 사용해야만 한다.

예를 들어, 이전에는 $("div p")를 사용했다면 이제는 jQuery("div p")를 사용해야 한다.
							jQuery.noConflict();

							// Use jQuery via jQuery(...)
							jQuery(document).ready(function(){
								jQuery("div#jQuery").css("font-weight","bold");
							});

							// Use Prototype with $(...), etc.
							document.observe("dom:loaded", function() {
								$('prototype').setStyle({
									fontSize: '10px'
								});
							});
						
2. jQuery.noConflict()를 호출하면서 변수에 할당하면 짧은 이름을 사용할 수 있다.
							 var j = jQuery.noConflict();

							 j(document).ready(function(){
								 j("div#jQuery").css("font-weight","bold");
							 });
						
3. jQuery 코드를 클로저 내부로 캡슐화한다.
							 jQuery.noConflict();

							(function($){
								$("div#jQuery").css("font-weight","bold");
							})(jQuery);
						
ex) 페이지에서 Prototype 자바스크립트 라이브러리를 사용하며 $변수를 사용하는 경우 충돌하지 않도록 jQuery 라이브러리를 사용하려면?
4.2 jQuery.each를 사용하여 배열과 개체를 반복하여 처리하기
문제점) 요소의 배열이나 개체의 어트리뷰트를 반복적으로 루프를 돌리고 싶다면?
해결방법)
					$(document).ready( function() {
						var months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 
								  'September', 'October', 'November', 'December'];
						$.each(months, function(index, value) {
							$('#months').append('<li>' + value + '</li>');

							// $('#months').append('<li>' + this + '</li>'); value대신 this를 쓸수 있다.

						});

						var days = {Sunday: 0, Monday: 1, Tuesday: 2, Wednesday: 3, Thursday: 4, 
								Friday: 5, Saturday: 6 };
						$.each(days, function(key, value) {
							$('#days').append('<li>' + key + ' (' + value + ')</li>');

							//$('#days').append('<li>' + key + ' (' + this + ')</li>'); value대신 this를 쓸수 있다.
						
						});
					});		
				
개체와 배열 모두를 반복 처리하기 위해서 $.each() 메서드를 사용할 수 있다.
첫번째 매개변수는 반복적으로 처리할 개체나 배열이며, 두번째 매개변수는 각각 요소에 대해 실행될 콜백 메서드이다.
개발자가 정의한 콜백함수가 실행될때 this 변수는 현재 반복되고 있는 요소의 값으로 설정된다.
4.3 jQuery.grep를 사용하여 배열 필터링하기
문제점) 배열에서 요소를 필터링하여 제거하고 싶다면?
해결방법)
					$(document).ready( function() {
						var months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 
								  'September', 'October', 'November', 'December'];
						months = $.grep(months, function(value, i){
							return (value.indexOf('J') == 0);
						});
						$('#months').html('<li>' + months.join('</li><li>') + '</li>');

						var days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday',
								  'Thursday', 'Friday', 'Saturday'];
						days = $.grep(days, function(value, i){
							return (i % 2) == 0;
						});
						$('#days').html('<li>' + days.join('</li><li>') + '</li>');
					});			
				
$.grep()메서드는 필터링된 배열을 반환하며 콜백 메서드는 두개의 매개변수를 가진다. 해당요소를 유지하려면 true를 제거하려면 false를 반환하면 된다. 짝수의 배열요소만 필터링하고 싶다면 나머지를 반환하는 (i%2)==0 인지를 검사하면 된다.
4.9 jQuery.data를 사용하여 DOM에 개체와 데이터 첨부하기
문제점) DOM요소에 메타데이타를 추가하는 것은 가비지 컬렉션 구현의 결점으로 인한 메모리 누수의 원인이 될수 있다.
					var node = document.getElementById('myId');
					node.onclick = function() {
						// Click handler
					};
					node.myObject = {
						label: document.getElementById('myLabel')
					};
				
※가비지 컬렉션(Garbage Collection) 이란?
시스템에서 더 이상 사용하지 않는 동적 할당된 메모리 블럭 혹은 개체를 찾아 자동적으로 다시 사용 가능한 자원으로 회수하는 것을 말한다. 더이상 접근이 불가능하게 된 순간부터 객체는 가비지 컬렉션의 대상이 된다.
해결방법)
jQuery는 메모리 누수를 피할 수 있도록 .data()라고 하는 직관적인 메서드를 제공한다. data() 함수는 DOM 요소에 임의의 데이터를 저장하기 위해 사용하는 함수이다.
					$('#myId').data('myObject', {
						label: $('#myLabel')[0]
					});

					var myObject = $('#myId').data('myObject');
					myObject.label;
				
data() 메서드를 사용하여 얻을 수 있는 또 다른 장점중 하나는 이 메서드가 대상 요소에 대해 getData와 setData 이벤트를 암시적으로 일으킨다는 것이다. setData 이벤트는 .data() 메서드를 호출할 때마다 발생한다.
					<div id="time" class="updateTime"></div>

					$(document).bind('setData', function(evt, key, value) {
						if ( key == 'clock' ) {
							$('.updateTime').html( value );
						}
					});

					setInterval(function() {
						$(document).data('clock', (new Date()).toString() );
					}, 1000);
				
상기 예제는 1초(1,000 밀리 초)마다 한번씩 문서 개체의 clock 데이터 속성을 업데이트하고 있는데, 이는 문서에 바인드된 setData 이벤트를 발생시켜서 현재 시간의 출력을 변경한다.
5.2 $(this)에 무슨 문제가 있을까?
문제점) 이벤트 처리기가 하나있는데, 이 이벤트 처리기가 다음과 같이 DOM 요소에 클래스를 추가하고 setTimeout()를 사용하여 1초 동안 기다린 후 클래스를 제거할 경우, setTimeout() 안쪽의 $(this).removeclass()가 아무런 동작을 하지 않는 문제가 생긴다.
					$(document).ready( function() {
						$('.clicky').click( function() {
							$(this).addClass('clicked');
							setTimeout( function() {
								$(this).removeClass('clicked');
							}, 1000 );
						 });
					});			
				
이유) 개체의 메서드로서 호출되는 것이 아닌 alert()나 setTimeout()처럼 직접적으로 함수가 호출되는 경우 this는 DOM 요소가 아닌 window 개체이다.
그래서 setTimeout()안쪽의 $(this).removeclass()를 호출하는 경우, 그것은 window개체로부터 클래스의 제거를 시도하게 된다.

반면 jQuery 이벤트 처리기에서는 this가 이벤트를 처리하는 DOM 요소를 가리킨다.즉 $(this)는 그 DOM 요소에 대한 jQuery 래퍼가 된다.
					<a href="#" id="test" onclick="clicked(this);">Test</a>

					function clicked( it ) {
						alert( it.id );            // 'test'
						alert( this.id );          // undefined
						alert( this === window );  // true (what?)
					}			
				
해결방법)은 setTimeout()을 호출하기 전에 변수에 this를 저장하는 것이다.
					$(document).ready( function() {
						$('.clicky').click( function() {
							var element = this;
							$(element).addClass('clicked');
							setTimeout( function() {
								$(element).removeClass('clicked');
							}, 1000 );
						 });
					});			
				
더 좋은 방법은 두 군데에서 $()를 호출하고 있기에 this 대신 $(this)를 변수에 저장하는 것이다.
					$(document).ready( function() {
						$('.clicky').click( function() {
							var $element = $(this);
							$element.addClass('clicked');
							setTimeout( function() {
								$element.removeClass('clicked');
							}, 1000 );
						 });
					});			
				
5.3 장황한 중복 제거하기
서로 다른 몇몇 이벤트들에 대해 동일한 행동을 취할 경우 jQuery의 .bind() 메서드를 사용하면
그러한 이벤트 처리기 모두를 연결할 수 있다.
					$(document).ready( function() {

						$('#country')
							.bind( 'change keyup', function() {
							   var value = $(this).val();
							   $('#state').toggle( value == 'US' );
							   $('#province').toggle( value == 'CA' );
							})
							.trigger('change');
					});
				
5.6 사용자 정의 반복기 작성하기
현재 jQuery 개체에는 여러 요소가 선택되어 있으며, 그러한 요소를 반복하여 루프를 돌면서 어떠한 처리를 해야 하기 위해서는 콜백 시간을
지연시켜 줄 setTimeout()을 사용하는 사용자 정의 반복기를 작성할 수 있다.
ex) .reveal 클래스를 가지는 텍스트를 0.5초마다 순차적으로 보이게 하려면?
					jQuery.slowEach = function( array, interval, callback ) {
						if( ! array.length ) return;
						var i = 0;
						next();

						function next() {
							if( callback.call( array[i], i, array[i] ) !== false )
								if( ++i < array.length )
									setTimeout( next, interval );
						}

						return array;
					};

					jQuery.fn.slowEach = function( interval, callback ) {
						return jQuery.slowEach( this, interval, callback );
					};
				
					// Show an element every half second
					$('.reveal').slowEach( 500, function() {
						$(this).show();
					});
				
.slowEach()는 비동기적이기 때문에(.slowEach()가 반환한 다음에 콜백 함수에 대한 호출이 일어난다), .slowEach()는 반환하였지만
콜백은 아직 완료되기 전인 상황에서 jQuery 개체나 요소를 변경한다면 그 또한 반복에 영향을 줄 수 있을 것이다.
5.7 어트리뷰트 토글하기
.toggleCheck() 플러그인은 체크박스 그룹에서 선택된 모든 체크박스 요소를 체크하거나 또는 체크를 해제한다.
7.1 요소를 슬라이딩 및 페이딩하기
- 동시에 요소를 슬라이딩하고 페이딩하기
					$(document).ready(function () {
					  $('#animate').click(function () {
						$('.box').animate({ opacity: 'toggle', height: 'toggle' });
						return false;
					  });
					});
				
7.2 위쪽으로 슬라이드하면서 요소를 보이게 하기
슬라이드를 하면서 요소를 보이게 하기 위해서 slideUp 메서드는 도움이 되지 못한다. 이는 위쪽을 기준으로 높이를 줄여가면서 요소를 숨길 것이기 때문이다.
위쪽으로 슬라이드를 하려면 얘니메이션을 적용할 요소에 절대위치를 지정하여 bottom:0으로 지정할 필요가 있다.
또한 slideToggle()을 사용하지 않은 이유는, 토글이 올바로 동작하기 위해서는 실제 높이를 어떻게든 알아낼수 있어야 하기 때문이다. 그런데 높이가 0에서 시작하는 요소의 경우는 jQuery가 전체 높이가 얼마나 되는지 알아낼 방법이 없다.
7.3 수평 아코디언 만들기
7.5 순차적인 효과 적용하기
						  $(document).ready(function () {
							var $boxes = $('.box').hide(),
							  div = 0;
							  
							$('#animate').click(function () {
							$($boxes[div++] || []).fadeIn('slow', arguments.callee);
							});
						  });
					
1. 해결방법) 순차적으로 효과를 적용하기위한 첫번째는 배열이 비어있는 경우를 위한 대비책으로 빈 배열([])을 전달하는 것이다.
						$($boxes[div++] || [])
					
이 코드는 인덱스 수를 증가시키면서, 만일 요소가 존재하지 않으면 jQuery에 빈 배열을 전달 할 것이다. jQuery 결과집합이 비어 있을 때 애니메이션을 수행하면 아무일도 일어나지 않는다. 즉, 비어있는 배열이 jQuery 컬렉션으로 사용되기에 코드실행을 종료하면서 콜백은 실행되지 않게 된다.
2. 해결방법) 두번째 트릭은 콜백 매개변수이다.
						arguments.callee
					
arguments는 모든 함수가 접근할 수 있는 지역변수를 참조하고 있는 자바스크립트 내의 키워드이다. arguments는 현재 실행중인 함수에 대한 참조를 arguments.callee 속성에 포함하여 재귀적으로 함수를 실행한다. 즉, jQuery 컬렉션을 계속해서 증가시키면서 애니메이션이 완료되면 재귀적으로 함수를 실행하는 것이다.
ECMAScript 5 명세에서는 arguments.callee의 사용이 제한될 것으로 예상되기에 가급적 사용을 자제하는 편이 좋다. 그런 이유로, jQuery 1.4버전에 들어서면서 jQuery 내부 코드중 이 속성을 사용하는 코드는 모두 제거되었다.
대안으로 아래의 예제와 같이 사용한다.
7.7 애니메이션 종료 및 리셋하기
ex) mouseover나 mouseout 이벤트시 특정 컨텐트 블록을 보여주거나 숨기는 애니메이션이 있다. 마우스를 이벤트 발생 영역 안팎으로 마구 움직인다면 얘니메이션이 계속 발생하는 문제가 생기는데 그 동작을 중간에 중단해야 한다면?
1. 해결방법) 버튼 위로 마우스가 올라갈 때마다 효과가 계속해서 반복되는 것을 막기 위해서는 후속 애니메이션이 큐에 들어가기에 앞서 stop() 명령을 삽입해 주어야 한다. 이 방법의 가장 큰 장점은 애니메이션의 흐름을 중간에서 멈춘다는 것이다.
						  $(document).ready(function () {
							$('#hover').hover(function () {
							  $('.box').stop().fadeTo(200, 1);
							}, function () {
							  $('.box').stop().fadeTo(200, 0);
							});
						  });				
					
8.1 이벤트에 처리기 연결하기
하나 이상의 이벤트에 대해 동일한 처리기 함수를 바인딩해야 하는 경우 다중 이벤트 기능을 사용한다.
						  $(document).ready(function () {
							$('div').bind('click keydown', function (e) {
							   alert('event');
							});
						  });				
					
jQuery1.4를 사용한다면 하나 이상의 자바스크립트 이벤트와 그 이벤트가 수행하는 처리기 함수를 개체로 구성하여 매개변수로 지정할 수도 있다.
					  $(document).ready(function () {
						$('div').bind({
						   click: function(){
						   ...
						   },
						   keydown: function(){
						   ...
						   },
						   keyup: function(){
						   ...
						   }
						});
					  });				
					 
특정함수를 바인딩 해제하려면 unbind() 함수에 해제할 함수를 전달해야 한다. 만일 unbind()에 함수를 전달하지 않는다면 그 이벤트에 바인드되어 있는 모든 이벤트 처리기가 제거될 것이기에 주의해야 한다.
					  function handler(e){
						alert('event');
					  }
					  $('div').bind('click keydown', handler);
					  $('div').unbind('click keydown', handler);
					
8.2 별개의 데이터를 갖는 처리기 함수 재사용하기
문제점) 상당히 많은 바인딩을 사용하지만 처리기 함수가 거의 비슷한 경우가 있을 수 있다. 이를 계속해서 반복 작성하여 코드의 양을 증가시키고 싶지 않다면?
해결책) bind()는 각각의 특정 처리기 함수와 함께 부가적인 data 매개변수도 가질 수 있다. data 값은 함수 내부에서 event.data를 통해 접근할 수 있는데, 여기서의 event는 jQuery에 의해 제공되는 이벤트 객체 매개변수이다.
						function buttonClicked(e){
							 jQuery('div.panel').hide();
							 jQuery('#panel'+e.data.panel).show();
							 jQuery('#desc').text('You clicked the '+e.data.color+' button');
						 }

						 jQuery('#button1').bind('click',{panel:1, color:'red'}, buttonClicked);
						 jQuery('#button2').bind('click',{panel:2, color:'blue'}, buttonClicked);
						 jQuery('#button3').bind('click',{panel:3, color:'green'}, buttonClicked);
					
event.data는 함수에게 계산된 값을 제공하기 위해서 사용된다.
8.4 특정 이벤트 처리기 동작시키기
특정 네임스페이스를 갖는 처리기를 강제로 실행시키러면 어떻게 해야 할까?
						$.fn.runMyPlugin = function(){
							retrun this.trigger('click.myPlugin');
						};					
					
만일 네임스페이스를 갖지 않는 처리기를 발생시키려면 어떻게 해야 할까?
						$('div.panels').trigger('click!');
					
네임스페이스를 갖는 것들을 제외한 모든 이벤트를 발생시킬 필요가 있다면 !연산자를 사용한다.
8.5 이벤트 처리기에 동적 데이터 전달하기
문제점) 어떤 값을 이벤트 처리기에 전달하고자 하지만 '바인딩 시점'에는 그 값을 알 수가 없으며, 처리기를 호출할 때마다 값이 변경된다.
해결 방법) 두가지 방법이 있다.

1. 추가 매개변수를 trigger()에 전달하기

						$('form').bind('submit', function(e, name, surname, age, extra){
							....
						});

						$('form').trigger('submit', ['John','Doe',28,{gender:'M'}]);
					
trigger()는 호출되는 처리기에 전달될 하나 이상의 값들을 가질 수 있다.
이러한 값들은 어떠한 타입이든 될 수 있고 개수에도 제한이 없다. 다만, 이 값이 하나 이상일 경우에는 배열로 감싸야 한다.

2. 사용자 정의 이벤트 개체를 trigger()에 전달하기

						$('form').bind('submit',function(e){
							// e.name, e.surname 등으로 어떤 작업 수행
						});

						$('form').trigger({
							type : 'submit',
							name : 'John',
							surname : 'Doe',
							age : 28,
							gender : 'M'
						});
					
사용자 정의 이벤트 개체를 전달한다면 전달한 각각의 값은 처리기가 받게 되는 이벤트 개체의 어트리뷰트를 통해서 접근할 수 있다.
이는 얼마나 많은 데이터를 전달하는지에 관계없이 처리기는 항상 단일 매개변수로서 이벤트 개체를 갖는다는 것을 의미한다.

개체를 전달하는 것은 사실 jQuery.Event의 인스턴스를 생성하는 것의 단축 표현이다.
						var e = $.Event('submit');
						e.name = 'John';
						e.surname = 'Doe';
						e.age = 28;
						e.gender = 'M';
						$('form').trigger(e);
					
8.8 event.target을 사용하여 정확한 요소 얻기
문제점) 코드가 이벤트 위임과 함께 이벤트 개체의 event.target 속성을 사용하고 있다고 가정해보자. 그리고 하나의 단일 이벤트 처리기가 컨테이너에 바인드되어 있는데, 그 처리기는 다양한 자식 요소를 관리하고 있다면 예상치 못하게 동작할 가능성이 있다. event.target은 간혹 예상한 요소의 내부에 있는 요소를 가리킬 수도 있기 때문이다.
해결 방법) event.target 속성은 이벤트를 가지고 있는 특정 요소를 참조한다.
closest()는 현재 요소로부터 시작하여 부모 요소로 올라가면서 특정 셀렉터와 일치하는 단일 요소를 반환한다.
						$('table').click(function(e){
							var $tr = $(e.target).closest('tr');
							//테이블의 열을 사용하여 어떤 작업을 한다.
						});
					
8.9 동시에 여러 개의 hover() 애니메이션이 수행되지 못하도록 하기
jQuery 애니메이션은 기본적으로 큐에 들어간다. 이는 몇개의 애니메이션을 추가한다면 큐에 들어간 순서대로 수행될것이라는 걸 의미한다.
stop() 메서드는 기본적으로 현재의 애니메이션을 중지할 뿐 이어지는 모든 애니메이션을 제거하진 않는다.
이 문제를 해결하려면 stop() 메서드의 매개변수로 true를 전달해야 한다. true로 설정하면 stop() 메서드는 큐에 있는 모든 애니메이션을 깨끗이 비울 것이다.
						$("#something").hover( 
						  function () { 
							$(this).stop(true).fadeTo(300, 1.0).animate({height:150}, 1000);
						  },  
						  function () { 
							$(this).stop(true).fadeTo(300, 0.8).animate({height:30}, 1000);
						  } 
						); 
					
8.10 새롭게 추가된 요소에서도 동작하는 이벤트 처리기
문제점) Ajax요청이나 간단한 jQuery 연산(append(), wrap() 등)에 의해 새로운 요소가 동적으로 추가된 후에 바인드된 이벤트 처리기가 갑자기 동작하지 않을 수 있다.
해결 방법) 이벤트를 바인딩하기 위해서 bind() 메서드 대신 live() 메서드를 사용한다. live() 메서드는 아직 존재하지 않는 요소에 대해서도 이벤트 처리기를 바인딩 할 수 있다.
						$("p").live("click", function(){ 
						  $(this).after("<p>Another paragraph!</p>"); 
						}); 
						$("p").live({ 
						  "mouseover": function() { 
							$(this).addClass("over"); 
						  }, 
						  "mouseout": function() { 
							$(this).removeClass("over"); 
						  } 
						}); 
					
왜 이벤트 처리기를 잃어 버리는 것일까?
CSS와는 달리 자바스크립트는 선언적인 언어가 아니라 명령적으로 해석되기 때문에 실행되는 시점에 셀렉터와 일치되는 요소들과만 바인드될 것이다. 클래스를 갖는 새로운 요소를 추가하거나 요소에서 클래스를 제거한다해도 동작은 그 요소를 위해 추가되거나 삭제되지 않을 것이다.
이벤트 캡처링과 이벤트 버블링

ㅇ 이벤트 캡처링(event capturing) - DOM 레벨 2 이벤트 모델

페이지에서 여러요소들이 있고 모두 동일한 마우스 좌표상에서 이벤트가 발생할 경우 여러 요소가 한번의 클릭에 모두 반응하도록 하는 전략을 이벤트 캡처링이라고 한다.
이벤트 캡처링에서, 모든것을 감싸고 있는 최상위의 요소에게 이벤트가 처음으로 주어지고, 그후에 연속해서 하위 요소로 전달된다.
<div><span><a>..</a></span></div>같은 계층구조에서 <a>태그가 클릭되면 가장 먼저 <div>가 이벤트를 받게 되고, <span>이 그 뒤를 이어서 받고, 마지막으로 <a>가 받게 된다.

ㅇ 이벤트 버블링(event bubbling) - DOM 레벨 0 이벤트 모델, DOM 레벨 2 이벤트 모델

이벤트는 가장 낮은 자식요소에서 처음으로 전달되고, 해당요소가 반응한 후에, 그 이벤트는 그 부모 요소로 전파된다. <div><span><a>..</a></span></div>에서는 <a>요소가 이벤트를 첫번째로 처리하고, <span>과 <div>가 차례대로 처리하게 된다.

jQuery는 브라우저 호환의 일관성을 제공하기 위해 버블링 모델을 지원하는 이벤트 핸들러들을 항상 사용한다. 그 결과, 이벤트에 첫번째로 응답하는 것이 항상 선택된 최하위 특정 자식 요소이다.

ㅇ 이벤트 버블링(event bubbling)의 부작용을 제거하기 위한 방법

이벤트 버블링의 부작용이란 동일한 마우스 좌표상에서 이벤트가 발생한 경우 A요소와 함께 동일한 좌표상에 있는 B요소에도 이벤트가 발생하여 예상치 못한 결과를 발생한 경우를 말한다.
  1. 1. 이벤트 객체에 접근한다.

    이벤트 객체는 이벤트가 발생한 위치의 요소객체를 포함하고 있으며 이벤트 객체를 활용하면 이벤트가 발생한 마우스의 위치와 같은 이벤트 정보를 얻을 수 있다.
    이벤트 객체를 사용하려면 함수에 event라는 매개변수를 전달한 후 event.target 프로퍼티를 사용하여 DOM에서 어느 요소가 이벤트를 처음으로 받은 요소인지를 체크한다.
    즉 이벤트가 발생한 DOM요소인 this 키워드가 이벤트를 처음 받은 요소와 일치할 경우에만 구문을 실행하는 것이다.
  2. 2. 이벤트전파를 멈추게 하기 위해 .stopPropagetion() 메서드를 사용한다.

ㅇ 이벤트 위임

이벤트 위임을 사용해야 하는 이유?
예를 들어, 정보를 담은 커다란 테이블이 있고, 각각의 행에 click핸들러를 필요로 하는 버튼이 있다고 가정해 보자. 묵시적인 순환을 통해 click 핸들러를 할당하게 되면 내부적으로 루프를 돌게 되므로 성능이 매우 나빠질 뿐만 아니라 모든 핸들러를 관리하기 위해 메모리를 많이 사용하게 된다.

이렇게 하는 대신 이벤트 위임을 사용하여 DOM의 조상요소에 단 한개의 click핸들러만 할당하고 event.target 프로퍼티를 사용하여 어느 요소에 마우스 클릭이 발생했는지 알고자 .is()메서드를 사용할 수 있다. .is()메서드는 지정한 선택자표현식에 대응하는 jQuery객체가 있는지를 검사하며 해당하는 요소가 하나라도 있으면 true를 반환한다. 예를 들어 $(event.target).is('.button')는 클릭된 요소에 button 클래스가 할당되어 있는지 여부를 검사한다.

jQuery - 플러그인 만들기

A Plugin Development Pattern

12.4 플러그인으로 옵션 전달하기
1. 옵션은 options 개체를 통해서 사용자 정의 플러그인 메서드로 전달하는 것이 가장 좋다.
						jQuery.fn.pulse = function(options) {
							...
						};
					
2. 객체 확장하기
옵션을 제공하는 경우에는 적절한 기본 옵션값들을 제공한다.
또한 기본 옵션 개체를 선언한 다음 extend() 메서드를 사용하여 기본값을 사용자가 지정한 값으로 재정의한다.
						jQuery.fn.pulse = function(options) {
							//기본 옵션들과 전달된 옵션들을 병합하여 객체를 확장한다.
							var opts = jQuery.extend({}, jQuery.fn.pulse.defaults, options);

							return this.each(function() {

							   for(var i = 0;i<opts.pulses;i++) {
								   jQuery(this).fadeTo(opts.speed,opts.fadeLow).fadeTo(opts.speed,opts.fadeHigh);
							   }

							   // Reset to normal
							   jQuery(this).fadeTo(opts.speed,1);
							});
						};

						//플러그인의 기본 옵션들
						jQuery.fn.pulse.defaults = {
						  speed: "slow",
						  pulses: 2,
						  fadeLow: 0.2,
						  fadeHigh: 1
						};
					

* 객체확장 유틸리티 함수 $.extend()

$.extend(target, source1, source2);
- 이 구문은 source1와 source2 객체의 프로퍼티를 target 객체에 합친다. target 객체는 함수의 결과값으로 반환된다.
- source1과 source2에 모두 e라는 프로퍼티가 있을 경우 source2에 있는 e의 값이 source1에 있는 e의 값보다 우선한다.
						function complex(options) {
							var opts = $.extend({
								option1 : defaultValue1,
								option1 : defaultValue2,
								option1 : defaultValue3,
								option1 : defaultValue4,
								option1 : defaultValue5,
								option1 : defaultValue6
							}, options || {});
							...
						}
					

opts 변수는 options 매개변수로 전달한 값과 기본값을 합친 값을 가진다.
주목할 점은 || {}를 사용하여 options 객체가 null이거나 undefined일 경우 빈객체인 {}를 사용한다.

12.6 플러그인에 전용(private) 함수 포함하기
전용함수는 일반적으로 플러그인을 감싸고 있는 익명함수에 정의될 수 있다. 전용함수가 익명함수안에 들어 있기 때문에 외부 코드에서는 전용 메서드를 확인할 수가 없다.
						;(function($){
						  $.fn.pulse = function(options) {
							...

							return this.each(function() {
								doPulse($(this), opts);
							});
						  };

						  function doPulse($obj,opts){
							...
						  }

						  $.fn.pulse.defaults = {
							...
						  };
						})(jQuery);
					
12.8 플러그인에 정적(static) 함수 추가하기
플러그인에 정적 메서드를 추가하기 위해서는 메서드를 추가했던 것과 유사한 방법으로 jQuery 개체를 확장하면 된다. 다만, 차이점은 함수가 jQuery 셀렉터를 사용하지 않고 호출된다는 것이다.
						;(function($){
						  $.fn.pulse = function(options) {
							...

							return this.each(function() {
								doPulse($(this), opts);
							});
						  };

						  function doPulse($obj,opts){
							...
						  }
						
						  // 정적함수를 위한 기본 네임스페이스를 정의한다.
						  $.pulse = {};
						
						  // 정적함수
						  $.pulse.impulse = function($obj) {
							var opts = {
							  speed: 2500,
							  pulses: 10,
							  fadeLow: 0.2,
							  fadeHigh: 0.8
							};
							doPulse($obj,opts);
						  }
						
						  $.fn.pulse.defaults = {
							...
						  };
						})(jQuery);
					
정적 함수를 사용하려면 단지 jQuery 개체를 이용하여 그 함수를 호출하기만 하면 된다. 이 함수는 jQuery 셀렉터 없이 호출되어야 하며, 단지 실행할 유효한 개체를 명시적으로 전달하기만 하면 된다.
						// 반환된 첫번째 요소에 대해 impluse 메서드를 호출한다.
						jQuery.pulse.impulse(jQuery('p:first'));
					
13.1 사용자 정의 툴팁 만들기
						function init_tooltip() {

						  // 요소가 존재하는가?
						  if (!$('.tooltip').length) {

							// 존재하지 않으면 함수를 빠져나가 더 이상 실행하지 않는다.
							return;
						  }

						  // 툴팁을 추가한다. (hidden).
						  $('body').append('<div id="tooltip_outer"><div id="tooltip_inner"></div></div>');

						  // 빈변수를 선언한다.
						  var $tt_title, $tt_alt;

						  var $tt = $('#tooltip_outer');
						  var $tt_i = $('#tooltip_inner');

						  // Watch for hover.
						  $('.tooltip').hover(function() {

							// title 정보를 변수에 저장한 후 title 정보를 제거한다
							if ($(this).attr('title')) {
							  $tt_title = $(this).attr('title');
							  $(this).attr('title', '');
							}

							// alt 정보를 변수에 저장한 후 alt 정보를 제거한다
							if ($(this).attr('alt')) {
							  $tt_alt = $(this).attr('alt');
							  $(this).attr('alt', '');
							}

							// title정보가 저장된 텍스트를 추가한다.
							$tt_i.html($tt_title);

							// 툴팁을 보이게 한다.
							$tt.show();
						  },
						  function() {

							// 툴팁을 숨긴다.
							$tt.hide();

							// title정보를 툴팁에서 제거하기 위해 빈텍스틀 추가한다.
							$tt_i.html('');

							// title정보를 만든다.
							if ($tt_title) {
							  $(this).attr('title', $tt_title);
							}

							// alt정보를 만든다.
							if ($tt_alt) {
							  $(this).attr('alt', $tt_alt);
							}

						  // 이동에 대한 함수를 작성한다.
						  }).mousemove(function(ev) {

							// 이벤트 좌표
							var $ev_x = ev.pageX;
							var $ev_y = ev.pageY;

							// 툴팁 좌표
							var $tt_x = $tt.outerWidth();
							var $tt_y = $tt.outerHeight();

							// Body 좌표
							var $bd_x = $('body').outerWidth();
							var $bd_y = $('body').outerHeight();

							// 툴팁을 이동시킨다.
							$tt.css({
							  'top': $ev_y + $tt_y > $bd_y ? $ev_y − $tt_y : $ev_y,
							  'left': $ev_x + $tt_x + 20 > $bd_x ? $ev_x − $tt_x − 10 : $ev_x + 15
							});
						  });
						}

						// Kick things off.
						$(document).ready(function() {
						  init_tooltip();
						});
					
13.2 파일 트리를 사용하여 탐색하기
						// Initialize.
						function init_tree() {

						  // Does element exist?
						  if (!$('ul.tree').length) {

							// If not, exit.
							return;
						  }

						  // 확장과 축소
						  $('p.tree_controls a.expand_all, p.tree_controls a.collapse_all').click(function() {

							// Look at the class.
							if ($(this).hasClass('expand_all')) {
								$(this).parent('p').next('ul').find('a.tree_trigger')
									   .addClass('trigger_expanded')
									   .end().find('ul').addClass('tree_expanded');
								return false;
							} else {
								$(this).parent('p').next('ul').find('a.tree_trigger')
									   .removeClass('trigger_expanded')
									   .end().find('ul').removeClass('tree_expanded');
							}

							// Nofollow.
							this.blur();
							return false;
						  });

						  //트리의 클릭 이벤트를 바인딩한다.
						  $('ul.tree a.tree_trigger').live('click', function() {

							// Is the next <ul> hidden?
							if ($(this).next('ul').is(':hidden')) {
							  $(this).addClass('trigger_expanded').next('ul')
									 .addClass('tree_expanded');
							} else {
							  $(this).removeClass('trigger_expanded').next('ul')
									 .removeClass('tree_expanded');
							}

							// Nofollow.
							this.blur();
							return false;
						  });

						  //마지막 <li>에 클래스를 추가한다.
						  $('ul.tree li:last-child').addClass('last');

						  //상태를 변경한다.
						  $('ul.tree_expanded').prev('a').addClass('trigger_expanded');
						}

						$(document).ready(function() {
						  init_tree();
						});