12장 좀 늦었지요 예예

12 views
Skip to first unread message

unread,
Nov 11, 2011, 10:23:43 AM11/11/11
to IBKSYSTEM
소스코드 입니다.

<html>
<head>
<style type="text/css">
.odd {
background: grey;
}

.cur {
background: yellow;
}

.light {
background: lightgrey;
}

.rest {

}

.from {

}

.query {

}

.dark {

}

.suggest {

}

.submit {

}
</style>
<script type="text/javascript">
// written by Dean Edwards, 2005
// with input from Tino Zijdel, Matthias Miller, Diego Perini

// http://dean.edwards.name/weblog/2005/10/add-event/

function addEvent(element, type, handler) {
if (element.addEventListener) {
element.addEventListener(type, handler, false);
} else {
// assign each event handler a unique ID
if (!handler.$$guid) handler.$$guid = addEvent.guid++;
// create a hash table of event types for the element
if (!element.events) element.events = {};
// create a hash table of event handlers for each element/event pair
var handlers = element.events[type];
if (!handlers) {
handlers = element.events[type] = {};
// store the existing event handler (if there is one)
if (element["on" + type]) {
handlers[0] = element["on" + type];
}
}
// store the event handler in the hash table
handlers[handler.$$guid] = handler;
// assign a global event handler to do all the work
element["on" + type] = handleEvent;
}
};
// a counter used to create unique IDs
addEvent.guid = 1;

function removeEvent(element, type, handler) {
if (element.removeEventListener) {
element.removeEventListener(type, handler, false);
} else {
// delete the event handler from the hash table
if (element.events && element.events[type]) {
delete element.events[type][handler.$$guid];
}
}
};

function handleEvent(event) {
var returnValue = true;
// grab the event object (IE uses a global event object)
event = event || fixEvent(((this.ownerDocument || this.document ||
this).parentWindow || window).event);
// get a reference to the hash table of event handlers
var handlers = this.events[event.type];
// execute each event handler
for (var i in handlers) {
this.$$handleEvent = handlers[i];
if (this.$$handleEvent(event) === false) {
returnValue = false;
}
}
return returnValue;
};

function fixEvent(event) {
// add W3C standard event methods
event.preventDefault = fixEvent.preventDefault;
event.stopPropagation = fixEvent.stopPropagation;
return event;
};
fixEvent.preventDefault = function() {
this.returnValue = false;
};
fixEvent.stopPropagation = function() {
this.cancelBubble = true;
};

function domReady( f ) {
// If the DOM is already loaded, execute the function right away
if ( domReady.done ) return f();

// If we’ve already added a function
if ( domReady.timer ) {
// Add it to the list of functions to execute
domReady.ready.push( f );
} else {
// Attach an event for when the page finishes loading,
// just in case it finishes first. Uses addEvent.
addEvent( window, "load", isDOMReady );

// Initialize the array of functions to execute
domReady.ready = [ f ];

// Check to see if the DOM is ready as quickly as possible
domReady.timer = setInterval( isDOMReady, 13 );
}
}

// Checks to see if the DOM is ready for navigation
function isDOMReady() {
// If we already figured out that the page is ready, ignore
if ( domReady.done ) return false;

// Check to see if a number of functions and elements are
// able to be accessed
if ( document && document.getElementsByTagName &&
document.getElementById && document.body ) {

// If they’re ready, we can stop checking
clearInterval( domReady.timer );
domReady.timer = null;

// Execute all the functions that were waiting
for ( var i = 0; i < domReady.ready.length; i++ )
domReady.ready[i]();

// Remember that we’re now done
domReady.ready = null;
domReady.done = true;
}
}

// Get a style property (name) of a specific element (elem)
function getStyle( elem, name ) {
// If the property exists in style[], then it’s been set recently
(and is current)
if (elem.style[name])
return elem.style[name];

// Otherwise, try to use IE’s method
else if (elem.currentStyle)
return elem.currentStyle[name];

// Or the W3C’s method, if it exists
else if (document.defaultView &&
document.defaultView.getComputedStyle) {
// It uses the traditional ‘text-align’ style of rule writing,
instead of textAlign
name = name.replace(/([A-Z])/g,"-$1");
name = name.toLowerCase();

// Get the style object and get the value of the property (if
it exists)
var s = document.defaultView.getComputedStyle(elem,"");
return s && s.getPropertyValue(name);

// Otherwise, we’re using some other browser
} else
return null;
}

