AJAX기술은 거창한 기술이 아니다. 민감한 사용자가 아닌경우(또는 개발자가 좀 더 섬세하게 웹환경을 구성했었다면) 기존 방식의 웹 화면과 달라지는 것을 거의 찾을 수 없다.
하지만 서버와의 데이터통신량의 감소(mobile환경을 고려한다면 이 또한 중요하다), 비동기식 송/수신 기능 확장을 통한 기존 웹에서 구현하기 힘들던 다양한 기능들을 생각한다면 개발자의 입장에서는 결코 무시할 수 없는 기술임에 틀림없다.
이런 생각으로 AJAX에 대해 공부를 시작하다보면 쉽게 좌절감에 빠지게 되는데, 그 원인을 대충 살펴보면
1. javascript에 대한 사용이 너무 복잡하다.
2. 막상 어디다 적용할지를 모르겠다.
등이 있다.
2번 문제를 고심하던 중에 평소 사용하고 있는 블로그(tistory)에도 AJAX기능이 사용됨을 확인하고 사용중인 기능분석을 통해 AJAX 기술에 대한 친밀도와 체계적인 정리를 해보기위해 문서작성을 시작했다.
** 저작권에 위배되지 않으리라고 생각되지만 혹시라도 저작권 위반이라면 자삭(?)하겠습니다..
tistory는 Tatter & Company에서 만든 블로그 툴을 다음에서 서비스하는 형태다.
이 블로그 툴의 AJAX호출모듈은 EAF2.js라는 이름으로 사용되고 있는데 이는 Eolin.Application.Framework의 약자다.
이제 EAF2.js를 하나씩 분석해보자.
EAF2는 크게 Standardizer,PageMaster,HTTPRequest,FileUploadRequest,PageHolder,KeyCode로 구성되어 있다. 이 중 HTTPRequest는 서버와의 AJAX호출 모듈을 담당하며, PageMaster는 페이지에 메시지 표시, Standardizer는 페이지의 기본정보관리, FileUploadRequest는 파일 upload관리? 등을 담당한다.
일단 전체 소스를 살펴보자.
소스보기.. 소스접기..
function getObject(target) { try { switch ( typeof (target)) { case "undefined" : return null ; case "object" : return target; default : return document.getElementById(target); }; } catch (e) { return null ; }; }; Standardizer.prototype.namespace = "Eolin.Application.Framework" ; Standardizer.prototype.name = "Eolin Standardizer" ; Standardizer.prototype.verion = "1.0" ; Standardizer.prototype.copyright = "Copyright (c) 2005, Tatter & Company. All rights reserved." ; function Standardizer() {}; Standardizer.prototype.isIE = (navigator.userAgent.indexOf("MSIE" ) >= 0); Standardizer.prototype.isFirefox = (navigator.userAgent.indexOf("Firefox" ) >= 0); Standardizer.prototype.isSafari = (navigator.userAgent.indexOf("Safari" ) >= 0); Standardizer.prototype.isOpera = (!Standardizer.prototype.isIE && (navigator.userAgent.indexOf("Opera" ) >= 0)); Standardizer.prototype.isMozilla = (!Standardizer.prototype.isIE && !Standardizer.prototype.isFirefox && !Standardizer.prototype.isSafari && !Standardizer.prototype.isOpera && (navigator.userAgent.indexOf("Mozilla" ) >= 0)); Standardizer.prototype.addEventListener = function (object) { if (object.addEventListener) return ; object.addEventListener = function addEventListener(type, listener, useCapture) { this .attachEvent( "on" + type, listener); }; }; Standardizer.prototype.removeEventListener = function (object) { if (object.removeEventListener) return ; object.removeEventListener = function removeEventListener(type, listener, useCapture) { this .detachEvent( "on" + type, listener); }; }; Standardizer.prototype.event = function (event) { if (window.event) { event = window.event; if (event.target) return event; if (event.srcElement) event.target = event.srcElement; if (event.preventDefault == undefined) event.preventDefault = function () { this .returnValue = false ; }; }; return event; }; Standardizer.prototype.getScrollTop = function () { return ( this .isSafari ? document.body.scrollTop: document.documentElement.scrollTop); }; Standardizer.prototype.getScrollLeft = function () { return ( this .isSafari ? document.body.scrollLeft: document.documentElement.scrollLeft); }; var STD = new Standardizer(); STD.addEventListener(window); var KeyCode = new function () { this .framework = "Eolin AJAX Framework" ; this .name = "Eolin LogViewer" ; this .verion = "1.0" ; this .copyright = "Copyright (c) 2005, Tatter & Company. All rights reserved." ; this .A = 65; this .B = 66; this .C = 67; this .D = 68; this .E = 69; this .F = 70; this .G = 71; this .H = 72; this .I = 73; this .J = 74; this .K = 75; this .L = 76; this .M = 77; this .N = 78; this .O = 79; this .P = 80; this .Q = 81; this .R = 82; this .S = 83; this .T = 84; this .U = 85; this .V = 86; this .W = 87; this .X = 88; this .Y = 89; this .Z = 90; this .Down = 40; this .Up = 38; this .Left = 37; this .Right = 39; }; PageMaster.prototype.namespace = "Eolin.Application.Framework" ; PageMaster.prototype.name = "Eolin Page Master" ; PageMaster.prototype.verion = "1.0" ; PageMaster.prototype.copyright = "Copyright (c) 2005, Tatter & Company. All rights reserved." ; PageMaster.prototype.message = "아직 처리중인 작업이 있습니다." ; function PageMaster() { this ._status = null ; this ._messages = new Array(); this ._requests = new Array(); this ._holders = new Array(); this ._timer = null ; window.addEventListener("load" , PageMaster.prototype._onLoad, false ); window.addEventListener("beforeunload" , PageMaster.prototype._onBeforeUnload, false ); }; PageMaster.prototype._onLoad = function (event) { PM._status = document.createElement("div" ); PM._status.style.position = "absolute" ; PM._status.style.color = "white" ; PM._status.style.backgroundColor = "navy" ; PM._status.style.margin = "0px" ; PM._status.style.paddingLeft = "10px" ; PM._status.style.paddingRight = "10px" ; STD.addEventListener(window); window.addEventListener("scroll" , PageMaster.prototype._updateStatus, false ); window.addEventListener("resize" , PageMaster.prototype._updateStatus, false ); }; PageMaster.prototype._showStatus = function () { if (PM._status.parentNode == document.body) return ; document.body.appendChild(this ._status); this ._updateStatus(); }; PageMaster.prototype._hideStatus = function () { if (PM._status.parentNode == document.body) document.body.removeChild(PM._status); }; PageMaster.prototype._updateStatus = function () { if (PM._status.parentNode == document.body) { PM._status.style.top = (!STD.isSafari ? document.documentElement.scrollTop: document.body.scrollTop) + "px" ; PM._status.style.left = ((!STD.isSafari ? document.documentElement.scrollLeft: document.body.scrollLeft) + document.documentElement.clientWidth - PM._status.offsetWidth) + "px" ; }; PM.updateMessages(); }; PageMaster.prototype.showMessage = function (message, align, valign, timeout) { if (( typeof (message) != "string" ) || (message.length == 0)) return - 1; if (align == undefined) align = "center" ; if (valign == undefined) valign = "middle" ; if (timeout == undefined) timeout = 3000; var oMessage = document.createElement( "div" ); oMessage.innerHTML = message; oMessage.style.position = "absolute" ; oMessage.style.color = "white" ; oMessage.style.backgroundColor = "green" ; oMessage.style.margin = "0px" ; oMessage.style.paddingLeft = "10px" ; oMessage.style.paddingRight = "10px" ; oMessage._align = align; oMessage._valign = valign; document.body.appendChild(oMessage); var index = this ._messages.push(oMessage) - 1; this .updateMessages(); window.setTimeout("PM._hideMessage(" + index + ")" , timeout); return index; }; PageMaster.prototype._hideMessage = function (index) { document.body.removeChild(this ._messages[index]); this ._messages.splice(index, 1, null ); while (( this ._messages.length > 0) && ( this ._messages[ this ._messages.length - 1] == null )) this ._messages.pop(); }; PageMaster.prototype.updateMessages = function () { for ( var i = 0; i < this ._messages.length; i++) { if ( this ._messages[i] == null ) continue ; switch ( this ._messages[i]._align) { case "left" : this ._messages[i].style.left = STD.getScrollLeft() + "px" ; break ; case "center" : this ._messages[i].style.left = (STD.getScrollLeft() + (document.documentElement.clientWidth - this ._messages[i].offsetWidth) / 2) + "px" ; break ; case "right" : this ._messages[i].style.left = (STD.getScrollLeft() + document.documentElement.clientWidth - this ._messages[i].offsetWidth) + "px" ; break ; }; switch ( this ._messages[i]._valign) { case "top" : this ._messages[i].style.top = STD.getScrollTop() + "px" ; break ; case "middle" : this ._messages[i].style.top = (STD.getScrollTop() + (document.documentElement.clientHeight - this ._messages[i].offsetHeight) / 2) + "px" ; break ; case "bottom" : this ._messages[i].style.top = (STD.getScrollTop() + document.documentElement.clientHeight - this ._messages[i].offsetHeight) + "px" ; break ; }; }; }; PageMaster.prototype.addRequest = function (request, message) { this ._requests.push( new Array(request, message)); if ( this ._status) { if (message != undefined) { this ._status.innerHTML += message; this ._showStatus(); } } }; PageMaster.prototype.removeRequest = function (request) { for ( var i = 0; i < this ._requests.length; i++) { if ( this ._requests[i][0] == request) { this ._requests.splice(i, 1); break ; }; }; var message = "" ; for ( var i = 0; i < this ._requests.length; i++) { if ( this ._requests[i][1] != undefined) message += this ._requests[i][1]; }; if ( this ._status) { this ._status.innerHTML = message; if (message.length == 0) this ._hideStatus(); else this ._updateStatus(); } }; PageMaster.prototype.addHolder = function (holder) { this ._holders.push(holder); }; PageMaster.prototype.removeHolder = function (holder) { for ( var i = 0; i < this ._holders.length; i++) { if ( this ._holders[i] == holder) { this ._holders.splice(i, 1); return ; }; }; }; PageMaster.prototype.showPanel = function (panel, halign, valign) { try { if ( typeof (panel) == "string" ) panel = document.getElementById(panel); if ( typeof (panel) != "object" ) return ; var clientHeight = STD.isOpera ? document.body.clientHeight: document.documentElement.clientHeight; panel.style.position = "absolute" ; panel.style.display = "block" ; switch (halign) { case "left" : panel.style.left = STD.getScrollLeft() + "px" ; break ; default : case "center" : panel.style.left = (STD.getScrollLeft() + (document.documentElement.clientWidth - panel.offsetWidth) / 2) + "px" ; break ; case "right" : panel.style.left = (STD.getScrollLeft() + document.documentElement.clientWidth - panel.offsetWidth) + "px" ; break ; }; switch (valign) { case "top" : panel.style.top = STD.getScrollTop() + "px" ; break ; default : case "middle" : panel.style.top = (STD.getScrollTop() + (clientHeight - panel.offsetHeight) / 2) + "px" ; break ; case "bottom" : panel.style.top = (STD.getScrollTop() + clientHeight - panel.offsetHeight) + "px" ; break ; }; } catch (e) {}; }; PageMaster.prototype._onBeforeUnload = function (event) { event = STD.event(event); if (PM._requests.length > 0) { event.returnValue = PM.message; return ; }; for ( var i = 0; i < PM._holders.length; i++) { if (PM._holders[i].isHolding()) { event.returnValue = PM._holders[i].message; return ; }; }; }; var PM = new PageMaster(); HTTPRequest.prototype.namespace = "Eolin.Application.Framework" ; HTTPRequest.prototype.name = "Eolin HTTPXMLRequest Processor" ; HTTPRequest.prototype.verion = "1.0" ; HTTPRequest.prototype.copyright = "Copyright (c) 2005, Tatter & Company. All rights reserved." ; HTTPRequest.prototype.method = "GET" ; HTTPRequest.prototype.url = null ; HTTPRequest.prototype.contentType = "application/x-www-form-urlencoded" ; HTTPRequest.prototype.content = "" ; HTTPRequest.prototype.async = true ; HTTPRequest.prototype.cache = false ; HTTPRequest.prototype.persistent = true ; HTTPRequest.prototype.message = "Requesting..." ; HTTPRequest.prototype.onVerify = function () { return ( this .getText( "/response/error" ) == 0) }; HTTPRequest.prototype.onExecute = function () {}; HTTPRequest.prototype.onSuccess = function () {}; HTTPRequest.prototype.onError = function () {}; function HTTPRequest() { switch (arguments.length) { case 0: break ; case 1: this .url = arguments[0]; break ; default : case 3: this .async = arguments[2]; case 2: this .method = arguments[0]; this .url = arguments[1]; break ; }; try { this ._request = new XMLHttpRequest(); } catch (e) { var objectNames = [ "MSXML2.XMLHTTP.5.0" , "MSXML2.XMLHTTP.4.0" , "MSXML2.XMLHTTP.3.0" , "MSXML2.XMLHTTP" , "Microsoft.XMLHTTP" ]; for ( var i = 0; i < objectNames.length; i++) { try { this ._request = new ActiveXObject(objectNames[i]); break ; } catch (e) {}; }; if ( this ._request == null ) { return null ; }; }; this ._properties = new Array(); this ._attributes = new Array(); this ._userData = new Array(); }; HTTPRequest.prototype.presetProperty = function (object, property, success, error) { if (error == undefined) { error = object[property]; if (success == error) return ; }; object[property] = success; if (success == error) return ; this ._properties.push( new Array(object, property, error)); }; HTTPRequest.prototype.presetAttribute = function (object, attribute, success, error) { if (error == undefined) { error = object.getAttribute(attribute); if (success == error) return ; }; object.setAttribute(attribute, success); if (success == error) return ; this ._attributes.push( new Array(object, attribute, error)); }; HTTPRequest.prototype.send = function () { if ( this .persistent) PM.addRequest( this ); if ( this .async) { var instance = this ; this ._request.onreadystatechange = function () { if (instance._request.readyState == 4) { if (instance.persistent) PM.removeRequest(instance); if (instance.onVerify()) instance.onSuccess(); else { for ( var i = 0; i < instance._properties.length; i++) instance._properties[i][0][instance._properties[i][1]] = instance._properties[i][2]; for ( var i = 0; i < instance._attributes.length; i++) instance._attributes[i][0].setAttribute(instance._attributes[i][1], instance._attributes[i][2]); instance.onError(); }; }; }; }; if ( this .cache) this ._request.open( this .method, this .url, this .async); else if ( this .url.lastIndexOf( "?" ) >= 0) this ._request.open( this .method, this .url + "&__T__=" + ( new Date()).getTime(), this .async); else this ._request.open( this .method, this .url + "?__T__=" + ( new Date()).getTime(), this .async); if (STD.isFirefox) this ._request.setRequestHeader( "Referer" , location.href); if (arguments.length > 0) this .content = arguments[0]; if ( this .content.length > 0) this ._request.setRequestHeader( "Content-Type" , this .contentType); this ._request.send( this .content); if (! this .async) { if ( this .persistent) PM.removeRequest( this ); if ( this .onVerify()) this .onSuccess(); else { for ( var i = 0; i < this ._properties.length; i++) this ._properties[i][0][ this ._properties[i][1]] = this ._properties[i][2]; for ( var i = 0; i < this ._attributes.length; i++) this ._attributes[i][0].setAttribute( this ._attributes[i][1], this ._attributes[i][2]); this .onError(); }; }; }; HTTPRequest.prototype.getText = function (path) { try { if (path == undefined) return this ._request.responseText; var directives = path.split( "/" ); if (directives[0] != "" ) return null ; var cursor = this ._request.responseXML.documentElement; if (cursor.nodeName != directives[1]) return null ; for ( var i = 2; i < directives.length; i++) { for ( var j = 0; j < cursor.childNodes.length; j++) { if (cursor.childNodes[j].nodeName == directives[i]) { cursor = cursor.childNodes[j]; j = -1; break ; }; }; if (j != -1) return null ; }; if (cursor.text) return cursor.text; return this ._getText(cursor); } catch (e) { return null ; }; }; HTTPRequest.prototype._getText = function (node) { var text = "" ; if (node.nodeValue) text += node.nodeValue; for ( var i = 0; i < node.childNodes.length; i++) text += this ._getText(node.childNodes[i]); return text; }; FileUploadRequest.prototype.namespace = "Eolin.Application.Framework" ; FileUploadRequest.prototype.name = "Eolin File Upload Request" ; FileUploadRequest.prototype.verion = "1.0" ; FileUploadRequest.prototype.copyright = "Copyright (c) 2005, Tatter & Company. All rights reserved." ; FileUploadRequest.prototype.message = "Uploading..." ; FileUploadRequest.prototype.autoDelete = false ; function FileUploadRequest() {}; FileUploadRequest.prototype.reset = function () { if ( typeof ( this ._form) == "object" ) { STD.removeEventListener(this ._form); this ._form.removeEventListener( "submit" , FileUploadRequest.prototype._onsubmit, false ); }; if ( typeof ( this ._target) == "object" ) { STD.removeEventListener(this ._target); this ._target.removeEventListener( "load" , FileUploadRequest.prototype._onload, false ); }; }; FileUploadRequest.prototype.bind = function (form, target) { this .reset(); switch ( typeof (form)) { case "object" : this ._form = form; break ; case "string" : this ._form = document.getElementById(form); if ( this ._form) break ; default : return false ; }; switch ( typeof (target)) { case "object" : this ._target = target; break ; case "string" : this ._target = document.getElementById(target); if ( this ._target) break ; default : return false ; }; if ( this ._form.target != this ._target.name) this ._form.target = this ._target.name; STD.addEventListener(this ._form); this ._form.addEventListener( "submit" , FileUploadRequest.prototype._onsubmit, false ); STD.addEventListener(this ._target); this ._form.upload = function () { PM.addRequest(this ._instance, "Uploading..." ); this .submit(); }; this ._target.addEventListener( "load" , FileUploadRequest.prototype._onload, false ); this ._form._instance = this ; this ._target._instance = this ; return true ; }; FileUploadRequest.prototype._onsubmit = function (event) { event = STD.event(event); event.target._instance.setRunning(true ); }; FileUploadRequest.prototype._onload = function (event) { event = STD.event(event); var instance = event.target ? event.target._instance: this ._instance; PM.removeRequest(instance); }; PageHolder.prototype.namespace = "Eolin.Application.Framework" ; PageHolder.prototype.name = "Eolin Page Holder" ; PageHolder.prototype.verion = "1.0" ; PageHolder.prototype.copyright = "Copyright (c) 2005, Tatter & Company. All rights reserved." ; PageHolder.prototype.message = "Wait.." ; PageHolder.prototype.autoDelete = false ; function PageHolder(hold, message) { PM.addHolder(this ); switch (arguments.length) { default : case 2: this .message = message; case 1: this ._holding = hold; break ; case 0: this ._holding = true ; break ; }; }; PageHolder.prototype.isHolding = function () { return this ._holding; }; PageHolder.prototype.hold = function () { this ._holding = true ; }; PageHolder.prototype.release = function () { this ._holding = false ; };<br />
소스접기..
일단 소스를 하나씩 보기 전에 자바스크립트의 기본에 대해 알아보자.
소스를 확인해 보면 method 확장을 prototype이란 것을 사용해서 하고 있는 모습니다...
prototype이 뭘까? 다음 블로그에 자세한 개념이 설명되어 있다.
javascript에 prototype은 무엇인가? 1부javascript에 prototype은 무엇인가? 2부 요약해 보면 생성객체에 메소드를 정의하면 새로운 객체가 생성될때마다 동일한 메소드가 계속 생성되어 문제가 되므로 function 함수에 존재하는 Prototype 객체를 가르키는 prototype 이란 프로퍼티를 사용하여
이 Prototype 객체에 공유되어있는 프로퍼티와 메소드가 객체 생성시 해당 생성자 function으로 생성한 모든 인스턴스에서 공유되게 사용하는 것이다.^^;
즉, prototype을 사용하면 객체생성시 마다 메소드, 프로퍼티가 하나씩 생기는게 아니라 모든 생성자가 하나의 메소드, 프로퍼티를 공유해서 사용하게 된다.
이를 통하여 메소드 관리를 체계적으로 처리할 수 있다.
javascript에서 객체에서 추가되는 메소드, 프로퍼티, 프로퍼티밑의 메소드, 프로퍼티밑의 메소드 밑에 프로퍼티 그 어느것도 결국에 객체에서는 array형태로 관리한다는 개념을 인식하도록 하자!!
소스에서 하나 더 알고 넘어가야 할 부분이 있다.
바로 자바스크립트의 이벤트 종류!!
다음 블로그에 자세한 내용을 확인할수 있다.
자바스트립트 이벤트 종류 요약을 해보면(^^;)
DOM 레벨 0 으로 불리우는 dom 이 정의 되기 이전에 각종 브라우저 에서 사용하던 방식에서는
document.id명.onclick = clickEvent; 을 이용하여 이벤트에 메소드를 정의할 수 있었고,
DOM 레벨 2 의 이벤트(IE 를 제외한 대부분의 브라우저가 지원)에서는
addEventListener, removeEventListener 라는 메소드를 사용하는데
인자로는 이벤트명(DOM0에서 on을 뺀다), 이벤트시 처리할메소드,이벤트전파캡쳐단계(?) 등을 정의하게 된다.
인터넷익스플로어는 특이하게도 attachEvent, detachEvent 를 사용하며
인자는 이벤트명(DOM0의 이벤트명), 이벤트시 처리할 메소드 만을 사용한다.
즉, 같은 javasctipt 코드를 실행했을때
function connectTest()
{
alert(window.addEventListener);
}
파이어폭스에서는
function addEventListener() {
[native code]
}
라는 alert이 뜨고
explore에서는
undefined 라는 메시지가 뜨게 된다.
attachEvent를 alert 해보면 반대의 현상이 발생된다.
이벤트에 대해서도 알아보자.
브라우저에서는 키보드나 마우스입력같은 외부의 입력에 대해 이벤트를 인식하게 되는데 이 또한 익스플로어와 파이어폭스의 사용이 다르다.
간단히 확인해 보면
파이어폭스에서는 event라는 값을 사용하며,
인터넷익스플로어에서는 window.event라는 값을 사용하게 된다.
이에대한 사용법은 예제를 살펴보면 다시 확인하겠다.
event에 대한 property도 뒤에서 살펴보자!!!
참고로 event명은 다음과 같은것들이 있다.
onkeydown - 모든 키를 눌렀을때 onkeyup - 모든 키를 눌렀다 땠을때 onkeypress - 실제로 글자가 써질때 (shift, enter 같은 키는 인식하지 못한다)
다음 블로그에 자세한 내용이 기술되어 있다.
브라우저별 이벤트 처리 자... 이정도 공부를 했으니 이제 소스를 하나씩 살펴보기로 하자.
Recent Comment