// A function for hiding (using display) an element
function hide( elem ) {
// Find out what it’s current display state is
var curDisplay = getStyle( elem, 'display');

// Remember its display state for later
if ( curDisplay != 'none')
elem.$oldDisplay = curDisplay;

// Set the display to none (hiding the element)
elem.style.display = 'none';
}

// A function for showing (using display) an element
function show( elem ) {
// Set the display property back to what it use to be, or use
// 'block’, if no previous display had been saved
elem.style.display = elem.$oldDisplay || 'block';
}

// A generic function for performming AJAX requests
// It takes one argument, which is an object that contains a set of
options
// All of which are outline in the comments, below
function ajax( options ) {

// Load the options object with defaults, if no
// values were provided by the user
options = {
// The type of HTTP Request
type: options.type || "POST",

// The URL the request will be made to
url: options.url || "",

// How long to wait before considering the request to be a
timeout
timeout: options.timeout || 5000,

// Functions to call when the request fails, succeeds,
// or completes (either fail or succeed)
onComplete: options.onComplete || function(){},
onError: options.onError || function(){},
onSuccess: options.onSuccess || function(){},

// The data type that'll be returned from the server
// the default is simply to determine what data was returned
from the
// and act accordingly.
data: options.data || ""
};

// Create the request object
var xml = new XMLHttpRequest();

// Open the asynchronous POST request
xml.open(options.type, options.url, true);

// We're going to wait for a request for 5 seconds, before giving
up
var timeoutLength = options.timeout;

// Keep track of when the request has been succesfully completed
var requestDone = false;

// Initalize a callback which will fire 5 seconds from now,
cancelling
// the request (if it has not already occurred).
setTimeout(function(){
requestDone = true;
}, timeoutLength);

// Watch for when the state of the document gets updated
xml.onreadystatechange = function(){
// Wait until the data is fully loaded,
// and make sure that the request hasn't already timed out
if ( xml.readyState == 4 && !requestDone ) {

// Check to see if the request was successful
if ( httpSuccess( xml ) ) {

// Execute the success callback with the data returned
from the server
options.onSuccess( httpData( xml, options.type ) );

// Otherwise, an error occurred, so execute the error
callback
} else {
options.onError();
}

// Call the completion callback
options.onComplete();

// Clean up after ourselves, to avoid memory leaks
xml = null;
}
};

// Establish the connection to the server
xml.send();

// Determine the success of the HTTP response
function httpSuccess(r) {
try {
// If no server status is provided, and we're actually
// requesting a local file, then it was successful
return !r.status && location.protocol == "file:" ||

// Any status in the 200 range is good
( r.status >= 200 && r.status < 300 ) ||

// Successful if the document has not been modified
r.status == 304 ||

// Safari returns an empty status if the file has not
been modified
navigator.userAgent.indexOf("Safari") >= 0 && typeof
r.status == "undefined";
} catch(e){}

// If checking the status failed, then assume that the request
failed too
return false;
}

// Extract the correct data from the HTTP response
function httpData(r,type) {
// Get the content-type header
var ct = r.getResponseHeader("content-type");

// If no default type was provided, determine if some
// form of XML was returned from the server
var data = !type && ct && ct.indexOf("xml") >= 0;

// Get the XML Document object if XML was returned from
// the server, otherwise return the text contents returned by
the server
data = type == "xml" || data ? r.responseXML : r.responseText;

// If the specified type is "script", execute the returned
text
// response as if it was JavaScript
if ( type == "script" )
eval.call( window, data );

// Return the response data (either an XML Document or a text
string)
return data;
}

}
function id($id) {
return document.getElementById($id);
}

function delayedInput(opt) {
// The amount of time to wait before looking for new user input
opt.time = opt.time || 400;

// The minimum number of characters to wait for, before firing a
request
opt.chars = opt.chars != null ? opt.chars : 3;

// The callback to fire when the results popup should be opened,
// and possibly when a new request should be made
opt.open = opt.open || function(){};

// The callback to execute when the results popup should be closed
opt.close = opt.close || function(){};

// Should the focus of the field be taken into account, for
// opening/closing the results popup
opt.focus = opt.focus !== null ? opt.focus : false;

// Remember the original value that we're starting with
var old = opt.elem.value;

// And the current open/close state of the results popup
var state = false;

// Check to see if there's been a change in the input,
// at a given interval
setInterval(function(){
// The new input value
var newValue = opt.elem.value;

// The number of characters that've been entered
var len = newValue.length;

// Quickly check to see if the value has changed since the
last
// time that we checked the input
if ( old != newValue ) {

// If not enough characters have been entered, and the
'popup'
// is currently open
if ( len < opt.chars && state ) {

// Close the display
opt.close();

// And remember that it's closed
state = false;

// Otherwise, if the minimum number of characters have
been entered
// as long as its more than one character
} else if ( len >= opt.chars && len > 0 ) {

// Open the results popup with the current value
opt.open( newValue, state );

// Remember that the popup is current open
state = true;

}

// Save the current value for later
old = newValue;
}
}, opt.time );

// Watch for a key press
opt.elem.onkeyup = function(){
// If the keypress resulted in their being no more characters
left,
// close the results popup
if ( this.value.length == 0 ) {
// Close the popup
opt.close();

// Remember that it's closed
state = false;
}
};

// If we're also checking for user focus (to handle opening/
closing)
// the results popup
if ( opt.focus ) {
// Watch for when the user moves away from the input
opt.elem.onblur = function(){
// If its currently open
if ( state ) {
// Close the popup
opt.close();

// And remember that its closed
state = false;
}
}

// Watch for when the user focus' back on the popup
opt.elem.focus = function(){
// If it has a value, and its currently closed
if ( this.value.length != 0 && !state ) {
// Re-open the popup - but with a blank value
// (this lets the 'open' function know not to re-
retreive
// new results from the server, just re-open the popup).
opt.open( '', state );

// And remembr that the popup is open
state = true;
}
};
}
}

function addClass(ele, classname) {
ele.className += " " + classname;
}

function removeClass(ele, classname) {
var reg = new RegExp('(\\s|^)' + classname + '(\\s|$)');
ele.className = ele.className.replace(reg, ' ');
}

domReady(function(){
// Make sure that the results popup is closed, to begin with
hide( id("results") );

// Keep track of which users have already been entered
var doneUsers = {};

// Keep track of which user is currently selected
var curPos;

// Watch for input in the entry field
id("q").onkeydown = function(e){
// Get all of the users in the result set
var li = id("results").getElementsByTagName("li");

// If the [TAB] or [Enter] keys are pressed
if ( e.keyCode == 9 || e.keyCode == 13 ) {
// Reset the list of current users
loadDone();

// If the currently selected user is not in the list of
selcted
// users, add it on to the input
if ( !doneUsers[ curPos.id ] )
addUser( curPos );

// Stop the key from doing its normal action
e.preventDefault();
return false;

// If the up key is presssed
} else if ( e.keyCode == 38 )
// Select the previous user, or the last user (if we're at
the beginning)
return updatePos( curPos.previousSibling || li[ li.length
- 1 ] );

// If the down key is pressed
else if ( e.keyCode == 40 )
// Select the next user, or the first user (if we're at
the end)
return updatePos( curPos.nextSibling || li[0] );
};

// Initialize the delayed input checks on our input
delayedInput({
// We're attaching to the input text field
elem: id("q"),

// We're going to start searching after only 1 character of
input
chars: 1,

// When the text field loses focus, close the results popup
focus: true,

// Handle when the result popup should be opened up
open: function(q,open){
// Get the last word out of the comma-separated list of
words
var w = trim( q.substr( q.lastIndexOf(',')+1,
q.length ) );

// Make sure that we're dealing with a word, at least
if ( w ) {
// Show the loading spinner animation
show( id("qloading") );

// Make sure that no user is currently selected
curPos = null;

// Get the UL that holds all the results
var results = id("results").lastChild;

// And empty it out
results.innerHTML = "";

// Do a request for new data
ajax({
// Do a simple GET request to the CGI script which
// returns an HTML block of LI elements
type: "GET",
url: "users/" + w,

// Watch for when the HTML comes back
onSuccess: function(html){
// Insert it in to the results UL
results.innerHTML = html;

// And hide the loading animation
hide( id("qloading") );

// Re-initalize the list of users that we've
pulled in
loadDone();

// Go through each of the returned users
var li = results.getElementsByTagName( "li" );
for ( var i = 0; i < li.length; i++ ) {

// If we're already added the user, remove
the LI for it
if ( doneUsers [ li[i].id ] )
results.removeChild( li[i--] );

// Otherwise, bind some events to the user
li
else {

// Whenever the user mouses over the
li,
// set it to be the currently
hilighted user
li[i].onmouseover = function(){
updatePos( this );
};

// When the user is clicked
li[i].onclick = function(){
// Add the user to the input
addUser( this );

// And focus back on the input
again
id("q").focus();
};
}
}

// Go through the list of user li
li = results.getElementsByTagName( "li" );

// If there are no users left (we've added
them all)
if ( li.length == 0 )
// Then hide the results
hide( id("results") );

else {

// Add 'odd' classes to each of the
remaining users
// to give them a striping
for ( var i = 1; i < li.length; i += 2 )
addClass( li[i], "odd" );

// Set the currently selected user to the
first one
updatePos( li[0] );

// And then show the results
show( id("results") );
}
}
});
}
},

// When the popup needs to be closed
close: function(){
// Hide the result set
hide( id("results") );
}
});


function trim(s) {
return s.replace(/^\s+/,"").replace(/\s+$/, "");
}

// Change the hilite of the user that's currently selected
function updatePos( elem ) {
// Update the position to the currently selected element
curPos = elem;

// Get all the user li elements
var li = id("results").getElementsByTagName("li");

// Remove the 'cur' class from the currently selected one
for ( var i = 0; i < li.length; i++ )
removeClass( li[i], "cur" );

// And add the hilite to the current user item
addClass( curPos, "cur" );

return false;
}

// Re-initialize the list of users that have already been
// entered into the text input by the user
function loadDone() {
doneUsers = {};

// Go through the list of users (separated by commas)
var users = id("q").value.split(',');
for ( var i = 0; i < users.length; i++ ) {

// Save the username (as the key) in an object hash
doneUsers[ trim( users[i].toLowerCase() ) ] = true;
}
}

// Add a user to the input text field
function addUser( elem ) {
// The text value of the text input
var v = id("q").value;

// Add the user's name at the end of the end of the input
// Making sure that its separated with the correct comma
id("q").value =
( v.indexOf(',') >= 0 ? v.substr(0, v.lastIndexOf(',') +
2 ) : '' )
+ elem.id + ", ";

// Add the username to the master list (avoids having
// to completely re-load the list)
doneUsers[ elem.id ] = true;

// Remove the user li element
elem.parentNode.removeChild( elem );

// And hide the results list
hide( id("results") );
}
});

</script>
</head>
<body>
<form action="" method="POST" id="auto">
<div id="top">
<div id="mhead"><strong>Send Message</strong></div>
<div class="light">
<label>From:</label>
<div class="rest from">
<strong>John Resig</strong>
</div>
</div>
<div class="query dark">
<label>To:</label>
<div class="rest">
<img src="indicator.gif" id="qloading"/>
<input type="text" id="q" name="q"
autocomplete="off"/>
<div id="results"><div
class="suggest">Suggestions:</div><ul></ul></div>
</div>
</div>
<div class="light"><textarea></textarea></div>
<div class="submit"><input type="submit" value="&raquo;
Send"/></div>
</div>
</form>
</body>
</html>

한 파일에 통으로 작성했습니다 ㅎㅎ 서버사이드는 레일스로 간단하게 작성했었고요.
볼만한 코드는
function delayedInput(opt)
- 조금 늦게 이벤트를 감지합니다.
- opt 는 옵션 객체로 특이하게 사용되고 있습니다. 습관화되고 객체를 잘 보이게(사용 예제를 주석으로 붙인다던가) 하면
인수를 덕지덕지 붙이지 않고 편하게 복잡한 인자를 넘길 수 있는 방법인 것 같습니다.
- 조금 늦게 이벤트를 감지하는 건 ajax의 적용으로 인해 서버에 부하가 너무 많이 걸리는 것을 방지합니다.
- 그 외에도 여러가지로 적용할 수 있을 것 같습니다.
onkeydown
- 키보드 이벤트는 onkeydown으로 감지합니다. 브라우저마다 키보드 이벤트가 다르게 처리됩니다. 크롬의 경우에는 화살
표 키를 눌렀을 때 onkeydown으로 감지합니다.

이 정도가 이번 장에서 주목할 만한 내용이었습니다 ^^
Reply all
Reply to author
Forward
0 new messages