diff --git a/ChangeLog b/ChangeLog index 75af1b3664ec5bdb23e0efd74b1905161221f0f0..b2fa00024eb913dc4cf6251fc8db3d6354fe4a23 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,9 @@ +train-2011.09.01: + * /ws_api/set_key always returns returns value instead of HTTP 204 response: #219 + * update javascript mvc to 3.1.0. + * major interframe/window communication change using a hidden relay iframe to facilitate IE: #97(still open) + * link colors on browserid.org are consistent: #227 + train-2011.08.25: * created command line load generation tool and performance analysis work: #125 * beginning unit/functional tests for front end: #183 @@ -14,7 +20,8 @@ train-2011.08.25: * minify include.js by default: #206 * more than one email address can be added per dialog lifespan: #215 * verifyier no longer verifies assertions issued by another server. - + * (2011.08.31) no error message displayed if you try to authenticate with an invalid u/p: #222 + train-2011.08.18: * upon clickthrough of the email link, don't have the browser window close itself: #162 * passwords must be between 8 and 80 chars: #155 diff --git a/browserid/app.js b/browserid/app.js index d15177098832f7b4e9c0d516ab11875b7503f75c..a1688db343ffeb7a675a24680b3a881d14b9d0df 100644 --- a/browserid/app.js +++ b/browserid/app.js @@ -85,6 +85,17 @@ function router(app) { // simple redirects (internal for now) app.get('/register_iframe', internal_redirector('/dialog/register_iframe.html')); + // Used for a relay page for communication. + app.get(/^\/relay(\.html)?$/, function(req,res, next) { + // Allow the relay to be run within a frame + res.removeHeader('x-frame-options'); + res.render('relay.ejs', { + layout: false, + production: configuration.get('use_minified_resources') + }); + }); + + app.get('/', function(req,res) { res.render('index.ejs', {title: 'A Better Way to Sign In', fullpage: true}); }); diff --git a/browserid/compress.sh b/browserid/compress.sh index 21d53c72b3b6c6bc5bc8fbf89587c8d9291c19fa..c23c6bb7fc1a3c151f66dda3c4c67deea65e6a5e 100755 --- a/browserid/compress.sh +++ b/browserid/compress.sh @@ -32,6 +32,13 @@ cd dialog $UGLIFY < production.js > production.min.js mv production.min.js production.js +cd .. +steal/js relay/scripts/build.js +cd relay +$UGLIFY < production.js > production.min.js +mv production.min.js production.js + + echo '' echo '****Building BrowserID.org HTML, CSS, and JS****' echo '' diff --git a/browserid/static/css/style.css b/browserid/static/css/style.css index 09e35763dae0f81c7167cd922978e807dbfff4d1..228a0a20df61431e2a3ab4b560d820fd2cee0c4d 100644 --- a/browserid/static/css/style.css +++ b/browserid/static/css/style.css @@ -2,14 +2,16 @@ font-family: 'Shadows Into Light'; font-style: normal; font-weight: normal; - src: local('Shadows Into Light'), local('ShadowsIntoLight'), url('sil.ttf') format('truetype'); +/* src: local('Shadows Into Light'), local('ShadowsIntoLight'), url('sil.ttf') + * format('truetype');*/ } @font-face { font-family: 'Tenor Sans'; font-style: normal; font-weight: normal; - src: local('Tenor Sans'), local('TenorSans'), url('ts.ttf') format('truetype'); + /* src: local('Tenor Sans'), local('TenorSans'), url('ts.ttf') + * format('truetype');*/ } body { @@ -128,11 +130,17 @@ header > #manageLink:hover { opacity: 1; } -a { +header a { color: #fff; text-decoration: none; } +a { + color: #666; + text-decoration: none; + border-bottom: 1px dotted #666; +} + a:hover { opacity: .6; } @@ -166,9 +174,6 @@ footer .right img { margin-bottom: -5px; } -footer a { - color: #666; -} footer .right p { text-align: right; @@ -186,12 +191,6 @@ footer .copyright { padding: 0; } -#steps a { - color: #666; - text-decoration: none; - border-bottom: 1px dotted #666; -} - .step { margin: 1em 0 2em 0; padding: 0 0 0 50px; @@ -243,17 +242,17 @@ pre code { } #emailList { - font-size: 1.0em; - width: 4x00px; - margin: auto; + font-size: 1.0em; + width: 4x00px; + margin: auto; font-weight:bold; margin-top:32px; } #cancelaccount { - font-size: 1.0em; - width: 500px; - margin: auto; + font-size: 1.0em; + width: 500px; + margin: auto; margin-top:35px; } @@ -277,7 +276,7 @@ pre code { .meta { display:inline-block; float:right; - font:8pt Arial; + font:8pt Arial; } .meta a { cursor:pointer; diff --git a/browserid/static/css/style.css.bak b/browserid/static/css/style.css.bak new file mode 100644 index 0000000000000000000000000000000000000000..1afd0ae2d434477e9a279c1b030c38b6b3f7904b --- /dev/null +++ b/browserid/static/css/style.css.bak @@ -0,0 +1,322 @@ +@font-face { + font-family: 'Shadows Into Light'; + font-style: normal; + font-weight: normal; +/* src: local('Shadows Into Light'), local('ShadowsIntoLight'), url('sil.ttf') + * format('truetype');*/ +} + +@font-face { + font-family: 'Tenor Sans'; + font-style: normal; + font-weight: normal; + /* src: local('Tenor Sans'), local('TenorSans'), url('ts.ttf') + * format('truetype');*/ +} + +body { + padding: 0; + margin: 0; + font-size: 12px; + font-family: 'Tenor Sans', arial, serif; +} + +header { + border-top: 4px solid #333; + background-color: #333; + padding: 0 20% 0 20%; + margin: 0; + background: #008; + height: 230px; + color #fff; + min-width: 800px; +} + +header.quarter { + height: 50px; +} + +#labslogo { + margin: auto; + text-align: left; + background: url("/i/labs-logo.png") 0 0 no-repeat; + width: 161px; + height: 34px; + margin: 10px 0 0 110px; + color: transparent; + display: inline-block; +} + +.half h1 { + padding-top: .2em; + font-size: 3em; + font-weight: bold; + background: url("/i/browserid_logo.png") 0 0 no-repeat; + width: 366px; + height: 72px; + margin: 43px auto 0 auto; + clear: left; + color: transparent; +} + +.quarter h1 { + float: left; + width: 135px; + height: 35px; + margin: 7px 0 0 10px; +} + +.quarter h1 > a { + color: transparent; +} + + +h2 { + font-family: 'Shadows Into Light', arial, serif; + font-size: 3em; + font-weight: normal; + color: #fff; + margin: -4px auto 0 auto; + text-align: center; +} + +.quarter h2 { + font-size: 2em; + margin: .2em 0 0 .5em; + float: left; +} + + +header > #manageLink { + float: right; + background-color: #333; + color: #fff; + padding: 1px 10px 5px 10px; + -webkit-border-radius: 0 0 10px 10px; + -moz-border-radius: 0 0 10px 10px; + border-radius: 0 0 10px 10px; + display: none; +} + +.authenticated header > #manageLink { + display: block; +} + +header > #manageLink:hover { + opacity:1; + color: #AAA; +} + +.why { + width: 600px; + height: 130px; + padding: 10px 0; + margin: 0 auto; +} + +.why p { + font-size: 2em; + text-align: center; +} + +.why a, #cancellink { + color: #666; + text-decoration: none; + border-bottom: 1px dotted black; +} + +.why a:hover { + color: #000; + opacity: 1; +} + +a { + color: #fff; + text-decoration: none; +} + +a:hover { + opacity: .6; +} + +footer { + background-color: #F1F1F1; + border-top: 2px solid #ddd; + margin: 0; + margin-top: 100px; + padding: 0; + height: 200px; + font-size: 1.1em; +} + +footer > div { + width: 800px; + margin: auto; +} + +footer > div > div { + width: 400px; + padding: 10px; +} + +footer .right { + padding-top: 13px; + float: right; +} + +footer .right img { + margin-bottom: -5px; +} + +footer a { + color: #666; +} + +footer .right p { + text-align: right; +} + +footer .copyright { + font-weight: bold; + font-size: .8em; +} + +#steps { + list-style: none; + width: 800px; + margin: 0 auto; + padding: 0; +} + +#steps a { + color: #666; + text-decoration: none; + border-bottom: 1px dotted #666; +} + +.step { + margin: 1em 0 2em 0; + padding: 0 0 0 50px; + font-size: 14px; + position: relative; +} + +.step .number { + position: absolute; + top: -5px; + left: 0; + font-family: 'Shadows Into Light', arial, serif; + font-size: 4em; + font-weight: bold; + line-height: 1em; +} + +.step > h3 { + font-size: 1em; + margin: 0; + display: inline; +} + +.step > pre { + clear: both; +} + +.step > p, .step > ol { + margin-left: 50px; +} + +.prose { + font-size: 1.5em; +} + +.status { + margin: 0 auto; + width: 600px; + font-size: 1.2em; +} + +pre code { + padding: 10px 15px 10px 15px; + margin: .75em; + -webkit-border-radius: 10px; + -moz-border-radius: 10px; + border-radius: 10px 10px 10px 10px; + width: 650px; +} + +#emailList { + font-size: 1.0em; + width: 4x00px; + margin: auto; + font-weight:bold; + margin-top:32px; +} + +#cancelaccount { + font-size: 1.0em; + width: 500px; + margin: auto; + margin-top:35px; +} + +.email { + display:inline-block; +} +.emailblock a { + font-size:0.7em; + color:#405090; +} +.emailblock { + border: 1px solid #ddd; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + background-color:#f0f0f0; + width:500px; + padding:8px; + min-height:48px; + margin:16px auto; +} +.meta { + display:inline-block; + float:right; + font:8pt Arial; +} +.meta a { + cursor:pointer; +} +.keyblock { + font:8pt Arial; +} +.date { + font:8pt Arial; +} + +.buttonbox { + height: 25px; + width: 460px; + margin: auto; +} +.buttonbox > div { + float: left; + margin-right: 10px; +} + +.legal { + font-size: 1.3em; + width: 800px; + margin: 0 auto; +} + +.legal li { + margin-bottom: 7px; +} + +.legal h5 { + margin-bottom: 1px; + padding-bottom: 1px; + line-height: 100%; +} + +.legal p { + margin-top : 1px; +} diff --git a/browserid/static/dialog/dialog.js b/browserid/static/dialog/dialog.js index c5d82553597d171b7fcd540149012a402acf3e3f..3a17ff8f4b3108ea274e0c5ef9376db5b196930a 100644 --- a/browserid/static/dialog/dialog.js +++ b/browserid/static/dialog/dialog.js @@ -35,6 +35,10 @@ /*globals steal */ +window.console = window.console || { + log: function() {} +}; + steal.plugins( 'jquery/controller', // a widget factory 'jquery/controller/subscribe', // subscribe to OpenAjax.hub @@ -44,6 +48,7 @@ steal.plugins( .css("style") // loads styles .resources('jschannel', + 'base64', 'underscore-min', 'crypto', 'crypto-api', @@ -55,7 +60,7 @@ steal.plugins( 'browserid-errors', 'browserid-wait') // 3rd party script's (like jQueryUI), in resources folder - .models() // loads files in models folder + .models() // loads files in models folder .controllers('page', 'dialog', @@ -80,4 +85,10 @@ steal.plugins( 'forgotpassword.ejs', 'signin.ejs', 'wait.ejs' - ); // adds views to be added to build + ). + + then(function() { + $(function() { + $('body').dialog().show(); + }); + }); // adds views to be added to build diff --git a/browserid/static/dialog/resources/base64.js b/browserid/static/dialog/resources/base64.js new file mode 100644 index 0000000000000000000000000000000000000000..3da45c627a6605055f9d134c485a983dff1606cc --- /dev/null +++ b/browserid/static/dialog/resources/base64.js @@ -0,0 +1,69 @@ +;(function (window) { + + var + characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=', + fromCharCode = String.fromCharCode, + INVALID_CHARACTER_ERR = (function () { + // fabricate a suitable error object + try { document.createElement('$'); } + catch (error) { return error; }}()); + + // encoder + window.btoa || ( + window.btoa = function (string) { + var + a, b, b1, b2, b3, b4, c, i = 0, + len = string.length, max = Math.max, result = ''; + + while (i < len) { + a = string.charCodeAt(i++) || 0; + b = string.charCodeAt(i++) || 0; + c = string.charCodeAt(i++) || 0; + + if (max(a, b, c) > 0xFF) { + throw INVALID_CHARACTER_ERR; + } + + b1 = (a >> 2) & 0x3F; + b2 = ((a & 0x3) << 4) | ((b >> 4) & 0xF); + b3 = ((b & 0xF) << 2) | ((c >> 6) & 0x3); + b4 = c & 0x3F; + + if (!b) { + b3 = b4 = 64; + } else if (!c) { + b4 = 64; + } + result += characters.charAt(b1) + characters.charAt(b2) + characters.charAt(b3) + characters.charAt(b4); + } + return result; + }); + + // decoder + window.atob || ( + window.atob = function (string) { + string = string.replace(/=+$/, ''); + var + a, b, b1, b2, b3, b4, c, i = 0, + len = string.length, chars = []; + + if (len % 4 === 1) throw INVALID_CHARACTER_ERR; + + while (i < len) { + b1 = characters.indexOf(string.charAt(i++)); + b2 = characters.indexOf(string.charAt(i++)); + b3 = characters.indexOf(string.charAt(i++)); + b4 = characters.indexOf(string.charAt(i++)); + + a = ((b1 & 0x3F) << 2) | ((b2 >> 4) & 0x3); + b = ((b2 & 0xF) << 4) | ((b3 >> 2) & 0xF); + c = ((b3 & 0x3) << 6) | (b4 & 0x3F); + + chars.push(fromCharCode(a)); + b && chars.push(fromCharCode(b)); + c && chars.push(fromCharCode(c)); + } + return chars.join(''); + }); + +}(this)); diff --git a/browserid/static/dialog/resources/channel.js b/browserid/static/dialog/resources/channel.js index 43aa6703fb4c8d72d6a5b8c059c259dd4250d1d1..2af6fde45704fb3bfc136ce5b779e03f1c5f2e61 100644 --- a/browserid/static/dialog/resources/channel.js +++ b/browserid/static/dialog/resources/channel.js @@ -1,3 +1,4 @@ +/*global alert:true, setupNativeChannel:true, setupIFrameChannel:true*/ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * @@ -33,60 +34,97 @@ * * ***** END LICENSE BLOCK ***** */ -/*global alert:true, setupNativeChannel:true, setupHTMLChannel:true, Channel:true */ -function errorOut(trans, code) { - function getVerboseMessage(code) { - var msgs = { - "canceled": "user canceled selection", - "notImplemented": "the user tried to invoke behavior that's not yet implemented", - "serverError": "a technical problem was encountered while trying to communicate with BrowserID servers." - }; - var msg = msgs[code]; - if (!msg) { - alert("need verbose message for " + code); - msg = "unknown error"; - } - return msg; +// The way this works, when the dialog is opened from a web page, it opens +// the window with a #host=<requesting_host_name> parameter in its URL. +// window.setupChannel is called automatically when the dialog is opened. We +// assume that navigator.id.getVerifiedEmail was the function called, we will +// keep this assumption until we start experimenting. Since IE has some +// serious problems iwth postMessage from a window to a child window, we are now +// communicating not directly with the calling window, but with an iframe +// on the same domain as us that we place into the calling window. We use a +// function within this iframe to relay messages back to the calling window. +// We do so by searching for the frame within the calling window, and then +// getting a reference to the proxy function. When getVerifiedEmail is +// complete, it calls the proxy function in the iframe, which then sends a +// message back to the calling window. + + + +(function() { + // Read a page's GET URL variables and return them as an associative array. + function getUrlVars() { + var hashes = {}, + hash, + pairs = window.location.href.slice(window.location.href.indexOf('#') + 1).split('&'); + + for(var i = 0, pair; pair=pairs[i]; ++i) { + hash = pair.split('='); + hashes[hash[0]] = hash[1]; + } + return hashes; } - trans.error(code, getVerboseMessage(code)); - window.self.close(); -} + function getRelay() { + var frameWindow = window.opener.frames['browserid_relay']; + return frameWindow && frameWindow['browserid_relay']; + } + + + function errorOut(trans, code) { + function getVerboseMessage(code) { + var msgs = { + "canceled": "user canceled selection", + "notImplemented": "the user tried to invoke behavior that's not yet implemented", + "serverError": "a technical problem was encountered while trying to communicate with BrowserID servers." + }; + var msg = msgs[code]; + if (!msg) { + alert("need verbose message for " + code); + msg = "unknown error"; + } + return msg; + } + trans.error(code, getVerboseMessage(code)); + window.self.close(); + } -var setupChannel = function(controller) { - if (navigator.id && navigator.id.channel) - setupNativeChannel(controller); - else - setupHTMLChannel(controller); -}; -var setupNativeChannel = function(controller) { - navigator.id.channel.registerController(controller); -}; + window.setupChannel = function(controller) { + if (navigator.id && navigator.id.channel) + setupNativeChannel(controller); + else + setupIFrameChannel(controller); + }; -var setupHTMLChannel = function(controller) { - var chan = Channel.build( - { - window: window.opener, - origin: "*", - scope: "mozid" - }); + var setupNativeChannel = function(controller) { + navigator.id.channel.registerController(controller); + }; - var remoteOrigin; + var setupIFrameChannel = function(controller) { + var hash = getUrlVars(); + var origin = hash['host']; - chan.bind("getVerifiedEmail", function(trans, s) { - trans.delayReturn(true); + // TODO - Add a check for whether the dialog was opened by another window + // (has window.opener) as well as whether the relay function exists. + // If these conditions are not met, then print an appropriate message. function onsuccess(rv) { - trans.complete(rv); + // Get the relay here so that we ensure that the calling window is still + // open and we aren't causing a problem. + var relay = getRelay(); + if(relay) { + relay(rv, null); + } } function onerror(error) { - errorOut(trans, error); + var relay = getRelay(); + if(relay) { + relay(null, error); + } } - controller.getVerifiedEmail(trans.origin, onsuccess, onerror); - }); + controller.getVerifiedEmail(origin, onsuccess, onerror); + }; - return chan; -}; +}()); diff --git a/browserid/static/dialog/style.css b/browserid/static/dialog/style.css index 4e73d3384dcfc427f6e197c0ab15e38d0a7849d1..31a0c8a78fae8e1078d1b9b5f9849058d4426605 100644 --- a/browserid/static/dialog/style.css +++ b/browserid/static/dialog/style.css @@ -3,7 +3,8 @@ font-family: 'Tenor Sans'; font-style: normal; font-weight: normal; - src: local('Tenor Sans'), local('TenorSans'), url('/css/ts.ttf') format('truetype'); + /*src: local('Tenor Sans'), local('TenorSans'), url('/css/ts.ttf') + * format('truetype');*/ } body { diff --git a/browserid/static/funcunit/.gitignore b/browserid/static/funcunit/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..209b1559f2c28680dac09371a49924b24fbffec8 --- /dev/null +++ b/browserid/static/funcunit/.gitignore @@ -0,0 +1,5 @@ +.tmp* +*.log +docs/* +dist +synthetic/dist \ No newline at end of file diff --git a/browserid/static/funcunit/.gitmodules b/browserid/static/funcunit/.gitmodules new file mode 100644 index 0000000000000000000000000000000000000000..1e7130ff57700fbb99b88b7d31e3a26528eb46f6 --- /dev/null +++ b/browserid/static/funcunit/.gitmodules @@ -0,0 +1,4 @@ +[submodule "syn"] + path = syn + url = git://github.com/jupiterjs/syn.git + update = merge \ No newline at end of file diff --git a/browserid/static/funcunit/README b/browserid/static/funcunit/README new file mode 100644 index 0000000000000000000000000000000000000000..dee5b775581f3482634083a1150754ec3b74d841 --- /dev/null +++ b/browserid/static/funcunit/README @@ -0,0 +1,17 @@ +Relevant links: +1. http://jupiterit.com/#news/funcunit-fun-web-application-testing +2. http://groups.google.com/group/funcunit +3. http://twitter.com/funcunit +4. http://funcunit.com/ + + +Building a new FuncUnit +1. Clone the repository at http://github.com/jupiterjs/javascriptmvc +2. Use git submodule init and git submodule update to get the submodules. The only ones we'll need are steal and funcunit +3. Unzip funcunit/java/selenium-server.jar into a folder at funcunit/java/selenium-server/ +4. Make whatever changes you want to syn, funcunit, drivers, or selenium. +5. From the framework directory, on the command line run js funcunit/build.js. This will build the JS scripts and copy the relevant files. +6. Using a tool like 7-zip (for windows), zip up the java/selenium-server folder. Copy the selenium-server.jar into the funcunit/java folder. +7. Run js funcunit/build.js again (to copy the jars). + +That's it, funcunit/dist contains the same standalone funcunit placed into the standalone download. diff --git a/browserid/static/funcunit/autosuggest/auto_suggest.js b/browserid/static/funcunit/autosuggest/auto_suggest.js new file mode 100644 index 0000000000000000000000000000000000000000..0e496081f53d2dd14b3d7f7d1f9f553f0c661c19 --- /dev/null +++ b/browserid/static/funcunit/autosuggest/auto_suggest.js @@ -0,0 +1,495 @@ + /** + * o------------------------------------------------------------------------------o + * | This package is licensed under the Phpguru license. A quick summary is | + * | that for commercial use, there is a small one-time licensing fee to pay. For | + * | registered charities and educational institutes there is a reduced license | + * | fee available. You can read more at: | + * | | + * | http://www.phpguru.org/static/license.html | + * o------------------------------------------------------------------------------o + */ + + /** + * Global vars + */ + __AutoComplete = new Array(); + + /** + * Basic UA detection + */ + if (navigator.userAgent.match(/Opera/)) { + isIE = false; + isOpera = true; + opera_version = Number(navigator.userAgent.match(/Version\/([0-9.]+)/)[1]); + + } else { + + isIE = document.all ? true : false; + isOpera = false; + opera_version = 0; + } + + + /** + * Attachs the autocomplete object to a form element. Sets + * onkeypress event on the form element. + * + * @param string formElement Name of form element to attach to + * @param array data Array of strings of which to use as the autocomplete data + */ + function AutoComplete_Create (id, data) + { + __AutoComplete[id] = {'data':data, + 'isVisible':false, + 'element':document.getElementById(id), + 'dropdown':null, + 'highlighted':null}; + + __AutoComplete[id]['element'].setAttribute('autocomplete', 'off'); + __AutoComplete[id]['element'].onkeydown = function(e) {return AutoComplete_KeyDown(this.getAttribute('id'), e);} + __AutoComplete[id]['element'].onkeyup = function(e) {return AutoComplete_KeyUp(this.getAttribute('id'), e);} + __AutoComplete[id]['element'].onkeypress = function(e) {if (!e) e = window.event;if (e.keyCode == 13) return false;} + __AutoComplete[id]['element'].ondblclick = function(e) {AutoComplete_ShowDropdown(this.getAttribute('id'));} + __AutoComplete[id]['element'].onclick = function(e) {if (!e) e = window.event; e.cancelBubble = true; e.returnValue = false;} + + // Hides the dropdowns when document clicked + var docClick = function() + { + for (id in __AutoComplete) { + AutoComplete_HideDropdown(id); + } + } + + if (document.addEventListener) { + document.addEventListener('click', docClick, false); + } else if (document.attachEvent) { + document.attachEvent('onclick', docClick, false); + } + + + // Max number of items shown at once + if (arguments[2] != null) { + __AutoComplete[id]['maxitems'] = arguments[2]; + __AutoComplete[id]['firstItemShowing'] = 0; + __AutoComplete[id]['lastItemShowing'] = arguments[2] - 1; + } + + AutoComplete_CreateDropdown(id); + + // Prevent select dropdowns showing thru + if (isIE) { + __AutoComplete[id]['iframe'] = document.createElement('iframe'); + __AutoComplete[id]['iframe'].id = id +'_iframe'; + __AutoComplete[id]['iframe'].style.position = 'absolute'; + __AutoComplete[id]['iframe'].style.top = '0'; + __AutoComplete[id]['iframe'].style.left = '0'; + __AutoComplete[id]['iframe'].style.width = '0px'; + __AutoComplete[id]['iframe'].style.height = '0px'; + __AutoComplete[id]['iframe'].style.zIndex = '98'; + __AutoComplete[id]['iframe'].style.visibility = 'hidden'; + + __AutoComplete[id]['element'].parentNode.insertBefore(__AutoComplete[id]['iframe'], __AutoComplete[id]['element']); + } + } + + + /** + * Creates the dropdown layer + * + * @param string id The form elements id. Used to identify the correct dropdown. + */ + function AutoComplete_CreateDropdown(id) + { + var left = AutoComplete_GetLeft(__AutoComplete[id]['element']); + var top = AutoComplete_GetTop(__AutoComplete[id]['element']) + __AutoComplete[id]['element'].offsetHeight; + var width = __AutoComplete[id]['element'].offsetWidth; + + __AutoComplete[id]['dropdown'] = document.createElement('div'); + __AutoComplete[id]['dropdown'].className = 'autocomplete'; // Don't use setAttribute() + + __AutoComplete[id]['element'].parentNode.insertBefore(__AutoComplete[id]['dropdown'], __AutoComplete[id]['element']); + + // Position it + __AutoComplete[id]['dropdown'].style.left = left + 'px'; + __AutoComplete[id]['dropdown'].style.top = top + 'px'; + __AutoComplete[id]['dropdown'].style.width = width + 'px'; + __AutoComplete[id]['dropdown'].style.zIndex = '99'; + __AutoComplete[id]['dropdown'].style.visibility = 'hidden'; + } + + + /** + * Gets left coord of given element + * + * @param object element The element to get the left coord for + */ + function AutoComplete_GetLeft(element) + { + var curNode = element; + var left = 0; + + do { + left += curNode.offsetLeft; + + curNode = curNode.offsetParent; + + } while(curNode.tagName.toLowerCase() != 'body' && curNode.style.position != 'fixed'); + + return left; + } + + /** + * Gets top coord of given element + * + * @param object element The element to get the top coord for + */ + function AutoComplete_GetTop(element) + { + var curNode = element; + var top = 0; + + + do { + top += curNode.offsetTop; + curNode = curNode.offsetParent; + + } while(curNode.tagName.toLowerCase() != 'body' && curNode.style.position != 'fixed'); + + return top; + } + + + /** + * Shows the dropdown layer + * + * @param string id The form elements id. Used to identify the correct dropdown. + */ + function AutoComplete_ShowDropdown(id) + { + AutoComplete_HideAll(); + + var value = __AutoComplete[id]['element'].value; + var toDisplay = new Array(); + var newDiv = null; + var text = null; + var numItems = __AutoComplete[id]['dropdown'].childNodes.length; + + // Remove all child nodes from dropdown + while (__AutoComplete[id]['dropdown'].childNodes.length > 0) { + __AutoComplete[id]['dropdown'].removeChild(__AutoComplete[id]['dropdown'].childNodes[0]); + } + + // Go thru data searching for matches + for (i=0; i<__AutoComplete[id]['data'].length; ++i) { + if (__AutoComplete[id]['data'][i].indexOf(value) != -1) { + toDisplay[toDisplay.length] = __AutoComplete[id]['data'][i]; + } + } + + // No matches? + if (toDisplay.length == 0) { + AutoComplete_HideDropdown(id); + return; + } + + + + // Add data to the dropdown layer + for (i=0; i<toDisplay.length; ++i) { + newDiv = document.createElement('div'); + newDiv.className = 'autocomplete_item'; // Don't use setAttribute() + newDiv.setAttribute('id', 'autocomplete_item_' + i); + newDiv.setAttribute('index', i); + newDiv.style.zIndex = '99'; + + // Scrollbars are on display ? + if (toDisplay.length > __AutoComplete[id]['maxitems'] && navigator.userAgent.indexOf('MSIE') == -1) { + newDiv.style.width = __AutoComplete[id]['element'].offsetWidth - 22 + 'px'; + } + + newDiv.onmouseover = function() {AutoComplete_HighlightItem(__AutoComplete[id]['element'].getAttribute('id'), this.getAttribute('index'));}; + newDiv.onclick = function() {AutoComplete_SetValue(__AutoComplete[id]['element'].getAttribute('id')); AutoComplete_HideDropdown(__AutoComplete[id]['element'].getAttribute('id'));} + + text = document.createTextNode(toDisplay[i]); + newDiv.appendChild(text); + + __AutoComplete[id]['dropdown'].appendChild(newDiv); + } + + + // Too many items? + if (toDisplay.length > __AutoComplete[id]['maxitems']) { + __AutoComplete[id]['dropdown'].style.height = (__AutoComplete[id]['maxitems'] * 15) + 2 + 'px'; + + } else { + __AutoComplete[id]['dropdown'].style.height = ''; + } + + + /** + * Set left/top in case of document movement/scroll/window resize etc + */ + __AutoComplete[id]['dropdown'].style.left = AutoComplete_GetLeft(__AutoComplete[id]['element']); + __AutoComplete[id]['dropdown'].style.top = AutoComplete_GetTop(__AutoComplete[id]['element']) + __AutoComplete[id]['element'].offsetHeight; + + + // Show the iframe for IE + if (isIE) { + __AutoComplete[id]['iframe'].style.top = __AutoComplete[id]['dropdown'].style.top; + __AutoComplete[id]['iframe'].style.left = __AutoComplete[id]['dropdown'].style.left; + __AutoComplete[id]['iframe'].style.width = __AutoComplete[id]['dropdown'].offsetWidth; + __AutoComplete[id]['iframe'].style.height = __AutoComplete[id]['dropdown'].offsetHeight; + + __AutoComplete[id]['iframe'].style.visibility = 'visible'; + } + + + // Show dropdown + if (!__AutoComplete[id]['isVisible']) { + __AutoComplete[id]['dropdown'].style.visibility = 'visible'; + __AutoComplete[id]['isVisible'] = true; + } + + + // If now showing less items than before, reset the highlighted value + if (__AutoComplete[id]['dropdown'].childNodes.length != numItems) { + __AutoComplete[id]['highlighted'] = null; + } + } + + + /** + * Hides the dropdown layer + * + * @param string id The form elements id. Used to identify the correct dropdown. + */ + function AutoComplete_HideDropdown(id) + { + if (__AutoComplete[id]['iframe']) { + __AutoComplete[id]['iframe'].style.visibility = 'hidden'; + } + + + __AutoComplete[id]['dropdown'].style.visibility = 'hidden'; + __AutoComplete[id]['highlighted'] = null; + __AutoComplete[id]['isVisible'] = false; + } + + + /** + * Hides all dropdowns + */ + function AutoComplete_HideAll() + { + for (id in __AutoComplete) { + AutoComplete_HideDropdown(id); + } + } + + + /** + * Highlights a specific item + * + * @param string id The form elements id. Used to identify the correct dropdown. + * @param int index The index of the element in the dropdown to highlight + */ + function AutoComplete_HighlightItem(id, index) + { + if (__AutoComplete[id]['dropdown'].childNodes[index]) { + for (var i=0; i<__AutoComplete[id]['dropdown'].childNodes.length; ++i) { + if (__AutoComplete[id]['dropdown'].childNodes[i].className == 'autocomplete_item_highlighted') { + __AutoComplete[id]['dropdown'].childNodes[i].className = 'autocomplete_item'; + } + } + + __AutoComplete[id]['dropdown'].childNodes[index].className = 'autocomplete_item_highlighted'; + __AutoComplete[id]['highlighted'] = index; + } + } + + + /** + * Highlights the menu item with the given index + * + * @param string id The form elements id. Used to identify the correct dropdown. + * @param int index The index of the element in the dropdown to highlight + */ + function AutoComplete_Highlight(id, index) + { + // Out of bounds checking + if (index == 1 && __AutoComplete[id]['highlighted'] == __AutoComplete[id]['dropdown'].childNodes.length - 1) { + __AutoComplete[id]['dropdown'].childNodes[__AutoComplete[id]['highlighted']].className = 'autocomplete_item'; + __AutoComplete[id]['highlighted'] = null; + + } else if (index == -1 && __AutoComplete[id]['highlighted'] == 0) { + __AutoComplete[id]['dropdown'].childNodes[0].className = 'autocomplete_item'; + __AutoComplete[id]['highlighted'] = __AutoComplete[id]['dropdown'].childNodes.length; + } + + // Nothing highlighted at the moment + if (__AutoComplete[id]['highlighted'] == null) { + __AutoComplete[id]['dropdown'].childNodes[0].className = 'autocomplete_item_highlighted'; + __AutoComplete[id]['highlighted'] = 0; + + } else { + if (__AutoComplete[id]['dropdown'].childNodes[__AutoComplete[id]['highlighted']]) { + __AutoComplete[id]['dropdown'].childNodes[__AutoComplete[id]['highlighted']].className = 'autocomplete_item'; + } + + var newIndex = __AutoComplete[id]['highlighted'] + index; + + if (__AutoComplete[id]['dropdown'].childNodes[newIndex]) { + __AutoComplete[id]['dropdown'].childNodes[newIndex].className = 'autocomplete_item_highlighted'; + + __AutoComplete[id]['highlighted'] = newIndex; + } + } + } + + + /** + * Sets the input to a given value + * + * @param string id The form elements id. Used to identify the correct dropdown. + */ + function AutoComplete_SetValue(id) + { + __AutoComplete[id]['element'].value = __AutoComplete[id]['dropdown'].childNodes[__AutoComplete[id]['highlighted']].innerHTML; + } + + + /** + * Checks if the dropdown needs scrolling + * + * @param string id The form elements id. Used to identify the correct dropdown. + */ + function AutoComplete_ScrollCheck(id) + { + // Scroll down, or wrapping around from scroll up + if (__AutoComplete[id]['highlighted'] > __AutoComplete[id]['lastItemShowing']) { + __AutoComplete[id]['firstItemShowing'] = __AutoComplete[id]['highlighted'] - (__AutoComplete[id]['maxitems'] - 1); + __AutoComplete[id]['lastItemShowing'] = __AutoComplete[id]['highlighted']; + } + + // Scroll up, or wrapping around from scroll down + if (__AutoComplete[id]['highlighted'] < __AutoComplete[id]['firstItemShowing']) { + __AutoComplete[id]['firstItemShowing'] = __AutoComplete[id]['highlighted']; + __AutoComplete[id]['lastItemShowing'] = __AutoComplete[id]['highlighted'] + (__AutoComplete[id]['maxitems'] - 1); + } + + __AutoComplete[id]['dropdown'].scrollTop = __AutoComplete[id]['firstItemShowing'] * 15; + } + + + /** + * Function which handles the keypress event + * + * @param string id The form elements id. Used to identify the correct dropdown. + */ + function AutoComplete_KeyDown(id) + { + // Mozilla + if (arguments[1] != null) { + event = arguments[1]; + } + + var keyCode = event.keyCode; + + switch (keyCode) { + + // Return/Enter + case 13: + if (__AutoComplete[id]['highlighted'] != null) { + AutoComplete_SetValue(id); + AutoComplete_HideDropdown(id); + } + + event.returnValue = false; + event.cancelBubble = true; + break; + + // Escape + case 27: + AutoComplete_HideDropdown(id); + event.returnValue = false; + event.cancelBubble = true; + break; + + // Up arrow + case 38: + if (!__AutoComplete[id]['isVisible']) { + AutoComplete_ShowDropdown(id); + } + + AutoComplete_Highlight(id, -1); + AutoComplete_ScrollCheck(id, -1); + return false; + break; + + // Tab + case 9: + if (__AutoComplete[id]['isVisible']) { + AutoComplete_HideDropdown(id); + } + return; + + // Down arrow + case 40: + if (!__AutoComplete[id]['isVisible']) { + AutoComplete_ShowDropdown(id); + } + + AutoComplete_Highlight(id, 1); + AutoComplete_ScrollCheck(id, 1); + return false; + break; + } + } + + + /** + * Function which handles the keyup event + * + * @param string id The form elements id. Used to identify the correct dropdown. + */ + function AutoComplete_KeyUp(id) + { + // Mozilla + if (arguments[1] != null) { + event = arguments[1]; + } + + var keyCode = event.keyCode; + + switch (keyCode) { + case 13: + event.returnValue = false; + event.cancelBubble = true; + break; + + case 27: + AutoComplete_HideDropdown(id); + event.returnValue = false; + event.cancelBubble = true; + break; + + case 38: + case 40: + return false; + break; + + default: + AutoComplete_ShowDropdown(id); + break; + } + } + + /** + * Returns whether the dropdown is visible + * + * @param string id The form elements id. Used to identify the correct dropdown. + */ + function AutoComplete_isVisible(id) + { + return __AutoComplete[id]['dropdown'].style.visibility == 'visible'; + } \ No newline at end of file diff --git a/browserid/static/funcunit/autosuggest/autosuggest.css b/browserid/static/funcunit/autosuggest/autosuggest.css new file mode 100644 index 0000000000000000000000000000000000000000..ac88b8757bd3dc8c2bc371b88e7f97b641dc2721 --- /dev/null +++ b/browserid/static/funcunit/autosuggest/autosuggest.css @@ -0,0 +1,24 @@ +.autocomplete { + font-family: Tahoma; + font-size: 8pt; + background-color: white; + border: 1px solid black; + position: absolute; + cursor: default; + overflow: auto; + overflow-x: hidden; +} + +.autocomplete_item { + padding: 1px; + padding-left: 5px; + color: black; + width: 100%; +} + +.autocomplete_item_highlighted { + padding: 1px; + padding-left: 5px; + color: white; + background-color: #0A246A; +} \ No newline at end of file diff --git a/browserid/static/funcunit/autosuggest/autosuggest.html b/browserid/static/funcunit/autosuggest/autosuggest.html new file mode 100644 index 0000000000000000000000000000000000000000..8d2e0ff27f86d80b5fc0c2089d3c3dad8c1218b4 --- /dev/null +++ b/browserid/static/funcunit/autosuggest/autosuggest.html @@ -0,0 +1,26 @@ +<html> +<head> + <link rel="stylesheet" type="text/css" href="autosuggest.css" /> + <script type='text/javascript' src='auto_suggest.js'></script> + <script type='text/javascript'> + var data1 = [ + {value: "21", name: "JavaScriptMVC"}, + {value: "43", name: "Jupiter JavaScript Consulting"}, + {value: "46", name: "Jupiter JavaScript Training"}, + {value: "54", name: "Jupiter JavaScript Development"}, + {value: "55", name: "FuncUnit JavaScript Testing"}, + {value: "79", name: "Michael Jordan"} + ], + data2 = ["JavaScriptMVC","Jupiter JavaScript Consulting", + "Jupiter JavaScript Training","Jupiter JavaScript Development", + "FuncUnit JavaScript Testing","Michael Jordan"]; + + window.onload = function(){ + AutoComplete_Create("auto",data2) + } + </script> +</head> +<body> + <input id='auto'/> +</body> +</html> \ No newline at end of file diff --git a/browserid/static/funcunit/autosuggest/autosuggest.js b/browserid/static/funcunit/autosuggest/autosuggest.js new file mode 100644 index 0000000000000000000000000000000000000000..db3a8d0fbf07db3a06fefa0e0fc98f9444776f6f --- /dev/null +++ b/browserid/static/funcunit/autosuggest/autosuggest.js @@ -0,0 +1,17 @@ +steal('auto_suggest',function(){ + + var data1 = [ + {value: "21", name: "JavaScriptMVC"}, + {value: "43", name: "Jupiter JavaScript Consulting"}, + {value: "46", name: "Jupiter JavaScript Training"}, + {value: "54", name: "Jupiter JavaScript Development"}, + {value: "55", name: "FuncUnit JavaScript Testing"}, + {value: "79", name: "Michael Jordan"} + ], + data2 = ["JavaScriptMVC","Jupiter JavaScript Consulting", + "Jupiter JavaScript Training","Jupiter JavaScript Development", + "FuncUnit JavaScript Testing","Michael Jordan"]; + + //$("#auto").autoSuggest(data, {selectedItemProp: "name", searchObjProps: "name"}) + AutoComplete_Create("auto",data2) +}) \ No newline at end of file diff --git a/browserid/static/funcunit/autosuggest/autosuggest_test.js b/browserid/static/funcunit/autosuggest/autosuggest_test.js new file mode 100644 index 0000000000000000000000000000000000000000..991e6e6a99aef1143e5db234720a008d90b05d7e --- /dev/null +++ b/browserid/static/funcunit/autosuggest/autosuggest_test.js @@ -0,0 +1,14 @@ +module("autosuggest",{ + setup: function() { + S.open('autosuggest.html') + } +}); + +test("JavaScript results",function(){ + S('input').click().type("JavaScript") + + // wait until we have some results + S('.autocomplete_item').visible(function(){ + equal( S('.autocomplete_item').size(), 5, "there are 5 results") + }) +}); \ No newline at end of file diff --git a/browserid/static/funcunit/autosuggest/funcunit.html b/browserid/static/funcunit/autosuggest/funcunit.html new file mode 100644 index 0000000000000000000000000000000000000000..3bfeb58e5a127c6614d43ae97ee7e031707ba625 --- /dev/null +++ b/browserid/static/funcunit/autosuggest/funcunit.html @@ -0,0 +1,15 @@ +<html> +<head> + <link rel="stylesheet" type="text/css" href="../qunit/qunit.css" /> + <script type='text/javascript' src='../../steal/steal.js?funcunit'></script> + <script type='text/javascript' src='autosuggest_test.js'></script> + <title>AutoSuggest Test</title> +</head> +<body> + <h1 id="qunit-header">AutoSuggest Test Suite</h1> + <h2 id="qunit-banner"></h2> + <div id="qunit-testrunner-toolbar"></div> + <h2 id="qunit-userAgent"></h2> + <ol id="qunit-tests"></ol> +</body> +</html> \ No newline at end of file diff --git a/browserid/static/funcunit/build.js b/browserid/static/funcunit/build.js new file mode 100644 index 0000000000000000000000000000000000000000..e357f679c32dbd53a1202cad863a866d1606caf5 --- /dev/null +++ b/browserid/static/funcunit/build.js @@ -0,0 +1,79 @@ +load('steal/rhino/steal.js') + +load('funcunit/syn/build.js') + +steal.File('funcunit/dist').mkdir() +steal.File('funcunit/dist/funcunit').mkdir() +steal.File('funcunit/dist/funcunit/java').mkdir() +steal.File('funcunit/dist/funcunit/qunit').mkdir() +steal.File('funcunit/dist/funcunit/scripts').mkdir() +steal.File('funcunit/dist/steal').mkdir() +steal.File('funcunit/dist/steal/rhino').mkdir() +/** + * Build funcunit, user-extensions + */ +steal('//steal/build/pluginify/pluginify', function(s){ + steal.build.pluginify("funcunit",{ + global: "true", + destination: "funcunit/dist/funcunit/funcunit.js", + packagejquery: true + }) +}) +steal('//steal/build/pluginify/pluginify', function(s){ + steal.build.pluginify("funcunit/qunit",{ + global: "true", + destination: "funcunit/dist/funcunit/qunit.js", + packagejquery: true + }) +}) + +var i, fileName, cmd; + +// read: wrapped, jQuery, json, syn +var userFiles = + ["funcunit/java/extensions/fakesteal.js", + "funcunit/resources/jquery.js", + "funcunit/java/extensions/wrapped.js", + "funcunit/resources/json.js", + "funcunit/syn/dist/syn.js", + "funcunit/resources/selector.js"], + fileText, + userExtensionsText = ""; +for(var i=0; i<userFiles.length; i++){ + fileText = readFile(userFiles[i]); + userExtensionsText += fileText+"\n"; + print("appending "+userFiles[i]) +} +steal.File("funcunit/java/user-extensions.js").save(userExtensionsText); +print("saved user-extensions.js") + +/** + * Build the standalone funcunit + */ +var copyToDist = function(path){ + steal.File(path).copyTo("funcunit/dist/"+path) +} +var filesToCopy = [ + "funcunit/qunit/qunit.css", + "funcunit/java/selenium-server-standalone-2.0b3.jar", + "funcunit/java/selenium-java-client-driver.jar", + "funcunit/java/user-extensions.js", + "funcunit/scripts/run.js", + "steal/rhino/js.jar", + "steal/rhino/env.js", + "steal/rhino/loader.bat", + "steal/rhino/loader", + "funcunit/envjs", + "funcunit/envjs.bat", + "funcunit/settings.js", + "funcunit/loader.js", + "steal/rhino/steal.js", + "steal/rhino/utils.js", + "steal/rhino/file.js" +] + +for(var i = 0; i < filesToCopy.length; i++) { + copyToDist(filesToCopy[i]) +} + +print('FuncUnit is built') \ No newline at end of file diff --git a/browserid/static/funcunit/dependencies.json b/browserid/static/funcunit/dependencies.json new file mode 100644 index 0000000000000000000000000000000000000000..4c47e0736c53f4bbe1eb6be9d93a4dc1353a4856 --- /dev/null +++ b/browserid/static/funcunit/dependencies.json @@ -0,0 +1,3 @@ +{ + "funcunit/syn" : "https://github.com/jupiterjs/syn" +} \ No newline at end of file diff --git a/browserid/static/funcunit/docs.html b/browserid/static/funcunit/docs.html new file mode 100644 index 0000000000000000000000000000000000000000..2b771a760f79aa882404c45572b62c062fa9ada6 --- /dev/null +++ b/browserid/static/funcunit/docs.html @@ -0,0 +1,63 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" + "http://www.w3.org/TR/html4/strict.dtd"> +<html lang="en"> + <head> + <title>JavaScriptMVC</title> + <link rel="stylesheet" type='text/css' href='../documentjs/jmvcdoc/style.css' /> + <link rel="shortcut icon" href="../documentjs/jmvcdoc/images/favicon.ico" /> + </head> + <body> + + <div id='documentation'> + <div id='top'> + <div class="topCorner"><div> </div></div> + <div class="content"> + <div id="searchRoundCorners"> + <input id='search' type='input' disabled='true'/> + </div> + <div id='defaults'> + <ul id="menu" class="ui-menu"> + <li class="ui-menu-item"> + <a class="menuLink" href="#&search=*&who=index"><span class="menuSpan">Home</span></a> + </li> + <li class="ui-menu-item"> + <a class="menuLink" href="#favorites"><span class="menuSpan">Favorites</span></a> + </li> + <li class="ui-menu-item"> + <a class="menuLink" href="#&who=follow" title="Follow"><span class="menuSpan red">Follow</span></a> + </li> + <li class="ui-menu-item"> + <a class="menuLink" href="http://github.com/jupiterjs/funcunit" title="Code Repositories"><span class="menuSpan red">Code Repo</span></a> + </li> + </ul> + </div> + <div class="logo-text">FuncUnit <img src='../jmvc/images/funcunit_small.png' class="logo-image"/></div> + </div> + <div class="bottomCorner"><div> </div></div> + </div> + + <div id='bottom'> + + + <div id='left'> + <a>Loading ... </a> + </div> + <div id='doc_container'> + <div id='doc'> + + </div> + <div id="disqus_thread"></div> + </div> + </div> + </div> + <div id='low'> + <a href="http://jupiterit.com">© Jupiter IT - FuncUnit Training and Support</a> + </div> + <script type='text/javascript'> + DOCS_LOCATION = "docs/" //adds searchData to this + </script> + <script type='text/javascript' + src='../steal/steal.js?steal[app]=documentjs/jmvcdoc&steal[env]=production'> + </script> + </body> +</html> \ No newline at end of file diff --git a/browserid/static/funcunit/drivers/selenium.js b/browserid/static/funcunit/drivers/selenium.js new file mode 100644 index 0000000000000000000000000000000000000000..a79f5bd62dc58cf2f65c5f72af1375bd02bbadd1 --- /dev/null +++ b/browserid/static/funcunit/drivers/selenium.js @@ -0,0 +1,155 @@ +steal.then(function(){ + + // TODO: we should not do this if documenting ... + if (navigator.userAgent.match(/Rhino/) && !window.DocumentJS && !(steal && steal.pluginify)) { + + // configuration defaults + FuncUnit.serverHost = FuncUnit.serverHost || "localhost"; + FuncUnit.serverPort = FuncUnit.serverPort || 4444; + if(!FuncUnit.browsers){ + if(FuncUnit.jmvcRoot) + // run all browsers if you supply a jmvcRoot + // this is because a jmvcRoot means you're not running from filesystem, + // so safari and chrome will work correctly + FuncUnit.browsers = ["*firefox", "*iexplore", "*safari", "*googlechrome"] + else { + FuncUnit.browsers = ["*firefox"] + if(java.lang.System.getProperty("os.name").indexOf("Windows") != -1){ + FuncUnit.browsers.push("*iexplore") + } + } + } + + FuncUnit.startSelenium(); + (function(){ + var browser = 0, + fails = 0, + totals = 0; + //convert spaces to %20. + var location = /file:/.test(window.location.protocol) ? window.location.href.replace(/ /g,"%20") : window.location.href; + + + // overwrite QUnit.done to do the 'restarting' .... + QUnit.done = function(failures, total){ + FuncUnit.selenium.close(); + FuncUnit.selenium.stop(); + FuncUnit.endtime = new Date().getTime(); + var formattedtime = (FuncUnit.endtime - FuncUnit.starttime) / 1000; + + FuncUnit.browserDone(FuncUnit.browsers[browser], failures, total); + fails += failures; + totals += total; + + + browser++; + if (browser < FuncUnit.browsers.length) { + FuncUnit.browserStart( FuncUnit.browsers[browser] ); + + + FuncUnit.selenium = new DefaultSelenium(FuncUnit.serverHost, + FuncUnit.serverPort, FuncUnit.browsers[browser], location); + FuncUnit.starttime = new Date().getTime(); + FuncUnit.selenium.start(); + QUnit.restart(); + } else { + // Exit ... + if (java.lang.System.getProperty("os.name").indexOf("Windows") != -1) { + runCommand("cmd", "/C", 'taskkill /fi "Windowtitle eq selenium" > NUL') + //quit() + } + FuncUnit.done(fails, totals); + // + } + } + FuncUnit.browserStart(FuncUnit.browsers[0]); + + FuncUnit.selenium = new DefaultSelenium(FuncUnit.serverHost, + FuncUnit.serverPort, + FuncUnit.browsers[0], + location); + + FuncUnit.starttime = new Date().getTime(); + FuncUnit.selenium.start(); + + + FuncUnit._open = function(url){ + this.selenium.open(url); + }; + var confirms = [], prompts = []; + FuncUnit.confirm = function(answer){ + confirms.push(!!answer) + print(FuncUnit.jquery.toJSON(confirms)) + FuncUnit.selenium.getEval("_win().confirm = function(){var confirms = "+FuncUnit.jquery.toJSON(confirms)+ + ";return confirms.shift();};"); + } + FuncUnit.prompt = function(answer){ + prompts.push(answer) + FuncUnit.selenium.getEval("_win().prompt = function(){var prompts = "+FuncUnit.jquery.toJSON(prompts)+ + ";return prompts.shift();};"); + } + FuncUnit._onload = function(success, error){ + setTimeout(function(){ + FuncUnit.selenium.getEval("selenium.browserbot.getCurrentWindow().focus();selenium.browserbot.getCurrentWindow().document.documentElement.tabIndex = 0;"); + FuncUnit.selenium.getEval("_win().alert = function(){};"); + success(); + }, 1000) + }; + var convertToJson = function(arg){ + return arg === FuncUnit.window ? "selenium.browserbot.getCurrentWindow()" : FuncUnit.jquery.toJSON(arg) + + } + FuncUnit.$ = function(selector, context, method){ + var args = FuncUnit.makeArray(arguments); + var callbackPresent = false; + for (var a = 0; a < args.length; a++) { + if (a == 1) { //context + if (args[a] == FuncUnit.window.document) { + args[a] = "_doc()" + } + else { + if (typeof args[a] == "number") { + args[a] = "_win()[" + args[a] + "].document" + } + else + if (typeof args[a] == "string") { + args[a] = "_win()['" + args[a] + "'].document" + } + } + } + else { + if (args[a] == FuncUnit.window.document) { + args[a] = "_doc()" + } + else if (args[a] == FuncUnit.window) { + args[a] = "_win()" + } + else if (typeof args[a] == "function") { + callbackPresent = true; + var callback = args[a]; + args[a] = "Selenium.resume"; + } + else + args[a] = convertToJson(args[a]); + } + } + var response = FuncUnit.selenium.getEval("jQuery.wrapped(" + args.join(',') + ")"); + if(callbackPresent){ + return callback( eval("(" + response + ")") ) + } else { + return eval("(" + response + ")")// q[method].apply(q, args); + } + } + /** + * var val = S.eval("$(\".contacts\").controller().val()"); + * Appends "window." to the front of the string, so currently this method only works with one liners + * @param {Object} str + */ + FuncUnit.eval = function(str){ + return FuncUnit.selenium.getEval("selenium.browserbot.getCurrentWindow()."+str) + } + + + + })(); + } +}); \ No newline at end of file diff --git a/browserid/static/funcunit/drivers/standard.js b/browserid/static/funcunit/drivers/standard.js new file mode 100644 index 0000000000000000000000000000000000000000..a291dfe5ecc42547ae7afab9dfcbf5425640f36c --- /dev/null +++ b/browserid/static/funcunit/drivers/standard.js @@ -0,0 +1,174 @@ +steal.then(function() { + var readystate = document.readyState; + FuncUnit.jquery(window).load(function(){ + if(document.readyState != readystate) + FuncUnit.support.readystate = true; + }) + //don't do any of this if in rhino (IE selenium) + if (navigator.userAgent.match(/Rhino/)) { + return; + } + + + FuncUnit._window = null; + var newPage = true, changing; + var makeArray = function(arr, win){ + if(!win){ + win = window; + } + var narr = win.Array(); + for (var i = 0; i < arr.length; i++) { + narr.push(arr[i]) + } + return narr; + } + FuncUnit._open = function(url){ + changing = url; + if (newPage) { + FuncUnit._window = window.open(url, "funcunit"); + } + else { + FuncUnit._window.location = url; + + } + + } + var unloadLoader, + loadSuccess, + firstLoad = true, + currentDocument, + onload = function(){ + FuncUnit._window.document.documentElement.tabIndex = 0; + setTimeout(function(){ + FuncUnit._window.focus(); + var ls = loadSuccess + loadSuccess = null; + if (ls) { + ls(); + } + }, 0); + Syn.unbind(FuncUnit._window, "load", onload); + }, + onunload = function(){ + removeListeners(); + setTimeout(unloadLoader, 0) + + }, + removeListeners = function(){ + Syn.unbind(FuncUnit._window, "unload", onunload); + Syn.unbind(FuncUnit._window, "load", onload); + } + unloadLoader = function(){ + if(!firstLoad) // dont remove the first run, fixes issue in FF 3.6 + removeListeners(); + + Syn.bind(FuncUnit._window, "load", onload); + + //listen for unload to re-attach + Syn.bind(FuncUnit._window, "unload", onunload) + } + + //check for window location change, documentChange, then readyState complete -> fire load if you have one + var newDocument = false, poller = function(){ + if(FuncUnit._window.document == null){ + return + } + + if (FuncUnit._window.document !== currentDocument || newDocument) { //we have a new document + currentDocument = FuncUnit._window.document; + newDocument = true; + if (FuncUnit._window.document.readyState == "complete" && FuncUnit._window.location.href!="about:blank") { + var ls = loadSuccess; + loadSuccess = null; + if (ls) { + FuncUnit._window.focus(); + FuncUnit._window.document.documentElement.tabIndex = 0; + + ls(); + } + + } + } + + setTimeout(arguments.callee, 1000) + } + + FuncUnit._onload = function(success, error){ + loadSuccess = success; + if (!newPage) + return; + newPage = false; + if (FuncUnit.support.readystate) + { + poller(); + } + else { + unloadLoader(); + } + + } + var confirms = [], prompts = []; + FuncUnit.confirm = function(answer){ + confirms.push(!!answer) + } + FuncUnit.prompt = function(answer){ + prompts.push(answer) + } + FuncUnit._opened = function(){ + FuncUnit._window.alert = function(){} + FuncUnit._window.confirm = function(){ + var res = confirms.shift(); + return res; + } + FuncUnit._window.prompt = function(){ + return prompts.shift(); + } + } + FuncUnit.$ = function(selector, context, method){ + + var args = makeArray(arguments); + for (var i = 0; i < args.length; i++) { + args[i] = args[i] === FuncUnit.window ? FuncUnit._window : args[i] + } + + var selector = args.shift(), + context = args.shift(), + method = args.shift(), + q; + + //convert context + if (context == FuncUnit.window.document) { + context = FuncUnit._window.document + }else if(context === FuncUnit.window){ + context = FuncUnit._window; + }else if (typeof context == "number" || typeof context == "string") { + context = FuncUnit._window.frames[context].document; + } + if (selector == FuncUnit.window.document) { + selector = FuncUnit._window.document + }else if(selector === FuncUnit.window){ + selector = FuncUnit._window; + } + + // for trigger, we have to use the page's jquery because it uses jQuery's event system, which uses .data() in the page + if (FuncUnit._window.jQuery && method == 'trigger') { + args = makeArray(args, FuncUnit._window) + q = FuncUnit._window.jQuery(selector, context) + } else { + q = FuncUnit.jquery(selector, context); + } + return q[method].apply(q, args); + + + } + + FuncUnit.eval = function(str){ + return FuncUnit._window.eval(str) + } + + FuncUnit.jquery(window).unload(function(){ + if (FuncUnit._window) + FuncUnit._window.close(); + }) + +}); diff --git a/browserid/static/funcunit/envjs b/browserid/static/funcunit/envjs new file mode 100755 index 0000000000000000000000000000000000000000..2dd118c00d6af32ab7e4b7198b0afdfc746dfdfa --- /dev/null +++ b/browserid/static/funcunit/envjs @@ -0,0 +1,28 @@ +#!/bin/sh +# This file is a batch script that invokes loader +# ex: documentjs/doc cookbook/cookbook.html + +# Absolute path to this script. /home/user/bin/foo.sh + +TARGET_FILE=$0 + +cd `dirname $TARGET_FILE` +TARGET_FILE=`basename $TARGET_FILE` + +PHYS_DIR=`pwd -P` +SCRIPT=$PHYS_DIR/$TARGET_FILE + +# Absolute path this script is in. /home/user/bin +BASE=`dirname $SCRIPT`/ + +# Keeps the executing directory as the JMVC root. +cd $BASE.. + +# classpath +CP=$BASE../funcunit/java/selenium-java-client-driver.jar:$BASE../steal/rhino/js.jar + +# load the run.js file +LOADPATH=${BASE}scripts/run.js + +# call js.bat +. $BASE../steal/rhino/loader $1 $2 $3 $4 $5 $6 \ No newline at end of file diff --git a/browserid/static/funcunit/envjs.bat b/browserid/static/funcunit/envjs.bat new file mode 100755 index 0000000000000000000000000000000000000000..b26f031ee2cf9cf519688871674ae74ed3c73c44 --- /dev/null +++ b/browserid/static/funcunit/envjs.bat @@ -0,0 +1,19 @@ +@echo off +:: this file is a batch script that invokes loader.bat +:: ex: funcunit/envjs cookbook/qunit.html + +:: relative path to this script +set BASE=%~dps0 +set CMD=%0 + +:: classpath +SET CP=%BASE%java/selenium-java-client-driver.jar;%BASE%../steal/rhino/js.jar + +:: load the run.js file +SET LOADPATH=%BASE%scripts/run.js + +:: call js.bat +CALL %BASE%../steal/rhino/loader.bat %1 %2 %3 %4 %5 %6 + +:: report errors to CI/build wrapper(s) +if errorlevel 1 exit 1 \ No newline at end of file diff --git a/browserid/static/funcunit/funcunit.html b/browserid/static/funcunit/funcunit.html new file mode 100644 index 0000000000000000000000000000000000000000..79e02c6a790d1308fcd0514ef51bb5f62bd96818 --- /dev/null +++ b/browserid/static/funcunit/funcunit.html @@ -0,0 +1,20 @@ +<html> + <head> + <link rel="stylesheet" type="text/css" href="../funcunit/qunit/qunit.css" /> + <title>FuncUnit Test</title> + <style> + body { + margin: 0px; padding: 0px; + } + </style> +</head> + <body> + + <h1 id="qunit-header">funcunit Test Suite</h1> + <h2 id="qunit-banner"></h2> + <div id="qunit-testrunner-toolbar"></div> + <h2 id="qunit-userAgent"></h2> + <ol id="qunit-tests"></ol> + <script type='text/javascript' src='../steal/steal.js?steal[app]=funcunit/test/funcunit'></script> + </body> +</html> \ No newline at end of file diff --git a/browserid/static/funcunit/funcunit.js b/browserid/static/funcunit/funcunit.js new file mode 100644 index 0000000000000000000000000000000000000000..7d53b223eb94b0ef28d1af44697f6d85e8613841 --- /dev/null +++ b/browserid/static/funcunit/funcunit.js @@ -0,0 +1,1394 @@ +//what we need from javascriptmvc or other places +steal.plugins('funcunit/qunit', + 'funcunit/qunit/rhino') + .then('resources/jquery','resources/json','resources/selector') + .plugins('funcunit/syn') +//Now describe FuncUnit +.then(function(){ + + + +//this gets the global object, even in rhino +var window = (function(){return this }).call(null), + +//if there is an old FuncUnit, use that for settings + oldFunc = window.FuncUnit; + +/** + * @class FuncUnit + * @tag core + * @test test.html + * @download http://github.com/downloads/jupiterjs/funcunit/funcunit-beta-5.zip + +FuncUnit is a powerful functional testing framework written in JavaScript with a jQuery-like syntax. It provides an +approachable way to write maintainable cross browser tests. It is the first functional testing framework written +for JavaScript developers by JavaScript developers. + +FuncUnit extends [http://docs.jquery.com/QUnit QUnit]'s API with commands like [FuncUnit.prototype.click click], [FuncUnit.prototype.type type], +and [FuncUnit.prototype.drag drag]. The same tests can be run in browser or automated via Selenium. + +## Example: +The following tests that an AutoSuggest returns 5 results. +<a href='funcunit/autosuggest/funcunit.html'>See it in action!</a> (Make sure you turn off your popup blocker!) +@codestart +module("autosuggest",{ + setup: function() { + S.open('autosuggest.html') + } +}); + +test("JavaScript results",function(){ + S('input').click().type("JavaScript") + + // wait until we have some results + S('.autocomplete_item').visible(function(){ + equal( S('.autocomplete_item').size(), 5, "there are 5 results") + }) +}); +@codeend + +## Using FuncUnit + +FuncUnit works by loading QUnit, FuncUnit, and your tests into a web page. Your application is opened in a +separate browser window. The FuncUnit page then drives your application via clicks, types, and drags, reporting +pass/fail in the initial FuncUnit test page. + +### Loading a Test + +To get started with a basic test, run: + +@codestart +./js jquery/generate/controller Company.Widget +@codeend + +Open company/widget/funcunit.html in a browser. You should see a passing test. + +The first thing any FuncUnit page does is load its dependencies and a test. A FuncUnit page: + +1. Loads steal.js +2. Loads funcunit and qunit via steal.plugins +3. Loads one or more test files + +For more details on setting up a FuncUnit test, check out the [FuncUnit.setup Getting Set Up] guide. +For more details on using FuncUnit without Steal or JavaScriptMVC, check out the [FuncUnit.standalone Using Standalone FuncUnit] guide. + +### Writing a Test + +There are four types of commands in any FuncUnit tests: + +1. Actions - simulate a user interaction ([FuncUnit.prototype.click clicks], [FuncUnit.prototype.type types], [FuncUnit.prototype.drag drags], etc) +2. Waits - wait for conditions in the page before continuing the test ([FuncUnit.prototype.width width], [FuncUnit.prototype.visible visible], +[FuncUnit.prototype.text text], etc) +3. Getters - get page conditions to use in assertions ([FuncUnit.prototype.width width], [FuncUnit.prototype.hasClass hasClass], [FuncUnit.prototype.text text]) +4. QUnit commands - assertions and methods for setting up tests (module, test, ok, equals, etc) + +Tests follow a consistent pattern: + +0. Each test sets itself up in QUnit's [http://docs.jquery.com/QUnit/module setup] method by opening an application page with S.open. +1. Tests simulate a user action, like clicking a link. +2. Then they wait for some page condition to change, like a menu appearing. +3*. Then you assert something about your page, like the right number of links are visible. + +* This step isn't always necessary. You can write an entire test without assertions. If the wait condition fails, the test will fail. + +For more information on writing tests, check out the [FuncUnit.writing Writing Tests] guide. + +## Running a Test + +To run this test in browser, open funcunit.html in any browser (and turn off your popup blocker!). + +To run the same test automated via Selenium, run: + +@codestart +./funcunit/envjs path/to/funcunit.html +@codeend + +For more information about using Selenium, checkout the [FuncUnit.selenium Automated FuncUnit] guide. + +* Use envjs.bat for Windows users. + +## What is FuncUnit + +Under the hood, FuncUnit is built on several projects: + + - [http://seleniumhq.org/ Selenium] - used to open browsers and run automated tests + - [http://docs.jquery.com/Qunit QUnit] - Unit testing framework provides test running, assertions, and reporting + - [http://jquery.com/ jQuery] - used to look up elements, trigger events, and query for page conditions + - [http://www.envjs.com Env.js] - Rhino based headless browser used to load pages in the command line + - [http://www.mozilla.org/rhino/ Rhino] - command line JavaScript environment running in Java + - [https://github.com/jupiterjs/syn Syn] - event simulation library + +FuncUnit is designed to let JavaScript developers write tests in an easy to learn jQuery-like syntax. +The tests will run in browser, so developers can check for regressions as they work. The same tests also run +via Selenium, so QA can automate nightly builds or continuous integration. + +## Why FuncUnit + +TESTING IS PAINFUL. Everyone hates testing, and most front end developers simply don't test. There +are a few reasons for this: + +1. **Barriers to entry** - Difficult setup, installation, high cost, or difficult APIs. QTP costs $5K per license. +2. **Completely foreign APIs** - Testing frameworks often use other languages (Ruby, C#, Java) and new APIs. +3. **Debugging across platforms** - You can't use firebug to debug a test that's driven by PHP. +4. **Low fidelity event simulation** - Tests are often brittle or low fidelity because frameworks aren't designed to test heavy JavaScript apps, so +browser event simualation accuracy isn't a top priority. +5. **QA and developers can't communicate** - If only QA has the ability to run tests, sending bug reports is messy and time consuming. + +FuncUnit aims to fix these problems: + +1. FuncUnit is free and has no setup or installation (just requires Java and a browser). +2. FuncUnit devs know jQuery, and FuncUnit leverages that knowldge with a jQuery-like API. +3. You can run tests in browser and set Firebug breakpoints. +4. Syn.js is a low level event simuation library that goes to extra trouble to make sure each browser simulates events exactly as intended. +5. Since tests are just JS and HTML, they can be checked into a project and any dev can run them easily. QA just needs to send a URL to a broken +test case. + +There are many testing frameworks out there, but nothing comes close to being a complete solution for front end testing like FuncUnit does. + + * @constructor + * selects something in the other page + * @param {String|Function|Object} selector FuncUnit behaves differently depending if + * the selector is a string, a function, or an object. + * <h5>String</h5> + * The selector is treated as a css selector. + * jQuery/Sizzle is used as the selector so any selector it understands + * will work with funcUnit. FuncUnit does not perform the selection until a + * command is called upon this selector. This makes aliasing the selectors to + * JavaScript variables a great technique. + * <h5>Function</h5> + * If a function is provided, it will add that function to the action queue to be run + * after previous actions and waits. + * <h5>Object</h5> + * If you want to reference the window or document, pass <code>S.window</code> + * or <code>S.window.document</code> to the selector. + * + * @param {Number} [context] If provided, the context is the frame number in the + * document.frames array to use as the context of the selector. For example, if you + * want to select something in the first iframe of the page: + * + * S("a.mylink",0) + */ +FuncUnit = function(selector, context){ + // if someone wraps a funcunit selector + if(selector && selector.funcunit === true){ + return selector; + } + if(typeof selector == "function"){ + return FuncUnit.wait(0, selector); + } + + return new FuncUnit.init(selector, context) +} +/** + * @Static + */ +window.jQuery.extend(FuncUnit,oldFunc) +window.jQuery.extend(FuncUnit,{ + //move jquery and clear it out + jquery : jQuery.noConflict(true), +/** + * @attribute href + * The location of the page running the tests on the server and where relative paths passed in to [FuncUnit.static.open] will be + * referenced from. + * <p>This is typically where the test page runs on the server. It can be set before calls to [FuncUnit.static.open]:</p> +@codestart +test("opening something", function(){ + S.href = "http://localhost/tests/mytest.html" + S.open("../myapp") + ... +}) +@codeend + */ +// href comes from settings +/** + * @attribute jmvcRoot + * jmvcRoot should be set to url of JMVC's root folder. + * <p>This is used to calculate JMVC style paths (paths that begin with //). + * This is the prefered method of referencing pages if + * you want to test on the filesystem and test on the server.</p> + * <p>This is usually set in the global config file in <code>funcunit/settings.js</code> like:</p> +@codestart +FuncUnit = {jmvcRoot: "http://localhost/script/" } +@codeend + */ +// jmvcRoot comes from settings + +/** + * Opens a page. It will error if the page can't be opened before timeout. + * <h3>Example</h3> +@codestart +//a full url +S.open("http://localhost/app/app.html") + +//from jmvc root (FuncUnit.jmvcRoot must be set) +S.open("//app/app.html") +@codeend + + * <h3>Paths in Selenium</h3> + * Selenium runs the testing page from the filesystem and by default will look for pages on the filesystem unless provided a full + * url or information that can translate a partial path into a full url. FuncUnit uses [FuncUnit.static.jmvcRoot] + * and [FuncUnit.static.href] to + * translate partial paths. +<table> + <tr> + <th>path</th> + <th>jmvcRoot</th> + <th>href</th> + <th>resulting url</th> + </tr> + <tr> + <td>//myapp/mypage.html</td> + <td>null</td> + <td>null</td> + <td>file:///C:/development/cookbook/public/myapp/mypage.html</td> + </tr> + <tr> + <td>//myapp/mypage.html</td> + <td>http://localhost/</td> + <td></td> + <td>http://localhost/myapp/mypage.html</td> + </tr> + <tr> + <td>http://foo.com</td> + <td></td> + <td></td> + <td>http://foo.com</td> + </tr> + <tr> + <td>../mypage.html</td> + <td></td> + <td>http://localhost/myapp/funcunit.html</td> + <td>http://localhost/mypage.html</td> + </tr> +</table> + * + * @param {String} path a full or partial url to open. If a partial is given, + * @param {Function} callback + * @param {Number} timeout + */ +open: function( path, callback, timeout ) { + var fullPath = FuncUnit.getAbsolutePath(path), + temp; + if(typeof callback != 'function'){ + timeout = callback; + callback = undefined; + } + FuncUnit.add({ + method: function(success, error){ //function that actually does stuff, if this doesn't call success by timeout, error will be called, or can call error itself + steal.dev.log("Opening " + path) + FuncUnit._open(fullPath, error); + FuncUnit._onload(function(){ + FuncUnit._opened(); + success() + }, error); + }, + callback: callback, + error: "Page " + path + " not loaded in time!", + timeout: timeout || 30000 + }); +}, +/** + * @hide + * Gets a path, will use steal if present + * @param {String} path + */ +getAbsolutePath: function( path ) { + if(typeof(steal) == "undefined" || steal.root == null){ + return path; + } + var fullPath, + root = FuncUnit.jmvcRoot || steal.root.path; + + if (/^\/\//.test(path)) { + fullPath = new steal.File(path.substr(2)).joinFrom(root); + } + else { + fullPath = path; + } + + if(/^http/.test(path)) + fullPath = path; + return fullPath; +}, +/** + * @attribute browsers + * Used to configure the browsers selenium uses to run FuncUnit tests. + * If you need to learn how to configure selenium, and we haven't filled in this page, + * post a note on the forum and we will fill this out right away. + */ +// for feature detection +support : {}, +/** + * @attribute window + * Use this to refer to the window of the application page. You can also + * reference window.document. + * @codestart + * S(S.window).innerWidth(function(w){ + * ok(w > 1000, "window is more than 1000 px wide") + * }) + * @codeend + */ +window : { + document: {} +}, +_opened: function() {} +}); + +(function(){ + //the queue of commands waiting to be run + var queue = [], + //are we in a callback function (something we pass to a FuncUnit plugin) + incallback = false, + //where we should add things in a callback + currentPosition = 0; + + + FuncUnit. + /** + * @hide + * Adds a function to the queue. The function is passed within an object that + * can have several other properties: + * method : the method to be called. It will be provided a success and error function to call + * callback : an optional callback to be called after the function is done + * error : an error message if the command fails + * timeout : the time until success should be called + * bind : an object that will be 'this' of the success + * stop : + */ + add = function(handler){ + + //if we are in a callback, add to the current position + if (incallback) { + queue.splice(currentPosition,0,handler) + currentPosition++; + } + else { + //add to the end + queue.push(handler); + } + //if our queue has just started, stop qunit + //call done to call the next command + if (queue.length == 1 && ! incallback) { + stop(); + setTimeout(FuncUnit._done, 13) + } + } + //this is called after every command + // it gets the next function from the queue + FuncUnit._done = function(){ + var next, + timer, + speed = 0; + + if(FuncUnit.speed == "slow"){ + speed = 500; + } + else if (FuncUnit.speed){ + speed = FuncUnit.speed; + } + if (queue.length > 0) { + next = queue.shift(); + currentPosition = 0; + // set a timer that will error + + + //call next method + setTimeout(function(){ + timer = setTimeout(function(){ + next.stop && next.stop(); + ok(false, next.error); + FuncUnit._done(); + }, + (next.timeout || 10000) + speed) + + next.method( //success + function(){ + //make sure we don't create an error + clearTimeout(timer); + + //mark in callback so the next set of add get added to the front + + incallback = true; + if (next.callback) + next.callback.apply(next.bind || null, arguments); + incallback = false; + + + FuncUnit._done(); + }, //error + function(message){ + clearTimeout(timer); + ok(false, message); + FuncUnit._done(); + }) + + + }, speed); + + } + else { + start(); + } + } + FuncUnit. + /** + * Waits a timeout before running the next command. Wait is an action and gets + * added to the queue. + * @codestart + * S.wait(100, function(){ + * equals( S('#foo').innerWidth(), 100, "innerWidth is 100"); + * }) + * @codeend + * @param {Number} [time] The timeout in milliseconds. Defaults to 5000. + * @param {Function} [callback] A callback that will run + * after the wait has completed, + * but before any more queued actions. + */ + wait = function(time, callback){ + if(typeof time == 'function'){ + callback = time; + time = undefined; + } + time = time != null ? time : 5000 + FuncUnit.add({ + method : function(success, error){ + steal.dev.log("Waiting "+time) + setTimeout(success, time) + }, + callback : callback, + error : "Couldn't wait!", + timeout : time + 1000 + }); + return this; + } + + FuncUnit. + /** + * When a browser's native confirm dialog is used, this method is used to repress the dialog and simulate + * clicking OK or Cancel. Alerts are repressed by default in FuncUnit application windows. + * @codestart + * S.confirm(true); + * @codeend + * @param {Boolean} answer true if you want to click OK, false otherwise + */ + confirm = function(answer){} + + FuncUnit. + /** + * When a browser's native prompt dialog is used, this method is used to repress the dialog and simulate + * clicking typing something into the dialog. + * @codestart + * S.prompt("Harry Potter"); + * @codeend + * @param {String} answer Whatever you want to simulate a user typing in the prompt box + */ + prompt = function(answer){} + + /** + * @hide + * @function repeat + * Takes a function that will be called over and over until it is successful. + */ + FuncUnit.repeat = function(checker, callback, error, timeout){ + + if(typeof timeout == 'function'){ + error = callback; + callback = timeout; + + } + + var interval, + stopped = false , + stop = function(){ + clearTimeout(interval) + stopped = true; + }; + + FuncUnit.add({ + method : function(success, error){ + interval = setTimeout(function(){ + + var result = null; + try { + result = checker() + } + catch (e) { + //should we throw this too error? + } + + if (result) { + success(); + }else if(!stopped){ + interval = setTimeout(arguments.callee, 10) + } + + }, 10); + + + }, + callback : callback, + error : error, + timeout : timeout, + stop : stop + }); + + } + + + + FuncUnit.makeArray = function(arr){ + var narr = []; + for (var i = 0; i < arr.length; i++) { + narr[i] = arr[i] + } + return narr; + } + FuncUnit. + /** + * @hide + * Converts a string into a Native JS type. + * @param {Object} str + */ + convert = function(str){ + //if it is an object and not null, eval it + if (str !== null && typeof str == "object") { + return object; + } + str = String(str); + switch (str) { + case "false": + return false; + case "null": + return null; + case "true": + return true; + case "undefined": + return undefined; + default: + if (/^\d+\.\d+$/.test(str) || /^\d+$/.test(str)) { + return 1 * str; + } + + return str; + } + } + +})(); + + +/** + * @prototype + */ +FuncUnit.init = function(s, c){ + this.selector = s; + this.context = c == null ? FuncUnit.window.document : c; +} +FuncUnit.init.prototype = { + funcunit : true, + /** + * Types text into an element. This makes use of [Syn.type] and works in + * a very similar way. + * <h3>Quick Examples</h3> + * @codestart + * //types hello world + * S('#bar').type('hello world') + * + * //submits a form by typing \r + * S("input[name=age]").type("27\r") + * + * //types FuncUnit, then deletes the Unit + * S('#foo').type("FuncUnit\b\b\b\b") + * + * //types JavaScriptMVC, then removes the MVC + * S('#zar').type("JavaScriptMVC[left][left][left]"+ + * "[delete][delete][delete]") + * + * //types JavaScriptMVC, then selects the MVC and + * //deletes it + * S('#zar').type("JavaScriptMVC[shift]"+ + * "[left][left][left]"+ + * "[shift-up][delete]") + * @codeend + * <h2>Characters</h2> + * + * For a list of the characters you can type, check [Syn.keycodes]. + * + * @param {String} text the text you want to type + * @param {Function} [callback] a callback that is run after typing, but before the next action. + * @return {FuncUnit} returns the funcUnit for chaining. + */ + type: function( text, callback ) { + var selector = this.selector, + context = this.context; + FuncUnit.add({ + method : function(success, error){ + steal.dev.log("Typing "+text+" on "+selector) + FuncUnit.$(selector, context, "triggerSyn", "_type", text, success) + }, + callback : callback, + error : "Could not type " + text + " into " + this.selector, + bind : this + }); + return this; + }, + /** + * Waits until an element exists before running the next action. + * @codestart + * //waits until #foo exists before clicking it. + * S("#foo").exists().click() + * @codeend + * @param {Function} [callback] a callback that is run after the selector exists, but before the next action. + * @return {FuncUnit} returns the funcUnit for chaining. + */ + exists: function( callback ) { + if(true){ + return this.size(function(size){ + return size > 0; + }, callback) + } + return this.size() == 0; + }, + /** + * Waits until no elements are matched by the selector. Missing is equivalent to calling + * <code>.size(0, callback);</code> + * @codestart + * //waits until #foo leaves before continuing to the next action. + * S("#foo").missing() + * @codeend + * @param {Function} [callback] a callback that is run after the selector exists, but before the next action + * @return {FuncUnit} returns the funcUnit for chaining. + */ + missing: function( callback ) { + return this.size(0, callback) + }, + /** + * Waits until the funcUnit selector is visible. + * @codestart + * //waits until #foo is visible. + * S("#foo").visible() + * @codeend + * @param {Function} [callback] a callback that runs after the funcUnit is visible, but before the next action. + * @return [funcUnit] returns the funcUnit for chaining. + */ + visible: function( callback ) { + var self = this, + sel = this.selector, + ret; + this.selector += ":visible" + if(true){ + return this.size(function(size){ + return size > 0; + }, function(){ + self.selector = sel; + callback && callback(); + }) + }else{ + ret = this.size() > 0; + this.selector = sel; + return ret; + } + + }, + /** + * Waits until the selector is invisible. + * @codestart + * //waits until #foo is invisible. + * S("#foo").invisible() + * @codeend + * @param {Function} [callback] a callback that runs after the selector is invisible, but before the next action. + * @return [funcUnit] returns the funcUnit selector for chaining. + */ + invisible: function( callback ) { + var self = this, + sel = this.selector, + ret; + this.selector += ":visible" + return this.size(0, function(){ + self.selector = sel; + callback && callback(); + }) + }, + /** + * Drags an element into another element or coordinates. + * This takes the same paramameters as [Syn.prototype.move move]. + * @param {String|Object} options A selector or coordinates describing the motion of the drag. + * <h5>Options as a Selector</h5> + * Passing a string selector to drag the mouse. The drag runs to the center of the element + * matched by the selector. The following drags from the center of #foo to the center of #bar. + * @codestart + * S('#foo').drag('#bar') + * @codeend + * <h5>Options as Coordinates</h5> + * You can pass in coordinates as clientX and clientY: + * @codestart + * S('#foo').drag('100x200') + * @codeend + * Or as pageX and pageY + * @codestart + * S('#foo').drag('100X200') + * @codeend + * Or relative to the start position + * S('#foo').drag('+10 +20') + * <h5>Options as an Object</h5> + * You can configure the duration, start, and end point of a drag by passing in a json object. + * @codestart + * //drags from 0x0 to 100x100 in 2 seconds + * S('#foo').drag({ + * from: "0x0", + * to: "100x100", + * duration: 2000 + * }) + * @codeend + * @param {Function} [callback] a callback that runs after the drag, but before the next action. + * @return {funcUnit} returns the funcunit selector for chaining. + */ + drag: function( options, callback ) { + if(typeof options == 'string'){ + options = {to: options} + } + options.from = this.selector; + + var selector = this.selector, + context = this.context; + FuncUnit.add({ + method: function(success, error){ + steal.dev.log("dragging " + selector) + FuncUnit.$(selector, context, "triggerSyn", "_drag", options, success) + }, + callback: callback, + error: "Could not drag " + this.selector, + bind: this + }) + return this; + }, + /** + * Moves an element into another element or coordinates. This will trigger mouseover + * mouseouts accordingly. + * This takes the same paramameters as [Syn.prototype.move move]. + * @param {String|Object} options A selector or coordinates describing the motion of the move. + * <h5>Options as a Selector</h5> + * Passing a string selector to move the mouse. The move runs to the center of the element + * matched by the selector. The following moves from the center of #foo to the center of #bar. + * @codestart + * S('#foo').move('#bar') + * @codeend + * <h5>Options as Coordinates</h5> + * You can pass in coordinates as clientX and clientY: + * @codestart + * S('#foo').move('100x200') + * @codeend + * Or as pageX and pageY + * @codestart + * S('#foo').move('100X200') + * @codeend + * Or relative to the start position + * S('#foo').move('+10 +20') + * <h5>Options as an Object</h5> + * You can configure the duration, start, and end point of a move by passing in a json object. + * @codestart + * //drags from 0x0 to 100x100 in 2 seconds + * S('#foo').move({ + * from: "0x0", + * to: "100x100", + * duration: 2000 + * }) + * @codeend + * @param {Function} [callback] a callback that runs after the drag, but before the next action. + * @return {funcUnit} returns the funcunit selector for chaining. + */ + move: function( options, callback ) { + if(typeof options == 'string'){ + options = {to: options} + } + options.from = this.selector; + + var selector = this.selector, + context = this.context; + FuncUnit.add({ + method: function(success, error){ + steal.dev.log("moving " + selector) + FuncUnit.$(selector, context, "triggerSyn", "_move", options, success) + }, + callback: callback, + error: "Could not move " + this.selector, + bind: this + }); + return this; + }, + /** + * Scrolls an element in a particular direction by setting the scrollTop or srollLeft. + * @param {String} direction "left" or "top" + * @param {Number} amount number of pixels to scroll + * @param {Function} callback + */ + scroll: function( direction, amount, callback ) { + var selector = this.selector, + context = this.context, + direction = /left|right|x/i.test(direction)? "Left" : "Right"; + FuncUnit.add({ + method: function(success, error){ + steal.dev.log("setting " + selector + " scroll" + direction + " " + amount + " pixels") + FuncUnit.$(selector, context, "scroll" + direction, amount) + success(); + }, + callback: callback, + error: "Could not scroll " + this.selector, + bind: this + }); + return this; + }, + /** + * Waits a timeout before calling the next action. This is the same as + * [FuncUnit.prototype.wait]. + * @param {Number} [timeout] + * @param {Object} callback + */ + wait: function( timeout, callback ) { + FuncUnit.wait(timeout, callback) + }, + /** + * Returns a FuncUnit wrapped selector with + * selector appended to the current selector. + * @codestart + * S('#foo').find(".bar") //-> S("#foo .bar") + * @codeend + * @param {String} selector + * @return {FuncUnit} the funcunit wrapped selector. + */ + + find : function(selector){ + return FuncUnit(this.selector+" "+selector, this.context); + }, + /** + * Calls the callback function after all previous asynchronous actions have completed. Then + * is called with the funcunit object. + * @param {Object} callback + */ + then : function(callback){ + var self = this; + FuncUnit.wait(0, function(){ + callback.call(self, self); + }); + return this; + } +}; +//do traversers +var traversers = ["closest", + + +"next","prev","siblings","last","first"], + makeTraverser = function(name){ + FuncUnit.init.prototype[name] = function(selector){ + return FuncUnit( FuncUnit.$(this.selector, this.context, name+"Selector", selector), this.context ) + } + }; +for(var i =0; i < traversers.length; i++){ + makeTraverser(traversers[i]); +} + +// do clicks +var clicks = [ +/** + * @function click + * Clicks an element. This uses [Syn.prototype.click] to issue a: + * <ul> + * <li><code>mousedown</code></li> + * <li><code>focus</code> - if the element is focusable</li> + * <li><code>mouseup</code></li> + * <li><code>click</code></li> + * </ul> + * If no clientX/Y or pageX/Y is provided as options, the click happens at the + * center of the element. + * <p>For a right click or double click use [FuncUnit.prototype.rightClick] or + * [FuncUnit.prototype.dblclick].</p> + * <h3>Example</h3> + * @codestart + * //clicks the bar element + * S("#bar").click() + * @codeend + * @param {Object} [options] options to pass to the click event. Typically, this is clientX/Y or pageX/Y like: + * @codestart + * $('#foo').click({pageX: 200, pageY: 100}); + * @codeend + * You can pass it any of the serializable parameters you'd send to : + * [http://developer.mozilla.org/en/DOM/event.initMouseEvent initMouseEvent], but command keys are + * controlled by [FuncUnit.prototype.type]. + * @param {Function} [callback] a callback that runs after the click, but before the next action. + * @return {funcUnit} returns the funcunit selector for chaining. + */ +'click', +/** + * @function dblclick + * Double clicks an element by [FuncUnit.prototype.click clicking] it twice and triggering a dblclick event. + * @param {Object} options options to add to the mouse events. This works + * the same as [FuncUnit.prototype.click]'s options. + * @param {Function} [callback] a callback that runs after the double click, but before the next action. + * @return {funcUnit} returns the funcunit selector for chaining. + */ +'dblclick', +/** + * @function rightClick + * Right clicks an element. This typically results in a contextmenu event for browsers that + * support it. + * @param {Object} options options to add to the mouse events. This works + * the same as [FuncUnit.prototype.click]'s options. + * @param {Function} [callback] a callback that runs after the click, but before the next action. + * @return {funcUnit} returns the funcunit selector for chaining. + */ +'rightClick'], + makeClick = function(name){ + FuncUnit.init.prototype[name] = function(options, callback){ + if(typeof options == 'function'){ + callback = options; + options = {}; + } + var selector = this.selector, + context = this.context; + FuncUnit.add({ + method: function(success, error){ + options = options || {} + steal.dev.log("Clicking " + selector) + FuncUnit.$(selector, context, "triggerSyn", "_" + name, options, success) + }, + callback: callback, + error: "Could not " + name + " " + this.selector, + bind: this + }); + return this; + } + } + +for(var i=0; i < clicks.length; i++){ + makeClick(clicks[i]) +} + + +//list of jQuery functions we want, number is argument index +//for wait instead of getting value +FuncUnit.funcs = { +/** + * @function size + * Gets the number of elements matched by the selector or + * waits until the the selector is size. You can also + * provide a function that continues to the next action when + * it returns true. + * @codestart + * S(".recipe").size() //gets the number of recipes + * + * S(".recipe").size(2) //waits until there are 2 recipes + * + * //waits until size is count + * S(".recipe").size(function(size){ + * return size == count; + * }) + * @codeend + * @param {Number|Function} [size] number or a checking function. + * @param {Function} a callback that will run after this action completes. + * @return {Number} if the size parameter is not provided, size returns the number + * of elements matched. + */ +'size' : 0, +/** + * @function trigger + * Triggers an event on a set of elements in the page. Use it to trigger + * custom user events that a user can't easily simulate. Do NOT use + * it to simulate 'click' and 'keypress' events, that is what .click() and .type() + * are for. This only works if the page you are testing has jQuery in it. + * @codestart + * S('#foo').trigger("myCustomEvent") + * @codeend + * @param {String} eventType A string containing a JavaScript event type, such as click or submit. + */ +'trigger' : 100, +/** + * @attr data + * Gets data from jQuery.data or waits until data + * equals some value. + * @codestart + * S("#something").data("abc") //gets the abc data + * + * S("#something").data("abc","some") //waits until the data == some + * @codeend + * @param {String} data The data to get, or wait for. + * @param {Object|Function} [value] If provided uses this as a check before continuing to the next action. + * @param {Function} a callback that will run after this action completes. + * @return {Object} if the size parameter is not provided, returns + * the object. + */ +'data': 1, +/** + * @function attr + * Gets the value of an attribute from an element or waits until the attribute + * equals the attr param. + * @codestart + * //gets the abc attribute + * S("#something").attr("abc") + * + * //waits until the abc attribute == some + * S("#something").attr("abc","some") + * @codeend + * @param {String} data The attribute to get, or wait for. + * @param {String|Function} [value] If provided uses this as a check before continuing to the next action. + * @param {Function} a callback that will run after this action completes. + * @return {Object} if the attr parameter is not provided, returns + * the attribute. + */ +'attr' : 1, +/** + * @function hasClass + * @codestart + * //returns if the element has the class in its className + * S("#something").hasClass("selected"); + * + * //waits until #something has selected in its className + * S("#something").hasClass("selected",true); + * + * //waits until #something does not have selected in its className + * S("#something").hasClass("selected",false); + * @codeend + * @param {String} className The part of the className to search for. + * @param {Boolean|Function} [value] If provided uses this as a check before continuing to the next action. + * @param {Function} [callback] a callback that will run after this action completes. + * @return {Boolean|funcUnit} if the value parameter is not provided, returns + * if the className is found in the element's className. If a value paramters is provided, returns funcUnit for chaining. + */ +'hasClass' : 1, //makes wait +/** + * @function html + * Gets the [http://api.jquery.com/html/ html] from an element or waits until the html is a certain value. + * @codestart + * //checks foo's html has "JupiterJS" + * ok( /JupiterJS/.test( S('#foo').html() ) ) + * + * //waits until bar's html has JupiterJS + * S('#foo').html(/JupiterJS/) + * + * //waits until bar's html is JupiterJS + * S('#foo').html("JupiterJS") + * @codeend + * + * @param {String|Function} [html] If provided uses this as a check before continuing to the next action. + * @param {Function} [callback] a callback that will run after this action completes. + * @return {String|funcUnit} if the html parameter is provided, + * returns the funcUnit selector for chaining, otherwise returns the html of the selector. + */ +'html' : 0, +/** + * @function text + * Gets the [http://api.jquery.com/text/ text] from an element or waits until the text is a certain value. + * @codestart + * //checks foo's text has "JupiterJS" + * ok( /JupiterJS/.test( S('#foo').text() ) ) + * + * //waits until bar's text has JupiterJS + * S('#foo').text(/JupiterJS/) + * + * //waits until bar's text is JupiterJS + * S('#foo').text("JupiterJS") + * @codeend + * + * @param {String|Function} [text] If provided uses this as a check before continuing to the next action. + * @param {Function} [callback] a callback that will run after this action completes. + * @return {String|funcUnit} if the text parameter is provided, + * returns the funcUnit selector for chaining, otherwise returns the html of the selector. + */ +'text' : 0, +/** + * @function val + * Gets the [http://api.jquery.com/val/ val] from an element or waits until the val is a certain value. + * @codestart + * //checks foo's val has "JupiterJS" + * ok( /JupiterJS/.test( S('input#foo').val() ) ) + * + * //waits until bar's val has JupiterJS + * S('input#foo').val(/JupiterJS/) + * + * //waits until bar's val is JupiterJS + * S('input#foo').val("JupiterJS") + * @codeend + * + * @param {String|Function} [val] If provided uses this as a check before continuing to the next action. + * @param {Function} [callback] a callback that will run after this action completes. + * @return {String|funcUnit} if the val parameter is provided, + * returns the funcUnit selector for chaining, otherwise returns the html of the selector. + */ +'val' : 0, +/** + * @function css + * Gets a [http://api.jquery.com/css/ css] property from an element or waits until the property is + * a specified value. + * @codestart + * // gets the color + * S("#foo").css("color") + * + * // waits until the color is red + * S("#foo").css("color","red") + * @codeend + * + * @param {String} prop A css property to get or wait until it is a specified value. + * @param {String|Function} [val] If provided uses this as a check before continuing to the next action. + * @param {Function} [callback] a callback that will run after this action completes. + * @return {String|funcUnit} if the val parameter is provided, + * returns the funcUnit selector for chaining, otherwise returns the css of the selector. + */ +'css': 1, +/** + * @function offset + * Gets an element's [http://api.jquery.com/offset/ offset] or waits until + * the offset is a specified value. + * @codestart + * // gets the offset + * S("#foo").offset(); + * + * // waits until the offset is 100, 200 + * S("#foo").offset({top: 100, left: 200}) + * @codeend + * + * @param {Object|Function} [offset] If provided uses this as a check before continuing to the next action. Or you can + * provide a function that returns true to continue to the next action. + * @param {Function} [callback] a callback that will run after this action completes. + * @return {String|funcUnit} if the offset parameter is provided, + * returns the funcUnit selector for chaining, otherwise returns the css of the selector. + */ +'offset' : 0, +/** + * @function position + * Gets an element's [http://api.jquery.com/position/ position] or waits until + * the position is a specified value. + * @codestart + * // gets the position + * S("#foo").position(); + * + * // waits until the position is 100, 200 + * S("#foo").position({top: 100, left: 200}) + * @codeend + * + * @param {Object|Function} [position] If provided uses this as a check before continuing to the next action. Or you can + * provide a function that returns true to continue to the next action. + * @param {Function} [callback] a callback that will run after this action completes. + * @return {String|funcUnit} if the position parameter is provided, + * returns the funcUnit selector for chaining, otherwise returns the offset of the selector. + */ +'position' : 0, +/** + * @function scrollTop + * Gets an element's [http://api.jquery.com/scrollTop/ scrollTop] or waits until + * it equals a specified value. + * @codestart + * // gets the scrollTop + * S("#foo").scrollTop(); + * + * // waits until the scrollTop is 100 + * S("#foo").scrollTop(100) + * @codeend + * + * @param {Number|Function} [scrollTop] If provided uses this as a check before continuing to the next action. Or you can + * provide a function that returns true to continue to the next action. + * @param {Function} [callback] a callback that will run after this action completes. + * @return {String|funcUnit} if scrollTop is provided, + * returns the funcUnit selector for chaining, otherwise returns the scrollTop of the selector. + */ +'scrollTop' : 0, +/** + * @function scrollLeft + * Gets an element's [http://api.jquery.com/scrollLeft/ scrollLeft] or waits until + * it equals a specified value. + * @codestart + * // gets the scrollLeft + * S("#foo").scrollLeft(); + * + * // waits until the scrollLeft is 100 + * S("#foo").scrollLeft(100) + * @codeend + * + * @param {Number|Function} [scrollLeft] If provided uses this as a check before continuing to the next action. Or you can + * provide a function that returns true to continue to the next action. + * @param {Function} [callback] a callback that will run after this action completes. + * @return {String|funcUnit} if scrollLeft is provided, + * returns the funcUnit selector for chaining, otherwise returns the scrollLeft of the selector. + */ +'scrollLeft' : 0, +/** + * @function height + * Gets an element's [http://api.jquery.com/height/ height] or waits until + * it equals a specified value. + * @codestart + * // gets the height + * S("#foo").height(); + * + * // waits until the height is 100 + * S("#foo").height(100) + * @codeend + * + * @param {Number|Function} [height] If provided uses this as a check before continuing to the next action. Or you can + * provide a function that returns true to continue to the next action. + * @param {Function} [callback] a callback that will run after this action completes. + * @return {String|funcUnit} if height is provided, + * returns the funcUnit selector for chaining, otherwise returns the height of the selector. + */ +'height' : 0, +/** + * @function width + * Gets an element's [http://api.jquery.com/width/ width] or waits until + * it equals a specified value. + * @codestart + * // gets the width + * S("#foo").width(); + * + * // waits until the width is 100 + * S("#foo").width(100) + * @codeend + * + * @param {Number|Function} [width] If provided uses this as a check before continuing to the next action. Or you can + * provide a function that returns true to continue to the next action. + * @param {Function} [callback] a callback that will run after this action completes. + * @return {String|funcUnit} if width is provided, + * returns the funcUnit selector for chaining, otherwise returns the width of the selector. + */ +'width' : 0, +/** + * @function innerHeight + * Gets an element's [http://api.jquery.com/innerHeight/ innerHeight] or waits until + * it equals a specified value. + * @codestart + * // gets the innerHeight + * S("#foo").innerHeight(); + * + * // waits until the innerHeight is 100 + * S("#foo").innerHeight(100) + * @codeend + * + * @param {Number|Function} [innerHeight] If provided uses this as a check before continuing to the next action. Or you can + * provide a function that returns true to continue to the next action. + * @param {Function} [callback] a callback that will run after this action completes. + * @return {String|funcUnit} if innerHeight is provided, + * returns the funcUnit selector for chaining, otherwise returns the innerHeight of the selector. + */ +'innerHeight' : 0, +/** + * @function innerWidth + * Gets an element's [http://api.jquery.com/innerWidth/ innerWidth] or waits until + * it equals a specified value. + * @codestart + * // gets the innerWidth + * S("#foo").innerWidth(); + * + * // waits until the innerWidth is 100 + * S("#foo").innerWidth(100) + * @codeend + * + * @param {Number|Function} [innerWidth] If provided uses this as a check before continuing to the next action. Or you can + * provide a function that returns true to continue to the next action. + * @param {Function} [callback] a callback that will run after this action completes. + * @return {String|funcUnit} if innerWidth is provided, + * returns the funcUnit selector for chaining, otherwise returns the innerWidth of the selector. + */ +'innerWidth' : 0, +/** + * @function outerHeight + * Gets an element's [http://api.jquery.com/outerHeight/ outerHeight] or waits until + * it equals a specified value. + * @codestart + * // gets the outerHeight + * S("#foo").outerHeight(); + * + * // waits until the outerHeight is 100 + * S("#foo").outerHeight(100) + * @codeend + * + * @param {Number|Function} [outerHeight] If provided uses this as a check before continuing to the next action. Or you can + * provide a function that returns true to continue to the next action. + * @param {Function} [callback] a callback that will run after this action completes. + * @return {String|funcUnit} if outerHeight is provided, + * returns the funcUnit selector for chaining, otherwise returns the outerHeight of the selector. + */ +'outerHeight' : 0, +/** + * @function outerWidth + * Gets an element's [http://api.jquery.com/outerWidth/ outerWidth] or waits until + * it equals a specified value. + * @codestart + * // gets the outerWidth + * S("#foo").outerWidth(); + * + * // waits until the outerWidth is 100 + * S("#foo").outerWidth(100) + * @codeend + * + * @param {Number|Function} [outerWidth] If provided uses this as a check before continuing to the next action. Or you can + * provide a function that returns true to continue to the next action. + * @param {Function} [callback] a callback that will run after this action completes. + * @return {String|funcUnit} if outerWidth is provided, + * returns the funcUnit selector for chaining, otherwise returns the outerWidth of the selector. + */ +'outerWidth' : 0} + + +//makes a jQuery like command. +FuncUnit.makeFunc = function(fname, argIndex){ + + //makes a read / wait function + FuncUnit.init.prototype[fname] = function(){ + //assume last arg is callback + var args = FuncUnit.makeArray(arguments), + callback, + isWait = args.length > argIndex, + callback; + + args.unshift(this.selector,this.context,fname) + + if(isWait){ + //get the args greater and equal to argIndex + var tester = args[argIndex+3], + timeout = args[argIndex+4], + callback = args[argIndex+5], + testVal = tester, + errorMessage = "waiting for "+fname +" on " + this.selector; + + if(typeof timeout == 'function'){ + callback = timeout; + timeout = undefined; + } + + args.splice(argIndex+3, args.length- argIndex - 3); + + if(typeof tester != 'function'){ + errorMessage += " !== "+testVal + tester = function(val){ + + return QUnit.equiv(val, testVal) || + (testVal instanceof RegExp && testVal.test(val) ); + } + } + FuncUnit.repeat(function(){ + var ret = FuncUnit.$.apply(FuncUnit.$, args); + return tester(ret); + }, callback, errorMessage, timeout) + + return this; + }else{ + //get the value + steal.dev.log("Getting "+fname+" on "+this.selector) + return FuncUnit.$.apply(FuncUnit.$, args); + } + } +} + + + +for (var prop in FuncUnit.funcs) { + FuncUnit.makeFunc(prop, FuncUnit.funcs[prop]); +} + + +S = FuncUnit; + +// handle case where syn was loaded before FuncUnit +if(!FuncUnit.jquery.fn.triggerSyn){ + FuncUnit.jquery.fn.triggerSyn = jQuery.fn.triggerSyn; +} +}) +//now add drivers +.then('resources/selenium_start', +'drivers/selenium', +'drivers/standard') diff --git a/browserid/static/funcunit/generate_docs.html b/browserid/static/funcunit/generate_docs.html new file mode 100644 index 0000000000000000000000000000000000000000..83e9cc9ccabf8e38fd5b8f5911068c5e1bedec31 --- /dev/null +++ b/browserid/static/funcunit/generate_docs.html @@ -0,0 +1,8 @@ +<html> + <head></head> + <body> + <script type='text/javascript' src='../steal/steal.js?steal[app]=funcunit/test/funcunit'></script> + <script type='text/javascript' src='pages/init.js'></script> + <script type='text/javascript' src='pages/follow.js'></script> + </body> +</html> \ No newline at end of file diff --git a/browserid/static/funcunit/index.html b/browserid/static/funcunit/index.html new file mode 100644 index 0000000000000000000000000000000000000000..1100678b2f8b328ab79d746355c0d4ad1f93fb08 --- /dev/null +++ b/browserid/static/funcunit/index.html @@ -0,0 +1,63 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" + "http://www.w3.org/TR/html4/strict.dtd"> +<html lang="en"> + <head> + <title>JavaScriptMVC</title> + <link rel="stylesheet" type='text/css' href='documentjs/jmvcdoc/style.css' /> + <link rel="shortcut icon" href="documentjs/jmvcdoc/images/favicon.ico" /> + </head> + <body> + + <div id='documentation'> + <div id='top'> + <div class="topCorner"><div> </div></div> + <div class="content"> + <div id="searchRoundCorners"> + <input id='search' type='input' disabled='true'/> + </div> + <div id='defaults'> + <ul id="menu" class="ui-menu"> + <li class="ui-menu-item"> + <a class="menuLink" href="#&search=*&who=index"><span class="menuSpan">Home</span></a> + </li> + <li class="ui-menu-item"> + <a class="menuLink" href="#favorites"><span class="menuSpan">Favorites</span></a> + </li> + <li class="ui-menu-item"> + <a class="menuLink" href="#&who=follow" title="Follow"><span class="menuSpan red">Follow</span></a> + </li> + <li class="ui-menu-item"> + <a class="menuLink" href="http://github.com/jupiterjs/funcunit" title="Code Repositories"><span class="menuSpan red">Code Repo</span></a> + </li> + </ul> + </div> + <div class="logo-text">FuncUnit <img src='jmvc/images/funcunit_small.png' class="logo-image"/></div> + </div> + <div class="bottomCorner"><div> </div></div> + </div> + + <div id='bottom'> + + + <div id='left'> + <a>Loading ... </a> + </div> + <div id='doc_container'> + <div id='doc'> + + </div> + <div id="disqus_thread"></div> + </div> + </div> + </div> + <div id='low'> + <a href="http://jupiterit.com">© Jupiter IT - FuncUnit Training and Support</a> + </div> + <script type='text/javascript'> + DOCS_LOCATION = "funcunit/docs/" //adds searchData to this + </script> + <script type='text/javascript' + src='steal/steal.js?steal[app]=documentjs/jmvcdoc&steal[env]=production'> + </script> + </body> +</html> \ No newline at end of file diff --git a/browserid/static/funcunit/java/extensions/fakesteal.js b/browserid/static/funcunit/java/extensions/fakesteal.js new file mode 100644 index 0000000000000000000000000000000000000000..5faff48d85a5dddb1001a160b6ca7d23de131e76 --- /dev/null +++ b/browserid/static/funcunit/java/extensions/fakesteal.js @@ -0,0 +1,13 @@ +steal = function () { + for (var i = 0; i < arguments.length; i++) { + if (typeof arguments[i] == "function") { + arguments[i](jQuery); + } + } + + return steal; +}; + +steal.plugins = steal.plugin = steal.then = steal.resources = steal; + +steal.dev = function () { }; \ No newline at end of file diff --git a/browserid/static/funcunit/java/extensions/wrapped.js b/browserid/static/funcunit/java/extensions/wrapped.js new file mode 100644 index 0000000000000000000000000000000000000000..f9d23348a2450cd2481cf31d1f231a2ded0655b4 --- /dev/null +++ b/browserid/static/funcunit/java/extensions/wrapped.js @@ -0,0 +1,122 @@ +/** + * This file has the methods used to override Selenium's default behavior + */ + +Selenium.makeArray = function(arr, win){ + if(!win){ + win = window; + } + var narr = win.Array(); + for (var i = 0; i < arr.length; i++) { + narr.push(arr[i]) + } + return narr; +} +jQuery.wrapped = function(){ + var args = Selenium.makeArray(arguments), + selector = args.shift(), + context = args.shift(), + method = args.shift(), + q, a, res; + + + + for(var i=0; i < arguments.length; i++){ + if (typeof arguments[i] == 'function' && arguments[i] == Selenium.resume) { + Selenium.pause(); + } + } + if (_win().jQuery && method == 'trigger') { + q = _win().jQuery(selector, context) + args = Selenium.makeArray(args, _win()) + } else { + q = jQuery(selector, context); + } + + res = q[method].apply(q, args); + //need to convert to json + return jQuery.toJSON(res.jquery ? true : res) +}; +_win = function(){ + var sel = selenium.browserbot + return sel.getCurrentWindow() +}; +_winVars = function(){ + var sel = selenium.browserbot + return sel.getCurrentWindow() +}; +_doc = function(){ + var sel = selenium.browserbot + return sel.getCurrentWindow().document +}; +Selenium.pause = function(){ + Selenium.paused = true; +}; + +Selenium.resume = function(){ + Selenium.paused = false; + currentTest.continueTest(); +}; +(function(){ +var RRTest = RemoteRunner.prototype.continueTest; +RemoteRunner.prototype.continueTest = function(){ + if(Selenium.paused){ + return; + } + RRTest.call(this); +}; + +// IE9 has problems with open hanging. It was because this method would return true when win.document couldn't be accessed. +// I overwrite this method and check if it happens while page is unloading, then continue. +IEBrowserBot.prototype._windowClosed = function(win) { + try { + var c = win.closed; + // frame windows claim to be non-closed when their parents are closed + // but you can't access their document objects in that case + if (!c) { + try { + win.document; + } catch (de) { + if (de.message == "Permission denied") { + // the window is probably unloading, which means it's probably not closed yet + return false; + } + else if (/^Access is denied/.test(de.message)) { + // rare variation on "Permission denied"? + LOG.debug("IEBrowserBot.windowClosed: got " + de.message + " (this.pageUnloading=" + this.pageUnloading + "); assuming window is unloading, probably not closed yet"); + return false; + } else { + if(this.pageUnloading) + { + LOG.info("IEBrowserBot.windowClosed2: couldn't read win.document, assume closed: " + de.message + " (this.pageUnloading=" + this.pageUnloading + ")"); + return false; + } + // this is probably one of those frame window situations + LOG.debug("IEBrowserBot.windowClosed: couldn't read win.document, assume closed: " + de.message + " (this.pageUnloading=" + this.pageUnloading + ")"); + return true; + } + } + } + if (c == null) { + LOG.debug("IEBrowserBot.windowClosed: win.closed was null, assuming closed"); + return true; + } + return c; + } catch (e) { + LOG.debug("IEBrowserBot._windowClosed: Got an exception trying to read win.closed; we'll have to take a guess!"); + + if (browserVersion.isHTA) { + if (e.message == "Permission denied") { + // the window is probably unloading, which means it's not closed yet + return false; + } else { + // there's a good chance that we've lost contact with the window object if it is closed + return true; + } + } else { + // the window is probably unloading, which means it's not closed yet + return false; + } + } +}; +})() \ No newline at end of file diff --git a/browserid/static/funcunit/java/selenium-java-client-driver.jar b/browserid/static/funcunit/java/selenium-java-client-driver.jar new file mode 100644 index 0000000000000000000000000000000000000000..4a1269853fdee281b8f29fd7c21c415d6028299b Binary files /dev/null and b/browserid/static/funcunit/java/selenium-java-client-driver.jar differ diff --git a/browserid/static/funcunit/java/selenium-server-standalone-2.0b3.jar b/browserid/static/funcunit/java/selenium-server-standalone-2.0b3.jar new file mode 100644 index 0000000000000000000000000000000000000000..d512a9ee88c90cc371f069c3c75f872f6a853da8 Binary files /dev/null and b/browserid/static/funcunit/java/selenium-server-standalone-2.0b3.jar differ diff --git a/browserid/static/funcunit/java/user-extensions.js b/browserid/static/funcunit/java/user-extensions.js new file mode 100644 index 0000000000000000000000000000000000000000..f9eae150ad01c75a0df33c303fbd4daa0b7e310e --- /dev/null +++ b/browserid/static/funcunit/java/user-extensions.js @@ -0,0 +1,10072 @@ +steal = function () { + for (var i = 0; i < arguments.length; i++) { + if (typeof arguments[i] == "function") { + arguments[i](jQuery); + } + } + + return steal; +}; + +steal.plugins = steal.plugin = steal.then = steal.resources = steal; + +steal.dev = function () { }; +/*! + * jQuery JavaScript Library v1.4.4 + * http://jquery.com/ + * + * Copyright 2010, John Resig + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * Copyright 2010, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * + * Date: Thu Nov 11 19:04:53 2010 -0500 + */ +(function( window, undefined ) { + +// Use the correct document accordingly with window argument (sandbox) +var document = window.document; +var jQuery = (function() { + +// Define a local copy of jQuery +var jQuery = function( selector, context ) { + // The jQuery object is actually just the init constructor 'enhanced' + return new jQuery.fn.init( selector, context ); + }, + + // Map over jQuery in case of overwrite + _jQuery = window.jQuery, + + // Map over the $ in case of overwrite + _$ = window.$, + + // A central reference to the root jQuery(document) + rootjQuery, + + // A simple way to check for HTML strings or ID strings + // (both of which we optimize for) + quickExpr = /^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]+)$)/, + + // Is it a simple selector + isSimple = /^.[^:#\[\.,]*$/, + + // Check if a string has a non-whitespace character in it + rnotwhite = /\S/, + rwhite = /\s/, + + // Used for trimming whitespace + trimLeft = /^\s+/, + trimRight = /\s+$/, + + // Check for non-word characters + rnonword = /\W/, + + // Check for digits + rdigit = /\d/, + + // Match a standalone tag + rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/, + + // JSON RegExp + rvalidchars = /^[\],:{}\s]*$/, + rvalidescape = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, + rvalidtokens = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, + rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g, + + // Useragent RegExp + rwebkit = /(webkit)[ \/]([\w.]+)/, + ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/, + rmsie = /(msie) ([\w.]+)/, + rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/, + + // Keep a UserAgent string for use with jQuery.browser + userAgent = navigator.userAgent, + + // For matching the engine and version of the browser + browserMatch, + + // Has the ready events already been bound? + readyBound = false, + + // The functions to execute on DOM ready + readyList = [], + + // The ready event handler + DOMContentLoaded, + + // Save a reference to some core methods + toString = Object.prototype.toString, + hasOwn = Object.prototype.hasOwnProperty, + push = Array.prototype.push, + slice = Array.prototype.slice, + trim = String.prototype.trim, + indexOf = Array.prototype.indexOf, + + // [[Class]] -> type pairs + class2type = {}; + +jQuery.fn = jQuery.prototype = { + init: function( selector, context ) { + var match, elem, ret, doc; + + // Handle $(""), $(null), or $(undefined) + if ( !selector ) { + return this; + } + + // Handle $(DOMElement) + if ( selector.nodeType ) { + this.context = this[0] = selector; + this.length = 1; + return this; + } + + // The body element only exists once, optimize finding it + if ( selector === "body" && !context && document.body ) { + this.context = document; + this[0] = document.body; + this.selector = "body"; + this.length = 1; + return this; + } + + // Handle HTML strings + if ( typeof selector === "string" ) { + // Are we dealing with HTML string or an ID? + match = quickExpr.exec( selector ); + + // Verify a match, and that no context was specified for #id + if ( match && (match[1] || !context) ) { + + // HANDLE: $(html) -> $(array) + if ( match[1] ) { + doc = (context ? context.ownerDocument || context : document); + + // If a single string is passed in and it's a single tag + // just do a createElement and skip the rest + ret = rsingleTag.exec( selector ); + + if ( ret ) { + if ( jQuery.isPlainObject( context ) ) { + selector = [ document.createElement( ret[1] ) ]; + jQuery.fn.attr.call( selector, context, true ); + + } else { + selector = [ doc.createElement( ret[1] ) ]; + } + + } else { + ret = jQuery.buildFragment( [ match[1] ], [ doc ] ); + selector = (ret.cacheable ? ret.fragment.cloneNode(true) : ret.fragment).childNodes; + } + + return jQuery.merge( this, selector ); + + // HANDLE: $("#id") + } else { + elem = document.getElementById( match[2] ); + + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + if ( elem && elem.parentNode ) { + // Handle the case where IE and Opera return items + // by name instead of ID + if ( elem.id !== match[2] ) { + return rootjQuery.find( selector ); + } + + // Otherwise, we inject the element directly into the jQuery object + this.length = 1; + this[0] = elem; + } + + this.context = document; + this.selector = selector; + return this; + } + + // HANDLE: $("TAG") + } else if ( !context && !rnonword.test( selector ) ) { + this.selector = selector; + this.context = document; + selector = document.getElementsByTagName( selector ); + return jQuery.merge( this, selector ); + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return (context || rootjQuery).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return jQuery( context ).find( selector ); + } + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( jQuery.isFunction( selector ) ) { + return rootjQuery.ready( selector ); + } + + if (selector.selector !== undefined) { + this.selector = selector.selector; + this.context = selector.context; + } + + return jQuery.makeArray( selector, this ); + }, + + // Start with an empty selector + selector: "", + + // The current version of jQuery being used + jquery: "1.4.4", + + // The default length of a jQuery object is 0 + length: 0, + + // The number of elements contained in the matched element set + size: function() { + return this.length; + }, + + toArray: function() { + return slice.call( this, 0 ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + return num == null ? + + // Return a 'clean' array + this.toArray() : + + // Return just the object + ( num < 0 ? this.slice(num)[ 0 ] : this[ num ] ); + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems, name, selector ) { + // Build a new jQuery matched element set + var ret = jQuery(); + + if ( jQuery.isArray( elems ) ) { + push.apply( ret, elems ); + + } else { + jQuery.merge( ret, elems ); + } + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + ret.context = this.context; + + if ( name === "find" ) { + ret.selector = this.selector + (this.selector ? " " : "") + selector; + } else if ( name ) { + ret.selector = this.selector + "." + name + "(" + selector + ")"; + } + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + // (You can seed the arguments with an array of args, but this is + // only used internally.) + each: function( callback, args ) { + return jQuery.each( this, callback, args ); + }, + + ready: function( fn ) { + // Attach the listeners + jQuery.bindReady(); + + // If the DOM is already ready + if ( jQuery.isReady ) { + // Execute the function immediately + fn.call( document, jQuery ); + + // Otherwise, remember the function for later + } else if ( readyList ) { + // Add the function to the wait list + readyList.push( fn ); + } + + return this; + }, + + eq: function( i ) { + return i === -1 ? + this.slice( i ) : + this.slice( i, +i + 1 ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ), + "slice", slice.call(arguments).join(",") ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map(this, function( elem, i ) { + return callback.call( elem, i, elem ); + })); + }, + + end: function() { + return this.prevObject || jQuery(null); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: push, + sort: [].sort, + splice: [].splice +}; + +// Give the init function the jQuery prototype for later instantiation +jQuery.fn.init.prototype = jQuery.fn; + +jQuery.extend = jQuery.fn.extend = function() { + var options, name, src, copy, copyIsArray, clone, + target = arguments[0] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + target = arguments[1] || {}; + // skip the boolean and the target + i = 2; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !jQuery.isFunction(target) ) { + target = {}; + } + + // extend jQuery itself if only one argument is passed + if ( length === i ) { + target = this; + --i; + } + + for ( ; i < length; i++ ) { + // Only deal with non-null/undefined values + if ( (options = arguments[ i ]) != null ) { + // Extend the base object + for ( name in options ) { + src = target[ name ]; + copy = options[ name ]; + + // Prevent never-ending loop + if ( target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { + if ( copyIsArray ) { + copyIsArray = false; + clone = src && jQuery.isArray(src) ? src : []; + + } else { + clone = src && jQuery.isPlainObject(src) ? src : {}; + } + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend({ + noConflict: function( deep ) { + window.$ = _$; + + if ( deep ) { + window.jQuery = _jQuery; + } + + return jQuery; + }, + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Handle when the DOM is ready + ready: function( wait ) { + // A third-party is pushing the ready event forwards + if ( wait === true ) { + jQuery.readyWait--; + } + + // Make sure that the DOM is not already loaded + if ( !jQuery.readyWait || (wait !== true && !jQuery.isReady) ) { + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if ( !document.body ) { + return setTimeout( jQuery.ready, 1 ); + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + if ( readyList ) { + // Execute all of them + var fn, + i = 0, + ready = readyList; + + // Reset the list of functions + readyList = null; + + while ( (fn = ready[ i++ ]) ) { + fn.call( document, jQuery ); + } + + // Trigger any bound ready events + if ( jQuery.fn.trigger ) { + jQuery( document ).trigger( "ready" ).unbind( "ready" ); + } + } + } + }, + + bindReady: function() { + if ( readyBound ) { + return; + } + + readyBound = true; + + // Catch cases where $(document).ready() is called after the + // browser event has already occurred. + if ( document.readyState === "complete" ) { + // Handle it asynchronously to allow scripts the opportunity to delay ready + return setTimeout( jQuery.ready, 1 ); + } + + // Mozilla, Opera and webkit nightlies currently support this event + if ( document.addEventListener ) { + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", jQuery.ready, false ); + + // If IE event model is used + } else if ( document.attachEvent ) { + // ensure firing before onload, + // maybe late but safe also for iframes + document.attachEvent("onreadystatechange", DOMContentLoaded); + + // A fallback to window.onload, that will always work + window.attachEvent( "onload", jQuery.ready ); + + // If IE and not a frame + // continually check to see if the document is ready + var toplevel = false; + + try { + toplevel = window.frameElement == null; + } catch(e) {} + + if ( document.documentElement.doScroll && toplevel ) { + doScrollCheck(); + } + } + }, + + // See test/unit/core.js for details concerning isFunction. + // Since version 1.3, DOM methods and functions like alert + // aren't supported. They return false on IE (#2968). + isFunction: function( obj ) { + return jQuery.type(obj) === "function"; + }, + + isArray: Array.isArray || function( obj ) { + return jQuery.type(obj) === "array"; + }, + + // A crude way of determining if an object is a window + isWindow: function( obj ) { + return obj && typeof obj === "object" && "setInterval" in obj; + }, + + isNaN: function( obj ) { + return obj == null || !rdigit.test( obj ) || isNaN( obj ); + }, + + type: function( obj ) { + return obj == null ? + String( obj ) : + class2type[ toString.call(obj) ] || "object"; + }, + + isPlainObject: function( obj ) { + // Must be an Object. + // Because of IE, we also have to check the presence of the constructor property. + // Make sure that DOM nodes and window objects don't pass through, as well + if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) { + return false; + } + + // Not own constructor property must be Object + if ( obj.constructor && + !hasOwn.call(obj, "constructor") && + !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) { + return false; + } + + // Own properties are enumerated firstly, so to speed up, + // if last one is own, then all properties are own. + + var key; + for ( key in obj ) {} + + return key === undefined || hasOwn.call( obj, key ); + }, + + isEmptyObject: function( obj ) { + for ( var name in obj ) { + return false; + } + return true; + }, + + error: function( msg ) { + throw msg; + }, + + parseJSON: function( data ) { + if ( typeof data !== "string" || !data ) { + return null; + } + + // Make sure leading/trailing whitespace is removed (IE can't handle it) + data = jQuery.trim( data ); + + // Make sure the incoming data is actual JSON + // Logic borrowed from http://json.org/json2.js + if ( rvalidchars.test(data.replace(rvalidescape, "@") + .replace(rvalidtokens, "]") + .replace(rvalidbraces, "")) ) { + + // Try to use the native JSON parser first + return window.JSON && window.JSON.parse ? + window.JSON.parse( data ) : + (new Function("return " + data))(); + + } else { + jQuery.error( "Invalid JSON: " + data ); + } + }, + + noop: function() {}, + + // Evalulates a script in a global context + globalEval: function( data ) { + if ( data && rnotwhite.test(data) ) { + // Inspired by code by Andrea Giammarchi + // http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html + var head = document.getElementsByTagName("head")[0] || document.documentElement, + script = document.createElement("script"); + + script.type = "text/javascript"; + + if ( jQuery.support.scriptEval ) { + script.appendChild( document.createTextNode( data ) ); + } else { + script.text = data; + } + + // Use insertBefore instead of appendChild to circumvent an IE6 bug. + // This arises when a base node is used (#2709). + head.insertBefore( script, head.firstChild ); + head.removeChild( script ); + } + }, + + nodeName: function( elem, name ) { + return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase(); + }, + + // args is for internal usage only + each: function( object, callback, args ) { + var name, i = 0, + length = object.length, + isObj = length === undefined || jQuery.isFunction(object); + + if ( args ) { + if ( isObj ) { + for ( name in object ) { + if ( callback.apply( object[ name ], args ) === false ) { + break; + } + } + } else { + for ( ; i < length; ) { + if ( callback.apply( object[ i++ ], args ) === false ) { + break; + } + } + } + + // A special, fast, case for the most common use of each + } else { + if ( isObj ) { + for ( name in object ) { + if ( callback.call( object[ name ], name, object[ name ] ) === false ) { + break; + } + } + } else { + for ( var value = object[0]; + i < length && callback.call( value, i, value ) !== false; value = object[++i] ) {} + } + } + + return object; + }, + + // Use native String.trim function wherever possible + trim: trim ? + function( text ) { + return text == null ? + "" : + trim.call( text ); + } : + + // Otherwise use our own trimming functionality + function( text ) { + return text == null ? + "" : + text.toString().replace( trimLeft, "" ).replace( trimRight, "" ); + }, + + // results is for internal usage only + makeArray: function( array, results ) { + var ret = results || []; + + if ( array != null ) { + // The window, strings (and functions) also have 'length' + // The extra typeof function check is to prevent crashes + // in Safari 2 (See: #3039) + // Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930 + var type = jQuery.type(array); + + if ( array.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( array ) ) { + push.call( ret, array ); + } else { + jQuery.merge( ret, array ); + } + } + + return ret; + }, + + inArray: function( elem, array ) { + if ( array.indexOf ) { + return array.indexOf( elem ); + } + + for ( var i = 0, length = array.length; i < length; i++ ) { + if ( array[ i ] === elem ) { + return i; + } + } + + return -1; + }, + + merge: function( first, second ) { + var i = first.length, + j = 0; + + if ( typeof second.length === "number" ) { + for ( var l = second.length; j < l; j++ ) { + first[ i++ ] = second[ j ]; + } + + } else { + while ( second[j] !== undefined ) { + first[ i++ ] = second[ j++ ]; + } + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, inv ) { + var ret = [], retVal; + inv = !!inv; + + // Go through the array, only saving the items + // that pass the validator function + for ( var i = 0, length = elems.length; i < length; i++ ) { + retVal = !!callback( elems[ i ], i ); + if ( inv !== retVal ) { + ret.push( elems[ i ] ); + } + } + + return ret; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var ret = [], value; + + // Go through the array, translating each of the items to their + // new value (or values). + for ( var i = 0, length = elems.length; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret[ ret.length ] = value; + } + } + + return ret.concat.apply( [], ret ); + }, + + // A global GUID counter for objects + guid: 1, + + proxy: function( fn, proxy, thisObject ) { + if ( arguments.length === 2 ) { + if ( typeof proxy === "string" ) { + thisObject = fn; + fn = thisObject[ proxy ]; + proxy = undefined; + + } else if ( proxy && !jQuery.isFunction( proxy ) ) { + thisObject = proxy; + proxy = undefined; + } + } + + if ( !proxy && fn ) { + proxy = function() { + return fn.apply( thisObject || this, arguments ); + }; + } + + // Set the guid of unique handler to the same of original handler, so it can be removed + if ( fn ) { + proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++; + } + + // So proxy can be declared as an argument + return proxy; + }, + + // Mutifunctional method to get and set values to a collection + // The value/s can be optionally by executed if its a function + access: function( elems, key, value, exec, fn, pass ) { + var length = elems.length; + + // Setting many attributes + if ( typeof key === "object" ) { + for ( var k in key ) { + jQuery.access( elems, k, key[k], exec, fn, value ); + } + return elems; + } + + // Setting one attribute + if ( value !== undefined ) { + // Optionally, function values get executed if exec is true + exec = !pass && exec && jQuery.isFunction(value); + + for ( var i = 0; i < length; i++ ) { + fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass ); + } + + return elems; + } + + // Getting an attribute + return length ? fn( elems[0], key ) : undefined; + }, + + now: function() { + return (new Date()).getTime(); + }, + + // Use of jQuery.browser is frowned upon. + // More details: http://docs.jquery.com/Utilities/jQuery.browser + uaMatch: function( ua ) { + ua = ua.toLowerCase(); + + var match = rwebkit.exec( ua ) || + ropera.exec( ua ) || + rmsie.exec( ua ) || + ua.indexOf("compatible") < 0 && rmozilla.exec( ua ) || + []; + + return { browser: match[1] || "", version: match[2] || "0" }; + }, + + browser: {} +}); + +// Populate the class2type map +jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +}); + +browserMatch = jQuery.uaMatch( userAgent ); +if ( browserMatch.browser ) { + jQuery.browser[ browserMatch.browser ] = true; + jQuery.browser.version = browserMatch.version; +} + +// Deprecated, use jQuery.browser.webkit instead +if ( jQuery.browser.webkit ) { + jQuery.browser.safari = true; +} + +if ( indexOf ) { + jQuery.inArray = function( elem, array ) { + return indexOf.call( array, elem ); + }; +} + +// Verify that \s matches non-breaking spaces +// (IE fails on this test) +if ( !rwhite.test( "\xA0" ) ) { + trimLeft = /^[\s\xA0]+/; + trimRight = /[\s\xA0]+$/; +} + +// All jQuery objects should point back to these +rootjQuery = jQuery(document); + +// Cleanup functions for the document ready method +if ( document.addEventListener ) { + DOMContentLoaded = function() { + document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false ); + jQuery.ready(); + }; + +} else if ( document.attachEvent ) { + DOMContentLoaded = function() { + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if ( document.readyState === "complete" ) { + document.detachEvent( "onreadystatechange", DOMContentLoaded ); + jQuery.ready(); + } + }; +} + +// The DOM ready check for Internet Explorer +function doScrollCheck() { + if ( jQuery.isReady ) { + return; + } + + try { + // If IE is used, use the trick by Diego Perini + // http://javascript.nwbox.com/IEContentLoaded/ + document.documentElement.doScroll("left"); + } catch(e) { + setTimeout( doScrollCheck, 1 ); + return; + } + + // and execute any waiting functions + jQuery.ready(); +} + +// Expose jQuery to the global object +return (window.jQuery = window.$ = jQuery); + +})(); + + +(function() { + + jQuery.support = {}; + + var root = document.documentElement, + script = document.createElement("script"), + div = document.createElement("div"), + id = "script" + jQuery.now(); + + div.style.display = "none"; + div.innerHTML = " <link/><table></table><a href='/a' style='color:red;float:left;opacity:.55;'>a</a><input type='checkbox'/>"; + + var all = div.getElementsByTagName("*"), + a = div.getElementsByTagName("a")[0], + select = document.createElement("select"), + opt = select.appendChild( document.createElement("option") ); + + // Can't get basic test support + if ( !all || !all.length || !a ) { + return; + } + + jQuery.support = { + // IE strips leading whitespace when .innerHTML is used + leadingWhitespace: div.firstChild.nodeType === 3, + + // Make sure that tbody elements aren't automatically inserted + // IE will insert them into empty tables + tbody: !div.getElementsByTagName("tbody").length, + + // Make sure that link elements get serialized correctly by innerHTML + // This requires a wrapper element in IE + htmlSerialize: !!div.getElementsByTagName("link").length, + + // Get the style information from getAttribute + // (IE uses .cssText insted) + style: /red/.test( a.getAttribute("style") ), + + // Make sure that URLs aren't manipulated + // (IE normalizes it by default) + hrefNormalized: a.getAttribute("href") === "/a", + + // Make sure that element opacity exists + // (IE uses filter instead) + // Use a regex to work around a WebKit issue. See #5145 + opacity: /^0.55$/.test( a.style.opacity ), + + // Verify style float existence + // (IE uses styleFloat instead of cssFloat) + cssFloat: !!a.style.cssFloat, + + // Make sure that if no value is specified for a checkbox + // that it defaults to "on". + // (WebKit defaults to "" instead) + checkOn: div.getElementsByTagName("input")[0].value === "on", + + // Make sure that a selected-by-default option has a working selected property. + // (WebKit defaults to false instead of true, IE too, if it's in an optgroup) + optSelected: opt.selected, + + // Will be defined later + deleteExpando: true, + optDisabled: false, + checkClone: false, + scriptEval: false, + noCloneEvent: true, + boxModel: null, + inlineBlockNeedsLayout: false, + shrinkWrapBlocks: false, + reliableHiddenOffsets: true + }; + + // Make sure that the options inside disabled selects aren't marked as disabled + // (WebKit marks them as diabled) + select.disabled = true; + jQuery.support.optDisabled = !opt.disabled; + + script.type = "text/javascript"; + try { + script.appendChild( document.createTextNode( "window." + id + "=1;" ) ); + } catch(e) {} + + root.insertBefore( script, root.firstChild ); + + // Make sure that the execution of code works by injecting a script + // tag with appendChild/createTextNode + // (IE doesn't support this, fails, and uses .text instead) + if ( window[ id ] ) { + jQuery.support.scriptEval = true; + delete window[ id ]; + } + + // Test to see if it's possible to delete an expando from an element + // Fails in Internet Explorer + try { + delete script.test; + + } catch(e) { + jQuery.support.deleteExpando = false; + } + + root.removeChild( script ); + + if ( div.attachEvent && div.fireEvent ) { + div.attachEvent("onclick", function click() { + // Cloning a node shouldn't copy over any + // bound event handlers (IE does this) + jQuery.support.noCloneEvent = false; + div.detachEvent("onclick", click); + }); + div.cloneNode(true).fireEvent("onclick"); + } + + div = document.createElement("div"); + div.innerHTML = "<input type='radio' name='radiotest' checked='checked'/>"; + + var fragment = document.createDocumentFragment(); + fragment.appendChild( div.firstChild ); + + // WebKit doesn't clone checked state correctly in fragments + jQuery.support.checkClone = fragment.cloneNode(true).cloneNode(true).lastChild.checked; + + // Figure out if the W3C box model works as expected + // document.body must exist before we can do this + jQuery(function() { + var div = document.createElement("div"); + div.style.width = div.style.paddingLeft = "1px"; + + document.body.appendChild( div ); + jQuery.boxModel = jQuery.support.boxModel = div.offsetWidth === 2; + + if ( "zoom" in div.style ) { + // Check if natively block-level elements act like inline-block + // elements when setting their display to 'inline' and giving + // them layout + // (IE < 8 does this) + div.style.display = "inline"; + div.style.zoom = 1; + jQuery.support.inlineBlockNeedsLayout = div.offsetWidth === 2; + + // Check if elements with layout shrink-wrap their children + // (IE 6 does this) + div.style.display = ""; + div.innerHTML = "<div style='width:4px;'></div>"; + jQuery.support.shrinkWrapBlocks = div.offsetWidth !== 2; + } + + div.innerHTML = "<table><tr><td style='padding:0;display:none'></td><td>t</td></tr></table>"; + var tds = div.getElementsByTagName("td"); + + // Check if table cells still have offsetWidth/Height when they are set + // to display:none and there are still other visible table cells in a + // table row; if so, offsetWidth/Height are not reliable for use when + // determining if an element has been hidden directly using + // display:none (it is still safe to use offsets if a parent element is + // hidden; don safety goggles and see bug #4512 for more information). + // (only IE 8 fails this test) + jQuery.support.reliableHiddenOffsets = tds[0].offsetHeight === 0; + + tds[0].style.display = ""; + tds[1].style.display = "none"; + + // Check if empty table cells still have offsetWidth/Height + // (IE < 8 fail this test) + jQuery.support.reliableHiddenOffsets = jQuery.support.reliableHiddenOffsets && tds[0].offsetHeight === 0; + div.innerHTML = ""; + + document.body.removeChild( div ).style.display = "none"; + div = tds = null; + }); + + // Technique from Juriy Zaytsev + // http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/ + var eventSupported = function( eventName ) { + var el = document.createElement("div"); + eventName = "on" + eventName; + + var isSupported = (eventName in el); + if ( !isSupported ) { + el.setAttribute(eventName, "return;"); + isSupported = typeof el[eventName] === "function"; + } + el = null; + + return isSupported; + }; + + jQuery.support.submitBubbles = eventSupported("submit"); + jQuery.support.changeBubbles = eventSupported("change"); + + // release memory in IE + root = script = div = all = a = null; +})(); + + + +var windowData = {}, + rbrace = /^(?:\{.*\}|\[.*\])$/; + +jQuery.extend({ + cache: {}, + + // Please use with caution + uuid: 0, + + // Unique for each copy of jQuery on the page + expando: "jQuery" + jQuery.now(), + + // The following elements throw uncatchable exceptions if you + // attempt to add expando properties to them. + noData: { + "embed": true, + // Ban all objects except for Flash (which handle expandos) + "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000", + "applet": true + }, + + data: function( elem, name, data ) { + if ( !jQuery.acceptData( elem ) ) { + return; + } + + elem = elem == window ? + windowData : + elem; + + var isNode = elem.nodeType, + id = isNode ? elem[ jQuery.expando ] : null, + cache = jQuery.cache, thisCache; + + if ( isNode && !id && typeof name === "string" && data === undefined ) { + return; + } + + // Get the data from the object directly + if ( !isNode ) { + cache = elem; + + // Compute a unique ID for the element + } else if ( !id ) { + elem[ jQuery.expando ] = id = ++jQuery.uuid; + } + + // Avoid generating a new cache unless none exists and we + // want to manipulate it. + if ( typeof name === "object" ) { + if ( isNode ) { + cache[ id ] = jQuery.extend(cache[ id ], name); + + } else { + jQuery.extend( cache, name ); + } + + } else if ( isNode && !cache[ id ] ) { + cache[ id ] = {}; + } + + thisCache = isNode ? cache[ id ] : cache; + + // Prevent overriding the named cache with undefined values + if ( data !== undefined ) { + thisCache[ name ] = data; + } + + return typeof name === "string" ? thisCache[ name ] : thisCache; + }, + + removeData: function( elem, name ) { + if ( !jQuery.acceptData( elem ) ) { + return; + } + + elem = elem == window ? + windowData : + elem; + + var isNode = elem.nodeType, + id = isNode ? elem[ jQuery.expando ] : elem, + cache = jQuery.cache, + thisCache = isNode ? cache[ id ] : id; + + // If we want to remove a specific section of the element's data + if ( name ) { + if ( thisCache ) { + // Remove the section of cache data + delete thisCache[ name ]; + + // If we've removed all the data, remove the element's cache + if ( isNode && jQuery.isEmptyObject(thisCache) ) { + jQuery.removeData( elem ); + } + } + + // Otherwise, we want to remove all of the element's data + } else { + if ( isNode && jQuery.support.deleteExpando ) { + delete elem[ jQuery.expando ]; + + } else if ( elem.removeAttribute ) { + elem.removeAttribute( jQuery.expando ); + + // Completely remove the data cache + } else if ( isNode ) { + delete cache[ id ]; + + // Remove all fields from the object + } else { + for ( var n in elem ) { + delete elem[ n ]; + } + } + } + }, + + // A method for determining if a DOM node can handle the data expando + acceptData: function( elem ) { + if ( elem.nodeName ) { + var match = jQuery.noData[ elem.nodeName.toLowerCase() ]; + + if ( match ) { + return !(match === true || elem.getAttribute("classid") !== match); + } + } + + return true; + } +}); + +jQuery.fn.extend({ + data: function( key, value ) { + var data = null; + + if ( typeof key === "undefined" ) { + if ( this.length ) { + var attr = this[0].attributes, name; + data = jQuery.data( this[0] ); + + for ( var i = 0, l = attr.length; i < l; i++ ) { + name = attr[i].name; + + if ( name.indexOf( "data-" ) === 0 ) { + name = name.substr( 5 ); + dataAttr( this[0], name, data[ name ] ); + } + } + } + + return data; + + } else if ( typeof key === "object" ) { + return this.each(function() { + jQuery.data( this, key ); + }); + } + + var parts = key.split("."); + parts[1] = parts[1] ? "." + parts[1] : ""; + + if ( value === undefined ) { + data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]); + + // Try to fetch any internally stored data first + if ( data === undefined && this.length ) { + data = jQuery.data( this[0], key ); + data = dataAttr( this[0], key, data ); + } + + return data === undefined && parts[1] ? + this.data( parts[0] ) : + data; + + } else { + return this.each(function() { + var $this = jQuery( this ), + args = [ parts[0], value ]; + + $this.triggerHandler( "setData" + parts[1] + "!", args ); + jQuery.data( this, key, value ); + $this.triggerHandler( "changeData" + parts[1] + "!", args ); + }); + } + }, + + removeData: function( key ) { + return this.each(function() { + jQuery.removeData( this, key ); + }); + } +}); + +function dataAttr( elem, key, data ) { + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + data = elem.getAttribute( "data-" + key ); + + if ( typeof data === "string" ) { + try { + data = data === "true" ? true : + data === "false" ? false : + data === "null" ? null : + !jQuery.isNaN( data ) ? parseFloat( data ) : + rbrace.test( data ) ? jQuery.parseJSON( data ) : + data; + } catch( e ) {} + + // Make sure we set the data so it isn't changed later + jQuery.data( elem, key, data ); + + } else { + data = undefined; + } + } + + return data; +} + + + + +jQuery.extend({ + queue: function( elem, type, data ) { + if ( !elem ) { + return; + } + + type = (type || "fx") + "queue"; + var q = jQuery.data( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( !data ) { + return q || []; + } + + if ( !q || jQuery.isArray(data) ) { + q = jQuery.data( elem, type, jQuery.makeArray(data) ); + + } else { + q.push( data ); + } + + return q; + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + fn = queue.shift(); + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + } + + if ( fn ) { + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift("inprogress"); + } + + fn.call(elem, function() { + jQuery.dequeue(elem, type); + }); + } + } +}); + +jQuery.fn.extend({ + queue: function( type, data ) { + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + } + + if ( data === undefined ) { + return jQuery.queue( this[0], type ); + } + return this.each(function( i ) { + var queue = jQuery.queue( this, type, data ); + + if ( type === "fx" && queue[0] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + }); + }, + dequeue: function( type ) { + return this.each(function() { + jQuery.dequeue( this, type ); + }); + }, + + // Based off of the plugin by Clint Helfers, with permission. + // http://blindsignals.com/index.php/2009/07/jquery-delay/ + delay: function( time, type ) { + time = jQuery.fx ? jQuery.fx.speeds[time] || time : time; + type = type || "fx"; + + return this.queue( type, function() { + var elem = this; + setTimeout(function() { + jQuery.dequeue( elem, type ); + }, time ); + }); + }, + + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + } +}); + + + + +var rclass = /[\n\t]/g, + rspaces = /\s+/, + rreturn = /\r/g, + rspecialurl = /^(?:href|src|style)$/, + rtype = /^(?:button|input)$/i, + rfocusable = /^(?:button|input|object|select|textarea)$/i, + rclickable = /^a(?:rea)?$/i, + rradiocheck = /^(?:radio|checkbox)$/i; + +jQuery.props = { + "for": "htmlFor", + "class": "className", + readonly: "readOnly", + maxlength: "maxLength", + cellspacing: "cellSpacing", + rowspan: "rowSpan", + colspan: "colSpan", + tabindex: "tabIndex", + usemap: "useMap", + frameborder: "frameBorder" +}; + +jQuery.fn.extend({ + attr: function( name, value ) { + return jQuery.access( this, name, value, true, jQuery.attr ); + }, + + removeAttr: function( name, fn ) { + return this.each(function(){ + jQuery.attr( this, name, "" ); + if ( this.nodeType === 1 ) { + this.removeAttribute( name ); + } + }); + }, + + addClass: function( value ) { + if ( jQuery.isFunction(value) ) { + return this.each(function(i) { + var self = jQuery(this); + self.addClass( value.call(this, i, self.attr("class")) ); + }); + } + + if ( value && typeof value === "string" ) { + var classNames = (value || "").split( rspaces ); + + for ( var i = 0, l = this.length; i < l; i++ ) { + var elem = this[i]; + + if ( elem.nodeType === 1 ) { + if ( !elem.className ) { + elem.className = value; + + } else { + var className = " " + elem.className + " ", + setClass = elem.className; + + for ( var c = 0, cl = classNames.length; c < cl; c++ ) { + if ( className.indexOf( " " + classNames[c] + " " ) < 0 ) { + setClass += " " + classNames[c]; + } + } + elem.className = jQuery.trim( setClass ); + } + } + } + } + + return this; + }, + + removeClass: function( value ) { + if ( jQuery.isFunction(value) ) { + return this.each(function(i) { + var self = jQuery(this); + self.removeClass( value.call(this, i, self.attr("class")) ); + }); + } + + if ( (value && typeof value === "string") || value === undefined ) { + var classNames = (value || "").split( rspaces ); + + for ( var i = 0, l = this.length; i < l; i++ ) { + var elem = this[i]; + + if ( elem.nodeType === 1 && elem.className ) { + if ( value ) { + var className = (" " + elem.className + " ").replace(rclass, " "); + for ( var c = 0, cl = classNames.length; c < cl; c++ ) { + className = className.replace(" " + classNames[c] + " ", " "); + } + elem.className = jQuery.trim( className ); + + } else { + elem.className = ""; + } + } + } + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + var type = typeof value, + isBool = typeof stateVal === "boolean"; + + if ( jQuery.isFunction( value ) ) { + return this.each(function(i) { + var self = jQuery(this); + self.toggleClass( value.call(this, i, self.attr("class"), stateVal), stateVal ); + }); + } + + return this.each(function() { + if ( type === "string" ) { + // toggle individual class names + var className, + i = 0, + self = jQuery( this ), + state = stateVal, + classNames = value.split( rspaces ); + + while ( (className = classNames[ i++ ]) ) { + // check each className given, space seperated list + state = isBool ? state : !self.hasClass( className ); + self[ state ? "addClass" : "removeClass" ]( className ); + } + + } else if ( type === "undefined" || type === "boolean" ) { + if ( this.className ) { + // store className if set + jQuery.data( this, "__className__", this.className ); + } + + // toggle whole className + this.className = this.className || value === false ? "" : jQuery.data( this, "__className__" ) || ""; + } + }); + }, + + hasClass: function( selector ) { + var className = " " + selector + " "; + for ( var i = 0, l = this.length; i < l; i++ ) { + if ( (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) { + return true; + } + } + + return false; + }, + + val: function( value ) { + if ( !arguments.length ) { + var elem = this[0]; + + if ( elem ) { + if ( jQuery.nodeName( elem, "option" ) ) { + // attributes.value is undefined in Blackberry 4.7 but + // uses .value. See #6932 + var val = elem.attributes.value; + return !val || val.specified ? elem.value : elem.text; + } + + // We need to handle select boxes special + if ( jQuery.nodeName( elem, "select" ) ) { + var index = elem.selectedIndex, + values = [], + options = elem.options, + one = elem.type === "select-one"; + + // Nothing was selected + if ( index < 0 ) { + return null; + } + + // Loop through all the selected options + for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) { + var option = options[ i ]; + + // Don't return options that are disabled or in a disabled optgroup + if ( option.selected && (jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) && + (!option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" )) ) { + + // Get the specific value for the option + value = jQuery(option).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } + } + + return values; + } + + // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified + if ( rradiocheck.test( elem.type ) && !jQuery.support.checkOn ) { + return elem.getAttribute("value") === null ? "on" : elem.value; + } + + + // Everything else, we just grab the value + return (elem.value || "").replace(rreturn, ""); + + } + + return undefined; + } + + var isFunction = jQuery.isFunction(value); + + return this.each(function(i) { + var self = jQuery(this), val = value; + + if ( this.nodeType !== 1 ) { + return; + } + + if ( isFunction ) { + val = value.call(this, i, self.val()); + } + + // Treat null/undefined as ""; convert numbers to string + if ( val == null ) { + val = ""; + } else if ( typeof val === "number" ) { + val += ""; + } else if ( jQuery.isArray(val) ) { + val = jQuery.map(val, function (value) { + return value == null ? "" : value + ""; + }); + } + + if ( jQuery.isArray(val) && rradiocheck.test( this.type ) ) { + this.checked = jQuery.inArray( self.val(), val ) >= 0; + + } else if ( jQuery.nodeName( this, "select" ) ) { + var values = jQuery.makeArray(val); + + jQuery( "option", this ).each(function() { + this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0; + }); + + if ( !values.length ) { + this.selectedIndex = -1; + } + + } else { + this.value = val; + } + }); + } +}); + +jQuery.extend({ + attrFn: { + val: true, + css: true, + html: true, + text: true, + data: true, + width: true, + height: true, + offset: true + }, + + attr: function( elem, name, value, pass ) { + // don't set attributes on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 ) { + return undefined; + } + + if ( pass && name in jQuery.attrFn ) { + return jQuery(elem)[name](value); + } + + var notxml = elem.nodeType !== 1 || !jQuery.isXMLDoc( elem ), + // Whether we are setting (or getting) + set = value !== undefined; + + // Try to normalize/fix the name + name = notxml && jQuery.props[ name ] || name; + + // These attributes require special treatment + var special = rspecialurl.test( name ); + + // Safari mis-reports the default selected property of an option + // Accessing the parent's selectedIndex property fixes it + if ( name === "selected" && !jQuery.support.optSelected ) { + var parent = elem.parentNode; + if ( parent ) { + parent.selectedIndex; + + // Make sure that it also works with optgroups, see #5701 + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + } + + // If applicable, access the attribute via the DOM 0 way + // 'in' checks fail in Blackberry 4.7 #6931 + if ( (name in elem || elem[ name ] !== undefined) && notxml && !special ) { + if ( set ) { + // We can't allow the type property to be changed (since it causes problems in IE) + if ( name === "type" && rtype.test( elem.nodeName ) && elem.parentNode ) { + jQuery.error( "type property can't be changed" ); + } + + if ( value === null ) { + if ( elem.nodeType === 1 ) { + elem.removeAttribute( name ); + } + + } else { + elem[ name ] = value; + } + } + + // browsers index elements by id/name on forms, give priority to attributes. + if ( jQuery.nodeName( elem, "form" ) && elem.getAttributeNode(name) ) { + return elem.getAttributeNode( name ).nodeValue; + } + + // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set + // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + if ( name === "tabIndex" ) { + var attributeNode = elem.getAttributeNode( "tabIndex" ); + + return attributeNode && attributeNode.specified ? + attributeNode.value : + rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? + 0 : + undefined; + } + + return elem[ name ]; + } + + if ( !jQuery.support.style && notxml && name === "style" ) { + if ( set ) { + elem.style.cssText = "" + value; + } + + return elem.style.cssText; + } + + if ( set ) { + // convert the value to a string (all browsers do this but IE) see #1070 + elem.setAttribute( name, "" + value ); + } + + // Ensure that missing attributes return undefined + // Blackberry 4.7 returns "" from getAttribute #6938 + if ( !elem.attributes[ name ] && (elem.hasAttribute && !elem.hasAttribute( name )) ) { + return undefined; + } + + var attr = !jQuery.support.hrefNormalized && notxml && special ? + // Some attributes require a special call on IE + elem.getAttribute( name, 2 ) : + elem.getAttribute( name ); + + // Non-existent attributes return null, we normalize to undefined + return attr === null ? undefined : attr; + } +}); + + + + +var rnamespaces = /\.(.*)$/, + rformElems = /^(?:textarea|input|select)$/i, + rperiod = /\./g, + rspace = / /g, + rescape = /[^\w\s.|`]/g, + fcleanup = function( nm ) { + return nm.replace(rescape, "\\$&"); + }, + focusCounts = { focusin: 0, focusout: 0 }; + +/* + * A number of helper functions used for managing events. + * Many of the ideas behind this code originated from + * Dean Edwards' addEvent library. + */ +jQuery.event = { + + // Bind an event to an element + // Original by Dean Edwards + add: function( elem, types, handler, data ) { + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // For whatever reason, IE has trouble passing the window object + // around, causing it to be cloned in the process + if ( jQuery.isWindow( elem ) && ( elem !== window && !elem.frameElement ) ) { + elem = window; + } + + if ( handler === false ) { + handler = returnFalse; + } else if ( !handler ) { + // Fixes bug #7229. Fix recommended by jdalton + return; + } + + var handleObjIn, handleObj; + + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + } + + // Make sure that the function being executed has a unique ID + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure + var elemData = jQuery.data( elem ); + + // If no elemData is found then we must be trying to bind to one of the + // banned noData elements + if ( !elemData ) { + return; + } + + // Use a key less likely to result in collisions for plain JS objects. + // Fixes bug #7150. + var eventKey = elem.nodeType ? "events" : "__events__", + events = elemData[ eventKey ], + eventHandle = elemData.handle; + + if ( typeof events === "function" ) { + // On plain objects events is a fn that holds the the data + // which prevents this data from being JSON serialized + // the function does not need to be called, it just contains the data + eventHandle = events.handle; + events = events.events; + + } else if ( !events ) { + if ( !elem.nodeType ) { + // On plain objects, create a fn that acts as the holder + // of the values to avoid JSON serialization of event data + elemData[ eventKey ] = elemData = function(){}; + } + + elemData.events = events = {}; + } + + if ( !eventHandle ) { + elemData.handle = eventHandle = function() { + // Handle the second event of a trigger and when + // an event is called after a page has unloaded + return typeof jQuery !== "undefined" && !jQuery.event.triggered ? + jQuery.event.handle.apply( eventHandle.elem, arguments ) : + undefined; + }; + } + + // Add elem as a property of the handle function + // This is to prevent a memory leak with non-native events in IE. + eventHandle.elem = elem; + + // Handle multiple events separated by a space + // jQuery(...).bind("mouseover mouseout", fn); + types = types.split(" "); + + var type, i = 0, namespaces; + + while ( (type = types[ i++ ]) ) { + handleObj = handleObjIn ? + jQuery.extend({}, handleObjIn) : + { handler: handler, data: data }; + + // Namespaced event handlers + if ( type.indexOf(".") > -1 ) { + namespaces = type.split("."); + type = namespaces.shift(); + handleObj.namespace = namespaces.slice(0).sort().join("."); + + } else { + namespaces = []; + handleObj.namespace = ""; + } + + handleObj.type = type; + if ( !handleObj.guid ) { + handleObj.guid = handler.guid; + } + + // Get the current list of functions bound to this event + var handlers = events[ type ], + special = jQuery.event.special[ type ] || {}; + + // Init the event handler queue + if ( !handlers ) { + handlers = events[ type ] = []; + + // Check for a special event handler + // Only use addEventListener/attachEvent if the special + // events handler returns false + if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + // Bind the global event handler to the element + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle, false ); + + } else if ( elem.attachEvent ) { + elem.attachEvent( "on" + type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add the function to the element's handler list + handlers.push( handleObj ); + + // Keep track of which events have been used, for global triggering + jQuery.event.global[ type ] = true; + } + + // Nullify elem to prevent memory leaks in IE + elem = null; + }, + + global: {}, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, pos ) { + // don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + if ( handler === false ) { + handler = returnFalse; + } + + var ret, type, fn, j, i = 0, all, namespaces, namespace, special, eventType, handleObj, origType, + eventKey = elem.nodeType ? "events" : "__events__", + elemData = jQuery.data( elem ), + events = elemData && elemData[ eventKey ]; + + if ( !elemData || !events ) { + return; + } + + if ( typeof events === "function" ) { + elemData = events; + events = events.events; + } + + // types is actually an event object here + if ( types && types.type ) { + handler = types.handler; + types = types.type; + } + + // Unbind all events for the element + if ( !types || typeof types === "string" && types.charAt(0) === "." ) { + types = types || ""; + + for ( type in events ) { + jQuery.event.remove( elem, type + types ); + } + + return; + } + + // Handle multiple events separated by a space + // jQuery(...).unbind("mouseover mouseout", fn); + types = types.split(" "); + + while ( (type = types[ i++ ]) ) { + origType = type; + handleObj = null; + all = type.indexOf(".") < 0; + namespaces = []; + + if ( !all ) { + // Namespaced event handlers + namespaces = type.split("."); + type = namespaces.shift(); + + namespace = new RegExp("(^|\\.)" + + jQuery.map( namespaces.slice(0).sort(), fcleanup ).join("\\.(?:.*\\.)?") + "(\\.|$)"); + } + + eventType = events[ type ]; + + if ( !eventType ) { + continue; + } + + if ( !handler ) { + for ( j = 0; j < eventType.length; j++ ) { + handleObj = eventType[ j ]; + + if ( all || namespace.test( handleObj.namespace ) ) { + jQuery.event.remove( elem, origType, handleObj.handler, j ); + eventType.splice( j--, 1 ); + } + } + + continue; + } + + special = jQuery.event.special[ type ] || {}; + + for ( j = pos || 0; j < eventType.length; j++ ) { + handleObj = eventType[ j ]; + + if ( handler.guid === handleObj.guid ) { + // remove the given handler for the given type + if ( all || namespace.test( handleObj.namespace ) ) { + if ( pos == null ) { + eventType.splice( j--, 1 ); + } + + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + + if ( pos != null ) { + break; + } + } + } + + // remove generic event handler if no more handlers exist + if ( eventType.length === 0 || pos != null && eventType.length === 1 ) { + if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) { + jQuery.removeEvent( elem, type, elemData.handle ); + } + + ret = null; + delete events[ type ]; + } + } + + // Remove the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + var handle = elemData.handle; + if ( handle ) { + handle.elem = null; + } + + delete elemData.events; + delete elemData.handle; + + if ( typeof elemData === "function" ) { + jQuery.removeData( elem, eventKey ); + + } else if ( jQuery.isEmptyObject( elemData ) ) { + jQuery.removeData( elem ); + } + } + }, + + // bubbling is internal + trigger: function( event, data, elem /*, bubbling */ ) { + // Event object or event type + var type = event.type || event, + bubbling = arguments[3]; + + if ( !bubbling ) { + event = typeof event === "object" ? + // jQuery.Event object + event[ jQuery.expando ] ? event : + // Object literal + jQuery.extend( jQuery.Event(type), event ) : + // Just the event type (string) + jQuery.Event(type); + + if ( type.indexOf("!") >= 0 ) { + event.type = type = type.slice(0, -1); + event.exclusive = true; + } + + // Handle a global trigger + if ( !elem ) { + // Don't bubble custom events when global (to avoid too much overhead) + event.stopPropagation(); + + // Only trigger if we've ever bound an event for it + if ( jQuery.event.global[ type ] ) { + jQuery.each( jQuery.cache, function() { + if ( this.events && this.events[type] ) { + jQuery.event.trigger( event, data, this.handle.elem ); + } + }); + } + } + + // Handle triggering a single element + + // don't do events on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 ) { + return undefined; + } + + // Clean up in case it is reused + event.result = undefined; + event.target = elem; + + // Clone the incoming data, if any + data = jQuery.makeArray( data ); + data.unshift( event ); + } + + event.currentTarget = elem; + + // Trigger the event, it is assumed that "handle" is a function + var handle = elem.nodeType ? + jQuery.data( elem, "handle" ) : + (jQuery.data( elem, "__events__" ) || {}).handle; + + if ( handle ) { + handle.apply( elem, data ); + } + + var parent = elem.parentNode || elem.ownerDocument; + + // Trigger an inline bound script + try { + if ( !(elem && elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()]) ) { + if ( elem[ "on" + type ] && elem[ "on" + type ].apply( elem, data ) === false ) { + event.result = false; + event.preventDefault(); + } + } + + // prevent IE from throwing an error for some elements with some event types, see #3533 + } catch (inlineError) {} + + if ( !event.isPropagationStopped() && parent ) { + jQuery.event.trigger( event, data, parent, true ); + + } else if ( !event.isDefaultPrevented() ) { + var old, + target = event.target, + targetType = type.replace( rnamespaces, "" ), + isClick = jQuery.nodeName( target, "a" ) && targetType === "click", + special = jQuery.event.special[ targetType ] || {}; + + if ( (!special._default || special._default.call( elem, event ) === false) && + !isClick && !(target && target.nodeName && jQuery.noData[target.nodeName.toLowerCase()]) ) { + + try { + if ( target[ targetType ] ) { + // Make sure that we don't accidentally re-trigger the onFOO events + old = target[ "on" + targetType ]; + + if ( old ) { + target[ "on" + targetType ] = null; + } + + jQuery.event.triggered = true; + target[ targetType ](); + } + + // prevent IE from throwing an error for some elements with some event types, see #3533 + } catch (triggerError) {} + + if ( old ) { + target[ "on" + targetType ] = old; + } + + jQuery.event.triggered = false; + } + } + }, + + handle: function( event ) { + var all, handlers, namespaces, namespace_re, events, + namespace_sort = [], + args = jQuery.makeArray( arguments ); + + event = args[0] = jQuery.event.fix( event || window.event ); + event.currentTarget = this; + + // Namespaced event handlers + all = event.type.indexOf(".") < 0 && !event.exclusive; + + if ( !all ) { + namespaces = event.type.split("."); + event.type = namespaces.shift(); + namespace_sort = namespaces.slice(0).sort(); + namespace_re = new RegExp("(^|\\.)" + namespace_sort.join("\\.(?:.*\\.)?") + "(\\.|$)"); + } + + event.namespace = event.namespace || namespace_sort.join("."); + + events = jQuery.data(this, this.nodeType ? "events" : "__events__"); + + if ( typeof events === "function" ) { + events = events.events; + } + + handlers = (events || {})[ event.type ]; + + if ( events && handlers ) { + // Clone the handlers to prevent manipulation + handlers = handlers.slice(0); + + for ( var j = 0, l = handlers.length; j < l; j++ ) { + var handleObj = handlers[ j ]; + + // Filter the functions by class + if ( all || namespace_re.test( handleObj.namespace ) ) { + // Pass in a reference to the handler function itself + // So that we can later remove it + event.handler = handleObj.handler; + event.data = handleObj.data; + event.handleObj = handleObj; + + var ret = handleObj.handler.apply( this, args ); + + if ( ret !== undefined ) { + event.result = ret; + if ( ret === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + + if ( event.isImmediatePropagationStopped() ) { + break; + } + } + } + } + + return event.result; + }, + + props: "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "), + + fix: function( event ) { + if ( event[ jQuery.expando ] ) { + return event; + } + + // store a copy of the original event object + // and "clone" to set read-only properties + var originalEvent = event; + event = jQuery.Event( originalEvent ); + + for ( var i = this.props.length, prop; i; ) { + prop = this.props[ --i ]; + event[ prop ] = originalEvent[ prop ]; + } + + // Fix target property, if necessary + if ( !event.target ) { + // Fixes #1925 where srcElement might not be defined either + event.target = event.srcElement || document; + } + + // check if target is a textnode (safari) + if ( event.target.nodeType === 3 ) { + event.target = event.target.parentNode; + } + + // Add relatedTarget, if necessary + if ( !event.relatedTarget && event.fromElement ) { + event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement; + } + + // Calculate pageX/Y if missing and clientX/Y available + if ( event.pageX == null && event.clientX != null ) { + var doc = document.documentElement, + body = document.body; + + event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0); + event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0); + } + + // Add which for key events + if ( event.which == null && (event.charCode != null || event.keyCode != null) ) { + event.which = event.charCode != null ? event.charCode : event.keyCode; + } + + // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs) + if ( !event.metaKey && event.ctrlKey ) { + event.metaKey = event.ctrlKey; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + // Note: button is not normalized, so don't use it + if ( !event.which && event.button !== undefined ) { + event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) )); + } + + return event; + }, + + // Deprecated, use jQuery.guid instead + guid: 1E8, + + // Deprecated, use jQuery.proxy instead + proxy: jQuery.proxy, + + special: { + ready: { + // Make sure the ready event is setup + setup: jQuery.bindReady, + teardown: jQuery.noop + }, + + live: { + add: function( handleObj ) { + jQuery.event.add( this, + liveConvert( handleObj.origType, handleObj.selector ), + jQuery.extend({}, handleObj, {handler: liveHandler, guid: handleObj.handler.guid}) ); + }, + + remove: function( handleObj ) { + jQuery.event.remove( this, liveConvert( handleObj.origType, handleObj.selector ), handleObj ); + } + }, + + beforeunload: { + setup: function( data, namespaces, eventHandle ) { + // We only want to do this special case on windows + if ( jQuery.isWindow( this ) ) { + this.onbeforeunload = eventHandle; + } + }, + + teardown: function( namespaces, eventHandle ) { + if ( this.onbeforeunload === eventHandle ) { + this.onbeforeunload = null; + } + } + } + } +}; + +jQuery.removeEvent = document.removeEventListener ? + function( elem, type, handle ) { + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle, false ); + } + } : + function( elem, type, handle ) { + if ( elem.detachEvent ) { + elem.detachEvent( "on" + type, handle ); + } + }; + +jQuery.Event = function( src ) { + // Allow instantiation without the 'new' keyword + if ( !this.preventDefault ) { + return new jQuery.Event( src ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + // Event type + } else { + this.type = src; + } + + // timeStamp is buggy for some events on Firefox(#3843) + // So we won't rely on the native value + this.timeStamp = jQuery.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +function returnFalse() { + return false; +} +function returnTrue() { + return true; +} + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + preventDefault: function() { + this.isDefaultPrevented = returnTrue; + + var e = this.originalEvent; + if ( !e ) { + return; + } + + // if preventDefault exists run it on the original event + if ( e.preventDefault ) { + e.preventDefault(); + + // otherwise set the returnValue property of the original event to false (IE) + } else { + e.returnValue = false; + } + }, + stopPropagation: function() { + this.isPropagationStopped = returnTrue; + + var e = this.originalEvent; + if ( !e ) { + return; + } + // if stopPropagation exists run it on the original event + if ( e.stopPropagation ) { + e.stopPropagation(); + } + // otherwise set the cancelBubble property of the original event to true (IE) + e.cancelBubble = true; + }, + stopImmediatePropagation: function() { + this.isImmediatePropagationStopped = returnTrue; + this.stopPropagation(); + }, + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse +}; + +// Checks if an event happened on an element within another element +// Used in jQuery.event.special.mouseenter and mouseleave handlers +var withinElement = function( event ) { + // Check if mouse(over|out) are still within the same parent element + var parent = event.relatedTarget; + + // Firefox sometimes assigns relatedTarget a XUL element + // which we cannot access the parentNode property of + try { + // Traverse up the tree + while ( parent && parent !== this ) { + parent = parent.parentNode; + } + + if ( parent !== this ) { + // set the correct event type + event.type = event.data; + + // handle event if we actually just moused on to a non sub-element + jQuery.event.handle.apply( this, arguments ); + } + + // assuming we've left the element since we most likely mousedover a xul element + } catch(e) { } +}, + +// In case of event delegation, we only need to rename the event.type, +// liveHandler will take care of the rest. +delegate = function( event ) { + event.type = event.data; + jQuery.event.handle.apply( this, arguments ); +}; + +// Create mouseenter and mouseleave events +jQuery.each({ + mouseenter: "mouseover", + mouseleave: "mouseout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + setup: function( data ) { + jQuery.event.add( this, fix, data && data.selector ? delegate : withinElement, orig ); + }, + teardown: function( data ) { + jQuery.event.remove( this, fix, data && data.selector ? delegate : withinElement ); + } + }; +}); + +// submit delegation +if ( !jQuery.support.submitBubbles ) { + + jQuery.event.special.submit = { + setup: function( data, namespaces ) { + if ( this.nodeName.toLowerCase() !== "form" ) { + jQuery.event.add(this, "click.specialSubmit", function( e ) { + var elem = e.target, + type = elem.type; + + if ( (type === "submit" || type === "image") && jQuery( elem ).closest("form").length ) { + e.liveFired = undefined; + return trigger( "submit", this, arguments ); + } + }); + + jQuery.event.add(this, "keypress.specialSubmit", function( e ) { + var elem = e.target, + type = elem.type; + + if ( (type === "text" || type === "password") && jQuery( elem ).closest("form").length && e.keyCode === 13 ) { + e.liveFired = undefined; + return trigger( "submit", this, arguments ); + } + }); + + } else { + return false; + } + }, + + teardown: function( namespaces ) { + jQuery.event.remove( this, ".specialSubmit" ); + } + }; + +} + +// change delegation, happens here so we have bind. +if ( !jQuery.support.changeBubbles ) { + + var changeFilters, + + getVal = function( elem ) { + var type = elem.type, val = elem.value; + + if ( type === "radio" || type === "checkbox" ) { + val = elem.checked; + + } else if ( type === "select-multiple" ) { + val = elem.selectedIndex > -1 ? + jQuery.map( elem.options, function( elem ) { + return elem.selected; + }).join("-") : + ""; + + } else if ( elem.nodeName.toLowerCase() === "select" ) { + val = elem.selectedIndex; + } + + return val; + }, + + testChange = function testChange( e ) { + var elem = e.target, data, val; + + if ( !rformElems.test( elem.nodeName ) || elem.readOnly ) { + return; + } + + data = jQuery.data( elem, "_change_data" ); + val = getVal(elem); + + // the current data will be also retrieved by beforeactivate + if ( e.type !== "focusout" || elem.type !== "radio" ) { + jQuery.data( elem, "_change_data", val ); + } + + if ( data === undefined || val === data ) { + return; + } + + if ( data != null || val ) { + e.type = "change"; + e.liveFired = undefined; + return jQuery.event.trigger( e, arguments[1], elem ); + } + }; + + jQuery.event.special.change = { + filters: { + focusout: testChange, + + beforedeactivate: testChange, + + click: function( e ) { + var elem = e.target, type = elem.type; + + if ( type === "radio" || type === "checkbox" || elem.nodeName.toLowerCase() === "select" ) { + return testChange.call( this, e ); + } + }, + + // Change has to be called before submit + // Keydown will be called before keypress, which is used in submit-event delegation + keydown: function( e ) { + var elem = e.target, type = elem.type; + + if ( (e.keyCode === 13 && elem.nodeName.toLowerCase() !== "textarea") || + (e.keyCode === 32 && (type === "checkbox" || type === "radio")) || + type === "select-multiple" ) { + return testChange.call( this, e ); + } + }, + + // Beforeactivate happens also before the previous element is blurred + // with this event you can't trigger a change event, but you can store + // information + beforeactivate: function( e ) { + var elem = e.target; + jQuery.data( elem, "_change_data", getVal(elem) ); + } + }, + + setup: function( data, namespaces ) { + if ( this.type === "file" ) { + return false; + } + + for ( var type in changeFilters ) { + jQuery.event.add( this, type + ".specialChange", changeFilters[type] ); + } + + return rformElems.test( this.nodeName ); + }, + + teardown: function( namespaces ) { + jQuery.event.remove( this, ".specialChange" ); + + return rformElems.test( this.nodeName ); + } + }; + + changeFilters = jQuery.event.special.change.filters; + + // Handle when the input is .focus()'d + changeFilters.focus = changeFilters.beforeactivate; +} + +function trigger( type, elem, args ) { + args[0].type = type; + return jQuery.event.handle.apply( elem, args ); +} + +// Create "bubbling" focus and blur events +if ( document.addEventListener ) { + jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { + jQuery.event.special[ fix ] = { + setup: function() { + if ( focusCounts[fix]++ === 0 ) { + document.addEventListener( orig, handler, true ); + } + }, + teardown: function() { + if ( --focusCounts[fix] === 0 ) { + document.removeEventListener( orig, handler, true ); + } + } + }; + + function handler( e ) { + e = jQuery.event.fix( e ); + e.type = fix; + return jQuery.event.trigger( e, null, e.target ); + } + }); +} + +jQuery.each(["bind", "one"], function( i, name ) { + jQuery.fn[ name ] = function( type, data, fn ) { + // Handle object literals + if ( typeof type === "object" ) { + for ( var key in type ) { + this[ name ](key, data, type[key], fn); + } + return this; + } + + if ( jQuery.isFunction( data ) || data === false ) { + fn = data; + data = undefined; + } + + var handler = name === "one" ? jQuery.proxy( fn, function( event ) { + jQuery( this ).unbind( event, handler ); + return fn.apply( this, arguments ); + }) : fn; + + if ( type === "unload" && name !== "one" ) { + this.one( type, data, fn ); + + } else { + for ( var i = 0, l = this.length; i < l; i++ ) { + jQuery.event.add( this[i], type, handler, data ); + } + } + + return this; + }; +}); + +jQuery.fn.extend({ + unbind: function( type, fn ) { + // Handle object literals + if ( typeof type === "object" && !type.preventDefault ) { + for ( var key in type ) { + this.unbind(key, type[key]); + } + + } else { + for ( var i = 0, l = this.length; i < l; i++ ) { + jQuery.event.remove( this[i], type, fn ); + } + } + + return this; + }, + + delegate: function( selector, types, data, fn ) { + return this.live( types, data, fn, selector ); + }, + + undelegate: function( selector, types, fn ) { + if ( arguments.length === 0 ) { + return this.unbind( "live" ); + + } else { + return this.die( types, null, fn, selector ); + } + }, + + trigger: function( type, data ) { + return this.each(function() { + jQuery.event.trigger( type, data, this ); + }); + }, + + triggerHandler: function( type, data ) { + if ( this[0] ) { + var event = jQuery.Event( type ); + event.preventDefault(); + event.stopPropagation(); + jQuery.event.trigger( event, data, this[0] ); + return event.result; + } + }, + + toggle: function( fn ) { + // Save reference to arguments for access in closure + var args = arguments, + i = 1; + + // link all the functions, so any of them can unbind this click handler + while ( i < args.length ) { + jQuery.proxy( fn, args[ i++ ] ); + } + + return this.click( jQuery.proxy( fn, function( event ) { + // Figure out which function to execute + var lastToggle = ( jQuery.data( this, "lastToggle" + fn.guid ) || 0 ) % i; + jQuery.data( this, "lastToggle" + fn.guid, lastToggle + 1 ); + + // Make sure that clicks stop + event.preventDefault(); + + // and execute the function + return args[ lastToggle ].apply( this, arguments ) || false; + })); + }, + + hover: function( fnOver, fnOut ) { + return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); + } +}); + +var liveMap = { + focus: "focusin", + blur: "focusout", + mouseenter: "mouseover", + mouseleave: "mouseout" +}; + +jQuery.each(["live", "die"], function( i, name ) { + jQuery.fn[ name ] = function( types, data, fn, origSelector /* Internal Use Only */ ) { + var type, i = 0, match, namespaces, preType, + selector = origSelector || this.selector, + context = origSelector ? this : jQuery( this.context ); + + if ( typeof types === "object" && !types.preventDefault ) { + for ( var key in types ) { + context[ name ]( key, data, types[key], selector ); + } + + return this; + } + + if ( jQuery.isFunction( data ) ) { + fn = data; + data = undefined; + } + + types = (types || "").split(" "); + + while ( (type = types[ i++ ]) != null ) { + match = rnamespaces.exec( type ); + namespaces = ""; + + if ( match ) { + namespaces = match[0]; + type = type.replace( rnamespaces, "" ); + } + + if ( type === "hover" ) { + types.push( "mouseenter" + namespaces, "mouseleave" + namespaces ); + continue; + } + + preType = type; + + if ( type === "focus" || type === "blur" ) { + types.push( liveMap[ type ] + namespaces ); + type = type + namespaces; + + } else { + type = (liveMap[ type ] || type) + namespaces; + } + + if ( name === "live" ) { + // bind live handler + for ( var j = 0, l = context.length; j < l; j++ ) { + jQuery.event.add( context[j], "live." + liveConvert( type, selector ), + { data: data, selector: selector, handler: fn, origType: type, origHandler: fn, preType: preType } ); + } + + } else { + // unbind live handler + context.unbind( "live." + liveConvert( type, selector ), fn ); + } + } + + return this; + }; +}); + +function liveHandler( event ) { + var stop, maxLevel, related, match, handleObj, elem, j, i, l, data, close, namespace, ret, + elems = [], + selectors = [], + events = jQuery.data( this, this.nodeType ? "events" : "__events__" ); + + if ( typeof events === "function" ) { + events = events.events; + } + + // Make sure we avoid non-left-click bubbling in Firefox (#3861) + if ( event.liveFired === this || !events || !events.live || event.button && event.type === "click" ) { + return; + } + + if ( event.namespace ) { + namespace = new RegExp("(^|\\.)" + event.namespace.split(".").join("\\.(?:.*\\.)?") + "(\\.|$)"); + } + + event.liveFired = this; + + var live = events.live.slice(0); + + for ( j = 0; j < live.length; j++ ) { + handleObj = live[j]; + + if ( handleObj.origType.replace( rnamespaces, "" ) === event.type ) { + selectors.push( handleObj.selector ); + + } else { + live.splice( j--, 1 ); + } + } + + match = jQuery( event.target ).closest( selectors, event.currentTarget ); + + for ( i = 0, l = match.length; i < l; i++ ) { + close = match[i]; + + for ( j = 0; j < live.length; j++ ) { + handleObj = live[j]; + + if ( close.selector === handleObj.selector && (!namespace || namespace.test( handleObj.namespace )) ) { + elem = close.elem; + related = null; + + // Those two events require additional checking + if ( handleObj.preType === "mouseenter" || handleObj.preType === "mouseleave" ) { + event.type = handleObj.preType; + related = jQuery( event.relatedTarget ).closest( handleObj.selector )[0]; + } + + if ( !related || related !== elem ) { + elems.push({ elem: elem, handleObj: handleObj, level: close.level }); + } + } + } + } + + for ( i = 0, l = elems.length; i < l; i++ ) { + match = elems[i]; + + if ( maxLevel && match.level > maxLevel ) { + break; + } + + event.currentTarget = match.elem; + event.data = match.handleObj.data; + event.handleObj = match.handleObj; + + ret = match.handleObj.origHandler.apply( match.elem, arguments ); + + if ( ret === false || event.isPropagationStopped() ) { + maxLevel = match.level; + + if ( ret === false ) { + stop = false; + } + if ( event.isImmediatePropagationStopped() ) { + break; + } + } + } + + return stop; +} + +function liveConvert( type, selector ) { + return (type && type !== "*" ? type + "." : "") + selector.replace(rperiod, "`").replace(rspace, "&"); +} + +jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " + + "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + + "change select submit keydown keypress keyup error").split(" "), function( i, name ) { + + // Handle event binding + jQuery.fn[ name ] = function( data, fn ) { + if ( fn == null ) { + fn = data; + data = null; + } + + return arguments.length > 0 ? + this.bind( name, data, fn ) : + this.trigger( name ); + }; + + if ( jQuery.attrFn ) { + jQuery.attrFn[ name ] = true; + } +}); + +// Prevent memory leaks in IE +// Window isn't included so as not to unbind existing unload events +// More info: +// - http://isaacschlueter.com/2006/10/msie-memory-leaks/ +if ( window.attachEvent && !window.addEventListener ) { + jQuery(window).bind("unload", function() { + for ( var id in jQuery.cache ) { + if ( jQuery.cache[ id ].handle ) { + // Try/Catch is to handle iframes being unloaded, see #4280 + try { + jQuery.event.remove( jQuery.cache[ id ].handle.elem ); + } catch(e) {} + } + } + }); +} + + +/*! + * Sizzle CSS Selector Engine - v1.0 + * Copyright 2009, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * More information: http://sizzlejs.com/ + */ +(function(){ + +var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, + done = 0, + toString = Object.prototype.toString, + hasDuplicate = false, + baseHasDuplicate = true; + +// Here we check if the JavaScript engine is using some sort of +// optimization where it does not always call our comparision +// function. If that is the case, discard the hasDuplicate value. +// Thus far that includes Google Chrome. +[0, 0].sort(function() { + baseHasDuplicate = false; + return 0; +}); + +var Sizzle = function( selector, context, results, seed ) { + results = results || []; + context = context || document; + + var origContext = context; + + if ( context.nodeType !== 1 && context.nodeType !== 9 ) { + return []; + } + + if ( !selector || typeof selector !== "string" ) { + return results; + } + + var m, set, checkSet, extra, ret, cur, pop, i, + prune = true, + contextXML = Sizzle.isXML( context ), + parts = [], + soFar = selector; + + // Reset the position of the chunker regexp (start from head) + do { + chunker.exec( "" ); + m = chunker.exec( soFar ); + + if ( m ) { + soFar = m[3]; + + parts.push( m[1] ); + + if ( m[2] ) { + extra = m[3]; + break; + } + } + } while ( m ); + + if ( parts.length > 1 && origPOS.exec( selector ) ) { + + if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { + set = posProcess( parts[0] + parts[1], context ); + + } else { + set = Expr.relative[ parts[0] ] ? + [ context ] : + Sizzle( parts.shift(), context ); + + while ( parts.length ) { + selector = parts.shift(); + + if ( Expr.relative[ selector ] ) { + selector += parts.shift(); + } + + set = posProcess( selector, set ); + } + } + + } else { + // Take a shortcut and set the context if the root selector is an ID + // (but not if it'll be faster if the inner selector is an ID) + if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && + Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { + + ret = Sizzle.find( parts.shift(), context, contextXML ); + context = ret.expr ? + Sizzle.filter( ret.expr, ret.set )[0] : + ret.set[0]; + } + + if ( context ) { + ret = seed ? + { expr: parts.pop(), set: makeArray(seed) } : + Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); + + set = ret.expr ? + Sizzle.filter( ret.expr, ret.set ) : + ret.set; + + if ( parts.length > 0 ) { + checkSet = makeArray( set ); + + } else { + prune = false; + } + + while ( parts.length ) { + cur = parts.pop(); + pop = cur; + + if ( !Expr.relative[ cur ] ) { + cur = ""; + } else { + pop = parts.pop(); + } + + if ( pop == null ) { + pop = context; + } + + Expr.relative[ cur ]( checkSet, pop, contextXML ); + } + + } else { + checkSet = parts = []; + } + } + + if ( !checkSet ) { + checkSet = set; + } + + if ( !checkSet ) { + Sizzle.error( cur || selector ); + } + + if ( toString.call(checkSet) === "[object Array]" ) { + if ( !prune ) { + results.push.apply( results, checkSet ); + + } else if ( context && context.nodeType === 1 ) { + for ( i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) { + results.push( set[i] ); + } + } + + } else { + for ( i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && checkSet[i].nodeType === 1 ) { + results.push( set[i] ); + } + } + } + + } else { + makeArray( checkSet, results ); + } + + if ( extra ) { + Sizzle( extra, origContext, results, seed ); + Sizzle.uniqueSort( results ); + } + + return results; +}; + +Sizzle.uniqueSort = function( results ) { + if ( sortOrder ) { + hasDuplicate = baseHasDuplicate; + results.sort( sortOrder ); + + if ( hasDuplicate ) { + for ( var i = 1; i < results.length; i++ ) { + if ( results[i] === results[ i - 1 ] ) { + results.splice( i--, 1 ); + } + } + } + } + + return results; +}; + +Sizzle.matches = function( expr, set ) { + return Sizzle( expr, null, null, set ); +}; + +Sizzle.matchesSelector = function( node, expr ) { + return Sizzle( expr, null, null, [node] ).length > 0; +}; + +Sizzle.find = function( expr, context, isXML ) { + var set; + + if ( !expr ) { + return []; + } + + for ( var i = 0, l = Expr.order.length; i < l; i++ ) { + var match, + type = Expr.order[i]; + + if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { + var left = match[1]; + match.splice( 1, 1 ); + + if ( left.substr( left.length - 1 ) !== "\\" ) { + match[1] = (match[1] || "").replace(/\\/g, ""); + set = Expr.find[ type ]( match, context, isXML ); + + if ( set != null ) { + expr = expr.replace( Expr.match[ type ], "" ); + break; + } + } + } + } + + if ( !set ) { + set = context.getElementsByTagName( "*" ); + } + + return { set: set, expr: expr }; +}; + +Sizzle.filter = function( expr, set, inplace, not ) { + var match, anyFound, + old = expr, + result = [], + curLoop = set, + isXMLFilter = set && set[0] && Sizzle.isXML( set[0] ); + + while ( expr && set.length ) { + for ( var type in Expr.filter ) { + if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) { + var found, item, + filter = Expr.filter[ type ], + left = match[1]; + + anyFound = false; + + match.splice(1,1); + + if ( left.substr( left.length - 1 ) === "\\" ) { + continue; + } + + if ( curLoop === result ) { + result = []; + } + + if ( Expr.preFilter[ type ] ) { + match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); + + if ( !match ) { + anyFound = found = true; + + } else if ( match === true ) { + continue; + } + } + + if ( match ) { + for ( var i = 0; (item = curLoop[i]) != null; i++ ) { + if ( item ) { + found = filter( item, match, i, curLoop ); + var pass = not ^ !!found; + + if ( inplace && found != null ) { + if ( pass ) { + anyFound = true; + + } else { + curLoop[i] = false; + } + + } else if ( pass ) { + result.push( item ); + anyFound = true; + } + } + } + } + + if ( found !== undefined ) { + if ( !inplace ) { + curLoop = result; + } + + expr = expr.replace( Expr.match[ type ], "" ); + + if ( !anyFound ) { + return []; + } + + break; + } + } + } + + // Improper expression + if ( expr === old ) { + if ( anyFound == null ) { + Sizzle.error( expr ); + + } else { + break; + } + } + + old = expr; + } + + return curLoop; +}; + +Sizzle.error = function( msg ) { + throw "Syntax error, unrecognized expression: " + msg; +}; + +var Expr = Sizzle.selectors = { + order: [ "ID", "NAME", "TAG" ], + + match: { + ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, + CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, + NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/, + ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/, + TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/, + CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+\-]*)\))?/, + POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/, + PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/ + }, + + leftMatch: {}, + + attrMap: { + "class": "className", + "for": "htmlFor" + }, + + attrHandle: { + href: function( elem ) { + return elem.getAttribute( "href" ); + } + }, + + relative: { + "+": function(checkSet, part){ + var isPartStr = typeof part === "string", + isTag = isPartStr && !/\W/.test( part ), + isPartStrNotTag = isPartStr && !isTag; + + if ( isTag ) { + part = part.toLowerCase(); + } + + for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { + if ( (elem = checkSet[i]) ) { + while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} + + checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ? + elem || false : + elem === part; + } + } + + if ( isPartStrNotTag ) { + Sizzle.filter( part, checkSet, true ); + } + }, + + ">": function( checkSet, part ) { + var elem, + isPartStr = typeof part === "string", + i = 0, + l = checkSet.length; + + if ( isPartStr && !/\W/.test( part ) ) { + part = part.toLowerCase(); + + for ( ; i < l; i++ ) { + elem = checkSet[i]; + + if ( elem ) { + var parent = elem.parentNode; + checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false; + } + } + + } else { + for ( ; i < l; i++ ) { + elem = checkSet[i]; + + if ( elem ) { + checkSet[i] = isPartStr ? + elem.parentNode : + elem.parentNode === part; + } + } + + if ( isPartStr ) { + Sizzle.filter( part, checkSet, true ); + } + } + }, + + "": function(checkSet, part, isXML){ + var nodeCheck, + doneName = done++, + checkFn = dirCheck; + + if ( typeof part === "string" && !/\W/.test(part) ) { + part = part.toLowerCase(); + nodeCheck = part; + checkFn = dirNodeCheck; + } + + checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML ); + }, + + "~": function( checkSet, part, isXML ) { + var nodeCheck, + doneName = done++, + checkFn = dirCheck; + + if ( typeof part === "string" && !/\W/.test( part ) ) { + part = part.toLowerCase(); + nodeCheck = part; + checkFn = dirNodeCheck; + } + + checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML ); + } + }, + + find: { + ID: function( match, context, isXML ) { + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + return m && m.parentNode ? [m] : []; + } + }, + + NAME: function( match, context ) { + if ( typeof context.getElementsByName !== "undefined" ) { + var ret = [], + results = context.getElementsByName( match[1] ); + + for ( var i = 0, l = results.length; i < l; i++ ) { + if ( results[i].getAttribute("name") === match[1] ) { + ret.push( results[i] ); + } + } + + return ret.length === 0 ? null : ret; + } + }, + + TAG: function( match, context ) { + return context.getElementsByTagName( match[1] ); + } + }, + preFilter: { + CLASS: function( match, curLoop, inplace, result, not, isXML ) { + match = " " + match[1].replace(/\\/g, "") + " "; + + if ( isXML ) { + return match; + } + + for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { + if ( elem ) { + if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n]/g, " ").indexOf(match) >= 0) ) { + if ( !inplace ) { + result.push( elem ); + } + + } else if ( inplace ) { + curLoop[i] = false; + } + } + } + + return false; + }, + + ID: function( match ) { + return match[1].replace(/\\/g, ""); + }, + + TAG: function( match, curLoop ) { + return match[1].toLowerCase(); + }, + + CHILD: function( match ) { + if ( match[1] === "nth" ) { + // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' + var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec( + match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" || + !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); + + // calculate the numbers (first)n+(last) including if they are negative + match[2] = (test[1] + (test[2] || 1)) - 0; + match[3] = test[3] - 0; + } + + // TODO: Move to normal caching system + match[0] = done++; + + return match; + }, + + ATTR: function( match, curLoop, inplace, result, not, isXML ) { + var name = match[1].replace(/\\/g, ""); + + if ( !isXML && Expr.attrMap[name] ) { + match[1] = Expr.attrMap[name]; + } + + if ( match[2] === "~=" ) { + match[4] = " " + match[4] + " "; + } + + return match; + }, + + PSEUDO: function( match, curLoop, inplace, result, not ) { + if ( match[1] === "not" ) { + // If we're dealing with a complex expression, or a simple one + if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { + match[3] = Sizzle(match[3], null, null, curLoop); + + } else { + var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); + + if ( !inplace ) { + result.push.apply( result, ret ); + } + + return false; + } + + } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { + return true; + } + + return match; + }, + + POS: function( match ) { + match.unshift( true ); + + return match; + } + }, + + filters: { + enabled: function( elem ) { + return elem.disabled === false && elem.type !== "hidden"; + }, + + disabled: function( elem ) { + return elem.disabled === true; + }, + + checked: function( elem ) { + return elem.checked === true; + }, + + selected: function( elem ) { + // Accessing this property makes selected-by-default + // options in Safari work properly + elem.parentNode.selectedIndex; + + return elem.selected === true; + }, + + parent: function( elem ) { + return !!elem.firstChild; + }, + + empty: function( elem ) { + return !elem.firstChild; + }, + + has: function( elem, i, match ) { + return !!Sizzle( match[3], elem ).length; + }, + + header: function( elem ) { + return (/h\d/i).test( elem.nodeName ); + }, + + text: function( elem ) { + return "text" === elem.type; + }, + radio: function( elem ) { + return "radio" === elem.type; + }, + + checkbox: function( elem ) { + return "checkbox" === elem.type; + }, + + file: function( elem ) { + return "file" === elem.type; + }, + password: function( elem ) { + return "password" === elem.type; + }, + + submit: function( elem ) { + return "submit" === elem.type; + }, + + image: function( elem ) { + return "image" === elem.type; + }, + + reset: function( elem ) { + return "reset" === elem.type; + }, + + button: function( elem ) { + return "button" === elem.type || elem.nodeName.toLowerCase() === "button"; + }, + + input: function( elem ) { + return (/input|select|textarea|button/i).test( elem.nodeName ); + } + }, + setFilters: { + first: function( elem, i ) { + return i === 0; + }, + + last: function( elem, i, match, array ) { + return i === array.length - 1; + }, + + even: function( elem, i ) { + return i % 2 === 0; + }, + + odd: function( elem, i ) { + return i % 2 === 1; + }, + + lt: function( elem, i, match ) { + return i < match[3] - 0; + }, + + gt: function( elem, i, match ) { + return i > match[3] - 0; + }, + + nth: function( elem, i, match ) { + return match[3] - 0 === i; + }, + + eq: function( elem, i, match ) { + return match[3] - 0 === i; + } + }, + filter: { + PSEUDO: function( elem, match, i, array ) { + var name = match[1], + filter = Expr.filters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + + } else if ( name === "contains" ) { + return (elem.textContent || elem.innerText || Sizzle.getText([ elem ]) || "").indexOf(match[3]) >= 0; + + } else if ( name === "not" ) { + var not = match[3]; + + for ( var j = 0, l = not.length; j < l; j++ ) { + if ( not[j] === elem ) { + return false; + } + } + + return true; + + } else { + Sizzle.error( "Syntax error, unrecognized expression: " + name ); + } + }, + + CHILD: function( elem, match ) { + var type = match[1], + node = elem; + + switch ( type ) { + case "only": + case "first": + while ( (node = node.previousSibling) ) { + if ( node.nodeType === 1 ) { + return false; + } + } + + if ( type === "first" ) { + return true; + } + + node = elem; + + case "last": + while ( (node = node.nextSibling) ) { + if ( node.nodeType === 1 ) { + return false; + } + } + + return true; + + case "nth": + var first = match[2], + last = match[3]; + + if ( first === 1 && last === 0 ) { + return true; + } + + var doneName = match[0], + parent = elem.parentNode; + + if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) { + var count = 0; + + for ( node = parent.firstChild; node; node = node.nextSibling ) { + if ( node.nodeType === 1 ) { + node.nodeIndex = ++count; + } + } + + parent.sizcache = doneName; + } + + var diff = elem.nodeIndex - last; + + if ( first === 0 ) { + return diff === 0; + + } else { + return ( diff % first === 0 && diff / first >= 0 ); + } + } + }, + + ID: function( elem, match ) { + return elem.nodeType === 1 && elem.getAttribute("id") === match; + }, + + TAG: function( elem, match ) { + return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match; + }, + + CLASS: function( elem, match ) { + return (" " + (elem.className || elem.getAttribute("class")) + " ") + .indexOf( match ) > -1; + }, + + ATTR: function( elem, match ) { + var name = match[1], + result = Expr.attrHandle[ name ] ? + Expr.attrHandle[ name ]( elem ) : + elem[ name ] != null ? + elem[ name ] : + elem.getAttribute( name ), + value = result + "", + type = match[2], + check = match[4]; + + return result == null ? + type === "!=" : + type === "=" ? + value === check : + type === "*=" ? + value.indexOf(check) >= 0 : + type === "~=" ? + (" " + value + " ").indexOf(check) >= 0 : + !check ? + value && result !== false : + type === "!=" ? + value !== check : + type === "^=" ? + value.indexOf(check) === 0 : + type === "$=" ? + value.substr(value.length - check.length) === check : + type === "|=" ? + value === check || value.substr(0, check.length + 1) === check + "-" : + false; + }, + + POS: function( elem, match, i, array ) { + var name = match[2], + filter = Expr.setFilters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + } + } + } +}; + +var origPOS = Expr.match.POS, + fescape = function(all, num){ + return "\\" + (num - 0 + 1); + }; + +for ( var type in Expr.match ) { + Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) ); + Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) ); +} + +var makeArray = function( array, results ) { + array = Array.prototype.slice.call( array, 0 ); + + if ( results ) { + results.push.apply( results, array ); + return results; + } + + return array; +}; + +// Perform a simple check to determine if the browser is capable of +// converting a NodeList to an array using builtin methods. +// Also verifies that the returned array holds DOM nodes +// (which is not the case in the Blackberry browser) +try { + Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType; + +// Provide a fallback method if it does not work +} catch( e ) { + makeArray = function( array, results ) { + var i = 0, + ret = results || []; + + if ( toString.call(array) === "[object Array]" ) { + Array.prototype.push.apply( ret, array ); + + } else { + if ( typeof array.length === "number" ) { + for ( var l = array.length; i < l; i++ ) { + ret.push( array[i] ); + } + + } else { + for ( ; array[i]; i++ ) { + ret.push( array[i] ); + } + } + } + + return ret; + }; +} + +var sortOrder, siblingCheck; + +if ( document.documentElement.compareDocumentPosition ) { + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { + return a.compareDocumentPosition ? -1 : 1; + } + + return a.compareDocumentPosition(b) & 4 ? -1 : 1; + }; + +} else { + sortOrder = function( a, b ) { + var al, bl, + ap = [], + bp = [], + aup = a.parentNode, + bup = b.parentNode, + cur = aup; + + // The nodes are identical, we can exit early + if ( a === b ) { + hasDuplicate = true; + return 0; + + // If the nodes are siblings (or identical) we can do a quick check + } else if ( aup === bup ) { + return siblingCheck( a, b ); + + // If no parents were found then the nodes are disconnected + } else if ( !aup ) { + return -1; + + } else if ( !bup ) { + return 1; + } + + // Otherwise they're somewhere else in the tree so we need + // to build up a full list of the parentNodes for comparison + while ( cur ) { + ap.unshift( cur ); + cur = cur.parentNode; + } + + cur = bup; + + while ( cur ) { + bp.unshift( cur ); + cur = cur.parentNode; + } + + al = ap.length; + bl = bp.length; + + // Start walking down the tree looking for a discrepancy + for ( var i = 0; i < al && i < bl; i++ ) { + if ( ap[i] !== bp[i] ) { + return siblingCheck( ap[i], bp[i] ); + } + } + + // We ended someplace up the tree so do a sibling check + return i === al ? + siblingCheck( a, bp[i], -1 ) : + siblingCheck( ap[i], b, 1 ); + }; + + siblingCheck = function( a, b, ret ) { + if ( a === b ) { + return ret; + } + + var cur = a.nextSibling; + + while ( cur ) { + if ( cur === b ) { + return -1; + } + + cur = cur.nextSibling; + } + + return 1; + }; +} + +// Utility function for retreiving the text value of an array of DOM nodes +Sizzle.getText = function( elems ) { + var ret = "", elem; + + for ( var i = 0; elems[i]; i++ ) { + elem = elems[i]; + + // Get the text from text nodes and CDATA nodes + if ( elem.nodeType === 3 || elem.nodeType === 4 ) { + ret += elem.nodeValue; + + // Traverse everything else, except comment nodes + } else if ( elem.nodeType !== 8 ) { + ret += Sizzle.getText( elem.childNodes ); + } + } + + return ret; +}; + +// Check to see if the browser returns elements by name when +// querying by getElementById (and provide a workaround) +(function(){ + // We're going to inject a fake input element with a specified name + var form = document.createElement("div"), + id = "script" + (new Date()).getTime(), + root = document.documentElement; + + form.innerHTML = "<a name='" + id + "'/>"; + + // Inject it into the root element, check its status, and remove it quickly + root.insertBefore( form, root.firstChild ); + + // The workaround has to do additional checks after a getElementById + // Which slows things down for other browsers (hence the branching) + if ( document.getElementById( id ) ) { + Expr.find.ID = function( match, context, isXML ) { + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + + return m ? + m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? + [m] : + undefined : + []; + } + }; + + Expr.filter.ID = function( elem, match ) { + var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); + + return elem.nodeType === 1 && node && node.nodeValue === match; + }; + } + + root.removeChild( form ); + + // release memory in IE + root = form = null; +})(); + +(function(){ + // Check to see if the browser returns only elements + // when doing getElementsByTagName("*") + + // Create a fake element + var div = document.createElement("div"); + div.appendChild( document.createComment("") ); + + // Make sure no comments are found + if ( div.getElementsByTagName("*").length > 0 ) { + Expr.find.TAG = function( match, context ) { + var results = context.getElementsByTagName( match[1] ); + + // Filter out possible comments + if ( match[1] === "*" ) { + var tmp = []; + + for ( var i = 0; results[i]; i++ ) { + if ( results[i].nodeType === 1 ) { + tmp.push( results[i] ); + } + } + + results = tmp; + } + + return results; + }; + } + + // Check to see if an attribute returns normalized href attributes + div.innerHTML = "<a href='#'></a>"; + + if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && + div.firstChild.getAttribute("href") !== "#" ) { + + Expr.attrHandle.href = function( elem ) { + return elem.getAttribute( "href", 2 ); + }; + } + + // release memory in IE + div = null; +})(); + +if ( document.querySelectorAll ) { + (function(){ + var oldSizzle = Sizzle, + div = document.createElement("div"), + id = "__sizzle__"; + + div.innerHTML = "<p class='TEST'></p>"; + + // Safari can't handle uppercase or unicode characters when + // in quirks mode. + if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { + return; + } + + Sizzle = function( query, context, extra, seed ) { + context = context || document; + + // Make sure that attribute selectors are quoted + query = query.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']"); + + // Only use querySelectorAll on non-XML documents + // (ID selectors don't work in non-HTML documents) + if ( !seed && !Sizzle.isXML(context) ) { + if ( context.nodeType === 9 ) { + try { + return makeArray( context.querySelectorAll(query), extra ); + } catch(qsaError) {} + + // qSA works strangely on Element-rooted queries + // We can work around this by specifying an extra ID on the root + // and working up from there (Thanks to Andrew Dupont for the technique) + // IE 8 doesn't work on object elements + } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { + var old = context.getAttribute( "id" ), + nid = old || id; + + if ( !old ) { + context.setAttribute( "id", nid ); + } + + try { + return makeArray( context.querySelectorAll( "#" + nid + " " + query ), extra ); + + } catch(pseudoError) { + } finally { + if ( !old ) { + context.removeAttribute( "id" ); + } + } + } + } + + return oldSizzle(query, context, extra, seed); + }; + + for ( var prop in oldSizzle ) { + Sizzle[ prop ] = oldSizzle[ prop ]; + } + + // release memory in IE + div = null; + })(); +} + +(function(){ + var html = document.documentElement, + matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector, + pseudoWorks = false; + + try { + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( document.documentElement, "[test!='']:sizzle" ); + + } catch( pseudoError ) { + pseudoWorks = true; + } + + if ( matches ) { + Sizzle.matchesSelector = function( node, expr ) { + // Make sure that attribute selectors are quoted + expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']"); + + if ( !Sizzle.isXML( node ) ) { + try { + if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) { + return matches.call( node, expr ); + } + } catch(e) {} + } + + return Sizzle(expr, null, null, [node]).length > 0; + }; + } +})(); + +(function(){ + var div = document.createElement("div"); + + div.innerHTML = "<div class='test e'></div><div class='test'></div>"; + + // Opera can't find a second classname (in 9.6) + // Also, make sure that getElementsByClassName actually exists + if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) { + return; + } + + // Safari caches class attributes, doesn't catch changes (in 3.2) + div.lastChild.className = "e"; + + if ( div.getElementsByClassName("e").length === 1 ) { + return; + } + + Expr.order.splice(1, 0, "CLASS"); + Expr.find.CLASS = function( match, context, isXML ) { + if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { + return context.getElementsByClassName(match[1]); + } + }; + + // release memory in IE + div = null; +})(); + +function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + + if ( elem ) { + var match = false; + + elem = elem[dir]; + + while ( elem ) { + if ( elem.sizcache === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 && !isXML ){ + elem.sizcache = doneName; + elem.sizset = i; + } + + if ( elem.nodeName.toLowerCase() === cur ) { + match = elem; + break; + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + + if ( elem ) { + var match = false; + + elem = elem[dir]; + + while ( elem ) { + if ( elem.sizcache === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 ) { + if ( !isXML ) { + elem.sizcache = doneName; + elem.sizset = i; + } + + if ( typeof cur !== "string" ) { + if ( elem === cur ) { + match = true; + break; + } + + } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { + match = elem; + break; + } + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +if ( document.documentElement.contains ) { + Sizzle.contains = function( a, b ) { + return a !== b && (a.contains ? a.contains(b) : true); + }; + +} else if ( document.documentElement.compareDocumentPosition ) { + Sizzle.contains = function( a, b ) { + return !!(a.compareDocumentPosition(b) & 16); + }; + +} else { + Sizzle.contains = function() { + return false; + }; +} + +Sizzle.isXML = function( elem ) { + // documentElement is verified for cases where it doesn't yet exist + // (such as loading iframes in IE - #4833) + var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement; + + return documentElement ? documentElement.nodeName !== "HTML" : false; +}; + +var posProcess = function( selector, context ) { + var match, + tmpSet = [], + later = "", + root = context.nodeType ? [context] : context; + + // Position selectors must be done after the filter + // And so must :not(positional) so we move all PSEUDOs to the end + while ( (match = Expr.match.PSEUDO.exec( selector )) ) { + later += match[0]; + selector = selector.replace( Expr.match.PSEUDO, "" ); + } + + selector = Expr.relative[selector] ? selector + "*" : selector; + + for ( var i = 0, l = root.length; i < l; i++ ) { + Sizzle( selector, root[i], tmpSet ); + } + + return Sizzle.filter( later, tmpSet ); +}; + +// EXPOSE +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; +jQuery.expr[":"] = jQuery.expr.filters; +jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; + + +})(); + + +var runtil = /Until$/, + rparentsprev = /^(?:parents|prevUntil|prevAll)/, + // Note: This RegExp should be improved, or likely pulled from Sizzle + rmultiselector = /,/, + isSimple = /^.[^:#\[\.,]*$/, + slice = Array.prototype.slice, + POS = jQuery.expr.match.POS; + +jQuery.fn.extend({ + find: function( selector ) { + var ret = this.pushStack( "", "find", selector ), + length = 0; + + for ( var i = 0, l = this.length; i < l; i++ ) { + length = ret.length; + jQuery.find( selector, this[i], ret ); + + if ( i > 0 ) { + // Make sure that the results are unique + for ( var n = length; n < ret.length; n++ ) { + for ( var r = 0; r < length; r++ ) { + if ( ret[r] === ret[n] ) { + ret.splice(n--, 1); + break; + } + } + } + } + } + + return ret; + }, + + has: function( target ) { + var targets = jQuery( target ); + return this.filter(function() { + for ( var i = 0, l = targets.length; i < l; i++ ) { + if ( jQuery.contains( this, targets[i] ) ) { + return true; + } + } + }); + }, + + not: function( selector ) { + return this.pushStack( winnow(this, selector, false), "not", selector); + }, + + filter: function( selector ) { + return this.pushStack( winnow(this, selector, true), "filter", selector ); + }, + + is: function( selector ) { + return !!selector && jQuery.filter( selector, this ).length > 0; + }, + + closest: function( selectors, context ) { + var ret = [], i, l, cur = this[0]; + + if ( jQuery.isArray( selectors ) ) { + var match, selector, + matches = {}, + level = 1; + + if ( cur && selectors.length ) { + for ( i = 0, l = selectors.length; i < l; i++ ) { + selector = selectors[i]; + + if ( !matches[selector] ) { + matches[selector] = jQuery.expr.match.POS.test( selector ) ? + jQuery( selector, context || this.context ) : + selector; + } + } + + while ( cur && cur.ownerDocument && cur !== context ) { + for ( selector in matches ) { + match = matches[selector]; + + if ( match.jquery ? match.index(cur) > -1 : jQuery(cur).is(match) ) { + ret.push({ selector: selector, elem: cur, level: level }); + } + } + + cur = cur.parentNode; + level++; + } + } + + return ret; + } + + var pos = POS.test( selectors ) ? + jQuery( selectors, context || this.context ) : null; + + for ( i = 0, l = this.length; i < l; i++ ) { + cur = this[i]; + + while ( cur ) { + if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) { + ret.push( cur ); + break; + + } else { + cur = cur.parentNode; + if ( !cur || !cur.ownerDocument || cur === context ) { + break; + } + } + } + } + + ret = ret.length > 1 ? jQuery.unique(ret) : ret; + + return this.pushStack( ret, "closest", selectors ); + }, + + // Determine the position of an element within + // the matched set of elements + index: function( elem ) { + if ( !elem || typeof elem === "string" ) { + return jQuery.inArray( this[0], + // If it receives a string, the selector is used + // If it receives nothing, the siblings are used + elem ? jQuery( elem ) : this.parent().children() ); + } + // Locate the position of the desired element + return jQuery.inArray( + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[0] : elem, this ); + }, + + add: function( selector, context ) { + var set = typeof selector === "string" ? + jQuery( selector, context || this.context ) : + jQuery.makeArray( selector ), + all = jQuery.merge( this.get(), set ); + + return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ? + all : + jQuery.unique( all ) ); + }, + + andSelf: function() { + return this.add( this.prevObject ); + } +}); + +// A painfully simple check to see if an element is disconnected +// from a document (should be improved, where feasible). +function isDisconnected( node ) { + return !node || !node.parentNode || node.parentNode.nodeType === 11; +} + +jQuery.each({ + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return jQuery.dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, i, until ) { + return jQuery.dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return jQuery.nth( elem, 2, "nextSibling" ); + }, + prev: function( elem ) { + return jQuery.nth( elem, 2, "previousSibling" ); + }, + nextAll: function( elem ) { + return jQuery.dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return jQuery.dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, i, until ) { + return jQuery.dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, i, until ) { + return jQuery.dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return jQuery.sibling( elem.parentNode.firstChild, elem ); + }, + children: function( elem ) { + return jQuery.sibling( elem.firstChild ); + }, + contents: function( elem ) { + return jQuery.nodeName( elem, "iframe" ) ? + elem.contentDocument || elem.contentWindow.document : + jQuery.makeArray( elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var ret = jQuery.map( this, fn, until ); + + if ( !runtil.test( name ) ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + ret = jQuery.filter( selector, ret ); + } + + ret = this.length > 1 ? jQuery.unique( ret ) : ret; + + if ( (this.length > 1 || rmultiselector.test( selector )) && rparentsprev.test( name ) ) { + ret = ret.reverse(); + } + + return this.pushStack( ret, name, slice.call(arguments).join(",") ); + }; +}); + +jQuery.extend({ + filter: function( expr, elems, not ) { + if ( not ) { + expr = ":not(" + expr + ")"; + } + + return elems.length === 1 ? + jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] : + jQuery.find.matches(expr, elems); + }, + + dir: function( elem, dir, until ) { + var matched = [], + cur = elem[ dir ]; + + while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) { + if ( cur.nodeType === 1 ) { + matched.push( cur ); + } + cur = cur[dir]; + } + return matched; + }, + + nth: function( cur, result, dir, elem ) { + result = result || 1; + var num = 0; + + for ( ; cur; cur = cur[dir] ) { + if ( cur.nodeType === 1 && ++num === result ) { + break; + } + } + + return cur; + }, + + sibling: function( n, elem ) { + var r = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + r.push( n ); + } + } + + return r; + } +}); + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, keep ) { + if ( jQuery.isFunction( qualifier ) ) { + return jQuery.grep(elements, function( elem, i ) { + var retVal = !!qualifier.call( elem, i, elem ); + return retVal === keep; + }); + + } else if ( qualifier.nodeType ) { + return jQuery.grep(elements, function( elem, i ) { + return (elem === qualifier) === keep; + }); + + } else if ( typeof qualifier === "string" ) { + var filtered = jQuery.grep(elements, function( elem ) { + return elem.nodeType === 1; + }); + + if ( isSimple.test( qualifier ) ) { + return jQuery.filter(qualifier, filtered, !keep); + } else { + qualifier = jQuery.filter( qualifier, filtered ); + } + } + + return jQuery.grep(elements, function( elem, i ) { + return (jQuery.inArray( elem, qualifier ) >= 0) === keep; + }); +} + + + + +var rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g, + rleadingWhitespace = /^\s+/, + rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig, + rtagName = /<([\w:]+)/, + rtbody = /<tbody/i, + rhtml = /<|&#?\w+;/, + rnocache = /<(?:script|object|embed|option|style)/i, + // checked="checked" or checked (html5) + rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i, + raction = /\=([^="'>\s]+\/)>/g, + wrapMap = { + option: [ 1, "<select multiple='multiple'>", "</select>" ], + legend: [ 1, "<fieldset>", "</fieldset>" ], + thead: [ 1, "<table>", "</table>" ], + tr: [ 2, "<table><tbody>", "</tbody></table>" ], + td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ], + col: [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ], + area: [ 1, "<map>", "</map>" ], + _default: [ 0, "", "" ] + }; + +wrapMap.optgroup = wrapMap.option; +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +// IE can't serialize <link> and <script> tags normally +if ( !jQuery.support.htmlSerialize ) { + wrapMap._default = [ 1, "div<div>", "</div>" ]; +} + +jQuery.fn.extend({ + text: function( text ) { + if ( jQuery.isFunction(text) ) { + return this.each(function(i) { + var self = jQuery( this ); + + self.text( text.call(this, i, self.text()) ); + }); + } + + if ( typeof text !== "object" && text !== undefined ) { + return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) ); + } + + return jQuery.text( this ); + }, + + wrapAll: function( html ) { + if ( jQuery.isFunction( html ) ) { + return this.each(function(i) { + jQuery(this).wrapAll( html.call(this, i) ); + }); + } + + if ( this[0] ) { + // The elements to wrap the target around + var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true); + + if ( this[0].parentNode ) { + wrap.insertBefore( this[0] ); + } + + wrap.map(function() { + var elem = this; + + while ( elem.firstChild && elem.firstChild.nodeType === 1 ) { + elem = elem.firstChild; + } + + return elem; + }).append(this); + } + + return this; + }, + + wrapInner: function( html ) { + if ( jQuery.isFunction( html ) ) { + return this.each(function(i) { + jQuery(this).wrapInner( html.call(this, i) ); + }); + } + + return this.each(function() { + var self = jQuery( this ), + contents = self.contents(); + + if ( contents.length ) { + contents.wrapAll( html ); + + } else { + self.append( html ); + } + }); + }, + + wrap: function( html ) { + return this.each(function() { + jQuery( this ).wrapAll( html ); + }); + }, + + unwrap: function() { + return this.parent().each(function() { + if ( !jQuery.nodeName( this, "body" ) ) { + jQuery( this ).replaceWith( this.childNodes ); + } + }).end(); + }, + + append: function() { + return this.domManip(arguments, true, function( elem ) { + if ( this.nodeType === 1 ) { + this.appendChild( elem ); + } + }); + }, + + prepend: function() { + return this.domManip(arguments, true, function( elem ) { + if ( this.nodeType === 1 ) { + this.insertBefore( elem, this.firstChild ); + } + }); + }, + + before: function() { + if ( this[0] && this[0].parentNode ) { + return this.domManip(arguments, false, function( elem ) { + this.parentNode.insertBefore( elem, this ); + }); + } else if ( arguments.length ) { + var set = jQuery(arguments[0]); + set.push.apply( set, this.toArray() ); + return this.pushStack( set, "before", arguments ); + } + }, + + after: function() { + if ( this[0] && this[0].parentNode ) { + return this.domManip(arguments, false, function( elem ) { + this.parentNode.insertBefore( elem, this.nextSibling ); + }); + } else if ( arguments.length ) { + var set = this.pushStack( this, "after", arguments ); + set.push.apply( set, jQuery(arguments[0]).toArray() ); + return set; + } + }, + + // keepData is for internal use only--do not document + remove: function( selector, keepData ) { + for ( var i = 0, elem; (elem = this[i]) != null; i++ ) { + if ( !selector || jQuery.filter( selector, [ elem ] ).length ) { + if ( !keepData && elem.nodeType === 1 ) { + jQuery.cleanData( elem.getElementsByTagName("*") ); + jQuery.cleanData( [ elem ] ); + } + + if ( elem.parentNode ) { + elem.parentNode.removeChild( elem ); + } + } + } + + return this; + }, + + empty: function() { + for ( var i = 0, elem; (elem = this[i]) != null; i++ ) { + // Remove element nodes and prevent memory leaks + if ( elem.nodeType === 1 ) { + jQuery.cleanData( elem.getElementsByTagName("*") ); + } + + // Remove any remaining nodes + while ( elem.firstChild ) { + elem.removeChild( elem.firstChild ); + } + } + + return this; + }, + + clone: function( events ) { + // Do the clone + var ret = this.map(function() { + if ( !jQuery.support.noCloneEvent && !jQuery.isXMLDoc(this) ) { + // IE copies events bound via attachEvent when + // using cloneNode. Calling detachEvent on the + // clone will also remove the events from the orignal + // In order to get around this, we use innerHTML. + // Unfortunately, this means some modifications to + // attributes in IE that are actually only stored + // as properties will not be copied (such as the + // the name attribute on an input). + var html = this.outerHTML, + ownerDocument = this.ownerDocument; + + if ( !html ) { + var div = ownerDocument.createElement("div"); + div.appendChild( this.cloneNode(true) ); + html = div.innerHTML; + } + + return jQuery.clean([html.replace(rinlinejQuery, "") + // Handle the case in IE 8 where action=/test/> self-closes a tag + .replace(raction, '="$1">') + .replace(rleadingWhitespace, "")], ownerDocument)[0]; + } else { + return this.cloneNode(true); + } + }); + + // Copy the events from the original to the clone + if ( events === true ) { + cloneCopyEvent( this, ret ); + cloneCopyEvent( this.find("*"), ret.find("*") ); + } + + // Return the cloned set + return ret; + }, + + html: function( value ) { + if ( value === undefined ) { + return this[0] && this[0].nodeType === 1 ? + this[0].innerHTML.replace(rinlinejQuery, "") : + null; + + // See if we can take a shortcut and just use innerHTML + } else if ( typeof value === "string" && !rnocache.test( value ) && + (jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value )) && + !wrapMap[ (rtagName.exec( value ) || ["", ""])[1].toLowerCase() ] ) { + + value = value.replace(rxhtmlTag, "<$1></$2>"); + + try { + for ( var i = 0, l = this.length; i < l; i++ ) { + // Remove element nodes and prevent memory leaks + if ( this[i].nodeType === 1 ) { + jQuery.cleanData( this[i].getElementsByTagName("*") ); + this[i].innerHTML = value; + } + } + + // If using innerHTML throws an exception, use the fallback method + } catch(e) { + this.empty().append( value ); + } + + } else if ( jQuery.isFunction( value ) ) { + this.each(function(i){ + var self = jQuery( this ); + + self.html( value.call(this, i, self.html()) ); + }); + + } else { + this.empty().append( value ); + } + + return this; + }, + + replaceWith: function( value ) { + if ( this[0] && this[0].parentNode ) { + // Make sure that the elements are removed from the DOM before they are inserted + // this can help fix replacing a parent with child elements + if ( jQuery.isFunction( value ) ) { + return this.each(function(i) { + var self = jQuery(this), old = self.html(); + self.replaceWith( value.call( this, i, old ) ); + }); + } + + if ( typeof value !== "string" ) { + value = jQuery( value ).detach(); + } + + return this.each(function() { + var next = this.nextSibling, + parent = this.parentNode; + + jQuery( this ).remove(); + + if ( next ) { + jQuery(next).before( value ); + } else { + jQuery(parent).append( value ); + } + }); + } else { + return this.pushStack( jQuery(jQuery.isFunction(value) ? value() : value), "replaceWith", value ); + } + }, + + detach: function( selector ) { + return this.remove( selector, true ); + }, + + domManip: function( args, table, callback ) { + var results, first, fragment, parent, + value = args[0], + scripts = []; + + // We can't cloneNode fragments that contain checked, in WebKit + if ( !jQuery.support.checkClone && arguments.length === 3 && typeof value === "string" && rchecked.test( value ) ) { + return this.each(function() { + jQuery(this).domManip( args, table, callback, true ); + }); + } + + if ( jQuery.isFunction(value) ) { + return this.each(function(i) { + var self = jQuery(this); + args[0] = value.call(this, i, table ? self.html() : undefined); + self.domManip( args, table, callback ); + }); + } + + if ( this[0] ) { + parent = value && value.parentNode; + + // If we're in a fragment, just use that instead of building a new one + if ( jQuery.support.parentNode && parent && parent.nodeType === 11 && parent.childNodes.length === this.length ) { + results = { fragment: parent }; + + } else { + results = jQuery.buildFragment( args, this, scripts ); + } + + fragment = results.fragment; + + if ( fragment.childNodes.length === 1 ) { + first = fragment = fragment.firstChild; + } else { + first = fragment.firstChild; + } + + if ( first ) { + table = table && jQuery.nodeName( first, "tr" ); + + for ( var i = 0, l = this.length; i < l; i++ ) { + callback.call( + table ? + root(this[i], first) : + this[i], + i > 0 || results.cacheable || this.length > 1 ? + fragment.cloneNode(true) : + fragment + ); + } + } + + if ( scripts.length ) { + jQuery.each( scripts, evalScript ); + } + } + + return this; + } +}); + +function root( elem, cur ) { + return jQuery.nodeName(elem, "table") ? + (elem.getElementsByTagName("tbody")[0] || + elem.appendChild(elem.ownerDocument.createElement("tbody"))) : + elem; +} + +function cloneCopyEvent(orig, ret) { + var i = 0; + + ret.each(function() { + if ( this.nodeName !== (orig[i] && orig[i].nodeName) ) { + return; + } + + var oldData = jQuery.data( orig[i++] ), + curData = jQuery.data( this, oldData ), + events = oldData && oldData.events; + + if ( events ) { + delete curData.handle; + curData.events = {}; + + for ( var type in events ) { + for ( var handler in events[ type ] ) { + jQuery.event.add( this, type, events[ type ][ handler ], events[ type ][ handler ].data ); + } + } + } + }); +} + +jQuery.buildFragment = function( args, nodes, scripts ) { + var fragment, cacheable, cacheresults, + doc = (nodes && nodes[0] ? nodes[0].ownerDocument || nodes[0] : document); + + // Only cache "small" (1/2 KB) strings that are associated with the main document + // Cloning options loses the selected state, so don't cache them + // IE 6 doesn't like it when you put <object> or <embed> elements in a fragment + // Also, WebKit does not clone 'checked' attributes on cloneNode, so don't cache + if ( args.length === 1 && typeof args[0] === "string" && args[0].length < 512 && doc === document && + !rnocache.test( args[0] ) && (jQuery.support.checkClone || !rchecked.test( args[0] )) ) { + + cacheable = true; + cacheresults = jQuery.fragments[ args[0] ]; + if ( cacheresults ) { + if ( cacheresults !== 1 ) { + fragment = cacheresults; + } + } + } + + if ( !fragment ) { + fragment = doc.createDocumentFragment(); + jQuery.clean( args, doc, fragment, scripts ); + } + + if ( cacheable ) { + jQuery.fragments[ args[0] ] = cacheresults ? fragment : 1; + } + + return { fragment: fragment, cacheable: cacheable }; +}; + +jQuery.fragments = {}; + +jQuery.each({ + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var ret = [], + insert = jQuery( selector ), + parent = this.length === 1 && this[0].parentNode; + + if ( parent && parent.nodeType === 11 && parent.childNodes.length === 1 && insert.length === 1 ) { + insert[ original ]( this[0] ); + return this; + + } else { + for ( var i = 0, l = insert.length; i < l; i++ ) { + var elems = (i > 0 ? this.clone(true) : this).get(); + jQuery( insert[i] )[ original ]( elems ); + ret = ret.concat( elems ); + } + + return this.pushStack( ret, name, insert.selector ); + } + }; +}); + +jQuery.extend({ + clean: function( elems, context, fragment, scripts ) { + context = context || document; + + // !context.createElement fails in IE with an error but returns typeof 'object' + if ( typeof context.createElement === "undefined" ) { + context = context.ownerDocument || context[0] && context[0].ownerDocument || document; + } + + var ret = []; + + for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { + if ( typeof elem === "number" ) { + elem += ""; + } + + if ( !elem ) { + continue; + } + + // Convert html string into DOM nodes + if ( typeof elem === "string" && !rhtml.test( elem ) ) { + elem = context.createTextNode( elem ); + + } else if ( typeof elem === "string" ) { + // Fix "XHTML"-style tags in all browsers + elem = elem.replace(rxhtmlTag, "<$1></$2>"); + + // Trim whitespace, otherwise indexOf won't work as expected + var tag = (rtagName.exec( elem ) || ["", ""])[1].toLowerCase(), + wrap = wrapMap[ tag ] || wrapMap._default, + depth = wrap[0], + div = context.createElement("div"); + + // Go to html and back, then peel off extra wrappers + div.innerHTML = wrap[1] + elem + wrap[2]; + + // Move to the right depth + while ( depth-- ) { + div = div.lastChild; + } + + // Remove IE's autoinserted <tbody> from table fragments + if ( !jQuery.support.tbody ) { + + // String was a <table>, *may* have spurious <tbody> + var hasBody = rtbody.test(elem), + tbody = tag === "table" && !hasBody ? + div.firstChild && div.firstChild.childNodes : + + // String was a bare <thead> or <tfoot> + wrap[1] === "<table>" && !hasBody ? + div.childNodes : + []; + + for ( var j = tbody.length - 1; j >= 0 ; --j ) { + if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length ) { + tbody[ j ].parentNode.removeChild( tbody[ j ] ); + } + } + + } + + // IE completely kills leading whitespace when innerHTML is used + if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) { + div.insertBefore( context.createTextNode( rleadingWhitespace.exec(elem)[0] ), div.firstChild ); + } + + elem = div.childNodes; + } + + if ( elem.nodeType ) { + ret.push( elem ); + } else { + ret = jQuery.merge( ret, elem ); + } + } + + if ( fragment ) { + for ( i = 0; ret[i]; i++ ) { + if ( scripts && jQuery.nodeName( ret[i], "script" ) && (!ret[i].type || ret[i].type.toLowerCase() === "text/javascript") ) { + scripts.push( ret[i].parentNode ? ret[i].parentNode.removeChild( ret[i] ) : ret[i] ); + + } else { + if ( ret[i].nodeType === 1 ) { + ret.splice.apply( ret, [i + 1, 0].concat(jQuery.makeArray(ret[i].getElementsByTagName("script"))) ); + } + fragment.appendChild( ret[i] ); + } + } + } + + return ret; + }, + + cleanData: function( elems ) { + var data, id, cache = jQuery.cache, + special = jQuery.event.special, + deleteExpando = jQuery.support.deleteExpando; + + for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { + if ( elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()] ) { + continue; + } + + id = elem[ jQuery.expando ]; + + if ( id ) { + data = cache[ id ]; + + if ( data && data.events ) { + for ( var type in data.events ) { + if ( special[ type ] ) { + jQuery.event.remove( elem, type ); + + } else { + jQuery.removeEvent( elem, type, data.handle ); + } + } + } + + if ( deleteExpando ) { + delete elem[ jQuery.expando ]; + + } else if ( elem.removeAttribute ) { + elem.removeAttribute( jQuery.expando ); + } + + delete cache[ id ]; + } + } + } +}); + +function evalScript( i, elem ) { + if ( elem.src ) { + jQuery.ajax({ + url: elem.src, + async: false, + dataType: "script" + }); + } else { + jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" ); + } + + if ( elem.parentNode ) { + elem.parentNode.removeChild( elem ); + } +} + + + + +var ralpha = /alpha\([^)]*\)/i, + ropacity = /opacity=([^)]*)/, + rdashAlpha = /-([a-z])/ig, + rupper = /([A-Z])/g, + rnumpx = /^-?\d+(?:px)?$/i, + rnum = /^-?\d/, + + cssShow = { position: "absolute", visibility: "hidden", display: "block" }, + cssWidth = [ "Left", "Right" ], + cssHeight = [ "Top", "Bottom" ], + curCSS, + + getComputedStyle, + currentStyle, + + fcamelCase = function( all, letter ) { + return letter.toUpperCase(); + }; + +jQuery.fn.css = function( name, value ) { + // Setting 'undefined' is a no-op + if ( arguments.length === 2 && value === undefined ) { + return this; + } + + return jQuery.access( this, name, value, true, function( elem, name, value ) { + return value !== undefined ? + jQuery.style( elem, name, value ) : + jQuery.css( elem, name ); + }); +}; + +jQuery.extend({ + // Add in style property hooks for overriding the default + // behavior of getting and setting a style property + cssHooks: { + opacity: { + get: function( elem, computed ) { + if ( computed ) { + // We should always get a number back from opacity + var ret = curCSS( elem, "opacity", "opacity" ); + return ret === "" ? "1" : ret; + + } else { + return elem.style.opacity; + } + } + } + }, + + // Exclude the following css properties to add px + cssNumber: { + "zIndex": true, + "fontWeight": true, + "opacity": true, + "zoom": true, + "lineHeight": true + }, + + // Add in properties whose names you wish to fix before + // setting or getting the value + cssProps: { + // normalize float css property + "float": jQuery.support.cssFloat ? "cssFloat" : "styleFloat" + }, + + // Get and set the style property on a DOM Node + style: function( elem, name, value, extra ) { + // Don't set styles on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { + return; + } + + // Make sure that we're working with the right name + var ret, origName = jQuery.camelCase( name ), + style = elem.style, hooks = jQuery.cssHooks[ origName ]; + + name = jQuery.cssProps[ origName ] || origName; + + // Check if we're setting a value + if ( value !== undefined ) { + // Make sure that NaN and null values aren't set. See: #7116 + if ( typeof value === "number" && isNaN( value ) || value == null ) { + return; + } + + // If a number was passed in, add 'px' to the (except for certain CSS properties) + if ( typeof value === "number" && !jQuery.cssNumber[ origName ] ) { + value += "px"; + } + + // If a hook was provided, use that value, otherwise just set the specified value + if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value )) !== undefined ) { + // Wrapped to prevent IE from throwing errors when 'invalid' values are provided + // Fixes bug #5509 + try { + style[ name ] = value; + } catch(e) {} + } + + } else { + // If a hook was provided get the non-computed value from there + if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) { + return ret; + } + + // Otherwise just get the value from the style object + return style[ name ]; + } + }, + + css: function( elem, name, extra ) { + // Make sure that we're working with the right name + var ret, origName = jQuery.camelCase( name ), + hooks = jQuery.cssHooks[ origName ]; + + name = jQuery.cssProps[ origName ] || origName; + + // If a hook was provided get the computed value from there + if ( hooks && "get" in hooks && (ret = hooks.get( elem, true, extra )) !== undefined ) { + return ret; + + // Otherwise, if a way to get the computed value exists, use that + } else if ( curCSS ) { + return curCSS( elem, name, origName ); + } + }, + + // A method for quickly swapping in/out CSS properties to get correct calculations + swap: function( elem, options, callback ) { + var old = {}; + + // Remember the old values, and insert the new ones + for ( var name in options ) { + old[ name ] = elem.style[ name ]; + elem.style[ name ] = options[ name ]; + } + + callback.call( elem ); + + // Revert the old values + for ( name in options ) { + elem.style[ name ] = old[ name ]; + } + }, + + camelCase: function( string ) { + return string.replace( rdashAlpha, fcamelCase ); + } +}); + +// DEPRECATED, Use jQuery.css() instead +jQuery.curCSS = jQuery.css; + +jQuery.each(["height", "width"], function( i, name ) { + jQuery.cssHooks[ name ] = { + get: function( elem, computed, extra ) { + var val; + + if ( computed ) { + if ( elem.offsetWidth !== 0 ) { + val = getWH( elem, name, extra ); + + } else { + jQuery.swap( elem, cssShow, function() { + val = getWH( elem, name, extra ); + }); + } + + if ( val <= 0 ) { + val = curCSS( elem, name, name ); + + if ( val === "0px" && currentStyle ) { + val = currentStyle( elem, name, name ); + } + + if ( val != null ) { + // Should return "auto" instead of 0, use 0 for + // temporary backwards-compat + return val === "" || val === "auto" ? "0px" : val; + } + } + + if ( val < 0 || val == null ) { + val = elem.style[ name ]; + + // Should return "auto" instead of 0, use 0 for + // temporary backwards-compat + return val === "" || val === "auto" ? "0px" : val; + } + + return typeof val === "string" ? val : val + "px"; + } + }, + + set: function( elem, value ) { + if ( rnumpx.test( value ) ) { + // ignore negative width and height values #1599 + value = parseFloat(value); + + if ( value >= 0 ) { + return value + "px"; + } + + } else { + return value; + } + } + }; +}); + +if ( !jQuery.support.opacity ) { + jQuery.cssHooks.opacity = { + get: function( elem, computed ) { + // IE uses filters for opacity + return ropacity.test((computed && elem.currentStyle ? elem.currentStyle.filter : elem.style.filter) || "") ? + (parseFloat(RegExp.$1) / 100) + "" : + computed ? "1" : ""; + }, + + set: function( elem, value ) { + var style = elem.style; + + // IE has trouble with opacity if it does not have layout + // Force it by setting the zoom level + style.zoom = 1; + + // Set the alpha filter to set the opacity + var opacity = jQuery.isNaN(value) ? + "" : + "alpha(opacity=" + value * 100 + ")", + filter = style.filter || ""; + + style.filter = ralpha.test(filter) ? + filter.replace(ralpha, opacity) : + style.filter + ' ' + opacity; + } + }; +} + +if ( document.defaultView && document.defaultView.getComputedStyle ) { + getComputedStyle = function( elem, newName, name ) { + var ret, defaultView, computedStyle; + + name = name.replace( rupper, "-$1" ).toLowerCase(); + + if ( !(defaultView = elem.ownerDocument.defaultView) ) { + return undefined; + } + + if ( (computedStyle = defaultView.getComputedStyle( elem, null )) ) { + ret = computedStyle.getPropertyValue( name ); + if ( ret === "" && !jQuery.contains( elem.ownerDocument.documentElement, elem ) ) { + ret = jQuery.style( elem, name ); + } + } + + return ret; + }; +} + +if ( document.documentElement.currentStyle ) { + currentStyle = function( elem, name ) { + var left, rsLeft, + ret = elem.currentStyle && elem.currentStyle[ name ], + style = elem.style; + + // From the awesome hack by Dean Edwards + // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 + + // If we're not dealing with a regular pixel number + // but a number that has a weird ending, we need to convert it to pixels + if ( !rnumpx.test( ret ) && rnum.test( ret ) ) { + // Remember the original values + left = style.left; + rsLeft = elem.runtimeStyle.left; + + // Put in the new values to get a computed value out + elem.runtimeStyle.left = elem.currentStyle.left; + style.left = name === "fontSize" ? "1em" : (ret || 0); + ret = style.pixelLeft + "px"; + + // Revert the changed values + style.left = left; + elem.runtimeStyle.left = rsLeft; + } + + return ret === "" ? "auto" : ret; + }; +} + +curCSS = getComputedStyle || currentStyle; + +function getWH( elem, name, extra ) { + var which = name === "width" ? cssWidth : cssHeight, + val = name === "width" ? elem.offsetWidth : elem.offsetHeight; + + if ( extra === "border" ) { + return val; + } + + jQuery.each( which, function() { + if ( !extra ) { + val -= parseFloat(jQuery.css( elem, "padding" + this )) || 0; + } + + if ( extra === "margin" ) { + val += parseFloat(jQuery.css( elem, "margin" + this )) || 0; + + } else { + val -= parseFloat(jQuery.css( elem, "border" + this + "Width" )) || 0; + } + }); + + return val; +} + +if ( jQuery.expr && jQuery.expr.filters ) { + jQuery.expr.filters.hidden = function( elem ) { + var width = elem.offsetWidth, + height = elem.offsetHeight; + + return (width === 0 && height === 0) || (!jQuery.support.reliableHiddenOffsets && (elem.style.display || jQuery.css( elem, "display" )) === "none"); + }; + + jQuery.expr.filters.visible = function( elem ) { + return !jQuery.expr.filters.hidden( elem ); + }; +} + + + + +var jsc = jQuery.now(), + rscript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, + rselectTextarea = /^(?:select|textarea)/i, + rinput = /^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i, + rnoContent = /^(?:GET|HEAD)$/, + rbracket = /\[\]$/, + jsre = /\=\?(&|$)/, + rquery = /\?/, + rts = /([?&])_=[^&]*/, + rurl = /^(\w+:)?\/\/([^\/?#]+)/, + r20 = /%20/g, + rhash = /#.*$/, + + // Keep a copy of the old load method + _load = jQuery.fn.load; + +jQuery.fn.extend({ + load: function( url, params, callback ) { + if ( typeof url !== "string" && _load ) { + return _load.apply( this, arguments ); + + // Don't do a request if no elements are being requested + } else if ( !this.length ) { + return this; + } + + var off = url.indexOf(" "); + if ( off >= 0 ) { + var selector = url.slice(off, url.length); + url = url.slice(0, off); + } + + // Default to a GET request + var type = "GET"; + + // If the second parameter was provided + if ( params ) { + // If it's a function + if ( jQuery.isFunction( params ) ) { + // We assume that it's the callback + callback = params; + params = null; + + // Otherwise, build a param string + } else if ( typeof params === "object" ) { + params = jQuery.param( params, jQuery.ajaxSettings.traditional ); + type = "POST"; + } + } + + var self = this; + + // Request the remote document + jQuery.ajax({ + url: url, + type: type, + dataType: "html", + data: params, + complete: function( res, status ) { + // If successful, inject the HTML into all the matched elements + if ( status === "success" || status === "notmodified" ) { + // See if a selector was specified + self.html( selector ? + // Create a dummy div to hold the results + jQuery("<div>") + // inject the contents of the document in, removing the scripts + // to avoid any 'Permission Denied' errors in IE + .append(res.responseText.replace(rscript, "")) + + // Locate the specified elements + .find(selector) : + + // If not, just inject the full result + res.responseText ); + } + + if ( callback ) { + self.each( callback, [res.responseText, status, res] ); + } + } + }); + + return this; + }, + + serialize: function() { + return jQuery.param(this.serializeArray()); + }, + + serializeArray: function() { + return this.map(function() { + return this.elements ? jQuery.makeArray(this.elements) : this; + }) + .filter(function() { + return this.name && !this.disabled && + (this.checked || rselectTextarea.test(this.nodeName) || + rinput.test(this.type)); + }) + .map(function( i, elem ) { + var val = jQuery(this).val(); + + return val == null ? + null : + jQuery.isArray(val) ? + jQuery.map( val, function( val, i ) { + return { name: elem.name, value: val }; + }) : + { name: elem.name, value: val }; + }).get(); + } +}); + +// Attach a bunch of functions for handling common AJAX events +jQuery.each( "ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "), function( i, o ) { + jQuery.fn[o] = function( f ) { + return this.bind(o, f); + }; +}); + +jQuery.extend({ + get: function( url, data, callback, type ) { + // shift arguments if data argument was omited + if ( jQuery.isFunction( data ) ) { + type = type || callback; + callback = data; + data = null; + } + + return jQuery.ajax({ + type: "GET", + url: url, + data: data, + success: callback, + dataType: type + }); + }, + + getScript: function( url, callback ) { + return jQuery.get(url, null, callback, "script"); + }, + + getJSON: function( url, data, callback ) { + return jQuery.get(url, data, callback, "json"); + }, + + post: function( url, data, callback, type ) { + // shift arguments if data argument was omited + if ( jQuery.isFunction( data ) ) { + type = type || callback; + callback = data; + data = {}; + } + + return jQuery.ajax({ + type: "POST", + url: url, + data: data, + success: callback, + dataType: type + }); + }, + + ajaxSetup: function( settings ) { + jQuery.extend( jQuery.ajaxSettings, settings ); + }, + + ajaxSettings: { + url: location.href, + global: true, + type: "GET", + contentType: "application/x-www-form-urlencoded", + processData: true, + async: true, + /* + timeout: 0, + data: null, + username: null, + password: null, + traditional: false, + */ + // This function can be overriden by calling jQuery.ajaxSetup + xhr: function() { + return new window.XMLHttpRequest(); + }, + accepts: { + xml: "application/xml, text/xml", + html: "text/html", + script: "text/javascript, application/javascript", + json: "application/json, text/javascript", + text: "text/plain", + _default: "*/*" + } + }, + + ajax: function( origSettings ) { + var s = jQuery.extend(true, {}, jQuery.ajaxSettings, origSettings), + jsonp, status, data, type = s.type.toUpperCase(), noContent = rnoContent.test(type); + + s.url = s.url.replace( rhash, "" ); + + // Use original (not extended) context object if it was provided + s.context = origSettings && origSettings.context != null ? origSettings.context : s; + + // convert data if not already a string + if ( s.data && s.processData && typeof s.data !== "string" ) { + s.data = jQuery.param( s.data, s.traditional ); + } + + // Handle JSONP Parameter Callbacks + if ( s.dataType === "jsonp" ) { + if ( type === "GET" ) { + if ( !jsre.test( s.url ) ) { + s.url += (rquery.test( s.url ) ? "&" : "?") + (s.jsonp || "callback") + "=?"; + } + } else if ( !s.data || !jsre.test(s.data) ) { + s.data = (s.data ? s.data + "&" : "") + (s.jsonp || "callback") + "=?"; + } + s.dataType = "json"; + } + + // Build temporary JSONP function + if ( s.dataType === "json" && (s.data && jsre.test(s.data) || jsre.test(s.url)) ) { + jsonp = s.jsonpCallback || ("jsonp" + jsc++); + + // Replace the =? sequence both in the query string and the data + if ( s.data ) { + s.data = (s.data + "").replace(jsre, "=" + jsonp + "$1"); + } + + s.url = s.url.replace(jsre, "=" + jsonp + "$1"); + + // We need to make sure + // that a JSONP style response is executed properly + s.dataType = "script"; + + // Handle JSONP-style loading + var customJsonp = window[ jsonp ]; + + window[ jsonp ] = function( tmp ) { + if ( jQuery.isFunction( customJsonp ) ) { + customJsonp( tmp ); + + } else { + // Garbage collect + window[ jsonp ] = undefined; + + try { + delete window[ jsonp ]; + } catch( jsonpError ) {} + } + + data = tmp; + jQuery.handleSuccess( s, xhr, status, data ); + jQuery.handleComplete( s, xhr, status, data ); + + if ( head ) { + head.removeChild( script ); + } + }; + } + + if ( s.dataType === "script" && s.cache === null ) { + s.cache = false; + } + + if ( s.cache === false && noContent ) { + var ts = jQuery.now(); + + // try replacing _= if it is there + var ret = s.url.replace(rts, "$1_=" + ts); + + // if nothing was replaced, add timestamp to the end + s.url = ret + ((ret === s.url) ? (rquery.test(s.url) ? "&" : "?") + "_=" + ts : ""); + } + + // If data is available, append data to url for GET/HEAD requests + if ( s.data && noContent ) { + s.url += (rquery.test(s.url) ? "&" : "?") + s.data; + } + + // Watch for a new set of requests + if ( s.global && jQuery.active++ === 0 ) { + jQuery.event.trigger( "ajaxStart" ); + } + + // Matches an absolute URL, and saves the domain + var parts = rurl.exec( s.url ), + remote = parts && (parts[1] && parts[1].toLowerCase() !== location.protocol || parts[2].toLowerCase() !== location.host); + + // If we're requesting a remote document + // and trying to load JSON or Script with a GET + if ( s.dataType === "script" && type === "GET" && remote ) { + var head = document.getElementsByTagName("head")[0] || document.documentElement; + var script = document.createElement("script"); + if ( s.scriptCharset ) { + script.charset = s.scriptCharset; + } + script.src = s.url; + + // Handle Script loading + if ( !jsonp ) { + var done = false; + + // Attach handlers for all browsers + script.onload = script.onreadystatechange = function() { + if ( !done && (!this.readyState || + this.readyState === "loaded" || this.readyState === "complete") ) { + done = true; + jQuery.handleSuccess( s, xhr, status, data ); + jQuery.handleComplete( s, xhr, status, data ); + + // Handle memory leak in IE + script.onload = script.onreadystatechange = null; + if ( head && script.parentNode ) { + head.removeChild( script ); + } + } + }; + } + + // Use insertBefore instead of appendChild to circumvent an IE6 bug. + // This arises when a base node is used (#2709 and #4378). + head.insertBefore( script, head.firstChild ); + + // We handle everything using the script element injection + return undefined; + } + + var requestDone = false; + + // Create the request object + var xhr = s.xhr(); + + if ( !xhr ) { + return; + } + + // Open the socket + // Passing null username, generates a login popup on Opera (#2865) + if ( s.username ) { + xhr.open(type, s.url, s.async, s.username, s.password); + } else { + xhr.open(type, s.url, s.async); + } + + // Need an extra try/catch for cross domain requests in Firefox 3 + try { + // Set content-type if data specified and content-body is valid for this type + if ( (s.data != null && !noContent) || (origSettings && origSettings.contentType) ) { + xhr.setRequestHeader("Content-Type", s.contentType); + } + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + if ( jQuery.lastModified[s.url] ) { + xhr.setRequestHeader("If-Modified-Since", jQuery.lastModified[s.url]); + } + + if ( jQuery.etag[s.url] ) { + xhr.setRequestHeader("If-None-Match", jQuery.etag[s.url]); + } + } + + // Set header so the called script knows that it's an XMLHttpRequest + // Only send the header if it's not a remote XHR + if ( !remote ) { + xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest"); + } + + // Set the Accepts header for the server, depending on the dataType + xhr.setRequestHeader("Accept", s.dataType && s.accepts[ s.dataType ] ? + s.accepts[ s.dataType ] + ", */*; q=0.01" : + s.accepts._default ); + } catch( headerError ) {} + + // Allow custom headers/mimetypes and early abort + if ( s.beforeSend && s.beforeSend.call(s.context, xhr, s) === false ) { + // Handle the global AJAX counter + if ( s.global && jQuery.active-- === 1 ) { + jQuery.event.trigger( "ajaxStop" ); + } + + // close opended socket + xhr.abort(); + return false; + } + + if ( s.global ) { + jQuery.triggerGlobal( s, "ajaxSend", [xhr, s] ); + } + + // Wait for a response to come back + var onreadystatechange = xhr.onreadystatechange = function( isTimeout ) { + // The request was aborted + if ( !xhr || xhr.readyState === 0 || isTimeout === "abort" ) { + // Opera doesn't call onreadystatechange before this point + // so we simulate the call + if ( !requestDone ) { + jQuery.handleComplete( s, xhr, status, data ); + } + + requestDone = true; + if ( xhr ) { + xhr.onreadystatechange = jQuery.noop; + } + + // The transfer is complete and the data is available, or the request timed out + } else if ( !requestDone && xhr && (xhr.readyState === 4 || isTimeout === "timeout") ) { + requestDone = true; + xhr.onreadystatechange = jQuery.noop; + + status = isTimeout === "timeout" ? + "timeout" : + !jQuery.httpSuccess( xhr ) ? + "error" : + s.ifModified && jQuery.httpNotModified( xhr, s.url ) ? + "notmodified" : + "success"; + + var errMsg; + + if ( status === "success" ) { + // Watch for, and catch, XML document parse errors + try { + // process the data (runs the xml through httpData regardless of callback) + data = jQuery.httpData( xhr, s.dataType, s ); + } catch( parserError ) { + status = "parsererror"; + errMsg = parserError; + } + } + + // Make sure that the request was successful or notmodified + if ( status === "success" || status === "notmodified" ) { + // JSONP handles its own success callback + if ( !jsonp ) { + jQuery.handleSuccess( s, xhr, status, data ); + } + } else { + jQuery.handleError( s, xhr, status, errMsg ); + } + + // Fire the complete handlers + if ( !jsonp ) { + jQuery.handleComplete( s, xhr, status, data ); + } + + if ( isTimeout === "timeout" ) { + xhr.abort(); + } + + // Stop memory leaks + if ( s.async ) { + xhr = null; + } + } + }; + + // Override the abort handler, if we can (IE 6 doesn't allow it, but that's OK) + // Opera doesn't fire onreadystatechange at all on abort + try { + var oldAbort = xhr.abort; + xhr.abort = function() { + if ( xhr ) { + // oldAbort has no call property in IE7 so + // just do it this way, which works in all + // browsers + Function.prototype.call.call( oldAbort, xhr ); + } + + onreadystatechange( "abort" ); + }; + } catch( abortError ) {} + + // Timeout checker + if ( s.async && s.timeout > 0 ) { + setTimeout(function() { + // Check to see if the request is still happening + if ( xhr && !requestDone ) { + onreadystatechange( "timeout" ); + } + }, s.timeout); + } + + // Send the data + try { + xhr.send( noContent || s.data == null ? null : s.data ); + + } catch( sendError ) { + jQuery.handleError( s, xhr, null, sendError ); + + // Fire the complete handlers + jQuery.handleComplete( s, xhr, status, data ); + } + + // firefox 1.5 doesn't fire statechange for sync requests + if ( !s.async ) { + onreadystatechange(); + } + + // return XMLHttpRequest to allow aborting the request etc. + return xhr; + }, + + // Serialize an array of form elements or a set of + // key/values into a query string + param: function( a, traditional ) { + var s = [], + add = function( key, value ) { + // If value is a function, invoke it and return its value + value = jQuery.isFunction(value) ? value() : value; + s[ s.length ] = encodeURIComponent(key) + "=" + encodeURIComponent(value); + }; + + // Set traditional to true for jQuery <= 1.3.2 behavior. + if ( traditional === undefined ) { + traditional = jQuery.ajaxSettings.traditional; + } + + // If an array was passed in, assume that it is an array of form elements. + if ( jQuery.isArray(a) || a.jquery ) { + // Serialize the form elements + jQuery.each( a, function() { + add( this.name, this.value ); + }); + + } else { + // If traditional, encode the "old" way (the way 1.3.2 or older + // did it), otherwise encode params recursively. + for ( var prefix in a ) { + buildParams( prefix, a[prefix], traditional, add ); + } + } + + // Return the resulting serialization + return s.join("&").replace(r20, "+"); + } +}); + +function buildParams( prefix, obj, traditional, add ) { + if ( jQuery.isArray(obj) && obj.length ) { + // Serialize array item. + jQuery.each( obj, function( i, v ) { + if ( traditional || rbracket.test( prefix ) ) { + // Treat each array item as a scalar. + add( prefix, v ); + + } else { + // If array item is non-scalar (array or object), encode its + // numeric index to resolve deserialization ambiguity issues. + // Note that rack (as of 1.0.0) can't currently deserialize + // nested arrays properly, and attempting to do so may cause + // a server error. Possible fixes are to modify rack's + // deserialization algorithm or to provide an option or flag + // to force array serialization to be shallow. + buildParams( prefix + "[" + ( typeof v === "object" || jQuery.isArray(v) ? i : "" ) + "]", v, traditional, add ); + } + }); + + } else if ( !traditional && obj != null && typeof obj === "object" ) { + if ( jQuery.isEmptyObject( obj ) ) { + add( prefix, "" ); + + // Serialize object item. + } else { + jQuery.each( obj, function( k, v ) { + buildParams( prefix + "[" + k + "]", v, traditional, add ); + }); + } + + } else { + // Serialize scalar item. + add( prefix, obj ); + } +} + +// This is still on the jQuery object... for now +// Want to move this to jQuery.ajax some day +jQuery.extend({ + + // Counter for holding the number of active queries + active: 0, + + // Last-Modified header cache for next request + lastModified: {}, + etag: {}, + + handleError: function( s, xhr, status, e ) { + // If a local callback was specified, fire it + if ( s.error ) { + s.error.call( s.context, xhr, status, e ); + } + + // Fire the global callback + if ( s.global ) { + jQuery.triggerGlobal( s, "ajaxError", [xhr, s, e] ); + } + }, + + handleSuccess: function( s, xhr, status, data ) { + // If a local callback was specified, fire it and pass it the data + if ( s.success ) { + s.success.call( s.context, data, status, xhr ); + } + + // Fire the global callback + if ( s.global ) { + jQuery.triggerGlobal( s, "ajaxSuccess", [xhr, s] ); + } + }, + + handleComplete: function( s, xhr, status ) { + // Process result + if ( s.complete ) { + s.complete.call( s.context, xhr, status ); + } + + // The request was completed + if ( s.global ) { + jQuery.triggerGlobal( s, "ajaxComplete", [xhr, s] ); + } + + // Handle the global AJAX counter + if ( s.global && jQuery.active-- === 1 ) { + jQuery.event.trigger( "ajaxStop" ); + } + }, + + triggerGlobal: function( s, type, args ) { + (s.context && s.context.url == null ? jQuery(s.context) : jQuery.event).trigger(type, args); + }, + + // Determines if an XMLHttpRequest was successful or not + httpSuccess: function( xhr ) { + try { + // IE error sometimes returns 1223 when it should be 204 so treat it as success, see #1450 + return !xhr.status && location.protocol === "file:" || + xhr.status >= 200 && xhr.status < 300 || + xhr.status === 304 || xhr.status === 1223; + } catch(e) {} + + return false; + }, + + // Determines if an XMLHttpRequest returns NotModified + httpNotModified: function( xhr, url ) { + var lastModified = xhr.getResponseHeader("Last-Modified"), + etag = xhr.getResponseHeader("Etag"); + + if ( lastModified ) { + jQuery.lastModified[url] = lastModified; + } + + if ( etag ) { + jQuery.etag[url] = etag; + } + + return xhr.status === 304; + }, + + httpData: function( xhr, type, s ) { + var ct = xhr.getResponseHeader("content-type") || "", + xml = type === "xml" || !type && ct.indexOf("xml") >= 0, + data = xml ? xhr.responseXML : xhr.responseText; + + if ( xml && data.documentElement.nodeName === "parsererror" ) { + jQuery.error( "parsererror" ); + } + + // Allow a pre-filtering function to sanitize the response + // s is checked to keep backwards compatibility + if ( s && s.dataFilter ) { + data = s.dataFilter( data, type ); + } + + // The filter can actually parse the response + if ( typeof data === "string" ) { + // Get the JavaScript object, if JSON is used. + if ( type === "json" || !type && ct.indexOf("json") >= 0 ) { + data = jQuery.parseJSON( data ); + + // If the type is "script", eval it in global context + } else if ( type === "script" || !type && ct.indexOf("javascript") >= 0 ) { + jQuery.globalEval( data ); + } + } + + return data; + } + +}); + +/* + * Create the request object; Microsoft failed to properly + * implement the XMLHttpRequest in IE7 (can't request local files), + * so we use the ActiveXObject when it is available + * Additionally XMLHttpRequest can be disabled in IE7/IE8 so + * we need a fallback. + */ +if ( window.ActiveXObject ) { + jQuery.ajaxSettings.xhr = function() { + if ( window.location.protocol !== "file:" ) { + try { + return new window.XMLHttpRequest(); + } catch(xhrError) {} + } + + try { + return new window.ActiveXObject("Microsoft.XMLHTTP"); + } catch(activeError) {} + }; +} + +// Does this browser support XHR requests? +jQuery.support.ajax = !!jQuery.ajaxSettings.xhr(); + + + + +var elemdisplay = {}, + rfxtypes = /^(?:toggle|show|hide)$/, + rfxnum = /^([+\-]=)?([\d+.\-]+)(.*)$/, + timerId, + fxAttrs = [ + // height animations + [ "height", "marginTop", "marginBottom", "paddingTop", "paddingBottom" ], + // width animations + [ "width", "marginLeft", "marginRight", "paddingLeft", "paddingRight" ], + // opacity animations + [ "opacity" ] + ]; + +jQuery.fn.extend({ + show: function( speed, easing, callback ) { + var elem, display; + + if ( speed || speed === 0 ) { + return this.animate( genFx("show", 3), speed, easing, callback); + + } else { + for ( var i = 0, j = this.length; i < j; i++ ) { + elem = this[i]; + display = elem.style.display; + + // Reset the inline display of this element to learn if it is + // being hidden by cascaded rules or not + if ( !jQuery.data(elem, "olddisplay") && display === "none" ) { + display = elem.style.display = ""; + } + + // Set elements which have been overridden with display: none + // in a stylesheet to whatever the default browser style is + // for such an element + if ( display === "" && jQuery.css( elem, "display" ) === "none" ) { + jQuery.data(elem, "olddisplay", defaultDisplay(elem.nodeName)); + } + } + + // Set the display of most of the elements in a second loop + // to avoid the constant reflow + for ( i = 0; i < j; i++ ) { + elem = this[i]; + display = elem.style.display; + + if ( display === "" || display === "none" ) { + elem.style.display = jQuery.data(elem, "olddisplay") || ""; + } + } + + return this; + } + }, + + hide: function( speed, easing, callback ) { + if ( speed || speed === 0 ) { + return this.animate( genFx("hide", 3), speed, easing, callback); + + } else { + for ( var i = 0, j = this.length; i < j; i++ ) { + var display = jQuery.css( this[i], "display" ); + + if ( display !== "none" ) { + jQuery.data( this[i], "olddisplay", display ); + } + } + + // Set the display of the elements in a second loop + // to avoid the constant reflow + for ( i = 0; i < j; i++ ) { + this[i].style.display = "none"; + } + + return this; + } + }, + + // Save the old toggle function + _toggle: jQuery.fn.toggle, + + toggle: function( fn, fn2, callback ) { + var bool = typeof fn === "boolean"; + + if ( jQuery.isFunction(fn) && jQuery.isFunction(fn2) ) { + this._toggle.apply( this, arguments ); + + } else if ( fn == null || bool ) { + this.each(function() { + var state = bool ? fn : jQuery(this).is(":hidden"); + jQuery(this)[ state ? "show" : "hide" ](); + }); + + } else { + this.animate(genFx("toggle", 3), fn, fn2, callback); + } + + return this; + }, + + fadeTo: function( speed, to, easing, callback ) { + return this.filter(":hidden").css("opacity", 0).show().end() + .animate({opacity: to}, speed, easing, callback); + }, + + animate: function( prop, speed, easing, callback ) { + var optall = jQuery.speed(speed, easing, callback); + + if ( jQuery.isEmptyObject( prop ) ) { + return this.each( optall.complete ); + } + + return this[ optall.queue === false ? "each" : "queue" ](function() { + // XXX 'this' does not always have a nodeName when running the + // test suite + + var opt = jQuery.extend({}, optall), p, + isElement = this.nodeType === 1, + hidden = isElement && jQuery(this).is(":hidden"), + self = this; + + for ( p in prop ) { + var name = jQuery.camelCase( p ); + + if ( p !== name ) { + prop[ name ] = prop[ p ]; + delete prop[ p ]; + p = name; + } + + if ( prop[p] === "hide" && hidden || prop[p] === "show" && !hidden ) { + return opt.complete.call(this); + } + + if ( isElement && ( p === "height" || p === "width" ) ) { + // Make sure that nothing sneaks out + // Record all 3 overflow attributes because IE does not + // change the overflow attribute when overflowX and + // overflowY are set to the same value + opt.overflow = [ this.style.overflow, this.style.overflowX, this.style.overflowY ]; + + // Set display property to inline-block for height/width + // animations on inline elements that are having width/height + // animated + if ( jQuery.css( this, "display" ) === "inline" && + jQuery.css( this, "float" ) === "none" ) { + if ( !jQuery.support.inlineBlockNeedsLayout ) { + this.style.display = "inline-block"; + + } else { + var display = defaultDisplay(this.nodeName); + + // inline-level elements accept inline-block; + // block-level elements need to be inline with layout + if ( display === "inline" ) { + this.style.display = "inline-block"; + + } else { + this.style.display = "inline"; + this.style.zoom = 1; + } + } + } + } + + if ( jQuery.isArray( prop[p] ) ) { + // Create (if needed) and add to specialEasing + (opt.specialEasing = opt.specialEasing || {})[p] = prop[p][1]; + prop[p] = prop[p][0]; + } + } + + if ( opt.overflow != null ) { + this.style.overflow = "hidden"; + } + + opt.curAnim = jQuery.extend({}, prop); + + jQuery.each( prop, function( name, val ) { + var e = new jQuery.fx( self, opt, name ); + + if ( rfxtypes.test(val) ) { + e[ val === "toggle" ? hidden ? "show" : "hide" : val ]( prop ); + + } else { + var parts = rfxnum.exec(val), + start = e.cur() || 0; + + if ( parts ) { + var end = parseFloat( parts[2] ), + unit = parts[3] || "px"; + + // We need to compute starting value + if ( unit !== "px" ) { + jQuery.style( self, name, (end || 1) + unit); + start = ((end || 1) / e.cur()) * start; + jQuery.style( self, name, start + unit); + } + + // If a +=/-= token was provided, we're doing a relative animation + if ( parts[1] ) { + end = ((parts[1] === "-=" ? -1 : 1) * end) + start; + } + + e.custom( start, end, unit ); + + } else { + e.custom( start, val, "" ); + } + } + }); + + // For JS strict compliance + return true; + }); + }, + + stop: function( clearQueue, gotoEnd ) { + var timers = jQuery.timers; + + if ( clearQueue ) { + this.queue([]); + } + + this.each(function() { + // go in reverse order so anything added to the queue during the loop is ignored + for ( var i = timers.length - 1; i >= 0; i-- ) { + if ( timers[i].elem === this ) { + if (gotoEnd) { + // force the next step to be the last + timers[i](true); + } + + timers.splice(i, 1); + } + } + }); + + // start the next in the queue if the last step wasn't forced + if ( !gotoEnd ) { + this.dequeue(); + } + + return this; + } + +}); + +function genFx( type, num ) { + var obj = {}; + + jQuery.each( fxAttrs.concat.apply([], fxAttrs.slice(0,num)), function() { + obj[ this ] = type; + }); + + return obj; +} + +// Generate shortcuts for custom animations +jQuery.each({ + slideDown: genFx("show", 1), + slideUp: genFx("hide", 1), + slideToggle: genFx("toggle", 1), + fadeIn: { opacity: "show" }, + fadeOut: { opacity: "hide" }, + fadeToggle: { opacity: "toggle" } +}, function( name, props ) { + jQuery.fn[ name ] = function( speed, easing, callback ) { + return this.animate( props, speed, easing, callback ); + }; +}); + +jQuery.extend({ + speed: function( speed, easing, fn ) { + var opt = speed && typeof speed === "object" ? jQuery.extend({}, speed) : { + complete: fn || !fn && easing || + jQuery.isFunction( speed ) && speed, + duration: speed, + easing: fn && easing || easing && !jQuery.isFunction(easing) && easing + }; + + opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration : + opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[opt.duration] : jQuery.fx.speeds._default; + + // Queueing + opt.old = opt.complete; + opt.complete = function() { + if ( opt.queue !== false ) { + jQuery(this).dequeue(); + } + if ( jQuery.isFunction( opt.old ) ) { + opt.old.call( this ); + } + }; + + return opt; + }, + + easing: { + linear: function( p, n, firstNum, diff ) { + return firstNum + diff * p; + }, + swing: function( p, n, firstNum, diff ) { + return ((-Math.cos(p*Math.PI)/2) + 0.5) * diff + firstNum; + } + }, + + timers: [], + + fx: function( elem, options, prop ) { + this.options = options; + this.elem = elem; + this.prop = prop; + + if ( !options.orig ) { + options.orig = {}; + } + } + +}); + +jQuery.fx.prototype = { + // Simple function for setting a style value + update: function() { + if ( this.options.step ) { + this.options.step.call( this.elem, this.now, this ); + } + + (jQuery.fx.step[this.prop] || jQuery.fx.step._default)( this ); + }, + + // Get the current size + cur: function() { + if ( this.elem[this.prop] != null && (!this.elem.style || this.elem.style[this.prop] == null) ) { + return this.elem[ this.prop ]; + } + + var r = parseFloat( jQuery.css( this.elem, this.prop ) ); + return r && r > -10000 ? r : 0; + }, + + // Start an animation from one number to another + custom: function( from, to, unit ) { + var self = this, + fx = jQuery.fx; + + this.startTime = jQuery.now(); + this.start = from; + this.end = to; + this.unit = unit || this.unit || "px"; + this.now = this.start; + this.pos = this.state = 0; + + function t( gotoEnd ) { + return self.step(gotoEnd); + } + + t.elem = this.elem; + + if ( t() && jQuery.timers.push(t) && !timerId ) { + timerId = setInterval(fx.tick, fx.interval); + } + }, + + // Simple 'show' function + show: function() { + // Remember where we started, so that we can go back to it later + this.options.orig[this.prop] = jQuery.style( this.elem, this.prop ); + this.options.show = true; + + // Begin the animation + // Make sure that we start at a small width/height to avoid any + // flash of content + this.custom(this.prop === "width" || this.prop === "height" ? 1 : 0, this.cur()); + + // Start by showing the element + jQuery( this.elem ).show(); + }, + + // Simple 'hide' function + hide: function() { + // Remember where we started, so that we can go back to it later + this.options.orig[this.prop] = jQuery.style( this.elem, this.prop ); + this.options.hide = true; + + // Begin the animation + this.custom(this.cur(), 0); + }, + + // Each step of an animation + step: function( gotoEnd ) { + var t = jQuery.now(), done = true; + + if ( gotoEnd || t >= this.options.duration + this.startTime ) { + this.now = this.end; + this.pos = this.state = 1; + this.update(); + + this.options.curAnim[ this.prop ] = true; + + for ( var i in this.options.curAnim ) { + if ( this.options.curAnim[i] !== true ) { + done = false; + } + } + + if ( done ) { + // Reset the overflow + if ( this.options.overflow != null && !jQuery.support.shrinkWrapBlocks ) { + var elem = this.elem, + options = this.options; + + jQuery.each( [ "", "X", "Y" ], function (index, value) { + elem.style[ "overflow" + value ] = options.overflow[index]; + } ); + } + + // Hide the element if the "hide" operation was done + if ( this.options.hide ) { + jQuery(this.elem).hide(); + } + + // Reset the properties, if the item has been hidden or shown + if ( this.options.hide || this.options.show ) { + for ( var p in this.options.curAnim ) { + jQuery.style( this.elem, p, this.options.orig[p] ); + } + } + + // Execute the complete function + this.options.complete.call( this.elem ); + } + + return false; + + } else { + var n = t - this.startTime; + this.state = n / this.options.duration; + + // Perform the easing function, defaults to swing + var specialEasing = this.options.specialEasing && this.options.specialEasing[this.prop]; + var defaultEasing = this.options.easing || (jQuery.easing.swing ? "swing" : "linear"); + this.pos = jQuery.easing[specialEasing || defaultEasing](this.state, n, 0, 1, this.options.duration); + this.now = this.start + ((this.end - this.start) * this.pos); + + // Perform the next step of the animation + this.update(); + } + + return true; + } +}; + +jQuery.extend( jQuery.fx, { + tick: function() { + var timers = jQuery.timers; + + for ( var i = 0; i < timers.length; i++ ) { + if ( !timers[i]() ) { + timers.splice(i--, 1); + } + } + + if ( !timers.length ) { + jQuery.fx.stop(); + } + }, + + interval: 13, + + stop: function() { + clearInterval( timerId ); + timerId = null; + }, + + speeds: { + slow: 600, + fast: 200, + // Default speed + _default: 400 + }, + + step: { + opacity: function( fx ) { + jQuery.style( fx.elem, "opacity", fx.now ); + }, + + _default: function( fx ) { + if ( fx.elem.style && fx.elem.style[ fx.prop ] != null ) { + fx.elem.style[ fx.prop ] = (fx.prop === "width" || fx.prop === "height" ? Math.max(0, fx.now) : fx.now) + fx.unit; + } else { + fx.elem[ fx.prop ] = fx.now; + } + } + } +}); + +if ( jQuery.expr && jQuery.expr.filters ) { + jQuery.expr.filters.animated = function( elem ) { + return jQuery.grep(jQuery.timers, function( fn ) { + return elem === fn.elem; + }).length; + }; +} + +function defaultDisplay( nodeName ) { + if ( !elemdisplay[ nodeName ] ) { + var elem = jQuery("<" + nodeName + ">").appendTo("body"), + display = elem.css("display"); + + elem.remove(); + + if ( display === "none" || display === "" ) { + display = "block"; + } + + elemdisplay[ nodeName ] = display; + } + + return elemdisplay[ nodeName ]; +} + + + + +var rtable = /^t(?:able|d|h)$/i, + rroot = /^(?:body|html)$/i; + +if ( "getBoundingClientRect" in document.documentElement ) { + jQuery.fn.offset = function( options ) { + var elem = this[0], box; + + if ( options ) { + return this.each(function( i ) { + jQuery.offset.setOffset( this, options, i ); + }); + } + + if ( !elem || !elem.ownerDocument ) { + return null; + } + + if ( elem === elem.ownerDocument.body ) { + return jQuery.offset.bodyOffset( elem ); + } + + try { + box = elem.getBoundingClientRect(); + } catch(e) {} + + var doc = elem.ownerDocument, + docElem = doc.documentElement; + + // Make sure we're not dealing with a disconnected DOM node + if ( !box || !jQuery.contains( docElem, elem ) ) { + return box || { top: 0, left: 0 }; + } + + var body = doc.body, + win = getWindow(doc), + clientTop = docElem.clientTop || body.clientTop || 0, + clientLeft = docElem.clientLeft || body.clientLeft || 0, + scrollTop = (win.pageYOffset || jQuery.support.boxModel && docElem.scrollTop || body.scrollTop ), + scrollLeft = (win.pageXOffset || jQuery.support.boxModel && docElem.scrollLeft || body.scrollLeft), + top = box.top + scrollTop - clientTop, + left = box.left + scrollLeft - clientLeft; + + return { top: top, left: left }; + }; + +} else { + jQuery.fn.offset = function( options ) { + var elem = this[0]; + + if ( options ) { + return this.each(function( i ) { + jQuery.offset.setOffset( this, options, i ); + }); + } + + if ( !elem || !elem.ownerDocument ) { + return null; + } + + if ( elem === elem.ownerDocument.body ) { + return jQuery.offset.bodyOffset( elem ); + } + + jQuery.offset.initialize(); + + var computedStyle, + offsetParent = elem.offsetParent, + prevOffsetParent = elem, + doc = elem.ownerDocument, + docElem = doc.documentElement, + body = doc.body, + defaultView = doc.defaultView, + prevComputedStyle = defaultView ? defaultView.getComputedStyle( elem, null ) : elem.currentStyle, + top = elem.offsetTop, + left = elem.offsetLeft; + + while ( (elem = elem.parentNode) && elem !== body && elem !== docElem ) { + if ( jQuery.offset.supportsFixedPosition && prevComputedStyle.position === "fixed" ) { + break; + } + + computedStyle = defaultView ? defaultView.getComputedStyle(elem, null) : elem.currentStyle; + top -= elem.scrollTop; + left -= elem.scrollLeft; + + if ( elem === offsetParent ) { + top += elem.offsetTop; + left += elem.offsetLeft; + + if ( jQuery.offset.doesNotAddBorder && !(jQuery.offset.doesAddBorderForTableAndCells && rtable.test(elem.nodeName)) ) { + top += parseFloat( computedStyle.borderTopWidth ) || 0; + left += parseFloat( computedStyle.borderLeftWidth ) || 0; + } + + prevOffsetParent = offsetParent; + offsetParent = elem.offsetParent; + } + + if ( jQuery.offset.subtractsBorderForOverflowNotVisible && computedStyle.overflow !== "visible" ) { + top += parseFloat( computedStyle.borderTopWidth ) || 0; + left += parseFloat( computedStyle.borderLeftWidth ) || 0; + } + + prevComputedStyle = computedStyle; + } + + if ( prevComputedStyle.position === "relative" || prevComputedStyle.position === "static" ) { + top += body.offsetTop; + left += body.offsetLeft; + } + + if ( jQuery.offset.supportsFixedPosition && prevComputedStyle.position === "fixed" ) { + top += Math.max( docElem.scrollTop, body.scrollTop ); + left += Math.max( docElem.scrollLeft, body.scrollLeft ); + } + + return { top: top, left: left }; + }; +} + +jQuery.offset = { + initialize: function() { + var body = document.body, container = document.createElement("div"), innerDiv, checkDiv, table, td, bodyMarginTop = parseFloat( jQuery.css(body, "marginTop") ) || 0, + html = "<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>"; + + jQuery.extend( container.style, { position: "absolute", top: 0, left: 0, margin: 0, border: 0, width: "1px", height: "1px", visibility: "hidden" } ); + + container.innerHTML = html; + body.insertBefore( container, body.firstChild ); + innerDiv = container.firstChild; + checkDiv = innerDiv.firstChild; + td = innerDiv.nextSibling.firstChild.firstChild; + + this.doesNotAddBorder = (checkDiv.offsetTop !== 5); + this.doesAddBorderForTableAndCells = (td.offsetTop === 5); + + checkDiv.style.position = "fixed"; + checkDiv.style.top = "20px"; + + // safari subtracts parent border width here which is 5px + this.supportsFixedPosition = (checkDiv.offsetTop === 20 || checkDiv.offsetTop === 15); + checkDiv.style.position = checkDiv.style.top = ""; + + innerDiv.style.overflow = "hidden"; + innerDiv.style.position = "relative"; + + this.subtractsBorderForOverflowNotVisible = (checkDiv.offsetTop === -5); + + this.doesNotIncludeMarginInBodyOffset = (body.offsetTop !== bodyMarginTop); + + body.removeChild( container ); + body = container = innerDiv = checkDiv = table = td = null; + jQuery.offset.initialize = jQuery.noop; + }, + + bodyOffset: function( body ) { + var top = body.offsetTop, + left = body.offsetLeft; + + jQuery.offset.initialize(); + + if ( jQuery.offset.doesNotIncludeMarginInBodyOffset ) { + top += parseFloat( jQuery.css(body, "marginTop") ) || 0; + left += parseFloat( jQuery.css(body, "marginLeft") ) || 0; + } + + return { top: top, left: left }; + }, + + setOffset: function( elem, options, i ) { + var position = jQuery.css( elem, "position" ); + + // set position first, in-case top/left are set even on static elem + if ( position === "static" ) { + elem.style.position = "relative"; + } + + var curElem = jQuery( elem ), + curOffset = curElem.offset(), + curCSSTop = jQuery.css( elem, "top" ), + curCSSLeft = jQuery.css( elem, "left" ), + calculatePosition = (position === "absolute" && jQuery.inArray('auto', [curCSSTop, curCSSLeft]) > -1), + props = {}, curPosition = {}, curTop, curLeft; + + // need to be able to calculate position if either top or left is auto and position is absolute + if ( calculatePosition ) { + curPosition = curElem.position(); + } + + curTop = calculatePosition ? curPosition.top : parseInt( curCSSTop, 10 ) || 0; + curLeft = calculatePosition ? curPosition.left : parseInt( curCSSLeft, 10 ) || 0; + + if ( jQuery.isFunction( options ) ) { + options = options.call( elem, i, curOffset ); + } + + if (options.top != null) { + props.top = (options.top - curOffset.top) + curTop; + } + if (options.left != null) { + props.left = (options.left - curOffset.left) + curLeft; + } + + if ( "using" in options ) { + options.using.call( elem, props ); + } else { + curElem.css( props ); + } + } +}; + + +jQuery.fn.extend({ + position: function() { + if ( !this[0] ) { + return null; + } + + var elem = this[0], + + // Get *real* offsetParent + offsetParent = this.offsetParent(), + + // Get correct offsets + offset = this.offset(), + parentOffset = rroot.test(offsetParent[0].nodeName) ? { top: 0, left: 0 } : offsetParent.offset(); + + // Subtract element margins + // note: when an element has margin: auto the offsetLeft and marginLeft + // are the same in Safari causing offset.left to incorrectly be 0 + offset.top -= parseFloat( jQuery.css(elem, "marginTop") ) || 0; + offset.left -= parseFloat( jQuery.css(elem, "marginLeft") ) || 0; + + // Add offsetParent borders + parentOffset.top += parseFloat( jQuery.css(offsetParent[0], "borderTopWidth") ) || 0; + parentOffset.left += parseFloat( jQuery.css(offsetParent[0], "borderLeftWidth") ) || 0; + + // Subtract the two offsets + return { + top: offset.top - parentOffset.top, + left: offset.left - parentOffset.left + }; + }, + + offsetParent: function() { + return this.map(function() { + var offsetParent = this.offsetParent || document.body; + while ( offsetParent && (!rroot.test(offsetParent.nodeName) && jQuery.css(offsetParent, "position") === "static") ) { + offsetParent = offsetParent.offsetParent; + } + return offsetParent; + }); + } +}); + + +// Create scrollLeft and scrollTop methods +jQuery.each( ["Left", "Top"], function( i, name ) { + var method = "scroll" + name; + + jQuery.fn[ method ] = function(val) { + var elem = this[0], win; + + if ( !elem ) { + return null; + } + + if ( val !== undefined ) { + // Set the scroll offset + return this.each(function() { + win = getWindow( this ); + + if ( win ) { + win.scrollTo( + !i ? val : jQuery(win).scrollLeft(), + i ? val : jQuery(win).scrollTop() + ); + + } else { + this[ method ] = val; + } + }); + } else { + win = getWindow( elem ); + + // Return the scroll offset + return win ? ("pageXOffset" in win) ? win[ i ? "pageYOffset" : "pageXOffset" ] : + jQuery.support.boxModel && win.document.documentElement[ method ] || + win.document.body[ method ] : + elem[ method ]; + } + }; +}); + +function getWindow( elem ) { + return jQuery.isWindow( elem ) ? + elem : + elem.nodeType === 9 ? + elem.defaultView || elem.parentWindow : + false; +} + + + + +// Create innerHeight, innerWidth, outerHeight and outerWidth methods +jQuery.each([ "Height", "Width" ], function( i, name ) { + + var type = name.toLowerCase(); + + // innerHeight and innerWidth + jQuery.fn["inner" + name] = function() { + return this[0] ? + parseFloat( jQuery.css( this[0], type, "padding" ) ) : + null; + }; + + // outerHeight and outerWidth + jQuery.fn["outer" + name] = function( margin ) { + return this[0] ? + parseFloat( jQuery.css( this[0], type, margin ? "margin" : "border" ) ) : + null; + }; + + jQuery.fn[ type ] = function( size ) { + // Get window width or height + var elem = this[0]; + if ( !elem ) { + return size == null ? null : this; + } + + if ( jQuery.isFunction( size ) ) { + return this.each(function( i ) { + var self = jQuery( this ); + self[ type ]( size.call( this, i, self[ type ]() ) ); + }); + } + + if ( jQuery.isWindow( elem ) ) { + // Everyone else use document.documentElement or document.body depending on Quirks vs Standards mode + return elem.document.compatMode === "CSS1Compat" && elem.document.documentElement[ "client" + name ] || + elem.document.body[ "client" + name ]; + + // Get document width or height + } else if ( elem.nodeType === 9 ) { + // Either scroll[Width/Height] or offset[Width/Height], whichever is greater + return Math.max( + elem.documentElement["client" + name], + elem.body["scroll" + name], elem.documentElement["scroll" + name], + elem.body["offset" + name], elem.documentElement["offset" + name] + ); + + // Get or set width or height on the element + } else if ( size === undefined ) { + var orig = jQuery.css( elem, type ), + ret = parseFloat( orig ); + + return jQuery.isNaN( ret ) ? orig : ret; + + // Set the width or height on the element (default to pixels if value is unitless) + } else { + return this.css( type, typeof size === "string" ? size : size + "px" ); + } + }; + +}); + + +})(window); + +/** + * This file has the methods used to override Selenium's default behavior + */ + +Selenium.makeArray = function(arr, win){ + if(!win){ + win = window; + } + var narr = win.Array(); + for (var i = 0; i < arr.length; i++) { + narr.push(arr[i]) + } + return narr; +} +jQuery.wrapped = function(){ + var args = Selenium.makeArray(arguments), + selector = args.shift(), + context = args.shift(), + method = args.shift(), + q, a, res; + + + + for(var i=0; i < arguments.length; i++){ + if (typeof arguments[i] == 'function' && arguments[i] == Selenium.resume) { + Selenium.pause(); + } + } + if (_win().jQuery && method == 'trigger') { + q = _win().jQuery(selector, context) + args = Selenium.makeArray(args, _win()) + } else { + q = jQuery(selector, context); + } + + res = q[method].apply(q, args); + //need to convert to json + return jQuery.toJSON(res.jquery ? true : res) +}; +_win = function(){ + var sel = selenium.browserbot + return sel.getCurrentWindow() +}; +_doc = function(){ + var sel = selenium.browserbot + return sel.getCurrentWindow().document +}; +Selenium.pause = function(){ + Selenium.paused = true; +}; + +Selenium.resume = function(){ + Selenium.paused = false; + currentTest.continueTest(); +}; +(function(){ +var RRTest = RemoteRunner.prototype.continueTest; +RemoteRunner.prototype.continueTest = function(){ + if(Selenium.paused){ + return; + } + RRTest.call(this); +}; + +IEBrowserBot.prototype._windowClosed = function(win) { + try { + var c = win.closed; + // frame windows claim to be non-closed when their parents are closed + // but you can't access their document objects in that case + if (!c) { + try { + win.document; + } catch (de) { + if (de.message == "Permission denied") { + // the window is probably unloading, which means it's probably not closed yet + return false; + } + else if (/^Access is denied/.test(de.message)) { + // rare variation on "Permission denied"? + LOG.debug("IEBrowserBot.windowClosed: got " + de.message + " (this.pageUnloading=" + this.pageUnloading + "); assuming window is unloading, probably not closed yet"); + return false; + } else { + if(this.pageUnloading) + { + LOG.info("IEBrowserBot.windowClosed2: couldn't read win.document, assume closed: " + de.message + " (this.pageUnloading=" + this.pageUnloading + ")"); + return false; + } + // this is probably one of those frame window situations + LOG.debug("IEBrowserBot.windowClosed: couldn't read win.document, assume closed: " + de.message + " (this.pageUnloading=" + this.pageUnloading + ")"); + return true; + } + } + } + if (c == null) { + LOG.debug("IEBrowserBot.windowClosed: win.closed was null, assuming closed"); + return true; + } + return c; + } catch (e) { + LOG.debug("IEBrowserBot._windowClosed: Got an exception trying to read win.closed; we'll have to take a guess!"); + + if (browserVersion.isHTA) { + if (e.message == "Permission denied") { + // the window is probably unloading, which means it's not closed yet + return false; + } else { + // there's a good chance that we've lost contact with the window object if it is closed + return true; + } + } else { + // the window is probably unloading, which means it's not closed yet + return false; + } + } +}; + +})() +/* + * jQuery JSON Plugin + * version: 2.1 (2009-08-14) + * + * This document is licensed as free software under the terms of the + * MIT License: http://www.opensource.org/licenses/mit-license.php + * + * Brantley Harris wrote this plugin. It is based somewhat on the JSON.org + * website's http://www.json.org/json2.js, which proclaims: + * "NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.", a sentiment that + * I uphold. + * + * It is also influenced heavily by MochiKit's serializeJSON, which is + * copyrighted 2005 by Bob Ippolito. + */ + steal('jquery').then(function(){ +(function($) { + /** jQuery.toJSON( json-serializble ) + Converts the given argument into a JSON respresentation. + + If an object has a "toJSON" function, that will be used to get the representation. + Non-integer/string keys are skipped in the object, as are keys that point to a function. + + json-serializble: + The *thing* to be converted. + **/ + $.toJSON = function(o, replacer, space, recurse) + { + if (typeof(JSON) == 'object' && JSON.stringify) + return JSON.stringify(o, replacer, space); + + if (!recurse && $.isFunction(replacer)) + o = replacer("", o); + + if (typeof space == "number") + space = " ".substring(0, space); + space = (typeof space == "string") ? space.substring(0, 10) : ""; + + var type = typeof(o); + + if (o === null) + return "null"; + + if (type == "undefined" || type == "function") + return undefined; + + if (type == "number" || type == "boolean") + return o + ""; + + if (type == "string") + return $.quoteString(o); + + if (type == 'object') + { + if (typeof o.toJSON == "function") + return $.toJSON( o.toJSON(), replacer, space, true ); + + if (o.constructor === Date) + { + var month = o.getUTCMonth() + 1; + if (month < 10) month = '0' + month; + + var day = o.getUTCDate(); + if (day < 10) day = '0' + day; + + var year = o.getUTCFullYear(); + + var hours = o.getUTCHours(); + if (hours < 10) hours = '0' + hours; + + var minutes = o.getUTCMinutes(); + if (minutes < 10) minutes = '0' + minutes; + + var seconds = o.getUTCSeconds(); + if (seconds < 10) seconds = '0' + seconds; + + var milli = o.getUTCMilliseconds(); + if (milli < 100) milli = '0' + milli; + if (milli < 10) milli = '0' + milli; + + return '"' + year + '-' + month + '-' + day + 'T' + + hours + ':' + minutes + ':' + seconds + + '.' + milli + 'Z"'; + } + + var process = ($.isFunction(replacer)) ? + function (k, v) { return replacer(k, v); } : + function (k, v) { return v; }, + nl = (space) ? "\n" : "", + sp = (space) ? " " : ""; + + if (o.constructor === Array) + { + var ret = []; + for (var i = 0; i < o.length; i++) + ret.push(( $.toJSON( process(i, o[i]), replacer, space, true ) || "null" ).replace(/^/gm, space)); + + return "[" + nl + ret.join("," + nl) + nl + "]"; + } + + var pairs = [], proplist; + if ($.isArray(replacer)) { + proplist = $.map(replacer, function (v) { + return (typeof v == "string" || typeof v == "number") ? + v + "" : + null; + }); + } + for (var k in o) { + var name, val, type = typeof k; + + if (proplist && $.inArray(k + "", proplist) == -1) + continue; + + if (type == "number") + name = '"' + k + '"'; + else if (type == "string") + name = $.quoteString(k); + else + continue; //skip non-string or number keys + + val = $.toJSON( process(k, o[k]), replacer, space, true ); + + if (typeof val == "undefined") + continue; //skip pairs where the value is a function. + + pairs.push((name + ":" + sp + val).replace(/^/gm, space)); + } + + return "{" + nl + pairs.join("," + nl) + nl + "}"; + } + }; + + /** jQuery.evalJSON(src) + Evaluates a given piece of json source. + **/ + $.evalJSON = function(src) + { + if (typeof(JSON) == 'object' && JSON.parse) + return JSON.parse(src); + return eval("(" + src + ")"); + }; + + /** jQuery.secureEvalJSON(src) + Evals JSON in a way that is *more* secure. + **/ + $.secureEvalJSON = function(src) + { + if (typeof(JSON) == 'object' && JSON.parse) + return JSON.parse(src); + + var filtered = src; + filtered = filtered.replace(/\\["\\\/bfnrtu]/g, '@'); + filtered = filtered.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']'); + filtered = filtered.replace(/(?:^|:|,)(?:\s*\[)+/g, ''); + + if (/^[\],:{}\s]*$/.test(filtered)) + return eval("(" + src + ")"); + else + throw new SyntaxError("Error parsing JSON, source is not valid."); + }; + + /** jQuery.quoteString(string) + Returns a string-repr of a string, escaping quotes intelligently. + Mostly a support function for toJSON. + + Examples: + >>> jQuery.quoteString("apple") + "apple" + + >>> jQuery.quoteString('"Where are we going?", she asked.') + "\"Where are we going?\", she asked." + **/ + $.quoteString = function(string) + { + if (string.match(_escapeable)) + { + return '"' + string.replace(_escapeable, function (a) + { + var c = _meta[a]; + if (typeof c === 'string') return c; + c = a.charCodeAt(); + return '\\u00' + Math.floor(c / 16).toString(16) + (c % 16).toString(16); + }) + '"'; + } + return '"' + string + '"'; + }; + + var _escapeable = /["\\\x00-\x1f\x7f-\x9f]/g; + + var _meta = { + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '"' : '\\"', + '\\': '\\\\' + }; +})(jQuery); +}) +(function(){ +(function() { + var extend = function( d, s ) { + var p; + for (p in s) { + d[p] = s[p]; + } + return d; + }, + // only uses browser detection for key events + browser = { + msie: !! (window.attachEvent && !window.opera), + opera: !! window.opera, + webkit: navigator.userAgent.indexOf('AppleWebKit/') > -1, + safari: navigator.userAgent.indexOf('AppleWebKit/') > -1 && navigator.userAgent.indexOf('Chrome/') === -1, + gecko: navigator.userAgent.indexOf('Gecko') > -1, + mobilesafari: !! navigator.userAgent.match(/Apple.*Mobile.*Safari/), + rhino: navigator.userAgent.match(/Rhino/) && true + }, + createEventObject = function( type, options, element ) { + var event = element.ownerDocument.createEventObject(); + return extend(event, options); + }, + data = {}, + id = 1, + expando = "_synthetic" + new Date().getTime(), + bind, unbind, key = /keypress|keyup|keydown/, + page = /load|unload|abort|error|select|change|submit|reset|focus|blur|resize|scroll/, + //this is maintained so we can click on html and blur the active element + activeElement, + + /** + * @class Syn + * @download funcunit/dist/syn.js + * @test funcunit/synthetic/qunit.html + * Syn is used to simulate user actions. It creates synthetic events and + * performs their default behaviors. + * + * <h2>Basic Use</h2> + * The following clicks an input element with <code>id='description'</code> + * and then types <code>'Hello World'</code>. + * + @codestart + Syn.click({},'description') + .type("Hello World") + @codeend + * <h2>User Actions and Events</h2> + * <p>Syn is typically used to simulate user actions as opposed to triggering events. Typing characters + * is an example of a user action. The keypress that represents an <code>'a'</code> + * character being typed is an example of an event. + * </p> + * <p> + * While triggering events is supported, it's much more useful to simulate actual user behavior. The + * following actions are supported by Syn: + * </p> + * <ul> + * <li><code>[Syn.prototype.click click]</code> - a mousedown, focus, mouseup, and click.</li> + * <li><code>[Syn.prototype.dblclick dblclick]</code> - two <code>click!</code> events followed by a <code>dblclick</code>.</li> + * <li><code>[Syn.prototype.key key]</code> - types a single character (keydown, keypress, keyup).</li> + * <li><code>[Syn.prototype.type type]</code> - types multiple characters into an element.</li> + * <li><code>[Syn.prototype.move move]</code> - moves the mouse from one position to another (triggering mouseover / mouseouts).</li> + * <li><code>[Syn.prototype.drag drag]</code> - a mousedown, followed by mousemoves, and a mouseup.</li> + * </ul> + * All actions run asynchronously. + * Click on the links above for more + * information on how to use the specific action. + * <h2>Asynchronous Callbacks</h2> + * Actions don't complete immediately. This is almost + * entirely because <code>focus()</code> + * doesn't run immediately in IE. + * If you provide a callback function to Syn, it will + * be called after the action is completed. + * <br/>The following checks that "Hello World" was entered correctly: + @codestart + Syn.click({},'description') + .type("Hello World", function(){ + + ok("Hello World" == document.getElementById('description').value) + }) + @codeend + <h2>Asynchronous Chaining</h2> + <p>You might have noticed the [Syn.prototype.then then] method. It provides chaining + so you can do a sequence of events with a single (final) callback. + </p><p> + If an element isn't provided to then, it uses the previous Syn's element. + </p> + The following does a lot of stuff before checking the result: + @codestart + Syn.type('ice water','title') + .type('ice and water','description') + .click({},'create') + .drag({to: 'favorites'},'newRecipe', + function(){ + ok($('#newRecipe').parents('#favorites').length); + }) + @codeend + + <h2>jQuery Helper</h2> + If jQuery is present, Syn adds a triggerSyn helper you can use like: + @codestart + $("#description").triggerSyn("type","Hello World"); + @codeend + * <h2>Key Event Recording</h2> + * <p>Every browser has very different rules for dispatching key events. + * As there is no way to feature detect how a browser handles key events, + * synthetic uses a description of how the browser behaves generated + * by a recording application. </p> + * <p> + * If you want to support a browser not currently supported, you can + * record that browser's key event description and add it to + * <code>Syn.key.browsers</code> by it's navigator agent. + * </p> + @codestart + Syn.key.browsers["Envjs\ Resig/20070309 PilotFish/1.2.0.10\1.6"] = { + 'prevent': + {"keyup":[],"keydown":["char","keypress"],"keypress":["char"]}, + 'character': + { ... } + } + @codeend + * <h2>Limitations</h2> + * Syn fully supports IE 6+, FF 3+, Chrome, Safari, Opera 10+. + * With FF 1+, drag / move events are only partially supported. They will + * not trigger mouseover / mouseout events.<br/> + * Safari crashes when a mousedown is triggered on a select. Syn will not + * create this event. + * <h2>Contributing to Syn</h2> + * Have we missed something? We happily accept patches. The following are + * important objects and properties of Syn: + * <ul> + * <li><code>Syn.create</code> - contains methods to setup, convert options, and create an event of a specific type.</li> + * <li><code>Syn.defaults</code> - default behavior by event type (except for keys).</li> + * <li><code>Syn.key.defaults</code> - default behavior by key.</li> + * <li><code>Syn.keycodes</code> - supported keys you can type.</li> + * </ul> + * <h2>Roll Your Own Functional Test Framework</h2> + * <p>Syn is really the foundation of JavaScriptMVC's functional testing framework - [FuncUnit]. + * But, we've purposely made Syn work without any dependencies in the hopes that other frameworks or + * testing solutions can use it as well. + * </p> + * @constructor + * Creates a synthetic event on the element. + * @param {Object} type + * @param {Object} options + * @param {Object} element + * @param {Object} callback + * @return Syn + */ + Syn = function( type, options, element, callback ) { + return (new Syn.init(type, options, element, callback)); + }; + + bind = function( el, ev, f ) { + return el.addEventListener ? el.addEventListener(ev, f, false) : el.attachEvent("on" + ev, f); + }; + unbind = function( el, ev, f ) { + return el.addEventListener ? el.removeEventListener(ev, f, false) : el.detachEvent("on" + ev, f); + }; + + /** + * @Static + */ + extend(Syn, { + /** + * Creates a new synthetic event instance + * @hide + * @param {Object} type + * @param {Object} options + * @param {Object} element + * @param {Object} callback + */ + init: function( type, options, element, callback ) { + var args = Syn.args(options, element, callback), + self = this; + this.queue = []; + this.element = args.element; + + //run event + if ( typeof this[type] === "function" ) { + this[type](args.options, args.element, function( defaults, el ) { + args.callback && args.callback.apply(self, arguments); + self.done.apply(self, arguments); + }); + } else { + this.result = Syn.trigger(type, args.options, args.element); + args.callback && args.callback.call(this, args.element, this.result); + } + }, + jquery: function( el, fast ) { + if ( window.FuncUnit && window.FuncUnit.jquery ) { + return window.FuncUnit.jquery; + } + if ( el ) { + return Syn.helpers.getWindow(el).jQuery || window.jQuery; + } + else { + return window.jQuery; + } + }, + /** + * Returns an object with the args for a Syn. + * @hide + * @return {Object} + */ + args: function() { + var res = {}, + i = 0; + for ( ; i < arguments.length; i++ ) { + if ( typeof arguments[i] === 'function' ) { + res.callback = arguments[i]; + } else if ( arguments[i] && arguments[i].jquery ) { + res.element = arguments[i][0]; + } else if ( arguments[i] && arguments[i].nodeName ) { + res.element = arguments[i]; + } else if ( res.options && typeof arguments[i] === 'string' ) { //we can get by id + res.element = document.getElementById(arguments[i]); + } + else if ( arguments[i] ) { + res.options = arguments[i]; + } + } + return res; + }, + click: function( options, element, callback ) { + Syn('click!', options, element, callback); + }, + /** + * @attribute defaults + * Default actions for events. Each default function is called with this as its + * element. It should return true if a timeout + * should happen after it. If it returns an element, a timeout will happen + * and the next event will happen on that element. + */ + defaults: { + focus: function() { + if (!Syn.support.focusChanges ) { + var element = this, + nodeName = element.nodeName.toLowerCase(); + Syn.data(element, "syntheticvalue", element.value); + + //TODO, this should be textarea too + //and this might be for only text style inputs ... hmmmmm .... + if ( nodeName === "input" || nodeName === "textarea" ) { + bind(element, "blur", function() { + if ( Syn.data(element, "syntheticvalue") != element.value ) { + + Syn.trigger("change", {}, element); + } + unbind(element, "blur", arguments.callee); + }); + + } + } + }, + submit: function() { + Syn.onParents(this, function( el ) { + if ( el.nodeName.toLowerCase() === 'form' ) { + el.submit(); + return false; + } + }); + } + }, + changeOnBlur: function( element, prop, value ) { + + bind(element, "blur", function() { + if ( value !== element[prop] ) { + Syn.trigger("change", {}, element); + } + unbind(element, "blur", arguments.callee); + }); + + }, + /** + * Returns the closest element of a particular type. + * @hide + * @param {Object} el + * @param {Object} type + */ + closest: function( el, type ) { + while ( el && el.nodeName.toLowerCase() !== type.toLowerCase() ) { + el = el.parentNode; + } + return el; + }, + /** + * adds jQuery like data (adds an expando) and data exists FOREVER :) + * @hide + * @param {Object} el + * @param {Object} key + * @param {Object} value + */ + data: function( el, key, value ) { + var d; + if (!el[expando] ) { + el[expando] = id++; + } + if (!data[el[expando]] ) { + data[el[expando]] = {}; + } + d = data[el[expando]]; + if ( value ) { + data[el[expando]][key] = value; + } else { + return data[el[expando]][key]; + } + }, + /** + * Calls a function on the element and all parents of the element until the function returns + * false. + * @hide + * @param {Object} el + * @param {Object} func + */ + onParents: function( el, func ) { + var res; + while ( el && res !== false ) { + res = func(el); + el = el.parentNode; + } + return el; + }, + //regex to match focusable elements + focusable: /^(a|area|frame|iframe|label|input|select|textarea|button|html|object)$/i, + /** + * Returns if an element is focusable + * @hide + * @param {Object} elem + */ + isFocusable: function( elem ) { + var attributeNode; + return (this.focusable.test(elem.nodeName) || + ((attributeNode = elem.getAttributeNode("tabIndex")) + && attributeNode.specified)) && Syn.isVisible(elem); + }, + /** + * Returns if an element is visible or not + * @hide + * @param {Object} elem + */ + isVisible: function( elem ) { + return (elem.offsetWidth && elem.offsetHeight) || (elem.clientWidth && elem.clientHeight); + }, + /** + * Gets the tabIndex as a number or null + * @hide + * @param {Object} elem + */ + tabIndex: function( elem ) { + var attributeNode = elem.getAttributeNode("tabIndex"); + return attributeNode && attributeNode.specified && (parseInt(elem.getAttribute('tabIndex')) || 0); + }, + bind: bind, + unbind: unbind, + browser: browser, + //some generic helpers + helpers: { + createEventObject: createEventObject, + createBasicStandardEvent: function( type, defaults, doc ) { + var event; + try { + event = doc.createEvent("Events"); + } catch (e2) { + event = doc.createEvent("UIEvents"); + } finally { + event.initEvent(type, true, true); + extend(event, defaults); + } + return event; + }, + inArray: function( item, array ) { + var i =0; + for ( ; i < array.length; i++ ) { + if ( array[i] === item ) { + return i; + } + } + return -1; + }, + getWindow: function( element ) { + return element.ownerDocument.defaultView || element.ownerDocument.parentWindow; + }, + extend: extend, + scrollOffset: function( win , set) { + var doc = win.document.documentElement, + body = win.document.body; + if(set){ + window.scrollTo(set.left, set.top); + + } else { + return { + left: (doc && doc.scrollLeft || body && body.scrollLeft || 0) + (doc.clientLeft || 0), + top: (doc && doc.scrollTop || body && body.scrollTop || 0) + (doc.clientTop || 0) + }; + } + + }, + scrollDimensions: function(win){ + var doc = win.document.documentElement, + body = win.document.body, + docWidth = doc.clientWidth, + docHeight = doc.clientHeight, + compat = win.document.compatMode === "CSS1Compat"; + + return { + height: compat && docHeight || + body.clientHeight || docHeight, + width: compat && docWidth || + body.clientWidth || docWidth + }; + }, + addOffset: function( options, el ) { + var jq = Syn.jquery(el), + off; + if ( typeof options === 'object' && options.clientX === undefined && options.clientY === undefined && options.pageX === undefined && options.pageY === undefined && jq ) { + el = jq(el); + off = el.offset(); + options.pageX = off.left + el.width() / 2; + options.pageY = off.top + el.height() / 2; + } + } + }, + // place for key data + key: { + ctrlKey: null, + altKey: null, + shiftKey: null, + metaKey: null + }, + //triggers an event on an element, returns true if default events should be run + /** + * Dispatches an event and returns true if default events should be run. + * @hide + * @param {Object} event + * @param {Object} element + * @param {Object} type + * @param {Object} autoPrevent + */ + dispatch: function( event, element, type, autoPrevent ) { + + // dispatchEvent doesn't always work in IE (mostly in a popup) + if ( element.dispatchEvent && event ) { + var preventDefault = event.preventDefault, + prevents = autoPrevent ? -1 : 0; + + //automatically prevents the default behavior for this event + //this is to protect agianst nasty browser freezing bug in safari + if ( autoPrevent ) { + bind(element, type, function( ev ) { + ev.preventDefault(); + unbind(this, type, arguments.callee); + }); + } + + + event.preventDefault = function() { + prevents++; + if (++prevents > 0 ) { + preventDefault.apply(this, []); + } + }; + element.dispatchEvent(event); + return prevents <= 0; + } else { + try { + window.event = event; + } catch (e) {} + //source element makes sure element is still in the document + return element.sourceIndex <= 0 || (element.fireEvent && element.fireEvent('on' + type, event)); + } + }, + /** + * @attribute + * @hide + * An object of eventType -> function that create that event. + */ + create: { + //-------- PAGE EVENTS --------------------- + page: { + event: function( type, options, element ) { + var doc = Syn.helpers.getWindow(element).document || document, + event; + if ( doc.createEvent ) { + event = doc.createEvent("Events"); + + event.initEvent(type, true, true); + return event; + } + else { + try { + event = createEventObject(type, options, element); + } + catch (e) {} + return event; + } + } + }, + // unique events + focus: { + event: function( type, options, element ) { + Syn.onParents(element, function( el ) { + if ( Syn.isFocusable(el) ) { + if ( el.nodeName.toLowerCase() !== 'html' ) { + el.focus(); + activeElement = el; + } + else if ( activeElement ) { + // TODO: The HTML element isn't focasable in IE, but it is + // in FF. We should detect this and do a true focus instead + // of just a blur + var doc = Syn.helpers.getWindow(element).document; + if ( doc !== window.document ) { + return false; + } else if ( doc.activeElement ) { + doc.activeElement.blur(); + activeElement = null; + } else { + activeElement.blur(); + activeElement = null; + } + + + } + return false; + } + }); + return true; + } + } + }, + /** + * @attribute support + * Feature detected properties of a browser's event system. + * Support has the following properties: + * <ul> + * <li><code>clickChanges</code> - clicking on an option element creates a change event.</li> + * <li><code>clickSubmits</code> - clicking on a form button submits the form.</li> + * <li><code>mouseupSubmits</code> - a mouseup on a form button submits the form.</li> + * <li><code>radioClickChanges</code> - clicking a radio button changes the radio.</li> + * <li><code>focusChanges</code> - focus/blur creates a change event.</li> + * <li><code>linkHrefJS</code> - An achor's href JavaScript is run.</li> + * <li><code>mouseDownUpClicks</code> - A mousedown followed by mouseup creates a click event.</li> + * <li><code>tabKeyTabs</code> - A tab key changes tabs.</li> + * <li><code>keypressOnAnchorClicks</code> - Keying enter on an anchor triggers a click.</li> + * </ul> + */ + support: { + clickChanges: false, + clickSubmits: false, + keypressSubmits: false, + mouseupSubmits: false, + radioClickChanges: false, + focusChanges: false, + linkHrefJS: false, + keyCharacters: false, + backspaceWorks: false, + mouseDownUpClicks: false, + tabKeyTabs: false, + keypressOnAnchorClicks: false, + optionClickBubbles: false, + ready: 0 + }, + /** + * Creates a synthetic event and dispatches it on the element. + * This will run any default actions for the element. + * Typically you want to use Syn, but if you want the return value, use this. + * @param {String} type + * @param {Object} options + * @param {HTMLElement} element + * @return {Boolean} true if default events were run, false if otherwise. + */ + trigger: function( type, options, element ) { + options || (options = {}); + + var create = Syn.create, + setup = create[type] && create[type].setup, + kind = key.test(type) ? 'key' : (page.test(type) ? "page" : "mouse"), + createType = create[type] || {}, + createKind = create[kind], + event, ret, autoPrevent, dispatchEl = element; + + //any setup code? + Syn.support.ready === 2 && setup && setup(type, options, element); + + autoPrevent = options._autoPrevent; + //get kind + delete options._autoPrevent; + + if ( createType.event ) { + ret = createType.event(type, options, element); + } else { + //convert options + options = createKind.options ? createKind.options(type, options, element) : options; + + if (!Syn.support.changeBubbles && /option/i.test(element.nodeName) ) { + dispatchEl = element.parentNode; //jQuery expects clicks on select + } + + //create the event + event = createKind.event(type, options, dispatchEl); + + //send the event + ret = Syn.dispatch(event, dispatchEl, type, autoPrevent); + } + + //run default behavior + ret && Syn.support.ready === 2 && Syn.defaults[type] && Syn.defaults[type].call(element, options, autoPrevent); + return ret; + }, + eventSupported: function( eventName ) { + var el = document.createElement("div"); + eventName = "on" + eventName; + + var isSupported = (eventName in el); + if (!isSupported ) { + el.setAttribute(eventName, "return;"); + isSupported = typeof el[eventName] === "function"; + } + el = null; + + return isSupported; + } + + }); + /** + * @Prototype + */ + extend(Syn.init.prototype, { + /** + * @function then + * <p> + * Then is used to chain a sequence of actions to be run one after the other. + * This is useful when many asynchronous actions need to be performed before some + * final check needs to be made. + * </p> + * <p>The following clicks and types into the <code>id='age'</code> element and then checks that only numeric characters can be entered.</p> + * <h3>Example</h3> + * @codestart + * Syn('click',{},'age') + * .then('type','I am 12',function(){ + * equals($('#age').val(),"12") + * }) + * @codeend + * If the element argument is undefined, then the last element is used. + * + * @param {String} type The type of event or action to create: "_click", "_dblclick", "_drag", "_type". + * @param {Object} options Optiosn to pass to the event. + * @param {String|HTMLElement} [element] A element's id or an element. If undefined, defaults to the previous element. + * @param {Function} [callback] A function to callback after the action has run, but before any future chained actions are run. + */ + then: function( type, options, element, callback ) { + if ( Syn.autoDelay ) { + this.delay(); + } + var args = Syn.args(options, element, callback), + self = this; + + + //if stack is empty run right away + //otherwise ... unshift it + this.queue.unshift(function( el, prevented ) { + + if ( typeof this[type] === "function" ) { + this.element = args.element || el; + this[type](args.options, this.element, function( defaults, el ) { + args.callback && args.callback.apply(self, arguments); + self.done.apply(self, arguments); + }); + } else { + this.result = Syn.trigger(type, args.options, args.element); + args.callback && args.callback.call(this, args.element, this.result); + return this; + } + }) + return this; + }, + /** + * Delays the next command a set timeout. + * @param {Number} [timeout] + * @param {Function} [callback] + */ + delay: function( timeout, callback ) { + if ( typeof timeout === 'function' ) { + callback = timeout; + timeout = null; + } + timeout = timeout || 600; + var self = this; + this.queue.unshift(function() { + setTimeout(function() { + callback && callback.apply(self, []) + self.done.apply(self, arguments); + }, timeout); + }); + return this; + }, + done: function( defaults, el ) { + el && (this.element = el); + if ( this.queue.length ) { + this.queue.pop().call(this, this.element, defaults); + } + + }, + /** + * @function click + * Clicks an element by triggering a mousedown, + * mouseup, + * and a click event. + * <h3>Example</h3> + * @codestart + * Syn.click({},'create',function(){ + * //check something + * }) + * @codeend + * You can also provide the coordinates of the click. + * If jQuery is present, it will set clientX and clientY + * for you. Here's how to set it yourself: + * @codestart + * Syn.click( + * {clientX: 20, clientY: 100}, + * 'create', + * function(){ + * //check something + * }) + * @codeend + * You can also provide pageX and pageY and Syn will convert it for you. + * @param {Object} options + * @param {HTMLElement} element + * @param {Function} callback + */ + "_click": function( options, element, callback, force ) { + Syn.helpers.addOffset(options, element); + Syn.trigger("mousedown", options, element); + + //timeout is b/c IE is stupid and won't call focus handlers + setTimeout(function() { + Syn.trigger("mouseup", options, element); + if (!Syn.support.mouseDownUpClicks || force ) { + Syn.trigger("click", options, element); + callback(true); + } else { + //we still have to run the default (presumably) + Syn.create.click.setup('click', options, element); + Syn.defaults.click.call(element); + //must give time for callback + setTimeout(function() { + callback(true); + }, 1); + } + + }, 1); + }, + /** + * Right clicks in browsers that support it (everyone but opera). + * @param {Object} options + * @param {Object} element + * @param {Object} callback + */ + "_rightClick": function( options, element, callback ) { + Syn.helpers.addOffset(options, element); + var mouseopts = extend(extend({}, Syn.mouse.browser.right.mouseup), options); + + Syn.trigger("mousedown", mouseopts, element); + + //timeout is b/c IE is stupid and won't call focus handlers + setTimeout(function() { + Syn.trigger("mouseup", mouseopts, element); + if ( Syn.mouse.browser.contextmenu ) { + Syn.trigger("contextmenu", extend(extend({}, Syn.mouse.browser.right.contextmenu), options), element); + } + callback(true); + }, 1); + }, + /** + * @function dblclick + * Dblclicks an element. This runs two [Syn.prototype.click click] events followed by + * a dblclick on the element. + * <h3>Example</h3> + * @codestart + * Syn.dblclick({},'open') + * @codeend + * @param {Object} options + * @param {HTMLElement} element + * @param {Function} callback + */ + "_dblclick": function( options, element, callback ) { + Syn.helpers.addOffset(options, element); + var self = this; + this._click(options, element, function() { + setTimeout(function() { + self._click(options, element, function() { + Syn.trigger("dblclick", options, element); + callback(true); + }, true); + }, 2); + + }); + } + }); + + var actions = ["click", "dblclick", "move", "drag", "key", "type", 'rightClick'], + makeAction = function( name ) { + Syn[name] = function( options, element, callback ) { + return Syn("_" + name, options, element, callback); + }; + Syn.init.prototype[name] = function( options, element, callback ) { + return this.then("_" + name, options, element, callback); + }; + }, + i = 0; + for ( ; i < actions.length; i++ ) { + makeAction(actions[i]); + } + /** + * Used for creating and dispatching synthetic events. + * @codestart + * new MVC.Syn('click').send(MVC.$E('id')) + * @codeend + * @constructor Sets up a synthetic event. + * @param {String} type type of event, ex: 'click' + * @param {optional:Object} options + */ + if ( window.jQuery || (window.FuncUnit && window.FuncUnit.jquery) ) { + ((window.FuncUnit && window.FuncUnit.jquery) || window.jQuery).fn.triggerSyn = function( type, options, callback ) { + Syn(type, options, this[0], callback); + return this; + }; + } + + window.Syn = Syn; +}()); +})(true); +(function(){ +//steal("synthetic").then(function() { +//handles mosue events +(function() { + + var h = Syn.helpers, + getWin = h.getWindow; + + Syn.mouse = {}; + h.extend(Syn.defaults, { + mousedown: function( options ) { + Syn.trigger("focus", {}, this) + }, + click: function() { + // prevents the access denied issue in IE if the click causes the element to be destroyed + var element = this; + try { + element.nodeType; + } catch (e) { + return; + } + //get old values + var href, radioChanged = Syn.data(element, "radioChanged"), + scope = getWin(element), + nodeName = element.nodeName.toLowerCase(); + + //this code was for restoring the href attribute to prevent popup opening + //if ((href = Syn.data(element, "href"))) { + // element.setAttribute('href', href) + //} + + //run href javascript + if (!Syn.support.linkHrefJS && /^\s*javascript:/.test(element.href) ) { + //eval js + var code = element.href.replace(/^\s*javascript:/, "") + + //try{ + if ( code != "//" && code.indexOf("void(0)") == -1 ) { + if ( window.selenium ) { + eval("with(selenium.browserbot.getCurrentWindow()){" + code + "}") + } else { + eval("with(scope){" + code + "}") + } + } + } + + //submit a form + if (!(Syn.support.clickSubmits) && (nodeName == "input" && element.type == "submit") || nodeName == 'button' ) { + + var form = Syn.closest(element, "form"); + if ( form ) { + Syn.trigger("submit", {}, form) + } + + } + //follow a link, probably needs to check if in an a. + if ( nodeName == "a" && element.href && !/^\s*javascript:/.test(element.href) ) { + + scope.location.href = element.href; + + } + + //change a checkbox + if ( nodeName == "input" && element.type == "checkbox" ) { + + //if(!Syn.support.clickChecks && !Syn.support.changeChecks){ + // element.checked = !element.checked; + //} + if (!Syn.support.clickChanges ) { + Syn.trigger("change", {}, element); + } + } + + //change a radio button + if ( nodeName == "input" && element.type == "radio" ) { // need to uncheck others if not checked + if ( radioChanged && !Syn.support.radioClickChanges ) { + Syn.trigger("change", {}, element); + } + } + // change options + if ( nodeName == "option" && Syn.data(element, "createChange") ) { + Syn.trigger("change", {}, element.parentNode); //does not bubble + Syn.data(element, "createChange", false) + } + } + }) + + //add create and setup behavior for mosue events + h.extend(Syn.create, { + mouse: { + options: function( type, options, element ) { + var doc = document.documentElement, + body = document.body, + center = [options.pageX || 0, options.pageY || 0], + //browser might not be loaded yet (doing support code) + left = Syn.mouse.browser && Syn.mouse.browser.left[type], + right = Syn.mouse.browser && Syn.mouse.browser.right[type]; + return h.extend({ + bubbles: true, + cancelable: true, + view: window, + detail: 1, + screenX: 1, + screenY: 1, + clientX: options.clientX || center[0] - (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc.clientLeft || 0), + clientY: options.clientY || center[1] - (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc.clientTop || 0), + ctrlKey: !! Syn.key.ctrlKey, + altKey: !! Syn.key.altKey, + shiftKey: !! Syn.key.shiftKey, + metaKey: !! Syn.key.metaKey, + button: left && left.button != null ? left.button : right && right.button || (type == 'contextmenu' ? 2 : 0), + relatedTarget: document.documentElement + }, options); + }, + event: function( type, defaults, element ) { //Everyone Else + var doc = getWin(element).document || document + if ( doc.createEvent ) { + var event; + + try { + event = doc.createEvent('MouseEvents'); + event.initMouseEvent(type, defaults.bubbles, defaults.cancelable, defaults.view, defaults.detail, defaults.screenX, defaults.screenY, defaults.clientX, defaults.clientY, defaults.ctrlKey, defaults.altKey, defaults.shiftKey, defaults.metaKey, defaults.button, defaults.relatedTarget); + } catch (e) { + event = h.createBasicStandardEvent(type, defaults, doc) + } + event.synthetic = true; + return event; + } else { + var event; + try { + event = h.createEventObject(type, defaults, element) + } + catch (e) {} + + return event; + } + + } + }, + click: { + setup: function( type, options, element ) { + var nodeName = element.nodeName.toLowerCase(), + type; + + //we need to manually 'check' in browser that can't check + //so checked has the right value + if (!Syn.support.clickChecks && !Syn.support.changeChecks && nodeName === "input" ) { + type = element.type.toLowerCase(); //pretty sure lowercase isn't needed + if ( type === 'checkbox' ) { + element.checked = !element.checked; + } + if ( type === "radio" ) { + //do the checks manually + if (!element.checked ) { //do nothing, no change + try { + Syn.data(element, "radioChanged", true); + } catch (e) {} + element.checked = true; + } + } + } + + if ( nodeName == "a" && element.href && !/^\s*javascript:/.test(element.href) ) { + + //save href + Syn.data(element, "href", element.href) + + //remove b/c safari/opera will open a new tab instead of changing the page + // this has been removed because newer versions don't have this problem + //element.setAttribute('href', 'javascript://') + //however this breaks scripts using the href + //we need to listen to this and prevent the default behavior + //and run the default behavior ourselves. Boo! + } + //if select or option, save old value and mark to change + if (/option/i.test(element.nodeName) ) { + var child = element.parentNode.firstChild, + i = -1; + while ( child ) { + if ( child.nodeType == 1 ) { + i++; + if ( child == element ) break; + } + child = child.nextSibling; + } + if ( i !== element.parentNode.selectedIndex ) { + //shouldn't this wait on triggering + //change? + element.parentNode.selectedIndex = i; + Syn.data(element, "createChange", true) + } + } + + } + }, + mousedown: { + setup: function( type, options, element ) { + var nn = element.nodeName.toLowerCase(); + //we have to auto prevent default to prevent freezing error in safari + if ( Syn.browser.safari && (nn == "select" || nn == "option") ) { + options._autoPrevent = true; + } + } + } + }); + //do support code + (function() { + if (!document.body ) { + setTimeout(arguments.callee, 1) + return; + } + var oldSynth = window.__synthTest; + window.__synthTest = function() { + Syn.support.linkHrefJS = true; + } + var div = document.createElement("div"), + checkbox, submit, form, input, select; + + div.innerHTML = "<form id='outer'>" + "<input name='checkbox' type='checkbox'/>" + "<input name='radio' type='radio' />" + "<input type='submit' name='submitter'/>" + "<input type='input' name='inputter'/>" + "<input name='one'>" + "<input name='two'/>" + "<a href='javascript:__synthTest()' id='synlink'></a>" + "<select><option></option></select>" + "</form>"; + document.documentElement.appendChild(div); + form = div.firstChild + checkbox = form.childNodes[0]; + submit = form.childNodes[2]; + select = form.getElementsByTagName('select')[0] + + checkbox.checked = false; + checkbox.onchange = function() { + Syn.support.clickChanges = true; + } + + Syn.trigger("click", {}, checkbox) + Syn.support.clickChecks = checkbox.checked; + + checkbox.checked = false; + + Syn.trigger("change", {}, checkbox); + + Syn.support.changeChecks = checkbox.checked; + + form.onsubmit = function( ev ) { + if ( ev.preventDefault ) ev.preventDefault(); + Syn.support.clickSubmits = true; + return false; + } + Syn.trigger("click", {}, submit) + + + + form.childNodes[1].onchange = function() { + Syn.support.radioClickChanges = true; + } + Syn.trigger("click", {}, form.childNodes[1]) + + + Syn.bind(div, 'click', function() { + Syn.support.optionClickBubbles = true; + Syn.unbind(div, 'click', arguments.callee) + }) + Syn.trigger("click", {}, select.firstChild) + + + Syn.support.changeBubbles = Syn.eventSupported('change'); + + //test if mousedown followed by mouseup causes click (opera), make sure there are no clicks after this + var clicksCount = 0 + div.onclick = function() { + Syn.support.mouseDownUpClicks = true; + //we should use this to check for opera potentially, but would + //be difficult to remove element correctly + //Syn.support.mouseDownUpRepeatClicks = (2 == (++clicksCount)) + } + Syn.trigger("mousedown", {}, div) + Syn.trigger("mouseup", {}, div) + + //setTimeout(function(){ + // Syn.trigger("mousedown",{},div) + // Syn.trigger("mouseup",{},div) + //},1) + + document.documentElement.removeChild(div); + + //check stuff + window.__synthTest = oldSynth; + Syn.support.ready++; + })(); + + +})() +//}); +})(true); +(function(){ +//steal("synthetic") +// .then("mouse") +// .then(function() { + Syn.key.browsers = { + webkit : { + 'prevent': + {"keyup":[],"keydown":["char","keypress"],"keypress":["char"]}, + 'character': + {"keydown":[0,"key"],"keypress":["char","char"],"keyup":[0,"key"]}, + 'specialChars': + {"keydown":[0,"char"],"keyup":[0,"char"]}, + 'navigation': + {"keydown":[0,"key"],"keyup":[0,"key"]}, + 'special': + {"keydown":[0,"key"],"keyup":[0,"key"]}, + 'tab': + {"keydown":[0,"char"],"keyup":[0,"char"]}, + 'pause-break': + {"keydown":[0,"key"],"keyup":[0,"key"]}, + 'caps': + {"keydown":[0,"key"],"keyup":[0,"key"]}, + 'escape': + {"keydown":[0,"key"],"keyup":[0,"key"]}, + 'num-lock': + {"keydown":[0,"key"],"keyup":[0,"key"]}, + 'scroll-lock': + {"keydown":[0,"key"],"keyup":[0,"key"]}, + 'print': + {"keyup":[0,"key"]}, + 'function': + {"keydown":[0,"key"],"keyup":[0,"key"]}, + '\r': + {"keydown":[0,"key"],"keypress":["char","key"],"keyup":[0,"key"]} + }, + gecko : { + 'prevent': + {"keyup":[],"keydown":["char"],"keypress":["char"]}, + 'character': + {"keydown":[0,"key"],"keypress":["char",0],"keyup":[0,"key"]}, + 'specialChars': + {"keydown":[0,"key"],"keypress":[0,"key"],"keyup":[0,"key"]}, + 'navigation': + {"keydown":[0,"key"],"keypress":[0,"key"],"keyup":[0,"key"]}, + 'special': + {"keydown":[0,"key"],"keyup":[0,"key"]}, + '\t': + {"keydown":[0,"key"],"keypress":[0,"key"],"keyup":[0,"key"]}, + 'pause-break': + {"keydown":[0,"key"],"keypress":[0,"key"],"keyup":[0,"key"]}, + 'caps': + {"keydown":[0,"key"],"keyup":[0,"key"]}, + 'escape': + {"keydown":[0,"key"],"keypress":[0,"key"],"keyup":[0,"key"]}, + 'num-lock': + {"keydown":[0,"key"],"keyup":[0,"key"]}, + 'scroll-lock': + {"keydown":[0,"key"],"keyup":[0,"key"]}, + 'print': + {"keyup":[0,"key"]}, + 'function': + {"keydown":[0,"key"],"keyup":[0,"key"]}, + '\r': + {"keydown":[0,"key"],"keypress":[0,"key"],"keyup":[0,"key"]} + }, + msie : { + 'prevent':{"keyup":[],"keydown":["char","keypress"],"keypress":["char"]}, + 'character':{"keydown":[null,"key"],"keypress":[null,"char"],"keyup":[null,"key"]}, + 'specialChars':{"keydown":[null,"char"],"keyup":[null,"char"]}, + 'navigation':{"keydown":[null,"key"],"keyup":[null,"key"]}, + 'special':{"keydown":[null,"key"],"keyup":[null,"key"]}, + 'tab':{"keydown":[null,"char"],"keyup":[null,"char"]}, + 'pause-break':{"keydown":[null,"key"],"keyup":[null,"key"]}, + 'caps':{"keydown":[null,"key"],"keyup":[null,"key"]}, + 'escape':{"keydown":[null,"key"],"keypress":[null,"key"],"keyup":[null,"key"]}, + 'num-lock':{"keydown":[null,"key"],"keyup":[null,"key"]}, + 'scroll-lock':{"keydown":[null,"key"],"keyup":[null,"key"]}, + 'print':{"keyup":[null,"key"]}, + 'function':{"keydown":[null,"key"],"keyup":[null,"key"]}, + '\r':{"keydown":[null,"key"],"keypress":[null,"key"],"keyup":[null,"key"]} + }, + opera : { + 'prevent': + {"keyup":[],"keydown":[],"keypress":["char"]}, + 'character': + {"keydown":[null,"key"],"keypress":[null,"char"],"keyup":[null,"key"]}, + 'specialChars': + {"keydown":[null,"char"],"keypress":[null,"char"],"keyup":[null,"char"]}, + 'navigation': + {"keydown":[null,"key"],"keypress":[null,"key"]}, + 'special': + {"keydown":[null,"key"],"keypress":[null,"key"],"keyup":[null,"key"]}, + 'tab': + {"keydown":[null,"char"],"keypress":[null,"char"],"keyup":[null,"char"]}, + 'pause-break': + {"keydown":[null,"key"],"keypress":[null,"key"],"keyup":[null,"key"]}, + 'caps': + {"keydown":[null,"key"],"keyup":[null,"key"]}, + 'escape': + {"keydown":[null,"key"],"keypress":[null,"key"]}, + 'num-lock': + {"keyup":[null,"key"],"keydown":[null,"key"],"keypress":[null,"key"]}, + 'scroll-lock': + {"keydown":[null,"key"],"keypress":[null,"key"],"keyup":[null,"key"]}, + 'print': + {}, + 'function': + {"keydown":[null,"key"],"keypress":[null,"key"],"keyup":[null,"key"]}, + '\r': + {"keydown":[null,"key"],"keypress":[null,"key"],"keyup":[null,"key"]} + } + }; + + Syn.mouse.browsers = { + webkit : {"right":{"mousedown":{"button":2,"which":3},"mouseup":{"button":2,"which":3},"contextmenu":{"button":2,"which":3}}, + "left":{"mousedown":{"button":0,"which":1},"mouseup":{"button":0,"which":1},"click":{"button":0,"which":1}}}, + opera: {"right":{"mousedown":{"button":2,"which":3},"mouseup":{"button":2,"which":3}}, + "left":{"mousedown":{"button":0,"which":1},"mouseup":{"button":0,"which":1},"click":{"button":0,"which":1}}}, + msie: { "right":{"mousedown":{"button":2},"mouseup":{"button":2},"contextmenu":{"button":0}}, + "left":{"mousedown":{"button":1},"mouseup":{"button":1},"click":{"button":0}}}, + chrome : {"right":{"mousedown":{"button":2,"which":3},"mouseup":{"button":2,"which":3},"contextmenu":{"button":2,"which":3}}, + "left":{"mousedown":{"button":0,"which":1},"mouseup":{"button":0,"which":1},"click":{"button":0,"which":1}}}, + gecko: {"left":{"mousedown":{"button":0,"which":1},"mouseup":{"button":0,"which":1},"click":{"button":0,"which":1}}, + "right":{"mousedown":{"button":2,"which":3},"mouseup":{"button":2,"which":3},"contextmenu":{"button":2,"which":3}}} + } + + //set browser + Syn.key.browser = + (function(){ + if(Syn.key.browsers[window.navigator.userAgent]){ + return Syn.key.browsers[window.navigator.userAgent]; + } + for(var browser in Syn.browser){ + if(Syn.browser[browser] && Syn.key.browsers[browser]){ + return Syn.key.browsers[browser] + } + } + return Syn.key.browsers.gecko; + })(); + + Syn.mouse.browser = + (function(){ + if(Syn.mouse.browsers[window.navigator.userAgent]){ + return Syn.mouse.browsers[window.navigator.userAgent]; + } + for(var browser in Syn.browser){ + if(Syn.browser[browser] && Syn.mouse.browsers[browser]){ + return Syn.mouse.browsers[browser] + } + } + return Syn.mouse.browsers.gecko; + })(); +//}); +})(true); +(function(){ +(function() { + var h = Syn.helpers, + S = Syn, + + // gets the selection of an input or textarea + getSelection = function( el ) { + // use selectionStart if we can + if ( el.selectionStart !== undefined ) { + // this is for opera, so we don't have to focus to type how we think we would + if ( document.activeElement && document.activeElement != el && el.selectionStart == el.selectionEnd && el.selectionStart == 0 ) { + return { + start: el.value.length, + end: el.value.length + }; + } + return { + start: el.selectionStart, + end: el.selectionEnd + } + } else { + //check if we aren't focused + try { + //try 2 different methods that work differently (IE breaks depending on type) + if ( el.nodeName.toLowerCase() == 'input' ) { + var real = h.getWindow(el).document.selection.createRange(), + r = el.createTextRange(); + r.setEndPoint("EndToStart", real); + + var start = r.text.length + return { + start: start, + end: start + real.text.length + } + } + else { + var real = h.getWindow(el).document.selection.createRange(), + r = real.duplicate(), + r2 = real.duplicate(), + r3 = real.duplicate(); + r2.collapse(); + r3.collapse(false); + r2.moveStart('character', -1) + r3.moveStart('character', -1) + //select all of our element + r.moveToElementText(el) + //now move our endpoint to the end of our real range + r.setEndPoint('EndToEnd', real); + var start = r.text.length - real.text.length, + end = r.text.length; + if ( start != 0 && r2.text == "" ) { + start += 2; + } + if ( end != 0 && r3.text == "" ) { + end += 2; + } + //if we aren't at the start, but previous is empty, we are at start of newline + return { + start: start, + end: end + } + } + } catch (e) { + return { + start: el.value.length, + end: el.value.length + }; + } + } + }, + // gets all focusable elements + getFocusable = function( el ) { + var document = h.getWindow(el).document, + res = []; + + var els = document.getElementsByTagName('*'), + len = els.length; + + for ( var i = 0; i < len; i++ ) { + Syn.isFocusable(els[i]) && els[i] != document.documentElement && res.push(els[i]) + } + return res; + + + }; + + /** + * @add Syn static + */ + h.extend(Syn, { + /** + * @attribute + * A list of the keys and their keycodes codes you can type. + * You can add type keys with + * @codestart + * Syn('key','delete','title'); + * + * //or + * + * Syn('type','One Two Three[left][left][delete]','title') + * @codeend + * + * The following are a list of keys you can type: + * @codestart text + * \b - backspace + * \t - tab + * \r - enter + * ' ' - space + * a-Z 0-9 - normal characters + * /!@#$*,.? - All other typeable characters + * page-up - scrolls up + * page-down - scrolls down + * end - scrolls to bottom + * home - scrolls to top + * insert - changes how keys are entered + * delete - deletes the next character + * left - moves cursor left + * right - moves cursor right + * up - moves the cursor up + * down - moves the cursor down + * f1-12 - function buttons + * shift, ctrl, alt - special keys + * pause-break - the pause button + * scroll-lock - locks scrolling + * caps - makes caps + * escape - escape button + * num-lock - allows numbers on keypad + * print - screen capture + * @codeend + */ + keycodes: { + //backspace + '\b': '8', + + //tab + '\t': '9', + + //enter + '\r': '13', + + //special + 'shift': '16', + 'ctrl': '17', + 'alt': '18', + + //weird + 'pause-break': '19', + 'caps': '20', + 'escape': '27', + 'num-lock': '144', + 'scroll-lock': '145', + 'print': '44', + + //navigation + 'page-up': '33', + 'page-down': '34', + 'end': '35', + 'home': '36', + 'left': '37', + 'up': '38', + 'right': '39', + 'down': '40', + 'insert': '45', + 'delete': '46', + + //normal characters + ' ': '32', + '0': '48', + '1': '49', + '2': '50', + '3': '51', + '4': '52', + '5': '53', + '6': '54', + '7': '55', + '8': '56', + '9': '57', + 'a': '65', + 'b': '66', + 'c': '67', + 'd': '68', + 'e': '69', + 'f': '70', + 'g': '71', + 'h': '72', + 'i': '73', + 'j': '74', + 'k': '75', + 'l': '76', + 'm': '77', + 'n': '78', + 'o': '79', + 'p': '80', + 'q': '81', + 'r': '82', + 's': '83', + 't': '84', + 'u': '85', + 'v': '86', + 'w': '87', + 'x': '88', + 'y': '89', + 'z': '90', + //normal-characters, numpad + 'num0': '96', + 'num1': '97', + 'num2': '98', + 'num3': '99', + 'num4': '100', + 'num5': '101', + 'num6': '102', + 'num7': '103', + 'num8': '104', + 'num9': '105', + '*': '106', + '+': '107', + '-': '109', + '.': '110', + //normal-characters, others + '/': '111', + ';': '186', + '=': '187', + ',': '188', + '-': '189', + '.': '190', + '/': '191', + '`': '192', + '[': '219', + '\\': '220', + ']': '221', + "'": '222', + + //ignore these, you shouldn't use them + 'left window key': '91', + 'right window key': '92', + 'select key': '93', + + + 'f1': '112', + 'f2': '113', + 'f3': '114', + 'f4': '115', + 'f5': '116', + 'f6': '117', + 'f7': '118', + 'f8': '119', + 'f9': '120', + 'f10': '121', + 'f11': '122', + 'f12': '123' + }, + + // what we can type in + typeable: /input|textarea/i, + + // selects text on an element + selectText: function( el, start, end ) { + if ( el.setSelectionRange ) { + if (!end ) { + el.focus(); + el.setSelectionRange(start, start); + } else { + el.selectionStart = start; + el.selectionEnd = end; + } + } else if ( el.createTextRange ) { + //el.focus(); + var r = el.createTextRange(); + r.moveStart('character', start); + end = end || start; + r.moveEnd('character', end - el.value.length); + + r.select(); + } + }, + getText: function( el ) { + //first check if the el has anything selected .. + if ( Syn.typeable.test(el.nodeName) ) { + var sel = getSelection(el); + return el.value.substring(sel.start, sel.end) + } + //otherwise get from page + var win = Syn.helpers.getWindow(el); + if ( win.getSelection ) { + return win.getSelection().toString(); + } + else if ( win.document.getSelection ) { + return win.document.getSelection().toString() + } + else { + return win.document.selection.createRange().text; + } + }, + getSelection: getSelection + }); + + h.extend(Syn.key, { + // retrieves a description of what events for this character should look like + data: function( key ) { + //check if it is described directly + if ( S.key.browser[key] ) { + return S.key.browser[key]; + } + for ( var kind in S.key.kinds ) { + if ( h.inArray(key, S.key.kinds[kind]) > -1 ) { + return S.key.browser[kind] + } + } + return S.key.browser.character + }, + + //returns the special key if special + isSpecial: function( keyCode ) { + var specials = S.key.kinds.special; + for ( var i = 0; i < specials.length; i++ ) { + if ( Syn.keycodes[specials[i]] == keyCode ) { + return specials[i]; + } + } + }, + /** + * @hide + * gets the options for a key and event type ... + * @param {Object} key + * @param {Object} event + */ + options: function( key, event ) { + var keyData = Syn.key.data(key); + + if (!keyData[event] ) { + //we shouldn't be creating this event + return null; + } + + var charCode = keyData[event][0], + keyCode = keyData[event][1], + result = {}; + + if ( keyCode == 'key' ) { + result.keyCode = Syn.keycodes[key] + } else if ( keyCode == 'char' ) { + result.keyCode = key.charCodeAt(0) + } else { + result.keyCode = keyCode; + } + + if ( charCode == 'char' ) { + result.charCode = key.charCodeAt(0) + } else if ( charCode !== null ) { + result.charCode = charCode; + } + + + return result + }, + //types of event keys + kinds: { + special: ["shift", 'ctrl', 'alt', 'caps'], + specialChars: ["\b"], + navigation: ["page-up", 'page-down', 'end', 'home', 'left', 'up', 'right', 'down', 'insert', 'delete'], + 'function': ['f1', 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9', 'f10', 'f11', 'f12'] + }, + //returns the default function + getDefault: function( key ) { + //check if it is described directly + if ( Syn.key.defaults[key] ) { + return Syn.key.defaults[key]; + } + for ( var kind in Syn.key.kinds ) { + if ( h.inArray(key, Syn.key.kinds[kind]) > -1 && Syn.key.defaults[kind] ) { + return Syn.key.defaults[kind]; + } + } + return Syn.key.defaults.character + }, + // default behavior when typing + defaults: { + 'character': function( options, scope, key, force, sel ) { + if (/num\d+/.test(key) ) { + key = key.match(/\d+/)[0] + } + + if ( force || (!S.support.keyCharacters && Syn.typeable.test(this.nodeName)) ) { + var current = this.value, + before = current.substr(0, sel.start), + after = current.substr(sel.end), + character = key; + + this.value = before + character + after; + //handle IE inserting \r\n + var charLength = character == "\n" && S.support.textareaCarriage ? 2 : character.length; + Syn.selectText(this, before.length + charLength) + } + }, + 'c': function( options, scope, key, force, sel ) { + if ( Syn.key.ctrlKey ) { + Syn.key.clipboard = Syn.getText(this) + } else { + Syn.key.defaults.character.apply(this, arguments); + } + }, + 'v': function( options, scope, key, force, sel ) { + if ( Syn.key.ctrlKey ) { + Syn.key.defaults.character.call(this, options, scope, Syn.key.clipboard, true, sel); + } else { + Syn.key.defaults.character.apply(this, arguments); + } + }, + 'a': function( options, scope, key, force, sel ) { + if ( Syn.key.ctrlKey ) { + Syn.selectText(this, 0, this.value.length) + } else { + Syn.key.defaults.character.apply(this, arguments); + } + }, + 'home': function() { + Syn.onParents(this, function( el ) { + if ( el.scrollHeight != el.clientHeight ) { + el.scrollTop = 0; + return false; + } + }) + }, + 'end': function() { + Syn.onParents(this, function( el ) { + if ( el.scrollHeight != el.clientHeight ) { + el.scrollTop = el.scrollHeight; + return false; + } + }) + }, + 'page-down': function() { + //find the first parent we can scroll + Syn.onParents(this, function( el ) { + if ( el.scrollHeight != el.clientHeight ) { + var ch = el.clientHeight + el.scrollTop += ch; + return false; + } + }) + }, + 'page-up': function() { + Syn.onParents(this, function( el ) { + if ( el.scrollHeight != el.clientHeight ) { + var ch = el.clientHeight + el.scrollTop -= ch; + return false; + } + }) + }, + '\b': function( options, scope, key, force, sel ) { + //this assumes we are deleting from the end + if (!S.support.backspaceWorks && Syn.typeable.test(this.nodeName) ) { + var current = this.value, + before = current.substr(0, sel.start), + after = current.substr(sel.end); + + if ( sel.start == sel.end && sel.start > 0 ) { + //remove a character + this.value = before.substring(0, before.length - 1) + after + Syn.selectText(this, sel.start - 1) + } else { + this.value = before + after; + Syn.selectText(this, sel.start) + } + + //set back the selection + } + }, + 'delete': function( options, scope, key, force, sel ) { + if (!S.support.backspaceWorks && Syn.typeable.test(this.nodeName) ) { + var current = this.value, + before = current.substr(0, sel.start), + after = current.substr(sel.end); + if ( sel.start == sel.end && sel.start <= this.value.length - 1 ) { + this.value = before + after.substring(1) + } else { + this.value = before + after; + + } + Syn.selectText(this, sel.start) + } + }, + '\r': function( options, scope, key, force, sel ) { + + var nodeName = this.nodeName.toLowerCase() + // submit a form + if (!S.support.keypressSubmits && nodeName == 'input' ) { + var form = Syn.closest(this, "form"); + if ( form ) { + Syn.trigger("submit", {}, form); + } + + } + //newline in textarea + if (!S.support.keyCharacters && nodeName == 'textarea' ) { + Syn.key.defaults.character.call(this, options, scope, "\n", undefined, sel) + } + // 'click' hyperlinks + if (!S.support.keypressOnAnchorClicks && nodeName == 'a' ) { + Syn.trigger("click", {}, this); + } + }, + // + // Gets all focusable elements. If the element (this) + // doesn't have a tabindex, finds the next element after. + // If the element (this) has a tabindex finds the element + // with the next higher tabindex OR the element with the same + // tabindex after it in the document. + // @return the next element + // + '\t': function( options, scope ) { + // focusable elements + var focusEls = getFocusable(this), + // the current element's tabindex + tabIndex = Syn.tabIndex(this), + // will be set to our guess for the next element + current = null, + // the next index we care about + currentIndex = 1000000000, + // set to true once we found 'this' element + found = false, + i = 0, + el, + //the tabindex of the tabable element we are looking at + elIndex, firstNotIndexed, prev; + orders = []; + for (; i < focusEls.length; i++ ) { + orders.push([focusEls[i], i]); + } + var sort = function( order1, order2 ) { + var el1 = order1[0], + el2 = order2[0], + tab1 = Syn.tabIndex(el1) || 0, + tab2 = Syn.tabIndex(el2) || 0; + if ( tab1 == tab2 ) { + return order1[1] - order2[1] + } else { + if ( tab1 == 0 ) { + return 1; + } else if ( tab2 == 0 ) { + return -1; + } else { + return tab1 - tab2; + } + } + } + orders.sort(sort); + //now find current + for ( i = 0; i < orders.length; i++ ) { + el = orders[i][0]; + if ( this == el ) { + if (!Syn.key.shiftKey ) { + current = orders[i + 1][0]; + if (!current ) { + current = orders[0][0] + } + } else { + current = orders[i - 1][0]; + if (!current ) { + current = orders[focusEls.length - 1][0] + } + } + + } + } + + //restart if we didn't find anything + if (!current ) { + current = firstNotIndexed; + } + current && current.focus(); + return current; + }, + 'left': function( options, scope, key, force, sel ) { + if ( Syn.typeable.test(this.nodeName) ) { + if ( Syn.key.shiftKey ) { + Syn.selectText(this, sel.start == 0 ? 0 : sel.start - 1, sel.end) + } else { + Syn.selectText(this, sel.start == 0 ? 0 : sel.start - 1) + } + } + }, + 'right': function( options, scope, key, force, sel ) { + if ( Syn.typeable.test(this.nodeName) ) { + if ( Syn.key.shiftKey ) { + Syn.selectText(this, sel.start, sel.end + 1 > this.value.length ? this.value.length : sel.end + 1) + } else { + Syn.selectText(this, sel.end + 1 > this.value.length ? this.value.length : sel.end + 1) + } + } + }, + 'up': function() { + if (/select/i.test(this.nodeName) ) { + + this.selectedIndex = this.selectedIndex ? this.selectedIndex - 1 : 0; + //set this to change on blur? + } + }, + 'down': function() { + if (/select/i.test(this.nodeName) ) { + Syn.changeOnBlur(this, "selectedIndex", this.selectedIndex) + this.selectedIndex = this.selectedIndex + 1; + //set this to change on blur? + } + }, + 'shift': function() { + return null; + } + } + }); + + + h.extend(Syn.create, { + keydown: { + setup: function( type, options, element ) { + if ( h.inArray(options, Syn.key.kinds.special) != -1 ) { + Syn.key[options + "Key"] = element; + } + } + }, + keypress: { + setup: function( type, options, element ) { + // if this browsers supports writing keys on events + // but doesn't write them if the element isn't focused + // focus on the element (ignored if already focused) + if ( S.support.keyCharacters && !S.support.keysOnNotFocused ) { + element.focus() + } + } + }, + keyup: { + setup: function( type, options, element ) { + if ( h.inArray(options, Syn.key.kinds.special) != -1 ) { + Syn.key[options + "Key"] = null; + } + } + }, + key: { + // return the options for a key event + options: function( type, options, element ) { + //check if options is character or has character + options = typeof options != "object" ? { + character: options + } : options; + + //don't change the orignial + options = h.extend({}, options) + if ( options.character ) { + h.extend(options, S.key.options(options.character, type)); + delete options.character; + } + + options = h.extend({ + ctrlKey: !! Syn.key.ctrlKey, + altKey: !! Syn.key.altKey, + shiftKey: !! Syn.key.shiftKey, + metaKey: !! Syn.key.metaKey + }, options) + + return options; + }, + // creates a key event + event: function( type, options, element ) { //Everyone Else + var doc = h.getWindow(element).document || document; + if ( doc.createEvent ) { + var event; + + try { + + event = doc.createEvent("KeyEvents"); + event.initKeyEvent(type, true, true, window, options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, options.keyCode, options.charCode); + } + catch (e) { + event = h.createBasicStandardEvent(type, options, doc); + } + event.synthetic = true; + return event; + } + else { + var event; + try { + event = h.createEventObject.apply(this, arguments); + h.extend(event, options) + } + catch (e) {} + + return event; + } + } + } + }); + + var convert = { + "enter": "\r", + "backspace": "\b", + "tab": "\t", + "space": " " + } + + /** + * @add Syn prototype + */ + h.extend(Syn.init.prototype, { + /** + * @function key + * Types a single key. The key should be + * a string that matches a + * [Syn.static.keycodes]. + * + * The following sends a carridge return + * to the 'name' element. + * @codestart + * Syn.key('\r','name') + * @codeend + * For each character, a keydown, keypress, and keyup is triggered if + * appropriate. + * @param {String} options + * @param {HTMLElement} [element] + * @param {Function} [callback] + * @return {HTMLElement} the element currently focused. + */ + _key: function( options, element, callback ) { + //first check if it is a special up + if (/-up$/.test(options) && h.inArray(options.replace("-up", ""), Syn.key.kinds.special) != -1 ) { + Syn.trigger('keyup', options.replace("-up", ""), element) + callback(true, element); + return; + } + + + var caret = Syn.typeable.test(element.nodeName) && getSelection(element), + key = convert[options] || options, + // should we run default events + runDefaults = Syn.trigger('keydown', key, element), + + // a function that gets the default behavior for a key + getDefault = Syn.key.getDefault, + + // how this browser handles preventing default events + prevent = Syn.key.browser.prevent, + + // the result of the default event + defaultResult, + + // options for keypress + keypressOptions = Syn.key.options(key, 'keypress') + + + if ( runDefaults ) { + //if the browser doesn't create keypresses for this key, run default + if (!keypressOptions ) { + defaultResult = getDefault(key).call(element, keypressOptions, h.getWindow(element), key, undefined, caret) + } else { + //do keypress + runDefaults = Syn.trigger('keypress', keypressOptions, element) + if ( runDefaults ) { + defaultResult = getDefault(key).call(element, keypressOptions, h.getWindow(element), key, undefined, caret) + } + } + } else { + //canceled ... possibly don't run keypress + if ( keypressOptions && h.inArray('keypress', prevent.keydown) == -1 ) { + Syn.trigger('keypress', keypressOptions, element) + } + } + if ( defaultResult && defaultResult.nodeName ) { + element = defaultResult + } + + if ( defaultResult !== null ) { + setTimeout(function() { + Syn.trigger('keyup', Syn.key.options(key, 'keyup'), element) + callback(runDefaults, element) + }, 1) + } else { + callback(runDefaults, element) + } + + + //do mouseup + return element; + // is there a keypress? .. if not , run default + // yes -> did we prevent it?, if not run ... + }, + /** + * @function type + * Types sequence of [Syn.key key actions]. Each + * character is typed, one at a type. + * Multi-character keys like 'left' should be + * enclosed in square brackents. + * + * The following types 'JavaScript MVC' then deletes the space. + * @codestart + * Syn.type('JavaScript MVC[left][left][left]\b','name') + * @codeend + * + * Type is able to handle (and move with) tabs (\t). + * The following simulates tabing and entering values in a form and + * eventually submitting the form. + * @codestart + * Syn.type("Justin\tMeyer\t27\tjustinbmeyer@gmail.com\r") + * @codeend + * @param {String} options the text to type + * @param {HTMLElement} [element] an element or an id of an element + * @param {Function} [callback] a function to callback + */ + _type: function( options, element, callback ) { + //break it up into parts ... + //go through each type and run + var parts = options.match(/(\[[^\]]+\])|([^\[])/g), + self = this, + runNextPart = function( runDefaults, el ) { + var part = parts.shift(); + if (!part ) { + callback(runDefaults, el); + return; + } + el = el || element; + if ( part.length > 1 ) { + part = part.substr(1, part.length - 2) + } + self._key(part, el, runNextPart) + } + + runNextPart(); + + } + }); + + + //do support code + (function() { + if (!document.body ) { + setTimeout(arguments.callee, 1) + return; + } + + var div = document.createElement("div"), + checkbox, submit, form, input, submitted = false, + anchor, textarea, inputter; + + div.innerHTML = "<form id='outer'>" + + "<input name='checkbox' type='checkbox'/>" + + "<input name='radio' type='radio' />" + + "<input type='submit' name='submitter'/>" + + "<input type='input' name='inputter'/>" + + "<input name='one'>" + + "<input name='two'/>" + + "<a href='#abc'></a>" + + "<textarea>1\n2</textarea>" + + "</form>"; + + document.documentElement.appendChild(div); + form = div.firstChild; + checkbox = form.childNodes[0]; + submit = form.childNodes[2]; + anchor = form.getElementsByTagName("a")[0]; + textarea = form.getElementsByTagName("textarea")[0]; + inputter = form.childNodes[3]; + + form.onsubmit = function( ev ) { + if ( ev.preventDefault ) ev.preventDefault(); + S.support.keypressSubmits = true; + ev.returnValue = false; + return false; + }; + // Firefox 4 won't write key events if the element isn't focused + inputter.focus(); + Syn.trigger("keypress", "\r", inputter); + + + Syn.trigger("keypress", "a", inputter); + S.support.keyCharacters = inputter.value == "a"; + + + inputter.value = "a"; + Syn.trigger("keypress", "\b", inputter); + S.support.backspaceWorks = inputter.value == ""; + + + + inputter.onchange = function() { + S.support.focusChanges = true; + } + inputter.focus(); + Syn.trigger("keypress", "a", inputter); + form.childNodes[5].focus(); // this will throw a change event + Syn.trigger("keypress", "b", inputter); + S.support.keysOnNotFocused = inputter.value == "ab"; + + //test keypress \r on anchor submits + S.bind(anchor, "click", function( ev ) { + if ( ev.preventDefault ) ev.preventDefault(); + S.support.keypressOnAnchorClicks = true; + ev.returnValue = false; + return false; + }) + Syn.trigger("keypress", "\r", anchor); + + S.support.textareaCarriage = textarea.value.length == 4 + document.documentElement.removeChild(div); + + S.support.ready++; + })(); +}()); +})(true); +(function() { + + // check if elementFromPageExists + (function() { + + // document body has to exists for this test + if (!document.body ) { + setTimeout(arguments.callee, 1) + return; + } + var div = document.createElement('div') + document.body.appendChild(div); + Syn.helpers.extend(div.style, { + width: "100px", + height: "10000px", + backgroundColor: "blue", + position: "absolute", + top: "10px", + left: "0px", + zIndex: 19999 + }); + document.body.scrollTop = 11; + if (!document.elementFromPoint ) { + return; + } + var el = document.elementFromPoint(3, 1) + if ( el == div ) { + Syn.support.elementFromClient = true; + } + else { + Syn.support.elementFromPage = true; + } + document.body.removeChild(div); + document.body.scrollTop = 0; + })(); + + + //gets an element from a point + var elementFromPoint = function( point, element ) { + var clientX = point.clientX, + clientY = point.clientY, + win = Syn.helpers.getWindow(element), + el; + + + + if ( Syn.support.elementFromPage ) { + var off = Syn.helpers.scrollOffset(win); + clientX = clientX + off.left; //convert to pageX + clientY = clientY + off.top; //convert to pageY + } + el = win.document.elementFromPoint ? win.document.elementFromPoint(clientX, clientY) : element; + if ( el === win.document.documentElement && (point.clientY < 0 || point.clientX < 0) ) { + return element; + } else { + return el; + } + }, + //creates an event at a certain point + createEventAtPoint = function( event, point, element ) { + var el = elementFromPoint(point, element) + Syn.trigger(event, point, el || element) + return el; + }, + // creates a mousemove event, but first triggering mouseout / mouseover if appropriate + mouseMove = function( point, element, last ) { + var el = elementFromPoint(point, element) + if ( last != el && el && last ) { + var options = Syn.helpers.extend({}, point); + options.relatedTarget = el; + Syn.trigger("mouseout", options, last); + options.relatedTarget = last; + Syn.trigger("mouseover", options, el); + } + + Syn.trigger("mousemove", point, el || element) + return el; + }, + // start and end are in clientX, clientY + startMove = function( start, end, duration, element, callback ) { + var startTime = new Date(), + distX = end.clientX - start.clientX, + distY = end.clientY - start.clientY, + win = Syn.helpers.getWindow(element), + current = elementFromPoint(start, element), + cursor = win.document.createElement('div'), + calls = 0; + move = function() { + //get what fraction we are at + var now = new Date(), + scrollOffset = Syn.helpers.scrollOffset(win), + fraction = (calls == 0 ? 0 : now - startTime) / duration, + options = { + clientX: distX * fraction + start.clientX, + clientY: distY * fraction + start.clientY + }; + calls++; + if ( fraction < 1 ) { + Syn.helpers.extend(cursor.style, { + left: (options.clientX + scrollOffset.left + 2) + "px", + top: (options.clientY + scrollOffset.top + 2) + "px" + }) + current = mouseMove(options, element, current) + setTimeout(arguments.callee, 15) + } + else { + current = mouseMove(end, element, current); + win.document.body.removeChild(cursor) + callback(); + } + } + Syn.helpers.extend(cursor.style, { + height: "5px", + width: "5px", + backgroundColor: "red", + position: "absolute", + zIndex: 19999, + fontSize: "1px" + }) + win.document.body.appendChild(cursor) + move(); + }, + startDrag = function( start, end, duration, element, callback ) { + createEventAtPoint("mousedown", start, element); + startMove(start, end, duration, element, function() { + createEventAtPoint("mouseup", end, element); + callback(); + }) + }, + center = function( el ) { + var j = Syn.jquery()(el), + o = j.offset(); + return { + pageX: o.left + (j.width() / 2), + pageY: o.top + (j.height() / 2) + } + }, + convertOption = function( option, win, from ) { + var page = /(\d+)[x ](\d+)/, + client = /(\d+)X(\d+)/, + relative = /([+-]\d+)[xX ]([+-]\d+)/ + //check relative "+22x-44" + if ( typeof option == 'string' && relative.test(option) && from ) { + var cent = center(from), + parts = option.match(relative); + option = { + pageX: cent.pageX + parseInt(parts[1]), + pageY: cent.pageY + parseInt(parts[2]) + } + } + if ( typeof option == 'string' && page.test(option) ) { + var parts = option.match(page) + option = { + pageX: parseInt(parts[1]), + pageY: parseInt(parts[2]) + } + } + if ( typeof option == 'string' && client.test(option) ) { + var parts = option.match(client) + option = { + clientX: parseInt(parts[1]), + clientY: parseInt(parts[2]) + } + } + if ( typeof option == 'string' ) { + option = Syn.jquery()(option, win.document)[0]; + } + if ( option.nodeName ) { + option = center(option) + } + if ( option.pageX ) { + var off = Syn.helpers.scrollOffset(win); + option = { + clientX: option.pageX - off.left, + clientY: option.pageY - off.top + } + } + return option; + }, + // if the client chords are not going to be visible ... scroll the page so they will be ... + adjust = function(from, to, win){ + if(from.clientY < 0){ + var off = Syn.helpers.scrollOffset(win); + var dimensions = Syn.helpers.scrollDimensions(win), + top = off.top + (from.clientY) - 100, + diff = top - off.top + + // first, lets see if we can scroll 100 px + if( top > 0){ + + } else { + top =0; + diff = -off.top; + } + console.log("moving", from.clientY,from.clientY - diff, off.top, top ) + from.clientY = from.clientY - diff; + to.clientY = to.clientY - diff; + Syn.helpers.scrollOffset(win,{top: top, left: off.left}); + + //throw "out of bounds!" + } + } + /** + * @add Syn prototype + */ + Syn.helpers.extend(Syn.init.prototype, { + /** + * @function move + * Moves the cursor from one point to another. + * + * ### Quick Example + * + * The following moves the cursor from (0,0) in + * the window to (100,100) in 1 second. + * + * Syn.move( + * { + * from: {clientX: 0, clientY: 0}, + * to: {clientX: 100, clientY: 100}, + * duration: 1000 + * }, + * document.document) + * + * ## Options + * + * There are many ways to configure the endpoints of the move. + * + * ### PageX and PageY + * + * If you pass pageX or pageY, these will get converted + * to client coordinates. + * + * Syn.move( + * { + * from: {pageX: 0, pageY: 0}, + * to: {pageX: 100, pageY: 100} + * }, + * document.document) + * + * ### String Coordinates + * + * You can set the pageX and pageY as strings like: + * + * Syn.move( + * { + * from: "0x0", + * to: "100x100" + * }, + * document.document) + * + * ### Element Coordinates + * + * If jQuery is present, you can pass an element as the from or to option + * and the coordinate will be set as the center of the element. + + * Syn.move( + * { + * from: $(".recipe")[0], + * to: $("#trash")[0] + * }, + * document.document) + * + * ### Query Strings + * + * If jQuery is present, you can pass a query string as the from or to option. + * + * Syn.move( + * { + * from: ".recipe", + * to: "#trash" + * }, + * document.document) + * + * ### No From + * + * If you don't provide a from, the element argument passed to Syn is used. + * + * Syn.move( + * { to: "#trash" }, + * 'myrecipe') + * + * ### Relative + * + * You can move the drag relative to the center of the from element. + * + * Syn.move("+20 +30", "myrecipe"); + * + * @param {Object} options options to configure the drag + * @param {HTMLElement} from the element to move + * @param {Function} callback a callback that happens after the drag motion has completed + */ + _move: function( options, from, callback ) { + //need to convert if elements + var win = Syn.helpers.getWindow(from), + fro = convertOption(options.from || from, win, from), + to = convertOption(options.to || options, win, from); + + options.adjust !== false && adjust(fro, to, win); + startMove(fro, to, options.duration || 500, from, callback); + + }, + /** + * @function drag + * Creates a mousedown and drags from one point to another. + * Check out [Syn.prototype.move move] for API details. + * + * @param {Object} options + * @param {Object} from + * @param {Object} callback + */ + _drag: function( options, from, callback ) { + //need to convert if elements + var win = Syn.helpers.getWindow(from), + fro = convertOption(options.from || from, win, from), + to = convertOption(options.to || options, win, from); + + options.adjust !== false && adjust(fro, to, win); + startDrag(fro, to, options.duration || 500, from, callback); + + } + }) +}()); +steal('jquery').then(function(){ + +(function($){ + var getWindow = function( element ) { + return element.ownerDocument.defaultView || element.ownerDocument.parentWindow + } + +/** + * Returns a unique selector for the matched element. + * @param {Object} target + */ +$.fn.prettySelector= function() { + var target = this[0]; + if(!target){ + return null + } + var selector = target.nodeName.toLowerCase(); + //always try to get an id + if(target.id){ + return "#"+target.id; + }else{ + var parent = target.parentNode; + while(parent){ + if(parent.id){ + selector = "#"+parent.id+" "+selector; + break; + }else{ + parent = parent.parentNode + } + } + } + if(target.className){ + selector += "."+target.className.split(" ")[0] + } + var others = $(selector, getWindow(target).document); //jquery should take care of the #foo if there + + if(others.length > 1){ + return selector+":eq("+others.index(target)+")"; + }else{ + return selector; + } +}; +$.each(["closest","find","next","prev","siblings","last","first"], function(i, name){ + $.fn[name+"Selector"] = function(selector){ + return this[name](selector).prettySelector(); + } +}); + + + + + +}(window.jQuery || window.FuncUnit.jQuery)); + + +}) diff --git a/browserid/static/funcunit/loader.js b/browserid/static/funcunit/loader.js new file mode 100644 index 0000000000000000000000000000000000000000..7457d808befc9955d205cd49e732d70fc69c50ba --- /dev/null +++ b/browserid/static/funcunit/loader.js @@ -0,0 +1,107 @@ +// This code is always run ... + +steal.then(function(){ + if (typeof FuncUnit == 'undefined') { + FuncUnit = {}; + } + // these are the + steal.extend(FuncUnit,{ + testStart: function(name){ + print("--" + name + "--") + }, + log: function(result, message){ + if (!message) + message = "" + print((result ? " PASS " : " FAIL ") + message) + }, + testDone: function(name, failures, total){ + print(" done - fail " + failures + ", pass " + total + "\n") + }, + moduleStart: function(name){ + print("MODULE " + name + "\n") + }, + moduleDone: function(name, failures, total){ + + }, + browserStart : function(name){ + print("BROWSER " + name + " ===== \n") + }, + browserDone : function(name, failures, total){ + print("\n"+name+" DONE " + failures + ", " + total + (FuncUnit.showTimestamps? (' - ' + + formattedtime + ' seconds'): "")) + }, + done: function(failures, total){ + print("\nALL DONEe - fail " + failures + ", pass " + total) + } + }); + /** + * Loads the FuncUnit page in EnvJS. This loads FuncUnit, but we probably want settings + * on it already .... + * + * 2 ways to include settings.js: + * 1. Manually before funcunit.js + * 2. FuncUnit.load will try to load settings.js if there hasn't been one loaded + */ + FuncUnit.load = function(page){ + //clear out steal ... you are done with it... + var extend = steal.extend; + steal = undefined; + load('steal/rhino/env.js'); + if (!navigator.userAgent.match(/Rhino/)){ + return; + } + + var dirArr = page.split("/"), + dir = dirArr.slice(0, dirArr.length - 1).join("/"), + settingsPath = dir + "/settings.js"; + + // if settings.js was already loaded, don't try to load it again + if (FuncUnit.browsers === undefined) { + //this gets the global object, even in rhino + var window = (function(){return this}).call(null), + backupFunc = window.FuncUnit; + + if(readFile('funcunit/settings.js')){ + load('funcunit/settings.js') + } + + // try to load a local settings + var foundSettings = false; + if(/^http/.test(settingsPath)){ + try { + readUrl(settingsPath) + foundSettings = true; + } + catch (e) {} + }else{ + if(readFile(settingsPath)){ + foundSettings = true; + } + + } + + if (foundSettings) { + print("Reading Settings From "+settingsPath) + load(settingsPath) + }else{ + print("Using Default Settings") + } + + extend(FuncUnit, backupFunc) + + + } + + Envjs(page, { + scriptTypes: { + "text/javascript": true, + "text/envjs": true, + "": true + }, + fireLoad: true, + logLevel: 2, + dontPrintUserAgent: true, + exitOnError : FuncUnit.exitOnError + }); + } +}) \ No newline at end of file diff --git a/browserid/static/funcunit/pages/example.js b/browserid/static/funcunit/pages/example.js new file mode 100644 index 0000000000000000000000000000000000000000..4083b32c2419b03f14eb53f197425a7b7c909bf8 --- /dev/null +++ b/browserid/static/funcunit/pages/example.js @@ -0,0 +1,69 @@ +/** +@page example 2.2. Test Examples +@parent FuncUnit + +## Srchr Smoke Test + +This guide will walk through creating a smoke test for the [http://javascriptmvc.com/srchr/srchr.html Srchr application]. +Srchr is a simple demo application that lets you search several sources for images. There is a search pane, +tabs, a history pane, and a results area. + +@image jmvc/images/srchr.png + +The purpose of a smoke test is to test enough functionality in an application to verify its working correctly, as +quickly as possible. + +In this smoke test we'll: + +1. click the Flickr search option +1. type "puppy" in the search box +1. wait for results to show up in the results panel +1. verify 10 results are visible +1. verify the history panel shows "puppy" + +Let's start by creating a skeleton test with some pseudocode: + +@codestart +module("Srchr",{ + setup: function() { + S.open("//srchr/srchr.html"); + } +}) + +test("Smoke Test", function(){ + // click search options + // type puppy + // wait for results + // verify 10 results + // verify history has puppy +}) +@codeend + +Now lets start to fill in these commands, leaving the actual selectors for last. + +@codestart +flickrInput.click(); +// \r means hit enter, which submits the form +searchInput.click().type("puppy\r"); +resultElements.visible(function(){ + equals(resultsElements.size(), 10, "There are 10 results"); + ok(/puppy/.test( historyEl.text() ), "History has puppy"); +}) +@codeend + +In this test, we're performing the search, waiting for results to appear, then asserting conditions of our page. +Here's the test with selectors filled in: + +@codestart + S('#cb_flickr').click(); + S('#query').click().type('puppy\r'); + + S('#flickr li').visible(function(){ + equals(S('#flickr li').size(), 10, 'There are 10 results') + ok( /puppy/.test( S('#history .text').text() ), 'History has puppy') + }) +@codeend + +That's it. Writing a working test is easy! + +*/ \ No newline at end of file diff --git a/browserid/static/funcunit/pages/follow.js b/browserid/static/funcunit/pages/follow.js new file mode 100644 index 0000000000000000000000000000000000000000..1bbbe0e8658fdc703c6d22828b638e4996206f23 --- /dev/null +++ b/browserid/static/funcunit/pages/follow.js @@ -0,0 +1,25 @@ +/** + * @page follow Follow FuncUnit + * <h1 class='addFavorite'>Following FuncUnit</h1> + * <h2>Twitter</h2> +<a href='http://twitter.com/funcunit' class='floatLeft'> + <img src='http://wiki.javascriptmvc.com/wiki/images/f/f7/Twitter.png' class='noborder'/> +</a> + + * Follow [http://twitter.com/funcunit @funcunit] on twitter for daily useful tips. + * <h2 class='spaced'>Blog</h2> +<a href='http://jupiterit.com/' class='floatLeft'> + <img src='http://wiki.javascriptmvc.com/wiki/images/e/e5/Blog.png' class='noborder'/> +</a> + + * Read [http://jupiterit.com/ JavaScriptMVC's Blog] for articles, techniques and ideas + * on maintainable JavaScript. + * <h2 class='spaced'>Email List</h2> + +<a href='http://forum.javascriptmvc.com/#Forum/funcunit' class='floatLeft'> + <img src='http://wiki.javascriptmvc.com/wiki/images/8/84/Discuss.png' class='noborder'/> +</a> + * Discuss ideas to make the project better or problems you are having on [http://forum.javascriptmvc.com/#Forum/funcunit FuncUnit's Forum] + * . + */ +// \ No newline at end of file diff --git a/browserid/static/funcunit/pages/init.js b/browserid/static/funcunit/pages/init.js new file mode 100644 index 0000000000000000000000000000000000000000..79696952b65947f79e8e53e57f84ceb96cac314d --- /dev/null +++ b/browserid/static/funcunit/pages/init.js @@ -0,0 +1,39 @@ +/* + * @page index home + * # FuncUnit <span class='subtitle'>Functional Testing for JavaScript Applications</span> + * + * FuncUnit is a free, open source, web application testing tool. It is the first complete functional testing tool built *for* + * JavaScript developers *by* JavaScript developers. If you know jQuery, you already know the API. + * + * <a class="big_button floatLeft" id="download" href="http://github.com/downloads/jupiterjs/funcunit/funcunit-beta-5.zip"> + * <span>Download FuncUnit</span><span class="label">Beta 5</span></a> + * <a href='http://github.com/downloads/jupiterjs/funcunit/funcunit-beta-5.zip' class='logo floatLeft'> + * @image jmvc/images/funcunit_medium.png + * </a> + * + * <h2 class='spaced'>More Information</h2> + * + * - [#&who=FuncUnit Docs] Documentation. + * - [http://jupiterjs.com/news/funcunit-fun-web-application-testing Article] The article originally releasing FuncUnit as a standalone piece from [http://v3.javascriptmvc.com/index.html JavaScriptMVC] + * - [http://forum.javascriptmvc.com/#Forum/funcunit Forums] Ask questions, get help. + * - [http://github.com/jupiterjs/funcunit Github] Contribute some patches. + * - [http://github.com/jupiterjs/funcunit/issues Issue Tracker] Submit a bug. + * - [http://github.com/jupiterjs/srchr Srchr Demo] See it in action. + * + */ + + /** + * @add FuncUnit +*/ +/** + * @class FuncUnit + * @tag home + */ + +/** + * @add Syn + */ +/** + * @class Syn + * @tag home + */ \ No newline at end of file diff --git a/browserid/static/funcunit/pages/mastering.js b/browserid/static/funcunit/pages/mastering.js new file mode 100644 index 0000000000000000000000000000000000000000..d93097310add2c5f93f0fa5286768de2f3d4748f --- /dev/null +++ b/browserid/static/funcunit/pages/mastering.js @@ -0,0 +1,166 @@ +/** + +@page mastering 2.1. Mastering the FuncUnit API +@parent FuncUnit + +Now that we've introduced the commands, this section will dive deeper into the challenges faced while writing FuncUnit tests and +some best practices. + +### 1. Asynchronous vs synchronous commands + +Most FuncUnit commands (all actions and waits) run asynchronously. This means when a .click() or .visible() method +runs, it doesn't actually perform a click or check for visible, but rather adds its method to a queue. After the first method +in the queue completes, the next one runs. For example: + +@codestart +S(".foo").click(); +S(".bar").visible(); +S(".myinput").type("abc"); +@codeend + +In this example, a click and a wait are added to the queue. The click runs (it is asynchronous). When it completes, +FuncUnit checks if ".bar" is visible (and keeps checking until it is). When this condition becomes true, the next command +in the queue runs, which types "abc". + +The reason they are asynchronous is to let you write linear FuncUnit tests without needing nested callbacks +for every command. As a result, tou can't set breakpoints in these methods, but there are other debugging methods. + +Assertions and getters are synchronous commands. Usually these commands are placed in callbacks for waits and actions. You +can set breakpoints in them and inspect the current state of your page. + +### 2. The S method + +Its important to realize the S command is NOT the $ command. It is named S because it acts similarly, but it does not +return a jQuery collection, and you can't call any jQuery methods on the result. + +However, the S method accepts any valid jQuery selector, allows chaining, and lets you call many jQuery like methods on it +(see the Getters section above). + +The reason S is not $ is because when in Selenium mode, the test runs in Rhino, sending commands across Selenium into +the browser. So S(".foo") sends JSON to the browser via Selenium that is later used as a parameter for $. Using $ wouldn't work, since only +text can be sent across the Selenium bridge, not objects. + +### 3. Finding the right wait + +After a user clicks or types in your page, something in your page changes. Something might appear, disappear, get wider, slide left, or show text. +A good text will take into account what changes after an action, and perform a wait on that condition. A bad test simply uses S.wait(1000) to wait +1 second before the next command. This is error prone, because under certain conditions, the page might be slower than 1 second, causing your test to +break. + +Finding the right wait makes your test bullet proof. + +### 4. Debugging tests + +Since you can't set breakpoints and step through actions/waits, you might wonder how you can effectively debug. Here are a few techniques. + +#### 1. Simplify + +If a test isn't working, comment out all other tests and even all commands after the one thats giving you trouble. Run the test. If it does what you expect, +uncomment one more command and run again. You can focus on the one part of your test thats giving you trouble. + +#### 2. Breakpoints in callbacks + +Waits and actions accept a callback that run after they complete. Inside, you can set breakpoints and inspect your page. You can also use console.logs +in callbacks to check conditions that are hard to inspect. + +#### 3. Use FuncUnit's logs + +Check Firebug's console and you'll during every command, it spits out what its doing. If a selector isn't working, go to your app window, and use jQuery in the +console to debug the selector. + +### 5. Reuse test code + +Often while writing tests for an app, you'll notice steps that need to happen over and over. For example, you need to click a tab in a tab widget and type in an input +to get to the screen you want to test. You can easily create test helper functions, which allow you to DRY your tests a bit. For example: + +@codestart +var openTab = function(tabName){ + S(".tab:contains('"+tabName+"')").click(); + S(".content").visible(); +} +@codeend + +### 6. Do you need assertions? + +As you write tests you'll begin to notice that assertions, while they give you a warm fuzzy feeling, aren't really all that necessary. You can perform waits for +the same conditions you'd check in assertions, your code looks more linear and readable without callbacks, and your tests will still fail if the waits fail. + +For example, the following are equivalent: + +@codestart +// wait for 5 li elements to be present +S(".menu li").size(5); + +// check if there are 5 li elements +S(".menu").exists(function(){ + equals(S(".menu li").size(), 5, "there are 5 li's"); +}) +@codeend + +### 7. Working with frames + +If your application makes use of iframes, providing a name attribute for your iframes will make testing easier. The second parameter of S is either the number or +name of your iframe: + +@codestart +// click ".foo" in the frame with name="myframe" +S(".foo", "myframe").click(); +@codeend + +If you're testing the interaction that causes the iframe to load, don't forget to perform a wait on some condition in the frame that signifies it has completed loading. + +### 8. Solving login + +When testing an application that requires login, the pattern that seems to work is using a login test that only runs in Selenium mode. When running in browser, +developers will already be logged in, so the test can be skipped. In Selenium however, a new browser instance is opened, so login is required. Here's an +example of a login test that does this: + +@codestart +test("login test", function () { + if (navigator.userAgent.match(/Rhino/)) { + S.open("/login") + S("#username").exists().click().type("superadmin") + S("#password").exists().click().type("password") + S(".submit input").exists().click() + + // wait for next page to load + S(".content").visible(function () { + ok(true, "logged in"); + }) + } else { + ok(true, "assuming you are logged in"); + } +}) +@codeend + +### 9. Use non-brittle selectors + +To make your tests as readable and future proof as possible, try to choose jQuery selectors that are both easy to understand and not likely to change. For example: + +#### Good selector + +@codestart +S(".contact:contains('Brian')"); +@codeend + +#### Bad selector + +@codestart +S(".contact:eq(4)"); +@codeend + +### 10. Use pseudocode + +Despite FuncUnit's easy to learn API, when you start to write a test, you're thinking in terms of user interactions, not jQuery selectors. So the easiest way to +write a test is to start with a method full of pseudocode, then fill in the selectors and commands. + +For example: + +@codestart +// click the top link +// wait for the edit form to appear +// click the first input, type Chicago +// click submit +// wait for the list to appear +@codeend + */ \ No newline at end of file diff --git a/browserid/static/funcunit/pages/selenium.js b/browserid/static/funcunit/pages/selenium.js new file mode 100644 index 0000000000000000000000000000000000000000..819661e267df4f7511d5ab47366c99ce29359e1e --- /dev/null +++ b/browserid/static/funcunit/pages/selenium.js @@ -0,0 +1,119 @@ +/** + +@page runselenium 3. Running Tests Via Selenium +@parent FuncUnit + +<h2>Automated Testing with Selenium</h2> +<p>FuncUnit has a command line mode, which allows you to run your tests as part of a checkin script or nightly build. +The Selenium server is used to automate opening browsers, and FuncUnit commands are sent to the test window via Selenium RC.</p> + +<p>The envjs script (written for both Windows and OS X/Linux), is used to load your test page (the +same one that runs tests in the browser) in Env.js, a simulated browser running on Rhino. The +test page recognizes its running in the Rhino context and issues commands to Selenium accordingly.</p> + +<p>Running these command line tests is simple:</p> +@codestart +my\path\to\envjs my\path\to\funcunit.html +@codeend +<p>Configuring settings for the command line mode will be covered next.</p> +<h3>Configuration</h3> +<p>FuncUnit loads a settings.js file every time it is runs in Selenium mode. This file defines +configuration that tells Selenium how to run. You can change which browsers run, their location, +the domain to serve from, and the speed of test execution.</p> +<p>FuncUnit looks first in the same directory as the funcunit page you're running tests from for +settings.js. For example if you're running FuncUnit like this:</p> +@codestart +funcunit\envjs mxui\combobox\funcunit.html +@codeend +<p>It will look first for mxui/combobox/settings.js.</p> +<p>Then it looks in its own root directory, where a default settings.js exists. +This is to allow you to create different settings for different projects.</p> +<h3>Setting Browsers</h3> +<p>FuncUnit.browsers is an array that defines which browsers Selenium opens and runs your tests in. +This is defined in settings.js. If this null it will default to a standard set of browsers for your OS +(FF and IE on Windows, FF on everything else). You populate it with strings like the following:</p> +@codestart +browsers: ["*firefox", "*iexplore", "*safari", "*googlechrome"] +@codeend + +To define a custom path to a browser, put this in the string following the browser name like this:</p> + +@codestart +browsers: ["*custom /path/to/my/browser"] +@codeend + +See the [http://release.seleniumhq.org/selenium-remote-control/0.9.0/doc/java/com/thoughtworks/selenium/DefaultSelenium.html#DefaultSelenium Selenium docs] for more information on customizing browsers and other settings.</p> + +## 64-bit Java + +Some users will find Selenium has trouble opening while using 64 bit java (on Windows). You will see an error like +Could not start Selenium session: Failed to start new browser session. This is because Selenium +looks in the 64-bit Program Files directory, and there is no Firefox there. To fix this, change +browsers to include the path like this: + +@codestart +FuncUnit.browsers = ["*firefox C:\\PROGRA~2\\MOZILL~1\\firefox.exe", "*iexplore"] +@codeend + +<h3>Filesystem for Faster Tests</h3> +<p>You might want to use envjs to open local funcunit pages, but test pages on your server. This is possible, you +just have to change FuncUnit.href or FuncUnit.jmvcRoot. This file can load locally while everything else is +using a server because it is a static file and loads static script files.</p> + +<p>Set jmvcRoot to point to the location you want your pages to load from, like this:</p> +@codestart +jmvcRoot: "localhost:8000" +@codeend + +<p>Then make sure your test paths contain // in them to signify something relative to the jmvcRoot. +For example, S.open("//funcunit/test/myapp.html") would open a page at +http://localhost:8000/funcunit/test/myapp.html.</p> + +<p>To load the command page from filesystem, start your test like you normally do:</p> +@codestart +funcunit\envjs path\to\funcunit.html +@codeend + +<h3>Running From Safari and Chrome</h3> +<p>Certain browsers, like Safari and Chrome, don't run Selenium tests from filesystem because +of security resrictions. To get around this you have to run pages served from a server. The +downside of this is the test takes longer to start up, compared to loading from filesystem.</p> +<p>To run served pages, you must 1) provide an absolute path in your envjs path and 2) provide an absolute path +in jmvcRoot.</p> +<p>For example, to run cookbook FuncUnit tests from Google Chrome, I'd set the browsers and jmvcRoot like this:</p> +@codestart + browsers: ["*googlechrome"], + jmvcRoot: "http://localhost:8000/framework/", +@codeend +<p>then I'd start up Selenium like this:</p> +@codestart +funcunit\envjs http://localhost:8000/framework/cookbook/funcunit.html +@codeend +<p>To run Safari 5 in Windows, you should use the safariproxy browser string like this:</p> +@codestart + browsers: ["*safariproxy"], +@codeend + +Mac Safari is just "*safari". + +<h3>Slow Mode</h3> +<p>You can slow down the amount of time between tests by setting FuncUnit.speed. By default, FuncUnit commands +in Selenium will run as soon as the previous command is complete. If you set FuncUnit.speed to "slow" this +becomes 500ms between commands. You may also provide a number of milliseconds. +Slow mode is useful while debugging.</p> + +<h2>Limitations</h2> +<ul> + <li>Selenium doesn't run Chrome/Opera/Safari on the filesystem.</li> +</ul> + +<h2>Troubleshooting</h2> + +<p>If you have trouble getting Selenium tests to run in IE, there are some settings that you can to change. First, disable the security settings for pages that run from the filesystem. To do this, open the Internet Options in IE and select the "Advanced" tab, and enable the option to "Allow active content to run in files on My Computer." This is what it looks like:</p> + +@image jmvc/images/iesecurity.png + +<p>You may also get an error that the popup blocker is enabled, which prevents the tests from running. It's actually not the popup blocker that causes this, but the fix is just as easy. Simply disable "Protected Mode" in IE, which is also in the Internet Options:</p> + +@image jmvc/images/iepopups.png + */ \ No newline at end of file diff --git a/browserid/static/funcunit/pages/setup.js b/browserid/static/funcunit/pages/setup.js new file mode 100644 index 0000000000000000000000000000000000000000..2fa89e170a57be4ea5bf261e746e46af9b19b2c4 --- /dev/null +++ b/browserid/static/funcunit/pages/setup.js @@ -0,0 +1,43 @@ +/** +@page setup 1. Getting Set Up +@parent FuncUnit + +## Getting Started with Generators + +This guide assumes you're using a full JavaScript download with Steal. The easiest way to get started +is to use generators to get a basic test in place. + +From a command line, cd to the root of the your JMVC directory and run: + +@codestart +./js jquery/generate/controller Company.Widget +@codeend + +This will create the following folder structure: + +@image jmvc/images/funcunitfolder.png + +<br />Open funcunit.html in a browser: + +@image jmvc/images/funcunithtml.png + +<br />If your popup blocker is off, a separate page (the application) opens in a separate window, an assertion runs, and your test passes. + +To add your own test, open widget_test.js and modify the existing test or add your own. + +Note that the jquery/generate/app or jquery/generate/plugin generators will create similar basic funcunit pages. + +## What's Actually Happening + +Funcunit.html is doing the following: + +1. Loading QUnit's CSS. +2. Loading steal.js and telling it to load widget_test.js as its top level JS file. +3. Adding the necessary HTML that QUnit needs. + +Steal.js loads first. It loads widget_test.js, which: + +1. Loads funcunit, and all its dependencies (including QUnit). +2. Defines a very basic test. + + */ \ No newline at end of file diff --git a/browserid/static/funcunit/pages/standalone.js b/browserid/static/funcunit/pages/standalone.js new file mode 100644 index 0000000000000000000000000000000000000000..5b118135d31be4810969a62aee9ab8374e4b674e --- /dev/null +++ b/browserid/static/funcunit/pages/standalone.js @@ -0,0 +1,73 @@ +/** + * +@page standalone Standalone FuncUnit +@parent FuncUnit +While FuncUnit is most often used as a JavaScriptMVC component, it can also be used +on its own. This guide will get you started with the standalone package. + +First [https://github.com/jupiterjs/funcunit/downloads download] the latest FuncUnit package. + +## Setup +Lets say you want to test pages/mypage.html and you've installed funcunit in test/funcunit.</br> +Steps: +<ul> +<li>Create a HTML file (pages/mypage_test.html) that loads qunit.css, +funcunit.js, and mypage_test.js. We'll create mypage_test.js in step #2. +@codestart html +<html> + <head> + <link href='../funcunit/<b>qunit.css</b>' + type='text/css' + rel='stylesheet' /> + <script src='../funcunit/<b>funcunit.js</b>' + type='text/javascript' ></script> + <script src='<b>mypage_test.js</b>' + type='text/javascript'></script> + <title>MyPage Test Suite</title> + </head> + <body> + <h1 id="qunit-header">MyPage Test Suite</h1> + <h2 id="qunit-banner"></h2> + <div id="qunit-testrunner-toolbar"></div> + <h2 id="qunit-userAgent"></h2> + <ol id="qunit-tests"></ol> + </body> +</html> +@codeend +</li> +<li>Create a JS file (pages/mypage_test.js) for your tests. The skeleton should like: +@codestart +module("APPNAME", { + setup: function() { + // opens the page you want to test + $.open("myPage.html"); + } +}) + +test("page has content", function(){ + ok( S("body *").size(), "There be elements in that there body") +}) +@codeend +</li> +<li>Open your html page (mytest.html) in a browser. Did it pass? If not check the paths. + +<div class='whisper'>P.S. Your page and test files don't have to be in the same folder; however, +on the filesystem, Firefox and Chrome don't let you access parent folders. We wanted the demo to work +without having to host these files.</div> + +</li> +<li>Now run your test in Selenium. In windows: +@codestart text +> envjs ../../pages/mypage_test.html +@codeend +In Linux / Mac: +@codestart text +> ./envjs ../../pages/mypage_test.html +@codeend + +This will run mytest.html on the filesystem. To run it served, just +pass in the url of your test page: <pre>envjs http://localhost/pages/mypage_test.html</pre>. + +</li> +</ul> + */ \ No newline at end of file diff --git a/browserid/static/funcunit/pages/writing.js b/browserid/static/funcunit/pages/writing.js new file mode 100644 index 0000000000000000000000000000000000000000..df60b2e0b50b104e48a5ff226b50fdea32b1ca4e --- /dev/null +++ b/browserid/static/funcunit/pages/writing.js @@ -0,0 +1,201 @@ +/** +@page writing 2. Writing FuncUnit Tests +@parent FuncUnit + + * <p>Writing tests is super easy and follows this pattern:</p> +<ol> + <li>Open a page with [FuncUnit.static.open S.open]. +@codestart +S.open("//myapp/myapp.html") +@codeend + </li> + <li>Do some things +@codestart +//click something +S('#myButton').click() + +//type something +S('#myInput').type("hello") +@codeend + </li> + + <li>Wait for the page to change: +@codestart +//Wait until it is visible +S('#myMenu').visible() + +//wait until something exists +S('#myArea').exists() +@codeend + </li> + <li>Check your page in a callback: +@codestart +S('#myMenu').visible(function(){ + //check that offset is right + equals(S('#myMenu').offset().left, 500, + "menu is in the right spot"); +}) +@codeend + </li> +</ol> +<h2>Actions, Waits, and Getters</h2> +<p>FuncUnit supports three types of commands: asynchronous actions and waits, +and synchronous getters.</p> +<p><b>Actions</b> are used to simulate user behavior such as clicking, typing, moving the mouse.</p> +<p><b>Waits</b> are used to pause the test script until a condition has been met.</p> +<p><b>Getters</b> are used to get information about elements in the page</p> +<p>Typically, a test looks like a series of action and wait commands followed by qUnit test of +the result of a getter command. Getter commands are almost always in a action or wait callback.</p> +<h3>Actions</h3> +Actions simulate user behavior. FuncUnit provides the following actions: + + - <code>[FuncUnit.static.open open]</code> - opens a page. + - <code>[FuncUnit.prototype.click click]</code> - clicks an element (mousedown, mouseup, click). + - <code>[FuncUnit.prototype.dblclick dblclick]</code> - two clicks followed by a dblclick. + - <code>[FuncUnit.prototype.rightClick rightClick]</code> - a right mousedown, mouseup, and contextmenu. + - <code>[FuncUnit.prototype.type type]</code> - types characters into an element. + - <code>[FuncUnit.prototype.move move]</code> - mousemove, mouseover, and mouseouts from one element to another. + - <code>[FuncUnit.prototype.drag drag]</code> - a drag motion from one element to another. + - <code>[FuncUnit.prototype.scroll scroll]</code> - scrolls an element. + +Actions run asynchronously, meaning they do not complete all their events immediately. +However, each action is queued so that you can write actions (and waits) linearly. + +The following might simulate typing and resizing a "resizable" textarea plugin: + +@codestart +S.open('resizableTextarea.html'); + +S('textarea').click().type("Hello World"); + +S('.resizer').drag("+20 +20"); +@codeend + +### Getters + +Getters are used to test the conditions of the page. Most getter commands correspond to a jQuery +method of the same name. The following getters are provided: + +<table style='font-family: monospace'> +<tr> + <th colspan='2'>Dimensions</th> <th>Attributes</th> <th>Position</th> <th>Selector</th> <th>Style</th> +</tr> +<tr> + <td>[FuncUnit.prototype.width width]</td> + <td>[FuncUnit.prototype.height height]</td> + <td>[FuncUnit.prototype.attr attr]</td> + <td>[FuncUnit.prototype.position position]</td> + <td>[FuncUnit.prototype.size size]</td> + <td>[FuncUnit.prototype.css css]</td> +</tr> +<tr> + <td>[FuncUnit.prototype.innerWidth innerWidth]</td> + <td>[FuncUnit.prototype.innerHeight innerHeight]</td> + <td>[FuncUnit.prototype.hasClass hasClass]</td> + <td>[FuncUnit.prototype.offset offset]</td> + <td>[FuncUnit.prototype.exists exists]</td> + <td>[FuncUnit.prototype.visible visible]</td> +</tr> +<tr> + <td>[FuncUnit.prototype.outerWidth outerWidth]</td> + <td>[FuncUnit.prototype.outerHeight outerHeight]</td> + <td>[FuncUnit.prototype.val val]</td> + <td>[FuncUnit.prototype.scrollLeft scrollLeft]</td> + <td>[FuncUnit.prototype.missing missing]</td> + <td>[FuncUnit.prototype.invisible invisible]</td> +</tr> +<tr> + <td colspan='2'></td> + <td>[FuncUnit.prototype.text text]</td> + <td>[FuncUnit.prototype.scrollTop scrollTop]</td> +</tr> +<tr> + <td colspan='2'></td> + <td>[FuncUnit.prototype.html html]</td> +</tr> +</table> + +Since getters run synchronously, it's important that they happen after the action or wait command completes. +This is why getters are typically found in an action or wait command's callback: + +The following performs a drag, then checks that the textarea is 20 pixels taller after the drag. + +@codestart +S.open('resizableTextarea.html'); + +var txtarea = S('textarea'), //save textarea reference + startingWidth = txtarea.width(), // save references to width and height + startingHeight = txtarea.height(); + +S('.resizer').drag("+20 +20", function(){ + equals(txtarea.width(), + startingWidth, + "width stays the same"); + + equals(txtarea.height(), + startingHeight+20, + "height got bigger"); +}); +@codeend + +### Waits + +Waits are used to wait for a specific condition to be met before continuing to the next wait or +action command. Like actions, waits execute asynchronously. They can be given a callback that runs after +their wait condition is met. + +#### Wait conditions + +Every getter commands can become a wait command when given a check value or function. +For example, the following waits until the width of an element is 200 pixels and tests its offset. + +@codestart +var sm = S("#sliderMenu"); +sm.width( 200, function(){ + + var offset = sm.offset(); + equals( offset.left, 200) + equals( offset.top, 200) +}) +@codeend + +#### Wait functions + +You can also provide a test function that when true, continues to the next action or wait command. +The following is equivalent to the previous example: + +@codestart +var sm = S("#sliderMenu"); + +sm.width( + function( width ) { + return width == 200; + }, + function(){ + var offset = sm.offset(); + equals( offset.left, 200) + equals( offset.top, 200) + } +) +@codeend + +<div class='whisper'>Notice that the test function is provided the width of the element to use to check.</div> + +#### Timeouts + +By default, wait commands will wait a 10s timeout period. If the condition isn't true after that time, the test will fail. You +can provide your own timeout for each wait condition as the parameter after the wait condition. For example, the following will check +if "#trigger" contains "I was triggered" for 5 seconds before failing the test. + +@codestart +("#trigger").text("I was triggered", 5000) +@codeend + +#### Timer waits + +In addition to all the jQuery-like wait functions, FuncUnit provides [FuncUnit.static.wait S.wait], which waits a timeout before continuing. +This function should be used with CAUTION. You should almost never need it, because its presence means brittle tests that depend on unreliable +timing conditions. Much better than a time based wait is a wait that depends on a page condition (like a menu element appearing). + + + */ \ No newline at end of file diff --git a/browserid/static/funcunit/qunit.html b/browserid/static/funcunit/qunit.html new file mode 100644 index 0000000000000000000000000000000000000000..ab5a76759674f75d295ad635b4d37236af9e9105 --- /dev/null +++ b/browserid/static/funcunit/qunit.html @@ -0,0 +1,22 @@ +<html> + <head> + <link rel="stylesheet" type="text/css" href="../funcunit/qunit/qunit.css" /> + <title>QUnit Test</title> + <style> + body { + margin: 0px; padding: 0px; + } + </style> + <script type='text/javascript' src='../steal/steal.js?steal[app]=funcunit/test/qunit'></script> + </head> + <body> + + <h1 id="qunit-header">funcunit Test Suite</h1> + <h2 id="qunit-banner"></h2> + <div id="qunit-testrunner-toolbar"></div> + <h2 id="qunit-userAgent"></h2> + <div id="test-content"></div> + <ol id="qunit-tests"></ol> + <div id="qunit-test-area"></div> + </body> +</html> \ No newline at end of file diff --git a/browserid/static/funcunit/qunit/qunit.css b/browserid/static/funcunit/qunit/qunit.css index b3c6db5237a285bc2d9357c26da8c7b9836cf81b..5714bf4a597e8d13ac34efdb7153ea3430f50f80 100644 --- a/browserid/static/funcunit/qunit/qunit.css +++ b/browserid/static/funcunit/qunit/qunit.css @@ -1,225 +1,119 @@ -/** - * QUnit - A JavaScript Unit Testing Framework - * - * http://docs.jquery.com/QUnit - * - * Copyright (c) 2011 John Resig, Jörn Zaefferer - * Dual licensed under the MIT (MIT-LICENSE.txt) - * or GPL (GPL-LICENSE.txt) licenses. - */ -/** Font Family and Sizes */ +ol#qunit-tests { + font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial; + margin:0; + padding:0; + list-style-position:inside; -#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult { - font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif; + font-size: smaller; } - -#qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } -#qunit-tests { font-size: smaller; } - - -/** Resets */ - -#qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult { - margin: 0; - padding: 0; -} - - -/** Header */ - -#qunit-header { - padding: 0.5em 0 0.5em 1em; - - color: #8699a4; - background-color: #0d3349; - - font-size: 1.5em; - line-height: 1em; - font-weight: normal; - - border-radius: 15px 15px 0 0; - -moz-border-radius: 15px 15px 0 0; - -webkit-border-top-right-radius: 15px; - -webkit-border-top-left-radius: 15px; +ol#qunit-tests li{ + padding:0.4em 0.5em 0.4em 2.5em; + border-bottom:1px solid #fff; + font-size:small; + list-style-position:inside; } - -#qunit-header a { - text-decoration: none; - color: #c2ccd1; -} - -#qunit-header a:hover, -#qunit-header a:focus { - color: #fff; -} - -#qunit-banner { - height: 5px; -} - -#qunit-testrunner-toolbar { - padding: 0.5em 0 0.5em 2em; - color: #5E740B; - background-color: #eee; -} - -#qunit-userAgent { - padding: 0.5em 0 0.5em 2.5em; - background-color: #2b81af; - color: #fff; - text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; -} - - -/** Tests: Pass/Fail */ - -#qunit-tests { - list-style-position: inside; -} - -#qunit-tests li { - padding: 0.4em 0.5em 0.4em 2.5em; - border-bottom: 1px solid #fff; - list-style-position: inside; -} - -#qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running { - display: none; -} - -#qunit-tests li strong { - cursor: pointer; -} - -#qunit-tests li a { - padding: 0.5em; - color: #c2ccd1; - text-decoration: none; -} -#qunit-tests li a:hover, -#qunit-tests li a:focus { - color: #000; -} - -#qunit-tests ol { - margin-top: 0.5em; - padding: 0.5em; - - background-color: #fff; - - border-radius: 15px; - -moz-border-radius: 15px; - -webkit-border-radius: 15px; - +ol#qunit-tests li ol{ box-shadow: inset 0px 2px 13px #999; -moz-box-shadow: inset 0px 2px 13px #999; -webkit-box-shadow: inset 0px 2px 13px #999; + margin-top:0.5em; + margin-left:0; + padding:0.5em; + background-color:#fff; + border-radius:15px; + -moz-border-radius: 15px; + -webkit-border-radius: 15px; } - -#qunit-tests table { - border-collapse: collapse; - margin-top: .2em; -} - -#qunit-tests th { - text-align: right; - vertical-align: top; - padding: 0 .5em 0 0; -} - -#qunit-tests td { - vertical-align: top; -} - -#qunit-tests pre { - margin: 0; - white-space: pre-wrap; - word-wrap: break-word; -} - -#qunit-tests del { - background-color: #e0f2be; - color: #374e0c; - text-decoration: none; -} - -#qunit-tests ins { - background-color: #ffcaca; - color: #500; - text-decoration: none; -} - -/*** Test Counts */ - -#qunit-tests b.counts { color: black; } -#qunit-tests b.passed { color: #5E740B; } -#qunit-tests b.failed { color: #710909; } - -#qunit-tests li li { - margin: 0.5em; - padding: 0.4em 0.5em 0.4em 0.5em; - background-color: #fff; - border-bottom: none; +ol#qunit-tests li li{ + border-bottom:none; + margin:0.5em; + background-color:#fff; list-style-position: inside; + padding:0.4em 0.5em 0.4em 0.5em; +} + +ol#qunit-tests li li.pass{ + border-left:26px solid #C6E746; + background-color:#fff; + color:#5E740B; + } +ol#qunit-tests li li.fail{ + border-left:26px solid #EE5757; + background-color:#fff; + color:#710909; +} +ol#qunit-tests li.pass{ + background-color:#D2E0E6; + color:#528CE0; +} +ol#qunit-tests li.fail{ + background-color:#EE5757; + color:#000; +} +ol#qunit-tests li strong { + cursor:pointer; +} +h1#qunit-header{ + background-color:#0d3349; + margin:0; + padding:0.5em 0 0.5em 1em; + color:#fff; + font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial; + border-top-right-radius:15px; + border-top-left-radius:15px; + -moz-border-radius-topright:15px; + -moz-border-radius-topleft:15px; + -webkit-border-top-right-radius:15px; + -webkit-border-top-left-radius:15px; + text-shadow: rgba(0, 0, 0, 0.5) 4px 4px 1px; +} +h2#qunit-banner{ + font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial; + height:5px; + margin:0; + padding:0; +} +h2#qunit-banner.qunit-pass{ + background-color:#C6E746; +} +h2#qunit-banner.qunit-fail, #qunit-testrunner-toolbar { + background-color:#EE5757; } - -/*** Passing Styles */ - -#qunit-tests li li.pass { - color: #5E740B; - background-color: #fff; - border-left: 26px solid #C6E746; -} - -#qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } -#qunit-tests .pass .test-name { color: #366097; } - -#qunit-tests .pass .test-actual, -#qunit-tests .pass .test-expected { color: #999999; } - -#qunit-banner.qunit-pass { background-color: #C6E746; } - -/*** Failing Styles */ - -#qunit-tests li li.fail { - color: #710909; - background-color: #fff; - border-left: 26px solid #EE5757; -} - -#qunit-tests > li:last-child { - border-radius: 0 0 15px 15px; - -moz-border-radius: 0 0 15px 15px; - -webkit-border-bottom-right-radius: 15px; - -webkit-border-bottom-left-radius: 15px; -} - -#qunit-tests .fail { color: #000000; background-color: #EE5757; } -#qunit-tests .fail .test-name, -#qunit-tests .fail .module-name { color: #000000; } - -#qunit-tests .fail .test-actual { color: #EE5757; } -#qunit-tests .fail .test-expected { color: green; } - -#qunit-banner.qunit-fail { background-color: #EE5757; } - - -/** Result */ - -#qunit-testresult { - padding: 0.5em 0.5em 0.5em 2.5em; - - color: #2b81af; - background-color: #D2E0E6; - - border-bottom: 1px solid white; -} - -/** Fixture */ - -#qunit-fixture { - position: absolute; - top: -10000px; - left: -10000px; +#qunit-testrunner-toolbar { + font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial; + padding:0; + /*width:80%;*/ + padding:0em 0 0.5em 2em; + font-size: small; +} +h2#qunit-userAgent { + font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial; + background-color:#2b81af; + margin:0; + padding:0; + color:#fff; + font-size: small; + padding:0.5em 0 0.5em 2.5em; + text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; } +p#qunit-testresult{ + font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial; + margin:0; + font-size: small; + color:#2b81af; + border-bottom-right-radius:15px; + border-bottom-left-radius:15px; + -moz-border-radius-bottomright:15px; + -moz-border-radius-bottomleft:15px; + -webkit-border-bottom-right-radius:15px; + -webkit-border-bottom-left-radius:15px; + background-color:#D2E0E6; + padding:0.5em 0.5em 0.5em 2.5em; +} +strong b.fail{ + color:#710909; + } +strong b.pass{ + color:#5E740B; + } diff --git a/browserid/static/funcunit/qunit/qunit.js b/browserid/static/funcunit/qunit/qunit.js index e00cca902b44fd2c8854e5f680ac6b78f2080a1d..16c5fdabd8e7266a6228a16eec9ae42befee65b2 100644 --- a/browserid/static/funcunit/qunit/qunit.js +++ b/browserid/static/funcunit/qunit/qunit.js @@ -1,303 +1,288 @@ -/** +/* @documentjs-ignore * QUnit - A JavaScript Unit Testing Framework + * + * http://docs.jquery.com/QUnit * + * Copyright (c) 2009 John Resig, J�rn Zaefferer + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + */ +steal.then(function(){ +(function(window) { + +/* + * QUnit - A JavaScript Unit Testing Framework + * * http://docs.jquery.com/QUnit * - * Copyright (c) 2011 John Resig, Jörn Zaefferer + * Copyright (c) 2009 John Resig, Jörn Zaefferer * Dual licensed under the MIT (MIT-LICENSE.txt) - * or GPL (GPL-LICENSE.txt) licenses. + * and GPL (GPL-LICENSE.txt) licenses. */ (function(window) { -var defined = { - setTimeout: typeof window.setTimeout !== "undefined", - sessionStorage: (function() { - try { - return !!sessionStorage.getItem; - } catch(e){ - return false; - } - })() -}; - -var testId = 0; +var QUnit = { -var Test = function(name, testName, expected, testEnvironmentArg, async, callback) { - this.name = name; - this.testName = testName; - this.expected = expected; - this.testEnvironmentArg = testEnvironmentArg; - this.async = async; - this.callback = callback; - this.assertions = []; -}; -Test.prototype = { + // Initialize the configuration options init: function() { - var tests = id("qunit-tests"); - if (tests) { - var b = document.createElement("strong"); - b.innerHTML = "Running " + this.name; - var li = document.createElement("li"); - li.appendChild( b ); - li.className = "running"; - li.id = this.id = "test-output" + testId++; - tests.appendChild( li ); - } - }, - setup: function() { - if (this.module != config.previousModule) { - if ( config.previousModule ) { - QUnit.moduleDone( { - name: config.previousModule, - failed: config.moduleStats.bad, - passed: config.moduleStats.all - config.moduleStats.bad, - total: config.moduleStats.all - } ); - } - config.previousModule = this.module; - config.moduleStats = { all: 0, bad: 0 }; - QUnit.moduleStart( { - name: this.module - } ); - } + extend(config, { + stats: { all: 0, bad: 0 }, + moduleStats: { all: 0, bad: 0 }, + started: +new Date, + updateRate: 1000, + blocking: false, + autostart: true, + autorun: false, + assertions: [], + filters: [], + queue: [] + }); + + var tests = id("qunit-tests"), + banner = id("qunit-banner"), + result = id("qunit-testresult"); - config.current = this; - this.testEnvironment = extend({ - setup: function() {}, - teardown: function() {} - }, this.moduleTestEnvironment); - if (this.testEnvironmentArg) { - extend(this.testEnvironment, this.testEnvironmentArg); + if ( tests ) { + tests.innerHTML = ""; } - QUnit.testStart( { - name: this.testName - } ); + if ( banner ) { + banner.className = ""; + } - // allow utility functions to access the current test environment - // TODO why?? - QUnit.current_testEnvironment = this.testEnvironment; + if ( result ) { + result.parentNode.removeChild( result ); + } + }, + + // call on start of module test to prepend name to all tests + module: function(name, testEnvironment) { + config.currentModuleName = name; - try { - if ( !config.pollution ) { - saveGlobal(); + synchronize(function() { + if ( config.currentModule ) { + QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all ); } - this.testEnvironment.setup.call(this.testEnvironment); - } catch(e) { - QUnit.ok( false, "Setup failed on " + this.testName + ": " + e.message ); - } + config.currentModule = name; + config.moduleTestEnvironment = testEnvironment; + config.moduleStats = { all: 0, bad: 0 }; + QUnit.moduleStart( name, testEnvironment ); + }, true); }, - run: function() { - if ( this.async ) { - QUnit.stop(); - } - if ( config.notrycatch ) { - this.callback.call(this.testEnvironment); - return; - } - try { - this.callback.call(this.testEnvironment); - } catch(e) { - fail("Test " + this.testName + " died, exception and test follows", e, this.callback); - QUnit.ok( false, "Died on test #" + (this.assertions.length + 1) + ": " + e.message + " - " + QUnit.jsDump.parse(e) ); - // else next test will carry the responsibility - saveGlobal(); - - // Restart the tests if they're blocking - if ( config.blocking ) { - start(); - } + asyncTest: function(testName, expected, callback) { + if ( arguments.length === 2 ) { + callback = expected; + expected = 0; } + + QUnit.test(testName, expected, callback, true); }, - teardown: function() { - try { - this.testEnvironment.teardown.call(this.testEnvironment); - checkPollution(); - } catch(e) { - QUnit.ok( false, "Teardown failed on " + this.testName + ": " + e.message ); + + test: function(testName, expected, callback, async) { + var name = testName, testEnvironment, testEnvironmentArg; + + if ( arguments.length === 2 ) { + callback = expected; + expected = null; } - }, - finish: function() { - if ( this.expected && this.expected != this.assertions.length ) { - QUnit.ok( false, "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run" ); + // is 2nd argument a testEnvironment? + if ( expected && typeof expected === 'object') { + testEnvironmentArg = expected; + expected = null; } - var good = 0, bad = 0, - tests = id("qunit-tests"); + if ( config.currentModuleName ) { + name = config.currentModuleName + " module: " + name; + } - config.stats.all += this.assertions.length; - config.moduleStats.all += this.assertions.length; + if ( !validTest(name) ) { + return; + } - if ( tests ) { - var ol = document.createElement("ol"); + synchronize(function() { - for ( var i = 0; i < this.assertions.length; i++ ) { - var assertion = this.assertions[i]; + testEnvironment = extend({ + setup: function() {}, + teardown: function() {} + }, config.moduleTestEnvironment); + if (testEnvironmentArg) { + extend(testEnvironment,testEnvironmentArg); + } + QUnit.testStart( testName, testEnvironment ); + + // allow utility functions to access the current test environment + QUnit.current_testEnvironment = testEnvironment; + + config.assertions = []; + config.expected = expected; + + var tests = id("qunit-tests"); + if (tests) { + var b = document.createElement("strong"); + b.innerHTML = "Running " + name; var li = document.createElement("li"); - li.className = assertion.result ? "pass" : "fail"; - li.innerHTML = assertion.message || (assertion.result ? "okay" : "failed"); - ol.appendChild( li ); - - if ( assertion.result ) { - good++; - } else { - bad++; - config.stats.bad++; - config.moduleStats.bad++; - } + li.appendChild( b ); + li.id = "current-test-output"; + tests.appendChild( li ) } - // store result when possible - if ( QUnit.config.reorder && defined.sessionStorage ) { - if (bad) { - sessionStorage.setItem("qunit-" + this.module + "-" + this.testName, bad); - } else { - sessionStorage.removeItem("qunit-" + this.module + "-" + this.testName); + try { + if ( !config.pollution ) { + saveGlobal(); } - } - if (bad == 0) { - ol.style.display = "none"; + testEnvironment.setup.call(testEnvironment); + } catch(e) { + QUnit.ok( false, "Setup failed on " + name + ": " + e.message ); } + }, true); - var b = document.createElement("strong"); - b.innerHTML = this.name + " <b class='counts'>(<b class='failed'>" + bad + "</b>, <b class='passed'>" + good + "</b>, " + this.assertions.length + ")</b>"; - - var a = document.createElement("a"); - a.innerHTML = "Rerun"; - a.href = QUnit.url({ filter: getText([b]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") }); + synchronize(function() { + if ( async ) { + QUnit.stop(); + } - addEvent(b, "click", function() { - var next = b.nextSibling.nextSibling, - display = next.style.display; - next.style.display = display === "none" ? "block" : "none"; - }); + try { + callback.call(testEnvironment); + } catch(e) { + fail("Test " + name + " died, exception and test follows", e, callback); + QUnit.ok( false, "Died on test #" + (config.assertions.length + 1) + ": " + e.message ); + // else next test will carry the responsibility + saveGlobal(); - addEvent(b, "dblclick", function(e) { - var target = e && e.target ? e.target : window.event.srcElement; - if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) { - target = target.parentNode; - } - if ( window.location && target.nodeName.toLowerCase() === "strong" ) { - window.location = QUnit.url({ filter: getText([target]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") }); + // Restart the tests if they're blocking + if ( config.blocking ) { + start(); } - }); + } + }, true); - var li = id(this.id); - li.className = bad ? "fail" : "pass"; - li.removeChild( li.firstChild ); - li.appendChild( b ); - li.appendChild( a ); - li.appendChild( ol ); + synchronize(function() { + try { + checkPollution(); + testEnvironment.teardown.call(testEnvironment); + } catch(e) { + QUnit.ok( false, "Teardown failed on " + name + ": " + e.message ); + } + }, true); - } else { - for ( var i = 0; i < this.assertions.length; i++ ) { - if ( !this.assertions[i].result ) { - bad++; - config.stats.bad++; - config.moduleStats.bad++; - } + synchronize(function() { + try { + QUnit.reset(); + } catch(e) { + fail("reset() failed, following Test " + name + ", exception and reset fn follows", e, reset); } - } - try { - QUnit.reset(); - } catch(e) { - fail("reset() failed, following Test " + this.testName + ", exception and reset fn follows", e, QUnit.reset); - } + if ( config.expected && config.expected != config.assertions.length ) { + QUnit.ok( false, "Expected " + config.expected + " assertions, but " + config.assertions.length + " were run" ); + } - QUnit.testDone( { - name: this.testName, - failed: bad, - passed: this.assertions.length - bad, - total: this.assertions.length - } ); - }, + var good = 0, bad = 0, + tests = id("qunit-tests"); - queue: function() { - var test = this; - synchronize(function() { - test.init(); - }); - function run() { - // each of these can by async - synchronize(function() { - test.setup(); - }); - synchronize(function() { - test.run(); - }); - synchronize(function() { - test.teardown(); - }); - synchronize(function() { - test.finish(); - }); - } - // defer when previous test run passed, if storage is available - var bad = QUnit.config.reorder && defined.sessionStorage && +sessionStorage.getItem("qunit-" + this.module + "-" + this.testName); - if (bad) { - run(); - } else { - synchronize(run); - }; - } + config.stats.all += config.assertions.length; + config.moduleStats.all += config.assertions.length; -}; + if ( tests ) { + var ol = document.createElement("ol"); + ol.style.display = "none"; -var QUnit = { + for ( var i = 0; i < config.assertions.length; i++ ) { + var assertion = config.assertions[i]; - // call on start of module test to prepend name to all tests - module: function(name, testEnvironment) { - config.currentModule = name; - config.currentModuleTestEnviroment = testEnvironment; - }, + var li = document.createElement("li"); + li.className = assertion.result ? "pass" : "fail"; + li.appendChild(document.createTextNode(assertion.message || "(no message)")); + ol.appendChild( li ); - asyncTest: function(testName, expected, callback) { - if ( arguments.length === 2 ) { - callback = expected; - expected = 0; - } + if ( assertion.result ) { + good++; + } else { + bad++; + config.stats.bad++; + config.moduleStats.bad++; + } + } - QUnit.test(testName, expected, callback, true); - }, + var b = document.createElement("strong"); + b.innerHTML = name + " <b style='color:black;'>(<b class='fail'>" + bad + "</b>, <b class='pass'>" + good + "</b>, " + config.assertions.length + ")</b>"; + + addEvent(b, "click", function() { + var next = b.nextSibling, display = next.style.display; + next.style.display = display === "none" ? "block" : "none"; + }); + + addEvent(b, "dblclick", function(e) { + var target = e && e.target ? e.target : window.event.srcElement; + if ( target.nodeName.toLowerCase() === "strong" ) { + var text = "", node = target.firstChild; + + while ( node.nodeType === 3 ) { + text += node.nodeValue; + node = node.nextSibling; + } - test: function(testName, expected, callback, async) { - var name = '<span class="test-name">' + testName + '</span>', testEnvironmentArg; + text = text.replace(/(^\s*|\s*$)/g, ""); - if ( arguments.length === 2 ) { - callback = expected; - expected = null; - } - // is 2nd argument a testEnvironment? - if ( expected && typeof expected === 'object') { - testEnvironmentArg = expected; - expected = null; - } + if ( window.location ) { + window.location.href = window.location.href.match(/^(.+?)(\?.*)?$/)[1] + "?" + encodeURIComponent(text); + } + } + }); - if ( config.currentModule ) { - name = '<span class="module-name">' + config.currentModule + "</span>: " + name; - } + var li = id("current-test-output"); + li.id = ""; + li.className = bad ? "fail" : "pass"; + li.removeChild( li.firstChild ); + li.appendChild( b ); + li.appendChild( ol ); + + if ( bad ) { + var toolbar = id("qunit-testrunner-toolbar"); + if ( toolbar ) { + toolbar.style.display = "block"; + id("qunit-filter-pass").disabled = null; + id("qunit-filter-missing").disabled = null; + } + } - if ( !validTest(config.currentModule + ": " + testName) ) { - return; - } + } else { + for ( var i = 0; i < config.assertions.length; i++ ) { + if ( !config.assertions[i].result ) { + bad++; + config.stats.bad++; + config.moduleStats.bad++; + } + } + } - var test = new Test(name, testName, expected, testEnvironmentArg, async, callback); - test.module = config.currentModule; - test.moduleTestEnvironment = config.currentModuleTestEnviroment; - test.queue(); - }, + QUnit.testDone( testName, bad, config.assertions.length ); + if ( !window.setTimeout && !config.queue.length ) { + done(); + } + }, true); + + if ( window.setTimeout && !config.doneTimer ) { + config.doneTimer = window.setTimeout(function(){ + if ( !config.queue.length ) { + done(); + } else { + synchronize( done ); + } + }, 13); + } + }, + /** * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through. */ expect: function(asserts) { - config.current.expected = asserts; + config.expected = asserts; }, /** @@ -305,15 +290,10 @@ var QUnit = { * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); */ ok: function(a, msg) { - a = !!a; - var details = { - result: a, - message: msg - }; - msg = escapeHtml(msg); - QUnit.log(details); - config.current.assertions.push({ - result: a, + QUnit.log(a, msg); + + config.assertions.push({ + result: !!a, message: msg }); }, @@ -331,74 +311,32 @@ var QUnit = { * @param String message (optional) */ equal: function(actual, expected, message) { - QUnit.push(expected == actual, actual, expected, message); + push(expected == actual, actual, expected, message); }, notEqual: function(actual, expected, message) { - QUnit.push(expected != actual, actual, expected, message); + push(expected != actual, actual, expected, message); }, - + deepEqual: function(actual, expected, message) { - QUnit.push(QUnit.equiv(actual, expected), actual, expected, message); + push(QUnit.equiv(actual, expected), actual, expected, message); }, notDeepEqual: function(actual, expected, message) { - QUnit.push(!QUnit.equiv(actual, expected), actual, expected, message); + push(!QUnit.equiv(actual, expected), actual, expected, message); }, strictEqual: function(actual, expected, message) { - QUnit.push(expected === actual, actual, expected, message); + push(expected === actual, actual, expected, message); }, notStrictEqual: function(actual, expected, message) { - QUnit.push(expected !== actual, actual, expected, message); + push(expected !== actual, actual, expected, message); }, - - raises: function(block, expected, message) { - var actual, ok = false; - - if (typeof expected === 'string') { - message = expected; - expected = null; - } - - try { - block(); - } catch (e) { - actual = e; - } - - if (actual) { - // we don't want to validate thrown error - if (!expected) { - ok = true; - // expected is a regexp - } else if (QUnit.objectType(expected) === "regexp") { - ok = expected.test(actual); - // expected is a constructor - } else if (actual instanceof expected) { - ok = true; - // expected is a validation function which returns true is validation passed - } else if (expected.call({}, actual) === true) { - ok = true; - } - } - - QUnit.ok(ok, message); - }, - + start: function() { - config.semaphore--; - if (config.semaphore > 0) { - // don't start until equal number of stop-calls - return; - } - if (config.semaphore < 0) { - // ignore if start is called more often then stop - config.semaphore = 0; - } // A slight delay, to avoid any current callbacks - if ( defined.setTimeout ) { + if ( window.setTimeout ) { window.setTimeout(function() { if ( config.timeout ) { clearTimeout(config.timeout); @@ -412,139 +350,45 @@ var QUnit = { process(); } }, - + stop: function(timeout) { - config.semaphore++; config.blocking = true; - if ( timeout && defined.setTimeout ) { - clearTimeout(config.timeout); + if ( timeout && window.setTimeout ) { config.timeout = window.setTimeout(function() { QUnit.ok( false, "Test timed out" ); QUnit.start(); }, timeout); } - } -}; - -// Backwards compatibility, deprecated -QUnit.equals = QUnit.equal; -QUnit.same = QUnit.deepEqual; - -// Maintain internal state -var config = { - // The queue of tests to run - queue: [], - - // block until document ready - blocking: true, - - // by default, run previously failed tests first - // very useful in combination with "Hide passed tests" checked - reorder: true, - - noglobals: false, - notrycatch: false -}; - -// Load paramaters -(function() { - var location = window.location || { search: "", protocol: "file:" }, - params = location.search.slice( 1 ).split( "&" ), - length = params.length, - urlParams = {}, - current; - - if ( params[ 0 ] ) { - for ( var i = 0; i < length; i++ ) { - current = params[ i ].split( "=" ); - current[ 0 ] = decodeURIComponent( current[ 0 ] ); - // allow just a key to turn on a flag, e.g., test.html?noglobals - current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true; - urlParams[ current[ 0 ] ] = current[ 1 ]; - if ( current[ 0 ] in config ) { - config[ current[ 0 ] ] = current[ 1 ]; - } - } - } - - QUnit.urlParams = urlParams; - config.filter = urlParams.filter; - - // Figure out if we're running the tests from a server or not - QUnit.isLocal = !!(location.protocol === 'file:'); -})(); - -// Expose the API as global variables, unless an 'exports' -// object exists, in that case we assume we're in CommonJS -if ( typeof exports === "undefined" || typeof require === "undefined" ) { - extend(window, QUnit); - window.QUnit = QUnit; -} else { - extend(exports, QUnit); - exports.QUnit = QUnit; -} - -// define these after exposing globals to keep them in these QUnit namespace only -extend(QUnit, { - config: config, - - // Initialize the configuration options - init: function() { - extend(config, { - stats: { all: 0, bad: 0 }, - moduleStats: { all: 0, bad: 0 }, - started: +new Date, - updateRate: 1000, - blocking: false, - autostart: true, - autorun: false, - filter: "", - queue: [], - semaphore: 0 - }); - - var tests = id( "qunit-tests" ), - banner = id( "qunit-banner" ), - result = id( "qunit-testresult" ); - - if ( tests ) { - tests.innerHTML = ""; - } - - if ( banner ) { - banner.className = ""; - } - - if ( result ) { - result.parentNode.removeChild( result ); - } - - if ( tests ) { - result = document.createElement( "p" ); - result.id = "qunit-testresult"; - result.className = "result"; - tests.parentNode.insertBefore( result, tests ); - result.innerHTML = 'Running...<br/> '; - } }, - + /** * Resets the test setup. Useful for tests that modify the DOM. - * - * If jQuery is available, uses jQuery's html(), otherwise just innerHTML. */ reset: function() { if ( window.jQuery ) { - jQuery( "#qunit-fixture" ).html( config.fixture ); - } else { - var main = id( 'qunit-fixture' ); - if ( main ) { - main.innerHTML = config.fixture; - } + jQuery("#main").html( config.fixture ); + jQuery.event.global = {}; + jQuery.ajaxSettings = extend({}, config.ajaxSettings); } }, - + restart : function(){ + config.currentModule = undefined; + this.init(); + for(var i =0; i < config.cachelist.length; i++){ + synchronize(config.cachelist[i]); + } + if (window.setTimeout && !config.doneTimer) { + config.doneTimer = window.setTimeout(function(){ + if (!config.queue.length) { + done(); + } + else { + synchronize(done); + } + }, 13); + } + }, /** * Trigger an event on an element. * @@ -564,115 +408,77 @@ extend(QUnit, { elem.fireEvent("on"+type); } }, - + // Safe object type checking is: function( type, obj ) { - return QUnit.objectType( obj ) == type; + return Object.prototype.toString.call( obj ) === "[object "+ type +"]"; }, + + // Logging callbacks + done: function(failures, total) {}, + log: function(result, message) {}, + testStart: function(name, testEnvironment) {}, + testDone: function(name, failures, total) {}, + moduleStart: function(name, testEnvironment) {}, + moduleDone: function(name, failures, total) {} +}; - objectType: function( obj ) { - if (typeof obj === "undefined") { - return "undefined"; - - // consider: typeof null === object - } - if (obj === null) { - return "null"; - } +// Backwards compatibility, deprecated +QUnit.equals = QUnit.equal; +QUnit.same = QUnit.deepEqual; - var type = Object.prototype.toString.call( obj ) - .match(/^\[object\s(.*)\]$/)[1] || ''; +// Maintain internal state +var config = { + // The queue of tests to run + queue: [], - switch (type) { - case 'Number': - if (isNaN(obj)) { - return "nan"; - } else { - return "number"; - } - case 'String': - case 'Boolean': - case 'Array': - case 'Date': - case 'RegExp': - case 'Function': - return type.toLowerCase(); - } - if (typeof obj === "object") { - return "object"; - } - return undefined; - }, + // block until document ready + blocking: true, + //a list of everything to run + cachelist : [] +}; - push: function(result, actual, expected, message) { - var details = { - result: result, - message: message, - actual: actual, - expected: expected - }; - - message = escapeHtml(message) || (result ? "okay" : "failed"); - message = '<span class="test-message">' + message + "</span>"; - expected = escapeHtml(QUnit.jsDump.parse(expected)); - actual = escapeHtml(QUnit.jsDump.parse(actual)); - var output = message + '<table><tr class="test-expected"><th>Expected: </th><td><pre>' + expected + '</pre></td></tr>'; - if (actual != expected) { - output += '<tr class="test-actual"><th>Result: </th><td><pre>' + actual + '</pre></td></tr>'; - output += '<tr class="test-diff"><th>Diff: </th><td><pre>' + QUnit.diff(expected, actual) +'</pre></td></tr>'; - } - if (!result) { - var source = sourceFromStacktrace(); - if (source) { - details.source = source; - output += '<tr class="test-source"><th>Source: </th><td><pre>' + escapeHtml(source) + '</pre></td></tr>'; - } +// Load paramaters +(function() { + var location = window.location || { search: "", protocol: "file:" }, + GETParams = location.search.slice(1).split('&'); + + for ( var i = 0; i < GETParams.length; i++ ) { + GETParams[i] = decodeURIComponent( GETParams[i] ); + if ( GETParams[i] === "noglobals" ) { + GETParams.splice( i, 1 ); + i--; + config.noglobals = true; + } else if ( GETParams[i].search('=') > -1 ) { + GETParams.splice( i, 1 ); + i--; } - output += "</table>"; - - QUnit.log(details); - - config.current.assertions.push({ - result: !!result, - message: output - }); - }, + } + + // restrict modules/tests by get parameters + config.filters = GETParams; + + // Figure out if we're running the tests from a server or not + QUnit.isLocal = !!(location.protocol === 'file:'); +})(); - url: function( params ) { - params = extend( extend( {}, QUnit.urlParams ), params ); - var querystring = "?", - key; - for ( key in params ) { - querystring += encodeURIComponent( key ) + "=" + - encodeURIComponent( params[ key ] ) + "&"; - } - return window.location.pathname + querystring.slice( 0, -1 ); - }, +// Expose the API as global variables, unless an 'exports' +// object exists, in that case we assume we're in CommonJS +if ( typeof exports === "undefined" || typeof require === "undefined" ) { + extend(window, QUnit); + window.QUnit = QUnit; +} else { + extend(exports, QUnit); + exports.QUnit = QUnit; +} - // Logging callbacks; all receive a single argument with the listed properties - // run test/logs.html for any related changes - begin: function() {}, - // done: { failed, passed, total, runtime } - done: function() {}, - // log: { result, actual, expected, message } - log: function() {}, - // testStart: { name } - testStart: function() {}, - // testDone: { name, failed, passed, total } - testDone: function() {}, - // moduleStart: { name } - moduleStart: function() {}, - // moduleDone: { name, failed, passed, total } - moduleDone: function() {} -}); +QUnit.config = config; if ( typeof document === "undefined" || document.readyState === "complete" ) { config.autorun = true; } addEvent(window, "load", function() { - QUnit.begin({}); - // Initialize the config, saving the execution queue var oldconfig = extend({}, config); QUnit.init(); @@ -684,198 +490,174 @@ addEvent(window, "load", function() { if ( userAgent ) { userAgent.innerHTML = navigator.userAgent; } - var banner = id("qunit-header"); - if ( banner ) { - banner.innerHTML = '<a href="' + QUnit.url({ filter: undefined }) + '"> ' + banner.innerHTML + '</a> ' + - '<label><input name="noglobals" type="checkbox"' + ( config.noglobals ? ' checked="checked"' : '' ) + '>noglobals</label>' + - '<label><input name="notrycatch" type="checkbox"' + ( config.notrycatch ? ' checked="checked"' : '' ) + '>notrycatch</label>'; - addEvent( banner, "change", function( event ) { - var params = {}; - params[ event.target.name ] = event.target.checked ? true : undefined; - window.location = QUnit.url( params ); - }); - } - + var toolbar = id("qunit-testrunner-toolbar"); if ( toolbar ) { + toolbar.style.display = "none"; + var filter = document.createElement("input"); filter.type = "checkbox"; filter.id = "qunit-filter-pass"; + filter.disabled = true; addEvent( filter, "click", function() { - var ol = document.getElementById("qunit-tests"); - if ( filter.checked ) { - ol.className = ol.className + " hidepass"; - } else { - var tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " "; - ol.className = tmp.replace(/ hidepass /, " "); - } - if ( defined.sessionStorage ) { - if (filter.checked) { - sessionStorage.setItem("qunit-filter-passed-tests", "true"); - } else { - sessionStorage.removeItem("qunit-filter-passed-tests"); + var li = document.getElementsByTagName("li"); + for ( var i = 0; i < li.length; i++ ) { + if ( li[i].className.indexOf("pass") > -1 ) { + li[i].style.display = filter.checked ? "none" : ""; } } }); - if ( defined.sessionStorage && sessionStorage.getItem("qunit-filter-passed-tests") ) { - filter.checked = true; - var ol = document.getElementById("qunit-tests"); - ol.className = ol.className + " hidepass"; - } toolbar.appendChild( filter ); var label = document.createElement("label"); label.setAttribute("for", "qunit-filter-pass"); label.innerHTML = "Hide passed tests"; toolbar.appendChild( label ); + + var missing = document.createElement("input"); + missing.type = "checkbox"; + missing.id = "qunit-filter-missing"; + missing.disabled = true; + addEvent( missing, "click", function() { + var li = document.getElementsByTagName("li"); + for ( var i = 0; i < li.length; i++ ) { + if ( li[i].className.indexOf("fail") > -1 && li[i].innerHTML.indexOf('missing test - untested code is broken code') > - 1 ) { + li[i].parentNode.parentNode.style.display = missing.checked ? "none" : "block"; + } + } + }); + toolbar.appendChild( missing ); + + label = document.createElement("label"); + label.setAttribute("for", "qunit-filter-missing"); + label.innerHTML = "Hide missing tests (untested code is broken code)"; + toolbar.appendChild( label ); } - var main = id('qunit-fixture'); + var main = id('main'); if ( main ) { config.fixture = main.innerHTML; } + if ( window.jQuery ) { + config.ajaxSettings = window.jQuery.ajaxSettings; + } + if (config.autostart) { QUnit.start(); } }); function done() { + if ( config.doneTimer && window.clearTimeout ) { + window.clearTimeout( config.doneTimer ); + config.doneTimer = null; + } + + if ( config.queue.length ) { + config.doneTimer = window.setTimeout(function(){ + if ( !config.queue.length ) { + done(); + } else { + synchronize( done ); + } + }, 13); + + return; + } + config.autorun = true; // Log the last module results if ( config.currentModule ) { - QUnit.moduleDone( { - name: config.currentModule, - failed: config.moduleStats.bad, - passed: config.moduleStats.all - config.moduleStats.bad, - total: config.moduleStats.all - } ); + QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all ); } var banner = id("qunit-banner"), tests = id("qunit-tests"), - runtime = +new Date - config.started, - passed = config.stats.all - config.stats.bad, - html = [ - 'Tests completed in ', - runtime, - ' milliseconds.<br/>', - '<span class="passed">', - passed, - '</span> tests of <span class="total">', - config.stats.all, - '</span> passed, <span class="failed">', - config.stats.bad, - '</span> failed.' - ].join(''); + html = ['Tests completed in ', + +new Date - config.started, ' milliseconds.<br/>', + '<span class="passed">', config.stats.all - config.stats.bad, '</span> tests of <span class="total">', config.stats.all, '</span> passed, <span class="failed">', config.stats.bad,'</span> failed.'].join(''); if ( banner ) { banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass"); } - if ( tests ) { - id( "qunit-testresult" ).innerHTML = html; - } + if ( tests ) { + var result = id("qunit-testresult"); - if ( typeof document !== "undefined" && document.title ) { - // show ✖ for good, ✔ for bad suite result in title - // use escape sequences in case file gets loaded with non-utf-8-charset - document.title = (config.stats.bad ? "\u2716" : "\u2714") + " " + document.title; + if ( !result ) { + result = document.createElement("p"); + result.id = "qunit-testresult"; + result.className = "result"; + tests.parentNode.insertBefore( result, tests.nextSibling ); + } + + result.innerHTML = html; } - QUnit.done( { - failed: config.stats.bad, - passed: passed, - total: config.stats.all, - runtime: runtime - } ); + QUnit.done( config.stats.bad, config.stats.all ); } function validTest( name ) { - var filter = config.filter, + var i = config.filters.length, run = false; - if ( !filter ) { + if ( !i ) { return true; } + + while ( i-- ) { + var filter = config.filters[i], + not = filter.charAt(0) == '!'; - var not = filter.charAt( 0 ) === "!"; - if ( not ) { - filter = filter.slice( 1 ); - } + if ( not ) { + filter = filter.slice(1); + } - if ( name.indexOf( filter ) !== -1 ) { - return !not; - } + if ( name.indexOf(filter) !== -1 ) { + return !not; + } - if ( not ) { - run = true; + if ( not ) { + run = true; + } } return run; } -// so far supports only Firefox, Chrome and Opera (buggy) -// could be extended in the future to use something like https://github.com/csnover/TraceKit -function sourceFromStacktrace() { - try { - throw new Error(); - } catch ( e ) { - if (e.stacktrace) { - // Opera - return e.stacktrace.split("\n")[6]; - } else if (e.stack) { - // Firefox, Chrome - return e.stack.split("\n")[4]; - } - } +function push(result, actual, expected, message) { + message = message || (result ? "okay" : "failed"); + QUnit.ok( result, message + ", expected: " + QUnit.jsDump.parse(expected) + " result: " + QUnit.jsDump.parse(actual) ); } -function escapeHtml(s) { - if (!s) { - return ""; - } - s = s + ""; - return s.replace(/[\&"<>\\]/g, function(s) { - switch(s) { - case "&": return "&"; - case "\\": return "\\\\"; - case '"': return '\"'; - case "<": return "<"; - case ">": return ">"; - default: return s; - } - }); -} - -function synchronize( callback ) { +function synchronize( callback , save) { config.queue.push( callback ); - + if(save){ + config.cachelist.push( callback ) + } if ( config.autorun && !config.blocking ) { process(); } } - function process() { var start = (new Date()).getTime(); while ( config.queue.length && !config.blocking ) { if ( config.updateRate <= 0 || (((new Date()).getTime() - start) < config.updateRate) ) { config.queue.shift()(); + } else { - window.setTimeout( process, 13 ); + setTimeout( process, 13 ); break; } } - if (!config.blocking && !config.queue.length) { - done(); - } } function saveGlobal() { config.pollution = []; - + if ( config.noglobals ) { for ( var key in window ) { config.pollution.push( key ); @@ -886,15 +668,17 @@ function saveGlobal() { function checkPollution( name ) { var old = config.pollution; saveGlobal(); - - var newGlobals = diff( config.pollution, old ); + + var newGlobals = diff( old, config.pollution ); if ( newGlobals.length > 0 ) { ok( false, "Introduced global variable(s): " + newGlobals.join(", ") ); + config.expected++; } - var deletedGlobals = diff( old, config.pollution ); + var deletedGlobals = diff( config.pollution, old ); if ( deletedGlobals.length > 0 ) { ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") ); + config.expected++; } } @@ -926,11 +710,7 @@ function fail(message, exception, callback) { function extend(a, b) { for ( var prop in b ) { - if ( b[prop] === undefined ) { - delete a[prop]; - } else { - a[prop] = b[prop]; - } + a[prop] = b[prop]; } return a; @@ -961,18 +741,67 @@ QUnit.equiv = function () { var callers = []; // stack to decide between skip/abort functions var parents = []; // stack to avoiding loops from circular referencing + + // Determine what is o. + function hoozit(o) { + if (QUnit.is("String", o)) { + return "string"; + + } else if (QUnit.is("Boolean", o)) { + return "boolean"; + + } else if (QUnit.is("Number", o)) { + + if (isNaN(o)) { + return "nan"; + } else { + return "number"; + } + + } else if (typeof o === "undefined") { + return "undefined"; + + // consider: typeof null === object + } else if (o === null) { + return "null"; + + // consider: typeof [] === object + } else if (QUnit.is( "Array", o)) { + return "array"; + + // consider: typeof new Date() === object + } else if (QUnit.is( "Date", o)) { + return "date"; + + // consider: /./ instanceof Object; + // /./ instanceof RegExp; + // typeof /./ === "function"; // => false in IE and Opera, + // true in FF and Safari + } else if (QUnit.is( "RegExp", o)) { + return "regexp"; + + } else if (typeof o === "object") { + return "object"; + + } else if (QUnit.is( "Function", o)) { + return "function"; + } else { + return undefined; + } + } + // Call the o related callback with the given arguments. function bindCallbacks(o, callbacks, args) { - var prop = QUnit.objectType(o); + var prop = hoozit(o); if (prop) { - if (QUnit.objectType(callbacks[prop]) === "function") { + if (hoozit(callbacks[prop]) === "function") { return callbacks[prop].apply(callbacks, args); } else { return callbacks[prop]; // or undefined } } } - + var callbacks = function () { // for string, boolean, number and null @@ -999,11 +828,11 @@ QUnit.equiv = function () { }, "date": function (b, a) { - return QUnit.objectType(b) === "date" && a.valueOf() === b.valueOf(); + return hoozit(b) === "date" && a.valueOf() === b.valueOf(); }, "regexp": function (b, a) { - return QUnit.objectType(b) === "regexp" && + return hoozit(b) === "regexp" && a.source === b.source && // the regex itself a.global === b.global && // and its modifers (gmi) ... a.ignoreCase === b.ignoreCase && @@ -1024,15 +853,15 @@ QUnit.equiv = function () { var len; // b could be an object literal here - if ( ! (QUnit.objectType(b) === "array")) { + if ( ! (hoozit(b) === "array")) { return false; - } - + } + len = a.length; if (len !== b.length) { // safe and faster return false; } - + //track reference to avoid circular references parents.push(a); for (i = 0; i < len; i++) { @@ -1065,7 +894,7 @@ QUnit.equiv = function () { callers.push(a.constructor); //track reference to avoid circular references parents.push(a); - + for (i in a) { // be strict: don't ensures hasOwnProperty and go deep loop = false; for(j=0;j<parents.length;j++){ @@ -1102,7 +931,7 @@ QUnit.equiv = function () { return (function (a, b) { if (a === b) { return true; // catch the most you can - } else if (a === null || b === null || typeof a === "undefined" || typeof b === "undefined" || QUnit.objectType(a) !== QUnit.objectType(b)) { + } else if (a === null || b === null || typeof a === "undefined" || typeof b === "undefined" || hoozit(a) !== hoozit(b)) { return false; // don't lose time with error prone cases } else { return bindCallbacks(a, callbacks, [b, a]); @@ -1131,7 +960,7 @@ QUnit.jsDump = (function() { return '"' + str.toString().replace(/"/g, '\\"') + '"'; }; function literal( o ) { - return o + ''; + return o + ''; }; function join( pre, arr, post ) { var s = jsDump.separator(), @@ -1144,21 +973,21 @@ QUnit.jsDump = (function() { return [ pre, inner + arr, base + post ].join(s); }; function array( arr ) { - var i = arr.length, ret = Array(i); + var i = arr.length, ret = Array(i); this.up(); while ( i-- ) - ret[i] = this.parse( arr[i] ); + ret[i] = this.parse( arr[i] ); this.down(); return join( '[', ret, ']' ); }; - + var reName = /^function (\w+)/; - + var jsDump = { parse:function( obj, type ) { //type is used mostly internally, you can fix a (custom)type in advance var parser = this.parsers[ type || this.typeOf(obj) ]; - type = typeof parser; - + type = typeof parser; + return type == 'function' ? parser.call( this, obj ) : type == 'string' ? parser : this.parsers.error; @@ -1175,7 +1004,7 @@ QUnit.jsDump = (function() { type = "date"; } else if (QUnit.is("Function", obj)) { type = "function"; - } else if (typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined") { + } else if (obj.setInterval && obj.document && !obj.nodeType) { type = "window"; } else if (obj.nodeType === 9) { type = "document"; @@ -1209,7 +1038,7 @@ QUnit.jsDump = (function() { this.parsers[name] = parser; }, // The next 3 are exposed so you can use them - quote:quote, + quote:quote, literal:literal, join:join, // @@ -1221,46 +1050,46 @@ QUnit.jsDump = (function() { error:'[ERROR]', //when no parser is found, shouldn't happen unknown: '[Unknown]', 'null':'null', - 'undefined':'undefined', + undefined:'undefined', 'function':function( fn ) { var ret = 'function', name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE if ( name ) ret += ' ' + name; ret += '('; - - ret = [ ret, QUnit.jsDump.parse( fn, 'functionArgs' ), '){'].join(''); - return join( ret, QUnit.jsDump.parse(fn,'functionCode'), '}' ); + + ret = [ ret, this.parse( fn, 'functionArgs' ), '){'].join(''); + return join( ret, this.parse(fn,'functionCode'), '}' ); }, array: array, nodelist: array, arguments: array, object:function( map ) { var ret = [ ]; - QUnit.jsDump.up(); + this.up(); for ( var key in map ) - ret.push( QUnit.jsDump.parse(key,'key') + ': ' + QUnit.jsDump.parse(map[key]) ); - QUnit.jsDump.down(); + ret.push( this.parse(key,'key') + ': ' + this.parse(map[key]) ); + this.down(); return join( '{', ret, '}' ); }, node:function( node ) { - var open = QUnit.jsDump.HTML ? '<' : '<', - close = QUnit.jsDump.HTML ? '>' : '>'; - + var open = this.HTML ? '<' : '<', + close = this.HTML ? '>' : '>'; + var tag = node.nodeName.toLowerCase(), ret = open + tag; - - for ( var a in QUnit.jsDump.DOMAttrs ) { - var val = node[QUnit.jsDump.DOMAttrs[a]]; + + for ( var a in this.DOMAttrs ) { + var val = node[this.DOMAttrs[a]]; if ( val ) - ret += ' ' + a + '=' + QUnit.jsDump.parse( val, 'attribute' ); + ret += ' ' + a + '=' + this.parse( val, 'attribute' ); } return ret + close + open + '/' + tag + close; }, functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function var l = fn.length; - if ( !l ) return ''; - + if ( !l ) return ''; + var args = Array(l); while ( l-- ) args[l] = String.fromCharCode(97+l);//97 is 'a' @@ -1281,168 +1110,15 @@ QUnit.jsDump = (function() { 'class':'className' }, HTML:false,//if true, entities are escaped ( <, >, \t, space and \n ) - indentChar:' ',//indentation unit - multiline:true //if true, items in a collection, are separated by a \n, else just a space. + indentChar:' ',//indentation unit + multiline:false //if true, items in a collection, are separated by a \n, else just a space. }; return jsDump; })(); -// from Sizzle.js -function getText( elems ) { - var ret = "", elem; - - for ( var i = 0; elems[i]; i++ ) { - elem = elems[i]; - - // Get the text from text nodes and CDATA nodes - if ( elem.nodeType === 3 || elem.nodeType === 4 ) { - ret += elem.nodeValue; - - // Traverse everything else, except comment nodes - } else if ( elem.nodeType !== 8 ) { - ret += getText( elem.childNodes ); - } - } - - return ret; -}; - -/* - * Javascript Diff Algorithm - * By John Resig (http://ejohn.org/) - * Modified by Chu Alan "sprite" - * - * Released under the MIT license. - * - * More Info: - * http://ejohn.org/projects/javascript-diff-algorithm/ - * - * Usage: QUnit.diff(expected, actual) - * - * QUnit.diff("the quick brown fox jumped over", "the quick fox jumps over") == "the quick <del>brown </del> fox <del>jumped </del><ins>jumps </ins> over" - */ -QUnit.diff = (function() { - function diff(o, n){ - var ns = new Object(); - var os = new Object(); - - for (var i = 0; i < n.length; i++) { - if (ns[n[i]] == null) - ns[n[i]] = { - rows: new Array(), - o: null - }; - ns[n[i]].rows.push(i); - } - - for (var i = 0; i < o.length; i++) { - if (os[o[i]] == null) - os[o[i]] = { - rows: new Array(), - n: null - }; - os[o[i]].rows.push(i); - } - - for (var i in ns) { - if (ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1) { - n[ns[i].rows[0]] = { - text: n[ns[i].rows[0]], - row: os[i].rows[0] - }; - o[os[i].rows[0]] = { - text: o[os[i].rows[0]], - row: ns[i].rows[0] - }; - } - } - - for (var i = 0; i < n.length - 1; i++) { - if (n[i].text != null && n[i + 1].text == null && n[i].row + 1 < o.length && o[n[i].row + 1].text == null && - n[i + 1] == o[n[i].row + 1]) { - n[i + 1] = { - text: n[i + 1], - row: n[i].row + 1 - }; - o[n[i].row + 1] = { - text: o[n[i].row + 1], - row: i + 1 - }; - } - } - - for (var i = n.length - 1; i > 0; i--) { - if (n[i].text != null && n[i - 1].text == null && n[i].row > 0 && o[n[i].row - 1].text == null && - n[i - 1] == o[n[i].row - 1]) { - n[i - 1] = { - text: n[i - 1], - row: n[i].row - 1 - }; - o[n[i].row - 1] = { - text: o[n[i].row - 1], - row: i - 1 - }; - } - } - - return { - o: o, - n: n - }; - } - - return function(o, n){ - o = o.replace(/\s+$/, ''); - n = n.replace(/\s+$/, ''); - var out = diff(o == "" ? [] : o.split(/\s+/), n == "" ? [] : n.split(/\s+/)); - - var str = ""; - - var oSpace = o.match(/\s+/g); - if (oSpace == null) { - oSpace = [" "]; - } - else { - oSpace.push(" "); - } - var nSpace = n.match(/\s+/g); - if (nSpace == null) { - nSpace = [" "]; - } - else { - nSpace.push(" "); - } - - if (out.n.length == 0) { - for (var i = 0; i < out.o.length; i++) { - str += '<del>' + out.o[i] + oSpace[i] + "</del>"; - } - } - else { - if (out.n[0].text == null) { - for (n = 0; n < out.o.length && out.o[n].text == null; n++) { - str += '<del>' + out.o[n] + oSpace[n] + "</del>"; - } - } - - for (var i = 0; i < out.n.length; i++) { - if (out.n[i].text == null) { - str += '<ins>' + out.n[i] + nSpace[i] + "</ins>"; - } - else { - var pre = ""; - - for (n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++) { - pre += '<del>' + out.o[n] + oSpace[n] + "</del>"; - } - str += " " + out.n[i].text + nSpace[i] + pre; - } - } - } +})(this); - return str; - }; -})(); })(this); +}).plugins("funcunit/qunit/rhino"); \ No newline at end of file diff --git a/browserid/static/funcunit/qunit/rhino/rhino.js b/browserid/static/funcunit/qunit/rhino/rhino.js new file mode 100644 index 0000000000000000000000000000000000000000..dd7b13cbe2325a1ab162cdb703b736676ee6b09c --- /dev/null +++ b/browserid/static/funcunit/qunit/rhino/rhino.js @@ -0,0 +1,18 @@ +// This maps qUnit's functions to report to FuncUnit ... +steal.then(function(){ + if (navigator.userAgent.match(/Rhino/) && !window.build_in_progress) { + //map QUnit messages to FuncUnit + ['log', + 'testStart', + 'testDone', + 'moduleStart', + 'moduleDone', + 'done'].forEach(function(item){ + QUnit[item] = function(){ + FuncUnit[item].apply(FuncUnit, arguments); + }; + + }) + + } +}) \ No newline at end of file diff --git a/browserid/static/funcunit/qunit/test/qunit.html b/browserid/static/funcunit/qunit/test/qunit.html new file mode 100644 index 0000000000000000000000000000000000000000..7a320ffb6ef2bc33fd15da3364cb697453865c29 --- /dev/null +++ b/browserid/static/funcunit/qunit/test/qunit.html @@ -0,0 +1,18 @@ +<html> + <head> + <link rel="stylesheet" type="text/css" href="../qunit.css" /> + <title>FuncUnit Test</title> + </head> + <body> + + <h1 id="qunit-header">funcunit Test Suite</h1> + <h2 id="qunit-banner"></h2> + <div id="qunit-testrunner-toolbar"></div> + <h2 id="qunit-userAgent"></h2> + <ol id="qunit-tests"></ol> + <script type='text/javascript' src='../../../steal/steal.js'></script> + <script type='text/javascript'> + steal('../qunit','test').start(); + </script> + </body> +</html> \ No newline at end of file diff --git a/browserid/static/funcunit/qunit/test/test.js b/browserid/static/funcunit/qunit/test/test.js new file mode 100644 index 0000000000000000000000000000000000000000..c0a63c905b15c27d0105afa7fc172f508ac2789d --- /dev/null +++ b/browserid/static/funcunit/qunit/test/test.js @@ -0,0 +1,7 @@ +test("something",function(){ + ok(true,"it's ok") +}) + +test("sanity",function(){ + equal(1+1,2,"one plus one equals two") +}) \ No newline at end of file diff --git a/browserid/static/funcunit/resources/jquery.js b/browserid/static/funcunit/resources/jquery.js new file mode 100644 index 0000000000000000000000000000000000000000..a4f114586ce4468aae29f8f241f145556377307f --- /dev/null +++ b/browserid/static/funcunit/resources/jquery.js @@ -0,0 +1,7179 @@ +/*! + * jQuery JavaScript Library v1.4.4 + * http://jquery.com/ + * + * Copyright 2010, John Resig + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * Copyright 2010, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * + * Date: Thu Nov 11 19:04:53 2010 -0500 + */ +(function( window, undefined ) { + +// Use the correct document accordingly with window argument (sandbox) +var document = window.document; +var jQuery = (function() { + +// Define a local copy of jQuery +var jQuery = function( selector, context ) { + // The jQuery object is actually just the init constructor 'enhanced' + return new jQuery.fn.init( selector, context ); + }, + + // Map over jQuery in case of overwrite + _jQuery = window.jQuery, + + // Map over the $ in case of overwrite + _$ = window.$, + + // A central reference to the root jQuery(document) + rootjQuery, + + // A simple way to check for HTML strings or ID strings + // (both of which we optimize for) + quickExpr = /^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]+)$)/, + + // Is it a simple selector + isSimple = /^.[^:#\[\.,]*$/, + + // Check if a string has a non-whitespace character in it + rnotwhite = /\S/, + rwhite = /\s/, + + // Used for trimming whitespace + trimLeft = /^\s+/, + trimRight = /\s+$/, + + // Check for non-word characters + rnonword = /\W/, + + // Check for digits + rdigit = /\d/, + + // Match a standalone tag + rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/, + + // JSON RegExp + rvalidchars = /^[\],:{}\s]*$/, + rvalidescape = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, + rvalidtokens = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, + rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g, + + // Useragent RegExp + rwebkit = /(webkit)[ \/]([\w.]+)/, + ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/, + rmsie = /(msie) ([\w.]+)/, + rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/, + + // Keep a UserAgent string for use with jQuery.browser + userAgent = navigator.userAgent, + + // For matching the engine and version of the browser + browserMatch, + + // Has the ready events already been bound? + readyBound = false, + + // The functions to execute on DOM ready + readyList = [], + + // The ready event handler + DOMContentLoaded, + + // Save a reference to some core methods + toString = Object.prototype.toString, + hasOwn = Object.prototype.hasOwnProperty, + push = Array.prototype.push, + slice = Array.prototype.slice, + trim = String.prototype.trim, + indexOf = Array.prototype.indexOf, + + // [[Class]] -> type pairs + class2type = {}; + +jQuery.fn = jQuery.prototype = { + init: function( selector, context ) { + var match, elem, ret, doc; + + // Handle $(""), $(null), or $(undefined) + if ( !selector ) { + return this; + } + + // Handle $(DOMElement) + if ( selector.nodeType ) { + this.context = this[0] = selector; + this.length = 1; + return this; + } + + // The body element only exists once, optimize finding it + if ( selector === "body" && !context && document.body ) { + this.context = document; + this[0] = document.body; + this.selector = "body"; + this.length = 1; + return this; + } + + // Handle HTML strings + if ( typeof selector === "string" ) { + // Are we dealing with HTML string or an ID? + match = quickExpr.exec( selector ); + + // Verify a match, and that no context was specified for #id + if ( match && (match[1] || !context) ) { + + // HANDLE: $(html) -> $(array) + if ( match[1] ) { + doc = (context ? context.ownerDocument || context : document); + + // If a single string is passed in and it's a single tag + // just do a createElement and skip the rest + ret = rsingleTag.exec( selector ); + + if ( ret ) { + if ( jQuery.isPlainObject( context ) ) { + selector = [ document.createElement( ret[1] ) ]; + jQuery.fn.attr.call( selector, context, true ); + + } else { + selector = [ doc.createElement( ret[1] ) ]; + } + + } else { + ret = jQuery.buildFragment( [ match[1] ], [ doc ] ); + selector = (ret.cacheable ? ret.fragment.cloneNode(true) : ret.fragment).childNodes; + } + + return jQuery.merge( this, selector ); + + // HANDLE: $("#id") + } else { + elem = document.getElementById( match[2] ); + + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + if ( elem && elem.parentNode ) { + // Handle the case where IE and Opera return items + // by name instead of ID + if ( elem.id !== match[2] ) { + return rootjQuery.find( selector ); + } + + // Otherwise, we inject the element directly into the jQuery object + this.length = 1; + this[0] = elem; + } + + this.context = document; + this.selector = selector; + return this; + } + + // HANDLE: $("TAG") + } else if ( !context && !rnonword.test( selector ) ) { + this.selector = selector; + this.context = document; + selector = document.getElementsByTagName( selector ); + return jQuery.merge( this, selector ); + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return (context || rootjQuery).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return jQuery( context ).find( selector ); + } + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( jQuery.isFunction( selector ) ) { + return rootjQuery.ready( selector ); + } + + if (selector.selector !== undefined) { + this.selector = selector.selector; + this.context = selector.context; + } + + return jQuery.makeArray( selector, this ); + }, + + // Start with an empty selector + selector: "", + + // The current version of jQuery being used + jquery: "1.4.4", + + // The default length of a jQuery object is 0 + length: 0, + + // The number of elements contained in the matched element set + size: function() { + return this.length; + }, + + toArray: function() { + return slice.call( this, 0 ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + return num == null ? + + // Return a 'clean' array + this.toArray() : + + // Return just the object + ( num < 0 ? this.slice(num)[ 0 ] : this[ num ] ); + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems, name, selector ) { + // Build a new jQuery matched element set + var ret = jQuery(); + + if ( jQuery.isArray( elems ) ) { + push.apply( ret, elems ); + + } else { + jQuery.merge( ret, elems ); + } + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + ret.context = this.context; + + if ( name === "find" ) { + ret.selector = this.selector + (this.selector ? " " : "") + selector; + } else if ( name ) { + ret.selector = this.selector + "." + name + "(" + selector + ")"; + } + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + // (You can seed the arguments with an array of args, but this is + // only used internally.) + each: function( callback, args ) { + return jQuery.each( this, callback, args ); + }, + + ready: function( fn ) { + // Attach the listeners + jQuery.bindReady(); + + // If the DOM is already ready + if ( jQuery.isReady ) { + // Execute the function immediately + fn.call( document, jQuery ); + + // Otherwise, remember the function for later + } else if ( readyList ) { + // Add the function to the wait list + readyList.push( fn ); + } + + return this; + }, + + eq: function( i ) { + return i === -1 ? + this.slice( i ) : + this.slice( i, +i + 1 ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ), + "slice", slice.call(arguments).join(",") ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map(this, function( elem, i ) { + return callback.call( elem, i, elem ); + })); + }, + + end: function() { + return this.prevObject || jQuery(null); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: push, + sort: [].sort, + splice: [].splice +}; + +// Give the init function the jQuery prototype for later instantiation +jQuery.fn.init.prototype = jQuery.fn; + +jQuery.extend = jQuery.fn.extend = function() { + var options, name, src, copy, copyIsArray, clone, + target = arguments[0] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + target = arguments[1] || {}; + // skip the boolean and the target + i = 2; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !jQuery.isFunction(target) ) { + target = {}; + } + + // extend jQuery itself if only one argument is passed + if ( length === i ) { + target = this; + --i; + } + + for ( ; i < length; i++ ) { + // Only deal with non-null/undefined values + if ( (options = arguments[ i ]) != null ) { + // Extend the base object + for ( name in options ) { + src = target[ name ]; + copy = options[ name ]; + + // Prevent never-ending loop + if ( target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { + if ( copyIsArray ) { + copyIsArray = false; + clone = src && jQuery.isArray(src) ? src : []; + + } else { + clone = src && jQuery.isPlainObject(src) ? src : {}; + } + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend({ + noConflict: function( deep ) { + window.$ = _$; + + if ( deep ) { + window.jQuery = _jQuery; + } + + return jQuery; + }, + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Handle when the DOM is ready + ready: function( wait ) { + // A third-party is pushing the ready event forwards + if ( wait === true ) { + jQuery.readyWait--; + } + + // Make sure that the DOM is not already loaded + if ( !jQuery.readyWait || (wait !== true && !jQuery.isReady) ) { + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if ( !document.body ) { + return setTimeout( jQuery.ready, 1 ); + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + if ( readyList ) { + // Execute all of them + var fn, + i = 0, + ready = readyList; + + // Reset the list of functions + readyList = null; + + while ( (fn = ready[ i++ ]) ) { + fn.call( document, jQuery ); + } + + // Trigger any bound ready events + if ( jQuery.fn.trigger ) { + jQuery( document ).trigger( "ready" ).unbind( "ready" ); + } + } + } + }, + + bindReady: function() { + if ( readyBound ) { + return; + } + + readyBound = true; + + // Catch cases where $(document).ready() is called after the + // browser event has already occurred. + if ( document.readyState === "complete" ) { + // Handle it asynchronously to allow scripts the opportunity to delay ready + return setTimeout( jQuery.ready, 1 ); + } + + // Mozilla, Opera and webkit nightlies currently support this event + if ( document.addEventListener ) { + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", jQuery.ready, false ); + + // If IE event model is used + } else if ( document.attachEvent ) { + // ensure firing before onload, + // maybe late but safe also for iframes + document.attachEvent("onreadystatechange", DOMContentLoaded); + + // A fallback to window.onload, that will always work + window.attachEvent( "onload", jQuery.ready ); + + // If IE and not a frame + // continually check to see if the document is ready + var toplevel = false; + + try { + toplevel = window.frameElement == null; + } catch(e) {} + + if ( document.documentElement.doScroll && toplevel ) { + doScrollCheck(); + } + } + }, + + // See test/unit/core.js for details concerning isFunction. + // Since version 1.3, DOM methods and functions like alert + // aren't supported. They return false on IE (#2968). + isFunction: function( obj ) { + return jQuery.type(obj) === "function"; + }, + + isArray: Array.isArray || function( obj ) { + return jQuery.type(obj) === "array"; + }, + + // A crude way of determining if an object is a window + isWindow: function( obj ) { + return obj && typeof obj === "object" && "setInterval" in obj; + }, + + isNaN: function( obj ) { + return obj == null || !rdigit.test( obj ) || isNaN( obj ); + }, + + type: function( obj ) { + return obj == null ? + String( obj ) : + class2type[ toString.call(obj) ] || "object"; + }, + + isPlainObject: function( obj ) { + // Must be an Object. + // Because of IE, we also have to check the presence of the constructor property. + // Make sure that DOM nodes and window objects don't pass through, as well + if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) { + return false; + } + + // Not own constructor property must be Object + if ( obj.constructor && + !hasOwn.call(obj, "constructor") && + !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) { + return false; + } + + // Own properties are enumerated firstly, so to speed up, + // if last one is own, then all properties are own. + + var key; + for ( key in obj ) {} + + return key === undefined || hasOwn.call( obj, key ); + }, + + isEmptyObject: function( obj ) { + for ( var name in obj ) { + return false; + } + return true; + }, + + error: function( msg ) { + throw msg; + }, + + parseJSON: function( data ) { + if ( typeof data !== "string" || !data ) { + return null; + } + + // Make sure leading/trailing whitespace is removed (IE can't handle it) + data = jQuery.trim( data ); + + // Make sure the incoming data is actual JSON + // Logic borrowed from http://json.org/json2.js + if ( rvalidchars.test(data.replace(rvalidescape, "@") + .replace(rvalidtokens, "]") + .replace(rvalidbraces, "")) ) { + + // Try to use the native JSON parser first + return window.JSON && window.JSON.parse ? + window.JSON.parse( data ) : + (new Function("return " + data))(); + + } else { + jQuery.error( "Invalid JSON: " + data ); + } + }, + + noop: function() {}, + + // Evalulates a script in a global context + globalEval: function( data ) { + if ( data && rnotwhite.test(data) ) { + // Inspired by code by Andrea Giammarchi + // http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html + var head = document.getElementsByTagName("head")[0] || document.documentElement, + script = document.createElement("script"); + + script.type = "text/javascript"; + + if ( jQuery.support.scriptEval ) { + script.appendChild( document.createTextNode( data ) ); + } else { + script.text = data; + } + + // Use insertBefore instead of appendChild to circumvent an IE6 bug. + // This arises when a base node is used (#2709). + head.insertBefore( script, head.firstChild ); + head.removeChild( script ); + } + }, + + nodeName: function( elem, name ) { + return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase(); + }, + + // args is for internal usage only + each: function( object, callback, args ) { + var name, i = 0, + length = object.length, + isObj = length === undefined || jQuery.isFunction(object); + + if ( args ) { + if ( isObj ) { + for ( name in object ) { + if ( callback.apply( object[ name ], args ) === false ) { + break; + } + } + } else { + for ( ; i < length; ) { + if ( callback.apply( object[ i++ ], args ) === false ) { + break; + } + } + } + + // A special, fast, case for the most common use of each + } else { + if ( isObj ) { + for ( name in object ) { + if ( callback.call( object[ name ], name, object[ name ] ) === false ) { + break; + } + } + } else { + for ( var value = object[0]; + i < length && callback.call( value, i, value ) !== false; value = object[++i] ) {} + } + } + + return object; + }, + + // Use native String.trim function wherever possible + trim: trim ? + function( text ) { + return text == null ? + "" : + trim.call( text ); + } : + + // Otherwise use our own trimming functionality + function( text ) { + return text == null ? + "" : + text.toString().replace( trimLeft, "" ).replace( trimRight, "" ); + }, + + // results is for internal usage only + makeArray: function( array, results ) { + var ret = results || []; + + if ( array != null ) { + // The window, strings (and functions) also have 'length' + // The extra typeof function check is to prevent crashes + // in Safari 2 (See: #3039) + // Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930 + var type = jQuery.type(array); + + if ( array.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( array ) ) { + push.call( ret, array ); + } else { + jQuery.merge( ret, array ); + } + } + + return ret; + }, + + inArray: function( elem, array ) { + if ( array.indexOf ) { + return array.indexOf( elem ); + } + + for ( var i = 0, length = array.length; i < length; i++ ) { + if ( array[ i ] === elem ) { + return i; + } + } + + return -1; + }, + + merge: function( first, second ) { + var i = first.length, + j = 0; + + if ( typeof second.length === "number" ) { + for ( var l = second.length; j < l; j++ ) { + first[ i++ ] = second[ j ]; + } + + } else { + while ( second[j] !== undefined ) { + first[ i++ ] = second[ j++ ]; + } + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, inv ) { + var ret = [], retVal; + inv = !!inv; + + // Go through the array, only saving the items + // that pass the validator function + for ( var i = 0, length = elems.length; i < length; i++ ) { + retVal = !!callback( elems[ i ], i ); + if ( inv !== retVal ) { + ret.push( elems[ i ] ); + } + } + + return ret; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var ret = [], value; + + // Go through the array, translating each of the items to their + // new value (or values). + for ( var i = 0, length = elems.length; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret[ ret.length ] = value; + } + } + + return ret.concat.apply( [], ret ); + }, + + // A global GUID counter for objects + guid: 1, + + proxy: function( fn, proxy, thisObject ) { + if ( arguments.length === 2 ) { + if ( typeof proxy === "string" ) { + thisObject = fn; + fn = thisObject[ proxy ]; + proxy = undefined; + + } else if ( proxy && !jQuery.isFunction( proxy ) ) { + thisObject = proxy; + proxy = undefined; + } + } + + if ( !proxy && fn ) { + proxy = function() { + return fn.apply( thisObject || this, arguments ); + }; + } + + // Set the guid of unique handler to the same of original handler, so it can be removed + if ( fn ) { + proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++; + } + + // So proxy can be declared as an argument + return proxy; + }, + + // Mutifunctional method to get and set values to a collection + // The value/s can be optionally by executed if its a function + access: function( elems, key, value, exec, fn, pass ) { + var length = elems.length; + + // Setting many attributes + if ( typeof key === "object" ) { + for ( var k in key ) { + jQuery.access( elems, k, key[k], exec, fn, value ); + } + return elems; + } + + // Setting one attribute + if ( value !== undefined ) { + // Optionally, function values get executed if exec is true + exec = !pass && exec && jQuery.isFunction(value); + + for ( var i = 0; i < length; i++ ) { + fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass ); + } + + return elems; + } + + // Getting an attribute + return length ? fn( elems[0], key ) : undefined; + }, + + now: function() { + return (new Date()).getTime(); + }, + + // Use of jQuery.browser is frowned upon. + // More details: http://docs.jquery.com/Utilities/jQuery.browser + uaMatch: function( ua ) { + ua = ua.toLowerCase(); + + var match = rwebkit.exec( ua ) || + ropera.exec( ua ) || + rmsie.exec( ua ) || + ua.indexOf("compatible") < 0 && rmozilla.exec( ua ) || + []; + + return { browser: match[1] || "", version: match[2] || "0" }; + }, + + browser: {} +}); + +// Populate the class2type map +jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +}); + +browserMatch = jQuery.uaMatch( userAgent ); +if ( browserMatch.browser ) { + jQuery.browser[ browserMatch.browser ] = true; + jQuery.browser.version = browserMatch.version; +} + +// Deprecated, use jQuery.browser.webkit instead +if ( jQuery.browser.webkit ) { + jQuery.browser.safari = true; +} + +if ( indexOf ) { + jQuery.inArray = function( elem, array ) { + return indexOf.call( array, elem ); + }; +} + +// Verify that \s matches non-breaking spaces +// (IE fails on this test) +if ( !rwhite.test( "\xA0" ) ) { + trimLeft = /^[\s\xA0]+/; + trimRight = /[\s\xA0]+$/; +} + +// All jQuery objects should point back to these +rootjQuery = jQuery(document); + +// Cleanup functions for the document ready method +if ( document.addEventListener ) { + DOMContentLoaded = function() { + document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false ); + jQuery.ready(); + }; + +} else if ( document.attachEvent ) { + DOMContentLoaded = function() { + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if ( document.readyState === "complete" ) { + document.detachEvent( "onreadystatechange", DOMContentLoaded ); + jQuery.ready(); + } + }; +} + +// The DOM ready check for Internet Explorer +function doScrollCheck() { + if ( jQuery.isReady ) { + return; + } + + try { + // If IE is used, use the trick by Diego Perini + // http://javascript.nwbox.com/IEContentLoaded/ + document.documentElement.doScroll("left"); + } catch(e) { + setTimeout( doScrollCheck, 1 ); + return; + } + + // and execute any waiting functions + jQuery.ready(); +} + +// Expose jQuery to the global object +return (window.jQuery = window.$ = jQuery); + +})(); + + +(function() { + + jQuery.support = {}; + + var root = document.documentElement, + script = document.createElement("script"), + div = document.createElement("div"), + id = "script" + jQuery.now(); + + div.style.display = "none"; + div.innerHTML = " <link/><table></table><a href='/a' style='color:red;float:left;opacity:.55;'>a</a><input type='checkbox'/>"; + + var all = div.getElementsByTagName("*"), + a = div.getElementsByTagName("a")[0], + select = document.createElement("select"), + opt = select.appendChild( document.createElement("option") ); + + // Can't get basic test support + if ( !all || !all.length || !a ) { + return; + } + + jQuery.support = { + // IE strips leading whitespace when .innerHTML is used + leadingWhitespace: div.firstChild.nodeType === 3, + + // Make sure that tbody elements aren't automatically inserted + // IE will insert them into empty tables + tbody: !div.getElementsByTagName("tbody").length, + + // Make sure that link elements get serialized correctly by innerHTML + // This requires a wrapper element in IE + htmlSerialize: !!div.getElementsByTagName("link").length, + + // Get the style information from getAttribute + // (IE uses .cssText insted) + style: /red/.test( a.getAttribute("style") ), + + // Make sure that URLs aren't manipulated + // (IE normalizes it by default) + hrefNormalized: a.getAttribute("href") === "/a", + + // Make sure that element opacity exists + // (IE uses filter instead) + // Use a regex to work around a WebKit issue. See #5145 + opacity: /^0.55$/.test( a.style.opacity ), + + // Verify style float existence + // (IE uses styleFloat instead of cssFloat) + cssFloat: !!a.style.cssFloat, + + // Make sure that if no value is specified for a checkbox + // that it defaults to "on". + // (WebKit defaults to "" instead) + checkOn: div.getElementsByTagName("input")[0].value === "on", + + // Make sure that a selected-by-default option has a working selected property. + // (WebKit defaults to false instead of true, IE too, if it's in an optgroup) + optSelected: opt.selected, + + // Will be defined later + deleteExpando: true, + optDisabled: false, + checkClone: false, + scriptEval: false, + noCloneEvent: true, + boxModel: null, + inlineBlockNeedsLayout: false, + shrinkWrapBlocks: false, + reliableHiddenOffsets: true + }; + + // Make sure that the options inside disabled selects aren't marked as disabled + // (WebKit marks them as diabled) + select.disabled = true; + jQuery.support.optDisabled = !opt.disabled; + + script.type = "text/javascript"; + try { + script.appendChild( document.createTextNode( "window." + id + "=1;" ) ); + } catch(e) {} + + root.insertBefore( script, root.firstChild ); + + // Make sure that the execution of code works by injecting a script + // tag with appendChild/createTextNode + // (IE doesn't support this, fails, and uses .text instead) + if ( window[ id ] ) { + jQuery.support.scriptEval = true; + delete window[ id ]; + } + + // Test to see if it's possible to delete an expando from an element + // Fails in Internet Explorer + try { + delete script.test; + + } catch(e) { + jQuery.support.deleteExpando = false; + } + + root.removeChild( script ); + + if ( div.attachEvent && div.fireEvent ) { + div.attachEvent("onclick", function click() { + // Cloning a node shouldn't copy over any + // bound event handlers (IE does this) + jQuery.support.noCloneEvent = false; + div.detachEvent("onclick", click); + }); + div.cloneNode(true).fireEvent("onclick"); + } + + div = document.createElement("div"); + div.innerHTML = "<input type='radio' name='radiotest' checked='checked'/>"; + + var fragment = document.createDocumentFragment(); + fragment.appendChild( div.firstChild ); + + // WebKit doesn't clone checked state correctly in fragments + jQuery.support.checkClone = fragment.cloneNode(true).cloneNode(true).lastChild.checked; + + // Figure out if the W3C box model works as expected + // document.body must exist before we can do this + jQuery(function() { + var div = document.createElement("div"); + div.style.width = div.style.paddingLeft = "1px"; + + document.body.appendChild( div ); + jQuery.boxModel = jQuery.support.boxModel = div.offsetWidth === 2; + + if ( "zoom" in div.style ) { + // Check if natively block-level elements act like inline-block + // elements when setting their display to 'inline' and giving + // them layout + // (IE < 8 does this) + div.style.display = "inline"; + div.style.zoom = 1; + jQuery.support.inlineBlockNeedsLayout = div.offsetWidth === 2; + + // Check if elements with layout shrink-wrap their children + // (IE 6 does this) + div.style.display = ""; + div.innerHTML = "<div style='width:4px;'></div>"; + jQuery.support.shrinkWrapBlocks = div.offsetWidth !== 2; + } + + div.innerHTML = "<table><tr><td style='padding:0;display:none'></td><td>t</td></tr></table>"; + var tds = div.getElementsByTagName("td"); + + // Check if table cells still have offsetWidth/Height when they are set + // to display:none and there are still other visible table cells in a + // table row; if so, offsetWidth/Height are not reliable for use when + // determining if an element has been hidden directly using + // display:none (it is still safe to use offsets if a parent element is + // hidden; don safety goggles and see bug #4512 for more information). + // (only IE 8 fails this test) + jQuery.support.reliableHiddenOffsets = tds[0].offsetHeight === 0; + + tds[0].style.display = ""; + tds[1].style.display = "none"; + + // Check if empty table cells still have offsetWidth/Height + // (IE < 8 fail this test) + jQuery.support.reliableHiddenOffsets = jQuery.support.reliableHiddenOffsets && tds[0].offsetHeight === 0; + div.innerHTML = ""; + + document.body.removeChild( div ).style.display = "none"; + div = tds = null; + }); + + // Technique from Juriy Zaytsev + // http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/ + var eventSupported = function( eventName ) { + var el = document.createElement("div"); + eventName = "on" + eventName; + + var isSupported = (eventName in el); + if ( !isSupported ) { + el.setAttribute(eventName, "return;"); + isSupported = typeof el[eventName] === "function"; + } + el = null; + + return isSupported; + }; + + jQuery.support.submitBubbles = eventSupported("submit"); + jQuery.support.changeBubbles = eventSupported("change"); + + // release memory in IE + root = script = div = all = a = null; +})(); + + + +var windowData = {}, + rbrace = /^(?:\{.*\}|\[.*\])$/; + +jQuery.extend({ + cache: {}, + + // Please use with caution + uuid: 0, + + // Unique for each copy of jQuery on the page + expando: "jQuery" + jQuery.now(), + + // The following elements throw uncatchable exceptions if you + // attempt to add expando properties to them. + noData: { + "embed": true, + // Ban all objects except for Flash (which handle expandos) + "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000", + "applet": true + }, + + data: function( elem, name, data ) { + if ( !jQuery.acceptData( elem ) ) { + return; + } + + elem = elem == window ? + windowData : + elem; + + var isNode = elem.nodeType, + id = isNode ? elem[ jQuery.expando ] : null, + cache = jQuery.cache, thisCache; + + if ( isNode && !id && typeof name === "string" && data === undefined ) { + return; + } + + // Get the data from the object directly + if ( !isNode ) { + cache = elem; + + // Compute a unique ID for the element + } else if ( !id ) { + elem[ jQuery.expando ] = id = ++jQuery.uuid; + } + + // Avoid generating a new cache unless none exists and we + // want to manipulate it. + if ( typeof name === "object" ) { + if ( isNode ) { + cache[ id ] = jQuery.extend(cache[ id ], name); + + } else { + jQuery.extend( cache, name ); + } + + } else if ( isNode && !cache[ id ] ) { + cache[ id ] = {}; + } + + thisCache = isNode ? cache[ id ] : cache; + + // Prevent overriding the named cache with undefined values + if ( data !== undefined ) { + thisCache[ name ] = data; + } + + return typeof name === "string" ? thisCache[ name ] : thisCache; + }, + + removeData: function( elem, name ) { + if ( !jQuery.acceptData( elem ) ) { + return; + } + + elem = elem == window ? + windowData : + elem; + + var isNode = elem.nodeType, + id = isNode ? elem[ jQuery.expando ] : elem, + cache = jQuery.cache, + thisCache = isNode ? cache[ id ] : id; + + // If we want to remove a specific section of the element's data + if ( name ) { + if ( thisCache ) { + // Remove the section of cache data + delete thisCache[ name ]; + + // If we've removed all the data, remove the element's cache + if ( isNode && jQuery.isEmptyObject(thisCache) ) { + jQuery.removeData( elem ); + } + } + + // Otherwise, we want to remove all of the element's data + } else { + if ( isNode && jQuery.support.deleteExpando ) { + delete elem[ jQuery.expando ]; + + } else if ( elem.removeAttribute ) { + elem.removeAttribute( jQuery.expando ); + + // Completely remove the data cache + } else if ( isNode ) { + delete cache[ id ]; + + // Remove all fields from the object + } else { + for ( var n in elem ) { + delete elem[ n ]; + } + } + } + }, + + // A method for determining if a DOM node can handle the data expando + acceptData: function( elem ) { + if ( elem.nodeName ) { + var match = jQuery.noData[ elem.nodeName.toLowerCase() ]; + + if ( match ) { + return !(match === true || elem.getAttribute("classid") !== match); + } + } + + return true; + } +}); + +jQuery.fn.extend({ + data: function( key, value ) { + var data = null; + + if ( typeof key === "undefined" ) { + if ( this.length ) { + var attr = this[0].attributes, name; + data = jQuery.data( this[0] ); + + for ( var i = 0, l = attr.length; i < l; i++ ) { + name = attr[i].name; + + if ( name.indexOf( "data-" ) === 0 ) { + name = name.substr( 5 ); + dataAttr( this[0], name, data[ name ] ); + } + } + } + + return data; + + } else if ( typeof key === "object" ) { + return this.each(function() { + jQuery.data( this, key ); + }); + } + + var parts = key.split("."); + parts[1] = parts[1] ? "." + parts[1] : ""; + + if ( value === undefined ) { + data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]); + + // Try to fetch any internally stored data first + if ( data === undefined && this.length ) { + data = jQuery.data( this[0], key ); + data = dataAttr( this[0], key, data ); + } + + return data === undefined && parts[1] ? + this.data( parts[0] ) : + data; + + } else { + return this.each(function() { + var $this = jQuery( this ), + args = [ parts[0], value ]; + + $this.triggerHandler( "setData" + parts[1] + "!", args ); + jQuery.data( this, key, value ); + $this.triggerHandler( "changeData" + parts[1] + "!", args ); + }); + } + }, + + removeData: function( key ) { + return this.each(function() { + jQuery.removeData( this, key ); + }); + } +}); + +function dataAttr( elem, key, data ) { + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + data = elem.getAttribute( "data-" + key ); + + if ( typeof data === "string" ) { + try { + data = data === "true" ? true : + data === "false" ? false : + data === "null" ? null : + !jQuery.isNaN( data ) ? parseFloat( data ) : + rbrace.test( data ) ? jQuery.parseJSON( data ) : + data; + } catch( e ) {} + + // Make sure we set the data so it isn't changed later + jQuery.data( elem, key, data ); + + } else { + data = undefined; + } + } + + return data; +} + + + + +jQuery.extend({ + queue: function( elem, type, data ) { + if ( !elem ) { + return; + } + + type = (type || "fx") + "queue"; + var q = jQuery.data( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( !data ) { + return q || []; + } + + if ( !q || jQuery.isArray(data) ) { + q = jQuery.data( elem, type, jQuery.makeArray(data) ); + + } else { + q.push( data ); + } + + return q; + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + fn = queue.shift(); + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + } + + if ( fn ) { + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift("inprogress"); + } + + fn.call(elem, function() { + jQuery.dequeue(elem, type); + }); + } + } +}); + +jQuery.fn.extend({ + queue: function( type, data ) { + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + } + + if ( data === undefined ) { + return jQuery.queue( this[0], type ); + } + return this.each(function( i ) { + var queue = jQuery.queue( this, type, data ); + + if ( type === "fx" && queue[0] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + }); + }, + dequeue: function( type ) { + return this.each(function() { + jQuery.dequeue( this, type ); + }); + }, + + // Based off of the plugin by Clint Helfers, with permission. + // http://blindsignals.com/index.php/2009/07/jquery-delay/ + delay: function( time, type ) { + time = jQuery.fx ? jQuery.fx.speeds[time] || time : time; + type = type || "fx"; + + return this.queue( type, function() { + var elem = this; + setTimeout(function() { + jQuery.dequeue( elem, type ); + }, time ); + }); + }, + + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + } +}); + + + + +var rclass = /[\n\t]/g, + rspaces = /\s+/, + rreturn = /\r/g, + rspecialurl = /^(?:href|src|style)$/, + rtype = /^(?:button|input)$/i, + rfocusable = /^(?:button|input|object|select|textarea)$/i, + rclickable = /^a(?:rea)?$/i, + rradiocheck = /^(?:radio|checkbox)$/i; + +jQuery.props = { + "for": "htmlFor", + "class": "className", + readonly: "readOnly", + maxlength: "maxLength", + cellspacing: "cellSpacing", + rowspan: "rowSpan", + colspan: "colSpan", + tabindex: "tabIndex", + usemap: "useMap", + frameborder: "frameBorder" +}; + +jQuery.fn.extend({ + attr: function( name, value ) { + return jQuery.access( this, name, value, true, jQuery.attr ); + }, + + removeAttr: function( name, fn ) { + return this.each(function(){ + jQuery.attr( this, name, "" ); + if ( this.nodeType === 1 ) { + this.removeAttribute( name ); + } + }); + }, + + addClass: function( value ) { + if ( jQuery.isFunction(value) ) { + return this.each(function(i) { + var self = jQuery(this); + self.addClass( value.call(this, i, self.attr("class")) ); + }); + } + + if ( value && typeof value === "string" ) { + var classNames = (value || "").split( rspaces ); + + for ( var i = 0, l = this.length; i < l; i++ ) { + var elem = this[i]; + + if ( elem.nodeType === 1 ) { + if ( !elem.className ) { + elem.className = value; + + } else { + var className = " " + elem.className + " ", + setClass = elem.className; + + for ( var c = 0, cl = classNames.length; c < cl; c++ ) { + if ( className.indexOf( " " + classNames[c] + " " ) < 0 ) { + setClass += " " + classNames[c]; + } + } + elem.className = jQuery.trim( setClass ); + } + } + } + } + + return this; + }, + + removeClass: function( value ) { + if ( jQuery.isFunction(value) ) { + return this.each(function(i) { + var self = jQuery(this); + self.removeClass( value.call(this, i, self.attr("class")) ); + }); + } + + if ( (value && typeof value === "string") || value === undefined ) { + var classNames = (value || "").split( rspaces ); + + for ( var i = 0, l = this.length; i < l; i++ ) { + var elem = this[i]; + + if ( elem.nodeType === 1 && elem.className ) { + if ( value ) { + var className = (" " + elem.className + " ").replace(rclass, " "); + for ( var c = 0, cl = classNames.length; c < cl; c++ ) { + className = className.replace(" " + classNames[c] + " ", " "); + } + elem.className = jQuery.trim( className ); + + } else { + elem.className = ""; + } + } + } + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + var type = typeof value, + isBool = typeof stateVal === "boolean"; + + if ( jQuery.isFunction( value ) ) { + return this.each(function(i) { + var self = jQuery(this); + self.toggleClass( value.call(this, i, self.attr("class"), stateVal), stateVal ); + }); + } + + return this.each(function() { + if ( type === "string" ) { + // toggle individual class names + var className, + i = 0, + self = jQuery( this ), + state = stateVal, + classNames = value.split( rspaces ); + + while ( (className = classNames[ i++ ]) ) { + // check each className given, space seperated list + state = isBool ? state : !self.hasClass( className ); + self[ state ? "addClass" : "removeClass" ]( className ); + } + + } else if ( type === "undefined" || type === "boolean" ) { + if ( this.className ) { + // store className if set + jQuery.data( this, "__className__", this.className ); + } + + // toggle whole className + this.className = this.className || value === false ? "" : jQuery.data( this, "__className__" ) || ""; + } + }); + }, + + hasClass: function( selector ) { + var className = " " + selector + " "; + for ( var i = 0, l = this.length; i < l; i++ ) { + if ( (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) { + return true; + } + } + + return false; + }, + + val: function( value ) { + if ( !arguments.length ) { + var elem = this[0]; + + if ( elem ) { + if ( jQuery.nodeName( elem, "option" ) ) { + // attributes.value is undefined in Blackberry 4.7 but + // uses .value. See #6932 + var val = elem.attributes.value; + return !val || val.specified ? elem.value : elem.text; + } + + // We need to handle select boxes special + if ( jQuery.nodeName( elem, "select" ) ) { + var index = elem.selectedIndex, + values = [], + options = elem.options, + one = elem.type === "select-one"; + + // Nothing was selected + if ( index < 0 ) { + return null; + } + + // Loop through all the selected options + for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) { + var option = options[ i ]; + + // Don't return options that are disabled or in a disabled optgroup + if ( option.selected && (jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) && + (!option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" )) ) { + + // Get the specific value for the option + value = jQuery(option).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } + } + + return values; + } + + // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified + if ( rradiocheck.test( elem.type ) && !jQuery.support.checkOn ) { + return elem.getAttribute("value") === null ? "on" : elem.value; + } + + + // Everything else, we just grab the value + return (elem.value || "").replace(rreturn, ""); + + } + + return undefined; + } + + var isFunction = jQuery.isFunction(value); + + return this.each(function(i) { + var self = jQuery(this), val = value; + + if ( this.nodeType !== 1 ) { + return; + } + + if ( isFunction ) { + val = value.call(this, i, self.val()); + } + + // Treat null/undefined as ""; convert numbers to string + if ( val == null ) { + val = ""; + } else if ( typeof val === "number" ) { + val += ""; + } else if ( jQuery.isArray(val) ) { + val = jQuery.map(val, function (value) { + return value == null ? "" : value + ""; + }); + } + + if ( jQuery.isArray(val) && rradiocheck.test( this.type ) ) { + this.checked = jQuery.inArray( self.val(), val ) >= 0; + + } else if ( jQuery.nodeName( this, "select" ) ) { + var values = jQuery.makeArray(val); + + jQuery( "option", this ).each(function() { + this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0; + }); + + if ( !values.length ) { + this.selectedIndex = -1; + } + + } else { + this.value = val; + } + }); + } +}); + +jQuery.extend({ + attrFn: { + val: true, + css: true, + html: true, + text: true, + data: true, + width: true, + height: true, + offset: true + }, + + attr: function( elem, name, value, pass ) { + // don't set attributes on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 ) { + return undefined; + } + + if ( pass && name in jQuery.attrFn ) { + return jQuery(elem)[name](value); + } + + var notxml = elem.nodeType !== 1 || !jQuery.isXMLDoc( elem ), + // Whether we are setting (or getting) + set = value !== undefined; + + // Try to normalize/fix the name + name = notxml && jQuery.props[ name ] || name; + + // These attributes require special treatment + var special = rspecialurl.test( name ); + + // Safari mis-reports the default selected property of an option + // Accessing the parent's selectedIndex property fixes it + if ( name === "selected" && !jQuery.support.optSelected ) { + var parent = elem.parentNode; + if ( parent ) { + parent.selectedIndex; + + // Make sure that it also works with optgroups, see #5701 + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + } + + // If applicable, access the attribute via the DOM 0 way + // 'in' checks fail in Blackberry 4.7 #6931 + if ( (name in elem || elem[ name ] !== undefined) && notxml && !special ) { + if ( set ) { + // We can't allow the type property to be changed (since it causes problems in IE) + if ( name === "type" && rtype.test( elem.nodeName ) && elem.parentNode ) { + jQuery.error( "type property can't be changed" ); + } + + if ( value === null ) { + if ( elem.nodeType === 1 ) { + elem.removeAttribute( name ); + } + + } else { + elem[ name ] = value; + } + } + + // browsers index elements by id/name on forms, give priority to attributes. + if ( jQuery.nodeName( elem, "form" ) && elem.getAttributeNode(name) ) { + return elem.getAttributeNode( name ).nodeValue; + } + + // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set + // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + if ( name === "tabIndex" ) { + var attributeNode = elem.getAttributeNode( "tabIndex" ); + + return attributeNode && attributeNode.specified ? + attributeNode.value : + rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? + 0 : + undefined; + } + + return elem[ name ]; + } + + if ( !jQuery.support.style && notxml && name === "style" ) { + if ( set ) { + elem.style.cssText = "" + value; + } + + return elem.style.cssText; + } + + if ( set ) { + // convert the value to a string (all browsers do this but IE) see #1070 + elem.setAttribute( name, "" + value ); + } + + // Ensure that missing attributes return undefined + // Blackberry 4.7 returns "" from getAttribute #6938 + if ( !elem.attributes[ name ] && (elem.hasAttribute && !elem.hasAttribute( name )) ) { + return undefined; + } + + var attr = !jQuery.support.hrefNormalized && notxml && special ? + // Some attributes require a special call on IE + elem.getAttribute( name, 2 ) : + elem.getAttribute( name ); + + // Non-existent attributes return null, we normalize to undefined + return attr === null ? undefined : attr; + } +}); + + + + +var rnamespaces = /\.(.*)$/, + rformElems = /^(?:textarea|input|select)$/i, + rperiod = /\./g, + rspace = / /g, + rescape = /[^\w\s.|`]/g, + fcleanup = function( nm ) { + return nm.replace(rescape, "\\$&"); + }, + focusCounts = { focusin: 0, focusout: 0 }; + +/* + * A number of helper functions used for managing events. + * Many of the ideas behind this code originated from + * Dean Edwards' addEvent library. + */ +jQuery.event = { + + // Bind an event to an element + // Original by Dean Edwards + add: function( elem, types, handler, data ) { + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // For whatever reason, IE has trouble passing the window object + // around, causing it to be cloned in the process + if ( jQuery.isWindow( elem ) && ( elem !== window && !elem.frameElement ) ) { + elem = window; + } + + if ( handler === false ) { + handler = returnFalse; + } else if ( !handler ) { + // Fixes bug #7229. Fix recommended by jdalton + return; + } + + var handleObjIn, handleObj; + + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + } + + // Make sure that the function being executed has a unique ID + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure + var elemData = jQuery.data( elem ); + + // If no elemData is found then we must be trying to bind to one of the + // banned noData elements + if ( !elemData ) { + return; + } + + // Use a key less likely to result in collisions for plain JS objects. + // Fixes bug #7150. + var eventKey = elem.nodeType ? "events" : "__events__", + events = elemData[ eventKey ], + eventHandle = elemData.handle; + + if ( typeof events === "function" ) { + // On plain objects events is a fn that holds the the data + // which prevents this data from being JSON serialized + // the function does not need to be called, it just contains the data + eventHandle = events.handle; + events = events.events; + + } else if ( !events ) { + if ( !elem.nodeType ) { + // On plain objects, create a fn that acts as the holder + // of the values to avoid JSON serialization of event data + elemData[ eventKey ] = elemData = function(){}; + } + + elemData.events = events = {}; + } + + if ( !eventHandle ) { + elemData.handle = eventHandle = function() { + // Handle the second event of a trigger and when + // an event is called after a page has unloaded + return typeof jQuery !== "undefined" && !jQuery.event.triggered ? + jQuery.event.handle.apply( eventHandle.elem, arguments ) : + undefined; + }; + } + + // Add elem as a property of the handle function + // This is to prevent a memory leak with non-native events in IE. + eventHandle.elem = elem; + + // Handle multiple events separated by a space + // jQuery(...).bind("mouseover mouseout", fn); + types = types.split(" "); + + var type, i = 0, namespaces; + + while ( (type = types[ i++ ]) ) { + handleObj = handleObjIn ? + jQuery.extend({}, handleObjIn) : + { handler: handler, data: data }; + + // Namespaced event handlers + if ( type.indexOf(".") > -1 ) { + namespaces = type.split("."); + type = namespaces.shift(); + handleObj.namespace = namespaces.slice(0).sort().join("."); + + } else { + namespaces = []; + handleObj.namespace = ""; + } + + handleObj.type = type; + if ( !handleObj.guid ) { + handleObj.guid = handler.guid; + } + + // Get the current list of functions bound to this event + var handlers = events[ type ], + special = jQuery.event.special[ type ] || {}; + + // Init the event handler queue + if ( !handlers ) { + handlers = events[ type ] = []; + + // Check for a special event handler + // Only use addEventListener/attachEvent if the special + // events handler returns false + if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + // Bind the global event handler to the element + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle, false ); + + } else if ( elem.attachEvent ) { + elem.attachEvent( "on" + type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add the function to the element's handler list + handlers.push( handleObj ); + + // Keep track of which events have been used, for global triggering + jQuery.event.global[ type ] = true; + } + + // Nullify elem to prevent memory leaks in IE + elem = null; + }, + + global: {}, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, pos ) { + // don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + if ( handler === false ) { + handler = returnFalse; + } + + var ret, type, fn, j, i = 0, all, namespaces, namespace, special, eventType, handleObj, origType, + eventKey = elem.nodeType ? "events" : "__events__", + elemData = jQuery.data( elem ), + events = elemData && elemData[ eventKey ]; + + if ( !elemData || !events ) { + return; + } + + if ( typeof events === "function" ) { + elemData = events; + events = events.events; + } + + // types is actually an event object here + if ( types && types.type ) { + handler = types.handler; + types = types.type; + } + + // Unbind all events for the element + if ( !types || typeof types === "string" && types.charAt(0) === "." ) { + types = types || ""; + + for ( type in events ) { + jQuery.event.remove( elem, type + types ); + } + + return; + } + + // Handle multiple events separated by a space + // jQuery(...).unbind("mouseover mouseout", fn); + types = types.split(" "); + + while ( (type = types[ i++ ]) ) { + origType = type; + handleObj = null; + all = type.indexOf(".") < 0; + namespaces = []; + + if ( !all ) { + // Namespaced event handlers + namespaces = type.split("."); + type = namespaces.shift(); + + namespace = new RegExp("(^|\\.)" + + jQuery.map( namespaces.slice(0).sort(), fcleanup ).join("\\.(?:.*\\.)?") + "(\\.|$)"); + } + + eventType = events[ type ]; + + if ( !eventType ) { + continue; + } + + if ( !handler ) { + for ( j = 0; j < eventType.length; j++ ) { + handleObj = eventType[ j ]; + + if ( all || namespace.test( handleObj.namespace ) ) { + jQuery.event.remove( elem, origType, handleObj.handler, j ); + eventType.splice( j--, 1 ); + } + } + + continue; + } + + special = jQuery.event.special[ type ] || {}; + + for ( j = pos || 0; j < eventType.length; j++ ) { + handleObj = eventType[ j ]; + + if ( handler.guid === handleObj.guid ) { + // remove the given handler for the given type + if ( all || namespace.test( handleObj.namespace ) ) { + if ( pos == null ) { + eventType.splice( j--, 1 ); + } + + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + + if ( pos != null ) { + break; + } + } + } + + // remove generic event handler if no more handlers exist + if ( eventType.length === 0 || pos != null && eventType.length === 1 ) { + if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) { + jQuery.removeEvent( elem, type, elemData.handle ); + } + + ret = null; + delete events[ type ]; + } + } + + // Remove the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + var handle = elemData.handle; + if ( handle ) { + handle.elem = null; + } + + delete elemData.events; + delete elemData.handle; + + if ( typeof elemData === "function" ) { + jQuery.removeData( elem, eventKey ); + + } else if ( jQuery.isEmptyObject( elemData ) ) { + jQuery.removeData( elem ); + } + } + }, + + // bubbling is internal + trigger: function( event, data, elem /*, bubbling */ ) { + // Event object or event type + var type = event.type || event, + bubbling = arguments[3]; + + if ( !bubbling ) { + event = typeof event === "object" ? + // jQuery.Event object + event[ jQuery.expando ] ? event : + // Object literal + jQuery.extend( jQuery.Event(type), event ) : + // Just the event type (string) + jQuery.Event(type); + + if ( type.indexOf("!") >= 0 ) { + event.type = type = type.slice(0, -1); + event.exclusive = true; + } + + // Handle a global trigger + if ( !elem ) { + // Don't bubble custom events when global (to avoid too much overhead) + event.stopPropagation(); + + // Only trigger if we've ever bound an event for it + if ( jQuery.event.global[ type ] ) { + jQuery.each( jQuery.cache, function() { + if ( this.events && this.events[type] ) { + jQuery.event.trigger( event, data, this.handle.elem ); + } + }); + } + } + + // Handle triggering a single element + + // don't do events on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 ) { + return undefined; + } + + // Clean up in case it is reused + event.result = undefined; + event.target = elem; + + // Clone the incoming data, if any + data = jQuery.makeArray( data ); + data.unshift( event ); + } + + event.currentTarget = elem; + + // Trigger the event, it is assumed that "handle" is a function + var handle = elem.nodeType ? + jQuery.data( elem, "handle" ) : + (jQuery.data( elem, "__events__" ) || {}).handle; + + if ( handle ) { + handle.apply( elem, data ); + } + + var parent = elem.parentNode || elem.ownerDocument; + + // Trigger an inline bound script + try { + if ( !(elem && elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()]) ) { + if ( elem[ "on" + type ] && elem[ "on" + type ].apply( elem, data ) === false ) { + event.result = false; + event.preventDefault(); + } + } + + // prevent IE from throwing an error for some elements with some event types, see #3533 + } catch (inlineError) {} + + if ( !event.isPropagationStopped() && parent ) { + jQuery.event.trigger( event, data, parent, true ); + + } else if ( !event.isDefaultPrevented() ) { + var old, + target = event.target, + targetType = type.replace( rnamespaces, "" ), + isClick = jQuery.nodeName( target, "a" ) && targetType === "click", + special = jQuery.event.special[ targetType ] || {}; + + if ( (!special._default || special._default.call( elem, event ) === false) && + !isClick && !(target && target.nodeName && jQuery.noData[target.nodeName.toLowerCase()]) ) { + + try { + if ( target[ targetType ] ) { + // Make sure that we don't accidentally re-trigger the onFOO events + old = target[ "on" + targetType ]; + + if ( old ) { + target[ "on" + targetType ] = null; + } + + jQuery.event.triggered = true; + target[ targetType ](); + } + + // prevent IE from throwing an error for some elements with some event types, see #3533 + } catch (triggerError) {} + + if ( old ) { + target[ "on" + targetType ] = old; + } + + jQuery.event.triggered = false; + } + } + }, + + handle: function( event ) { + var all, handlers, namespaces, namespace_re, events, + namespace_sort = [], + args = jQuery.makeArray( arguments ); + + event = args[0] = jQuery.event.fix( event || window.event ); + event.currentTarget = this; + + // Namespaced event handlers + all = event.type.indexOf(".") < 0 && !event.exclusive; + + if ( !all ) { + namespaces = event.type.split("."); + event.type = namespaces.shift(); + namespace_sort = namespaces.slice(0).sort(); + namespace_re = new RegExp("(^|\\.)" + namespace_sort.join("\\.(?:.*\\.)?") + "(\\.|$)"); + } + + event.namespace = event.namespace || namespace_sort.join("."); + + events = jQuery.data(this, this.nodeType ? "events" : "__events__"); + + if ( typeof events === "function" ) { + events = events.events; + } + + handlers = (events || {})[ event.type ]; + + if ( events && handlers ) { + // Clone the handlers to prevent manipulation + handlers = handlers.slice(0); + + for ( var j = 0, l = handlers.length; j < l; j++ ) { + var handleObj = handlers[ j ]; + + // Filter the functions by class + if ( all || namespace_re.test( handleObj.namespace ) ) { + // Pass in a reference to the handler function itself + // So that we can later remove it + event.handler = handleObj.handler; + event.data = handleObj.data; + event.handleObj = handleObj; + + var ret = handleObj.handler.apply( this, args ); + + if ( ret !== undefined ) { + event.result = ret; + if ( ret === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + + if ( event.isImmediatePropagationStopped() ) { + break; + } + } + } + } + + return event.result; + }, + + props: "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "), + + fix: function( event ) { + if ( event[ jQuery.expando ] ) { + return event; + } + + // store a copy of the original event object + // and "clone" to set read-only properties + var originalEvent = event; + event = jQuery.Event( originalEvent ); + + for ( var i = this.props.length, prop; i; ) { + prop = this.props[ --i ]; + event[ prop ] = originalEvent[ prop ]; + } + + // Fix target property, if necessary + if ( !event.target ) { + // Fixes #1925 where srcElement might not be defined either + event.target = event.srcElement || document; + } + + // check if target is a textnode (safari) + if ( event.target.nodeType === 3 ) { + event.target = event.target.parentNode; + } + + // Add relatedTarget, if necessary + if ( !event.relatedTarget && event.fromElement ) { + event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement; + } + + // Calculate pageX/Y if missing and clientX/Y available + if ( event.pageX == null && event.clientX != null ) { + var doc = document.documentElement, + body = document.body; + + event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0); + event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0); + } + + // Add which for key events + if ( event.which == null && (event.charCode != null || event.keyCode != null) ) { + event.which = event.charCode != null ? event.charCode : event.keyCode; + } + + // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs) + if ( !event.metaKey && event.ctrlKey ) { + event.metaKey = event.ctrlKey; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + // Note: button is not normalized, so don't use it + if ( !event.which && event.button !== undefined ) { + event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) )); + } + + return event; + }, + + // Deprecated, use jQuery.guid instead + guid: 1E8, + + // Deprecated, use jQuery.proxy instead + proxy: jQuery.proxy, + + special: { + ready: { + // Make sure the ready event is setup + setup: jQuery.bindReady, + teardown: jQuery.noop + }, + + live: { + add: function( handleObj ) { + jQuery.event.add( this, + liveConvert( handleObj.origType, handleObj.selector ), + jQuery.extend({}, handleObj, {handler: liveHandler, guid: handleObj.handler.guid}) ); + }, + + remove: function( handleObj ) { + jQuery.event.remove( this, liveConvert( handleObj.origType, handleObj.selector ), handleObj ); + } + }, + + beforeunload: { + setup: function( data, namespaces, eventHandle ) { + // We only want to do this special case on windows + if ( jQuery.isWindow( this ) ) { + this.onbeforeunload = eventHandle; + } + }, + + teardown: function( namespaces, eventHandle ) { + if ( this.onbeforeunload === eventHandle ) { + this.onbeforeunload = null; + } + } + } + } +}; + +jQuery.removeEvent = document.removeEventListener ? + function( elem, type, handle ) { + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle, false ); + } + } : + function( elem, type, handle ) { + if ( elem.detachEvent ) { + elem.detachEvent( "on" + type, handle ); + } + }; + +jQuery.Event = function( src ) { + // Allow instantiation without the 'new' keyword + if ( !this.preventDefault ) { + return new jQuery.Event( src ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + // Event type + } else { + this.type = src; + } + + // timeStamp is buggy for some events on Firefox(#3843) + // So we won't rely on the native value + this.timeStamp = jQuery.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +function returnFalse() { + return false; +} +function returnTrue() { + return true; +} + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + preventDefault: function() { + this.isDefaultPrevented = returnTrue; + + var e = this.originalEvent; + if ( !e ) { + return; + } + + // if preventDefault exists run it on the original event + if ( e.preventDefault ) { + e.preventDefault(); + + // otherwise set the returnValue property of the original event to false (IE) + } else { + e.returnValue = false; + } + }, + stopPropagation: function() { + this.isPropagationStopped = returnTrue; + + var e = this.originalEvent; + if ( !e ) { + return; + } + // if stopPropagation exists run it on the original event + if ( e.stopPropagation ) { + e.stopPropagation(); + } + // otherwise set the cancelBubble property of the original event to true (IE) + e.cancelBubble = true; + }, + stopImmediatePropagation: function() { + this.isImmediatePropagationStopped = returnTrue; + this.stopPropagation(); + }, + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse +}; + +// Checks if an event happened on an element within another element +// Used in jQuery.event.special.mouseenter and mouseleave handlers +var withinElement = function( event ) { + // Check if mouse(over|out) are still within the same parent element + var parent = event.relatedTarget; + + // Firefox sometimes assigns relatedTarget a XUL element + // which we cannot access the parentNode property of + try { + // Traverse up the tree + while ( parent && parent !== this ) { + parent = parent.parentNode; + } + + if ( parent !== this ) { + // set the correct event type + event.type = event.data; + + // handle event if we actually just moused on to a non sub-element + jQuery.event.handle.apply( this, arguments ); + } + + // assuming we've left the element since we most likely mousedover a xul element + } catch(e) { } +}, + +// In case of event delegation, we only need to rename the event.type, +// liveHandler will take care of the rest. +delegate = function( event ) { + event.type = event.data; + jQuery.event.handle.apply( this, arguments ); +}; + +// Create mouseenter and mouseleave events +jQuery.each({ + mouseenter: "mouseover", + mouseleave: "mouseout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + setup: function( data ) { + jQuery.event.add( this, fix, data && data.selector ? delegate : withinElement, orig ); + }, + teardown: function( data ) { + jQuery.event.remove( this, fix, data && data.selector ? delegate : withinElement ); + } + }; +}); + +// submit delegation +if ( !jQuery.support.submitBubbles ) { + + jQuery.event.special.submit = { + setup: function( data, namespaces ) { + if ( this.nodeName.toLowerCase() !== "form" ) { + jQuery.event.add(this, "click.specialSubmit", function( e ) { + var elem = e.target, + type = elem.type; + + if ( (type === "submit" || type === "image") && jQuery( elem ).closest("form").length ) { + e.liveFired = undefined; + return trigger( "submit", this, arguments ); + } + }); + + jQuery.event.add(this, "keypress.specialSubmit", function( e ) { + var elem = e.target, + type = elem.type; + + if ( (type === "text" || type === "password") && jQuery( elem ).closest("form").length && e.keyCode === 13 ) { + e.liveFired = undefined; + return trigger( "submit", this, arguments ); + } + }); + + } else { + return false; + } + }, + + teardown: function( namespaces ) { + jQuery.event.remove( this, ".specialSubmit" ); + } + }; + +} + +// change delegation, happens here so we have bind. +if ( !jQuery.support.changeBubbles ) { + + var changeFilters, + + getVal = function( elem ) { + var type = elem.type, val = elem.value; + + if ( type === "radio" || type === "checkbox" ) { + val = elem.checked; + + } else if ( type === "select-multiple" ) { + val = elem.selectedIndex > -1 ? + jQuery.map( elem.options, function( elem ) { + return elem.selected; + }).join("-") : + ""; + + } else if ( elem.nodeName.toLowerCase() === "select" ) { + val = elem.selectedIndex; + } + + return val; + }, + + testChange = function testChange( e ) { + var elem = e.target, data, val; + + if ( !rformElems.test( elem.nodeName ) || elem.readOnly ) { + return; + } + + data = jQuery.data( elem, "_change_data" ); + val = getVal(elem); + + // the current data will be also retrieved by beforeactivate + if ( e.type !== "focusout" || elem.type !== "radio" ) { + jQuery.data( elem, "_change_data", val ); + } + + if ( data === undefined || val === data ) { + return; + } + + if ( data != null || val ) { + e.type = "change"; + e.liveFired = undefined; + return jQuery.event.trigger( e, arguments[1], elem ); + } + }; + + jQuery.event.special.change = { + filters: { + focusout: testChange, + + beforedeactivate: testChange, + + click: function( e ) { + var elem = e.target, type = elem.type; + + if ( type === "radio" || type === "checkbox" || elem.nodeName.toLowerCase() === "select" ) { + return testChange.call( this, e ); + } + }, + + // Change has to be called before submit + // Keydown will be called before keypress, which is used in submit-event delegation + keydown: function( e ) { + var elem = e.target, type = elem.type; + + if ( (e.keyCode === 13 && elem.nodeName.toLowerCase() !== "textarea") || + (e.keyCode === 32 && (type === "checkbox" || type === "radio")) || + type === "select-multiple" ) { + return testChange.call( this, e ); + } + }, + + // Beforeactivate happens also before the previous element is blurred + // with this event you can't trigger a change event, but you can store + // information + beforeactivate: function( e ) { + var elem = e.target; + jQuery.data( elem, "_change_data", getVal(elem) ); + } + }, + + setup: function( data, namespaces ) { + if ( this.type === "file" ) { + return false; + } + + for ( var type in changeFilters ) { + jQuery.event.add( this, type + ".specialChange", changeFilters[type] ); + } + + return rformElems.test( this.nodeName ); + }, + + teardown: function( namespaces ) { + jQuery.event.remove( this, ".specialChange" ); + + return rformElems.test( this.nodeName ); + } + }; + + changeFilters = jQuery.event.special.change.filters; + + // Handle when the input is .focus()'d + changeFilters.focus = changeFilters.beforeactivate; +} + +function trigger( type, elem, args ) { + args[0].type = type; + return jQuery.event.handle.apply( elem, args ); +} + +// Create "bubbling" focus and blur events +if ( document.addEventListener ) { + jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { + jQuery.event.special[ fix ] = { + setup: function() { + if ( focusCounts[fix]++ === 0 ) { + document.addEventListener( orig, handler, true ); + } + }, + teardown: function() { + if ( --focusCounts[fix] === 0 ) { + document.removeEventListener( orig, handler, true ); + } + } + }; + + function handler( e ) { + e = jQuery.event.fix( e ); + e.type = fix; + return jQuery.event.trigger( e, null, e.target ); + } + }); +} + +jQuery.each(["bind", "one"], function( i, name ) { + jQuery.fn[ name ] = function( type, data, fn ) { + // Handle object literals + if ( typeof type === "object" ) { + for ( var key in type ) { + this[ name ](key, data, type[key], fn); + } + return this; + } + + if ( jQuery.isFunction( data ) || data === false ) { + fn = data; + data = undefined; + } + + var handler = name === "one" ? jQuery.proxy( fn, function( event ) { + jQuery( this ).unbind( event, handler ); + return fn.apply( this, arguments ); + }) : fn; + + if ( type === "unload" && name !== "one" ) { + this.one( type, data, fn ); + + } else { + for ( var i = 0, l = this.length; i < l; i++ ) { + jQuery.event.add( this[i], type, handler, data ); + } + } + + return this; + }; +}); + +jQuery.fn.extend({ + unbind: function( type, fn ) { + // Handle object literals + if ( typeof type === "object" && !type.preventDefault ) { + for ( var key in type ) { + this.unbind(key, type[key]); + } + + } else { + for ( var i = 0, l = this.length; i < l; i++ ) { + jQuery.event.remove( this[i], type, fn ); + } + } + + return this; + }, + + delegate: function( selector, types, data, fn ) { + return this.live( types, data, fn, selector ); + }, + + undelegate: function( selector, types, fn ) { + if ( arguments.length === 0 ) { + return this.unbind( "live" ); + + } else { + return this.die( types, null, fn, selector ); + } + }, + + trigger: function( type, data ) { + return this.each(function() { + jQuery.event.trigger( type, data, this ); + }); + }, + + triggerHandler: function( type, data ) { + if ( this[0] ) { + var event = jQuery.Event( type ); + event.preventDefault(); + event.stopPropagation(); + jQuery.event.trigger( event, data, this[0] ); + return event.result; + } + }, + + toggle: function( fn ) { + // Save reference to arguments for access in closure + var args = arguments, + i = 1; + + // link all the functions, so any of them can unbind this click handler + while ( i < args.length ) { + jQuery.proxy( fn, args[ i++ ] ); + } + + return this.click( jQuery.proxy( fn, function( event ) { + // Figure out which function to execute + var lastToggle = ( jQuery.data( this, "lastToggle" + fn.guid ) || 0 ) % i; + jQuery.data( this, "lastToggle" + fn.guid, lastToggle + 1 ); + + // Make sure that clicks stop + event.preventDefault(); + + // and execute the function + return args[ lastToggle ].apply( this, arguments ) || false; + })); + }, + + hover: function( fnOver, fnOut ) { + return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); + } +}); + +var liveMap = { + focus: "focusin", + blur: "focusout", + mouseenter: "mouseover", + mouseleave: "mouseout" +}; + +jQuery.each(["live", "die"], function( i, name ) { + jQuery.fn[ name ] = function( types, data, fn, origSelector /* Internal Use Only */ ) { + var type, i = 0, match, namespaces, preType, + selector = origSelector || this.selector, + context = origSelector ? this : jQuery( this.context ); + + if ( typeof types === "object" && !types.preventDefault ) { + for ( var key in types ) { + context[ name ]( key, data, types[key], selector ); + } + + return this; + } + + if ( jQuery.isFunction( data ) ) { + fn = data; + data = undefined; + } + + types = (types || "").split(" "); + + while ( (type = types[ i++ ]) != null ) { + match = rnamespaces.exec( type ); + namespaces = ""; + + if ( match ) { + namespaces = match[0]; + type = type.replace( rnamespaces, "" ); + } + + if ( type === "hover" ) { + types.push( "mouseenter" + namespaces, "mouseleave" + namespaces ); + continue; + } + + preType = type; + + if ( type === "focus" || type === "blur" ) { + types.push( liveMap[ type ] + namespaces ); + type = type + namespaces; + + } else { + type = (liveMap[ type ] || type) + namespaces; + } + + if ( name === "live" ) { + // bind live handler + for ( var j = 0, l = context.length; j < l; j++ ) { + jQuery.event.add( context[j], "live." + liveConvert( type, selector ), + { data: data, selector: selector, handler: fn, origType: type, origHandler: fn, preType: preType } ); + } + + } else { + // unbind live handler + context.unbind( "live." + liveConvert( type, selector ), fn ); + } + } + + return this; + }; +}); + +function liveHandler( event ) { + var stop, maxLevel, related, match, handleObj, elem, j, i, l, data, close, namespace, ret, + elems = [], + selectors = [], + events = jQuery.data( this, this.nodeType ? "events" : "__events__" ); + + if ( typeof events === "function" ) { + events = events.events; + } + + // Make sure we avoid non-left-click bubbling in Firefox (#3861) + if ( event.liveFired === this || !events || !events.live || event.button && event.type === "click" ) { + return; + } + + if ( event.namespace ) { + namespace = new RegExp("(^|\\.)" + event.namespace.split(".").join("\\.(?:.*\\.)?") + "(\\.|$)"); + } + + event.liveFired = this; + + var live = events.live.slice(0); + + for ( j = 0; j < live.length; j++ ) { + handleObj = live[j]; + + if ( handleObj.origType.replace( rnamespaces, "" ) === event.type ) { + selectors.push( handleObj.selector ); + + } else { + live.splice( j--, 1 ); + } + } + + match = jQuery( event.target ).closest( selectors, event.currentTarget ); + + for ( i = 0, l = match.length; i < l; i++ ) { + close = match[i]; + + for ( j = 0; j < live.length; j++ ) { + handleObj = live[j]; + + if ( close.selector === handleObj.selector && (!namespace || namespace.test( handleObj.namespace )) ) { + elem = close.elem; + related = null; + + // Those two events require additional checking + if ( handleObj.preType === "mouseenter" || handleObj.preType === "mouseleave" ) { + event.type = handleObj.preType; + related = jQuery( event.relatedTarget ).closest( handleObj.selector )[0]; + } + + if ( !related || related !== elem ) { + elems.push({ elem: elem, handleObj: handleObj, level: close.level }); + } + } + } + } + + for ( i = 0, l = elems.length; i < l; i++ ) { + match = elems[i]; + + if ( maxLevel && match.level > maxLevel ) { + break; + } + + event.currentTarget = match.elem; + event.data = match.handleObj.data; + event.handleObj = match.handleObj; + + ret = match.handleObj.origHandler.apply( match.elem, arguments ); + + if ( ret === false || event.isPropagationStopped() ) { + maxLevel = match.level; + + if ( ret === false ) { + stop = false; + } + if ( event.isImmediatePropagationStopped() ) { + break; + } + } + } + + return stop; +} + +function liveConvert( type, selector ) { + return (type && type !== "*" ? type + "." : "") + selector.replace(rperiod, "`").replace(rspace, "&"); +} + +jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " + + "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + + "change select submit keydown keypress keyup error").split(" "), function( i, name ) { + + // Handle event binding + jQuery.fn[ name ] = function( data, fn ) { + if ( fn == null ) { + fn = data; + data = null; + } + + return arguments.length > 0 ? + this.bind( name, data, fn ) : + this.trigger( name ); + }; + + if ( jQuery.attrFn ) { + jQuery.attrFn[ name ] = true; + } +}); + +// Prevent memory leaks in IE +// Window isn't included so as not to unbind existing unload events +// More info: +// - http://isaacschlueter.com/2006/10/msie-memory-leaks/ +if ( window.attachEvent && !window.addEventListener ) { + jQuery(window).bind("unload", function() { + for ( var id in jQuery.cache ) { + if ( jQuery.cache[ id ].handle ) { + // Try/Catch is to handle iframes being unloaded, see #4280 + try { + jQuery.event.remove( jQuery.cache[ id ].handle.elem ); + } catch(e) {} + } + } + }); +} + + +/*! + * Sizzle CSS Selector Engine - v1.0 + * Copyright 2009, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * More information: http://sizzlejs.com/ + */ +(function(){ + +var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, + done = 0, + toString = Object.prototype.toString, + hasDuplicate = false, + baseHasDuplicate = true; + +// Here we check if the JavaScript engine is using some sort of +// optimization where it does not always call our comparision +// function. If that is the case, discard the hasDuplicate value. +// Thus far that includes Google Chrome. +[0, 0].sort(function() { + baseHasDuplicate = false; + return 0; +}); + +var Sizzle = function( selector, context, results, seed ) { + results = results || []; + context = context || document; + + var origContext = context; + + if ( context.nodeType !== 1 && context.nodeType !== 9 ) { + return []; + } + + if ( !selector || typeof selector !== "string" ) { + return results; + } + + var m, set, checkSet, extra, ret, cur, pop, i, + prune = true, + contextXML = Sizzle.isXML( context ), + parts = [], + soFar = selector; + + // Reset the position of the chunker regexp (start from head) + do { + chunker.exec( "" ); + m = chunker.exec( soFar ); + + if ( m ) { + soFar = m[3]; + + parts.push( m[1] ); + + if ( m[2] ) { + extra = m[3]; + break; + } + } + } while ( m ); + + if ( parts.length > 1 && origPOS.exec( selector ) ) { + + if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { + set = posProcess( parts[0] + parts[1], context ); + + } else { + set = Expr.relative[ parts[0] ] ? + [ context ] : + Sizzle( parts.shift(), context ); + + while ( parts.length ) { + selector = parts.shift(); + + if ( Expr.relative[ selector ] ) { + selector += parts.shift(); + } + + set = posProcess( selector, set ); + } + } + + } else { + // Take a shortcut and set the context if the root selector is an ID + // (but not if it'll be faster if the inner selector is an ID) + if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && + Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { + + ret = Sizzle.find( parts.shift(), context, contextXML ); + context = ret.expr ? + Sizzle.filter( ret.expr, ret.set )[0] : + ret.set[0]; + } + + if ( context ) { + ret = seed ? + { expr: parts.pop(), set: makeArray(seed) } : + Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); + + set = ret.expr ? + Sizzle.filter( ret.expr, ret.set ) : + ret.set; + + if ( parts.length > 0 ) { + checkSet = makeArray( set ); + + } else { + prune = false; + } + + while ( parts.length ) { + cur = parts.pop(); + pop = cur; + + if ( !Expr.relative[ cur ] ) { + cur = ""; + } else { + pop = parts.pop(); + } + + if ( pop == null ) { + pop = context; + } + + Expr.relative[ cur ]( checkSet, pop, contextXML ); + } + + } else { + checkSet = parts = []; + } + } + + if ( !checkSet ) { + checkSet = set; + } + + if ( !checkSet ) { + Sizzle.error( cur || selector ); + } + + if ( toString.call(checkSet) === "[object Array]" ) { + if ( !prune ) { + results.push.apply( results, checkSet ); + + } else if ( context && context.nodeType === 1 ) { + for ( i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) { + results.push( set[i] ); + } + } + + } else { + for ( i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && checkSet[i].nodeType === 1 ) { + results.push( set[i] ); + } + } + } + + } else { + makeArray( checkSet, results ); + } + + if ( extra ) { + Sizzle( extra, origContext, results, seed ); + Sizzle.uniqueSort( results ); + } + + return results; +}; + +Sizzle.uniqueSort = function( results ) { + if ( sortOrder ) { + hasDuplicate = baseHasDuplicate; + results.sort( sortOrder ); + + if ( hasDuplicate ) { + for ( var i = 1; i < results.length; i++ ) { + if ( results[i] === results[ i - 1 ] ) { + results.splice( i--, 1 ); + } + } + } + } + + return results; +}; + +Sizzle.matches = function( expr, set ) { + return Sizzle( expr, null, null, set ); +}; + +Sizzle.matchesSelector = function( node, expr ) { + return Sizzle( expr, null, null, [node] ).length > 0; +}; + +Sizzle.find = function( expr, context, isXML ) { + var set; + + if ( !expr ) { + return []; + } + + for ( var i = 0, l = Expr.order.length; i < l; i++ ) { + var match, + type = Expr.order[i]; + + if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { + var left = match[1]; + match.splice( 1, 1 ); + + if ( left.substr( left.length - 1 ) !== "\\" ) { + match[1] = (match[1] || "").replace(/\\/g, ""); + set = Expr.find[ type ]( match, context, isXML ); + + if ( set != null ) { + expr = expr.replace( Expr.match[ type ], "" ); + break; + } + } + } + } + + if ( !set ) { + set = context.getElementsByTagName( "*" ); + } + + return { set: set, expr: expr }; +}; + +Sizzle.filter = function( expr, set, inplace, not ) { + var match, anyFound, + old = expr, + result = [], + curLoop = set, + isXMLFilter = set && set[0] && Sizzle.isXML( set[0] ); + + while ( expr && set.length ) { + for ( var type in Expr.filter ) { + if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) { + var found, item, + filter = Expr.filter[ type ], + left = match[1]; + + anyFound = false; + + match.splice(1,1); + + if ( left.substr( left.length - 1 ) === "\\" ) { + continue; + } + + if ( curLoop === result ) { + result = []; + } + + if ( Expr.preFilter[ type ] ) { + match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); + + if ( !match ) { + anyFound = found = true; + + } else if ( match === true ) { + continue; + } + } + + if ( match ) { + for ( var i = 0; (item = curLoop[i]) != null; i++ ) { + if ( item ) { + found = filter( item, match, i, curLoop ); + var pass = not ^ !!found; + + if ( inplace && found != null ) { + if ( pass ) { + anyFound = true; + + } else { + curLoop[i] = false; + } + + } else if ( pass ) { + result.push( item ); + anyFound = true; + } + } + } + } + + if ( found !== undefined ) { + if ( !inplace ) { + curLoop = result; + } + + expr = expr.replace( Expr.match[ type ], "" ); + + if ( !anyFound ) { + return []; + } + + break; + } + } + } + + // Improper expression + if ( expr === old ) { + if ( anyFound == null ) { + Sizzle.error( expr ); + + } else { + break; + } + } + + old = expr; + } + + return curLoop; +}; + +Sizzle.error = function( msg ) { + throw "Syntax error, unrecognized expression: " + msg; +}; + +var Expr = Sizzle.selectors = { + order: [ "ID", "NAME", "TAG" ], + + match: { + ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, + CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, + NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/, + ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/, + TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/, + CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+\-]*)\))?/, + POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/, + PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/ + }, + + leftMatch: {}, + + attrMap: { + "class": "className", + "for": "htmlFor" + }, + + attrHandle: { + href: function( elem ) { + return elem.getAttribute( "href" ); + } + }, + + relative: { + "+": function(checkSet, part){ + var isPartStr = typeof part === "string", + isTag = isPartStr && !/\W/.test( part ), + isPartStrNotTag = isPartStr && !isTag; + + if ( isTag ) { + part = part.toLowerCase(); + } + + for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { + if ( (elem = checkSet[i]) ) { + while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} + + checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ? + elem || false : + elem === part; + } + } + + if ( isPartStrNotTag ) { + Sizzle.filter( part, checkSet, true ); + } + }, + + ">": function( checkSet, part ) { + var elem, + isPartStr = typeof part === "string", + i = 0, + l = checkSet.length; + + if ( isPartStr && !/\W/.test( part ) ) { + part = part.toLowerCase(); + + for ( ; i < l; i++ ) { + elem = checkSet[i]; + + if ( elem ) { + var parent = elem.parentNode; + checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false; + } + } + + } else { + for ( ; i < l; i++ ) { + elem = checkSet[i]; + + if ( elem ) { + checkSet[i] = isPartStr ? + elem.parentNode : + elem.parentNode === part; + } + } + + if ( isPartStr ) { + Sizzle.filter( part, checkSet, true ); + } + } + }, + + "": function(checkSet, part, isXML){ + var nodeCheck, + doneName = done++, + checkFn = dirCheck; + + if ( typeof part === "string" && !/\W/.test(part) ) { + part = part.toLowerCase(); + nodeCheck = part; + checkFn = dirNodeCheck; + } + + checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML ); + }, + + "~": function( checkSet, part, isXML ) { + var nodeCheck, + doneName = done++, + checkFn = dirCheck; + + if ( typeof part === "string" && !/\W/.test( part ) ) { + part = part.toLowerCase(); + nodeCheck = part; + checkFn = dirNodeCheck; + } + + checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML ); + } + }, + + find: { + ID: function( match, context, isXML ) { + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + return m && m.parentNode ? [m] : []; + } + }, + + NAME: function( match, context ) { + if ( typeof context.getElementsByName !== "undefined" ) { + var ret = [], + results = context.getElementsByName( match[1] ); + + for ( var i = 0, l = results.length; i < l; i++ ) { + if ( results[i].getAttribute("name") === match[1] ) { + ret.push( results[i] ); + } + } + + return ret.length === 0 ? null : ret; + } + }, + + TAG: function( match, context ) { + return context.getElementsByTagName( match[1] ); + } + }, + preFilter: { + CLASS: function( match, curLoop, inplace, result, not, isXML ) { + match = " " + match[1].replace(/\\/g, "") + " "; + + if ( isXML ) { + return match; + } + + for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { + if ( elem ) { + if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n]/g, " ").indexOf(match) >= 0) ) { + if ( !inplace ) { + result.push( elem ); + } + + } else if ( inplace ) { + curLoop[i] = false; + } + } + } + + return false; + }, + + ID: function( match ) { + return match[1].replace(/\\/g, ""); + }, + + TAG: function( match, curLoop ) { + return match[1].toLowerCase(); + }, + + CHILD: function( match ) { + if ( match[1] === "nth" ) { + // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' + var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec( + match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" || + !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); + + // calculate the numbers (first)n+(last) including if they are negative + match[2] = (test[1] + (test[2] || 1)) - 0; + match[3] = test[3] - 0; + } + + // TODO: Move to normal caching system + match[0] = done++; + + return match; + }, + + ATTR: function( match, curLoop, inplace, result, not, isXML ) { + var name = match[1].replace(/\\/g, ""); + + if ( !isXML && Expr.attrMap[name] ) { + match[1] = Expr.attrMap[name]; + } + + if ( match[2] === "~=" ) { + match[4] = " " + match[4] + " "; + } + + return match; + }, + + PSEUDO: function( match, curLoop, inplace, result, not ) { + if ( match[1] === "not" ) { + // If we're dealing with a complex expression, or a simple one + if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { + match[3] = Sizzle(match[3], null, null, curLoop); + + } else { + var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); + + if ( !inplace ) { + result.push.apply( result, ret ); + } + + return false; + } + + } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { + return true; + } + + return match; + }, + + POS: function( match ) { + match.unshift( true ); + + return match; + } + }, + + filters: { + enabled: function( elem ) { + return elem.disabled === false && elem.type !== "hidden"; + }, + + disabled: function( elem ) { + return elem.disabled === true; + }, + + checked: function( elem ) { + return elem.checked === true; + }, + + selected: function( elem ) { + // Accessing this property makes selected-by-default + // options in Safari work properly + elem.parentNode.selectedIndex; + + return elem.selected === true; + }, + + parent: function( elem ) { + return !!elem.firstChild; + }, + + empty: function( elem ) { + return !elem.firstChild; + }, + + has: function( elem, i, match ) { + return !!Sizzle( match[3], elem ).length; + }, + + header: function( elem ) { + return (/h\d/i).test( elem.nodeName ); + }, + + text: function( elem ) { + return "text" === elem.type; + }, + radio: function( elem ) { + return "radio" === elem.type; + }, + + checkbox: function( elem ) { + return "checkbox" === elem.type; + }, + + file: function( elem ) { + return "file" === elem.type; + }, + password: function( elem ) { + return "password" === elem.type; + }, + + submit: function( elem ) { + return "submit" === elem.type; + }, + + image: function( elem ) { + return "image" === elem.type; + }, + + reset: function( elem ) { + return "reset" === elem.type; + }, + + button: function( elem ) { + return "button" === elem.type || elem.nodeName.toLowerCase() === "button"; + }, + + input: function( elem ) { + return (/input|select|textarea|button/i).test( elem.nodeName ); + } + }, + setFilters: { + first: function( elem, i ) { + return i === 0; + }, + + last: function( elem, i, match, array ) { + return i === array.length - 1; + }, + + even: function( elem, i ) { + return i % 2 === 0; + }, + + odd: function( elem, i ) { + return i % 2 === 1; + }, + + lt: function( elem, i, match ) { + return i < match[3] - 0; + }, + + gt: function( elem, i, match ) { + return i > match[3] - 0; + }, + + nth: function( elem, i, match ) { + return match[3] - 0 === i; + }, + + eq: function( elem, i, match ) { + return match[3] - 0 === i; + } + }, + filter: { + PSEUDO: function( elem, match, i, array ) { + var name = match[1], + filter = Expr.filters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + + } else if ( name === "contains" ) { + return (elem.textContent || elem.innerText || Sizzle.getText([ elem ]) || "").indexOf(match[3]) >= 0; + + } else if ( name === "not" ) { + var not = match[3]; + + for ( var j = 0, l = not.length; j < l; j++ ) { + if ( not[j] === elem ) { + return false; + } + } + + return true; + + } else { + Sizzle.error( "Syntax error, unrecognized expression: " + name ); + } + }, + + CHILD: function( elem, match ) { + var type = match[1], + node = elem; + + switch ( type ) { + case "only": + case "first": + while ( (node = node.previousSibling) ) { + if ( node.nodeType === 1 ) { + return false; + } + } + + if ( type === "first" ) { + return true; + } + + node = elem; + + case "last": + while ( (node = node.nextSibling) ) { + if ( node.nodeType === 1 ) { + return false; + } + } + + return true; + + case "nth": + var first = match[2], + last = match[3]; + + if ( first === 1 && last === 0 ) { + return true; + } + + var doneName = match[0], + parent = elem.parentNode; + + if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) { + var count = 0; + + for ( node = parent.firstChild; node; node = node.nextSibling ) { + if ( node.nodeType === 1 ) { + node.nodeIndex = ++count; + } + } + + parent.sizcache = doneName; + } + + var diff = elem.nodeIndex - last; + + if ( first === 0 ) { + return diff === 0; + + } else { + return ( diff % first === 0 && diff / first >= 0 ); + } + } + }, + + ID: function( elem, match ) { + return elem.nodeType === 1 && elem.getAttribute("id") === match; + }, + + TAG: function( elem, match ) { + return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match; + }, + + CLASS: function( elem, match ) { + return (" " + (elem.className || elem.getAttribute("class")) + " ") + .indexOf( match ) > -1; + }, + + ATTR: function( elem, match ) { + var name = match[1], + result = Expr.attrHandle[ name ] ? + Expr.attrHandle[ name ]( elem ) : + elem[ name ] != null ? + elem[ name ] : + elem.getAttribute( name ), + value = result + "", + type = match[2], + check = match[4]; + + return result == null ? + type === "!=" : + type === "=" ? + value === check : + type === "*=" ? + value.indexOf(check) >= 0 : + type === "~=" ? + (" " + value + " ").indexOf(check) >= 0 : + !check ? + value && result !== false : + type === "!=" ? + value !== check : + type === "^=" ? + value.indexOf(check) === 0 : + type === "$=" ? + value.substr(value.length - check.length) === check : + type === "|=" ? + value === check || value.substr(0, check.length + 1) === check + "-" : + false; + }, + + POS: function( elem, match, i, array ) { + var name = match[2], + filter = Expr.setFilters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + } + } + } +}; + +var origPOS = Expr.match.POS, + fescape = function(all, num){ + return "\\" + (num - 0 + 1); + }; + +for ( var type in Expr.match ) { + Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) ); + Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) ); +} + +var makeArray = function( array, results ) { + array = Array.prototype.slice.call( array, 0 ); + + if ( results ) { + results.push.apply( results, array ); + return results; + } + + return array; +}; + +// Perform a simple check to determine if the browser is capable of +// converting a NodeList to an array using builtin methods. +// Also verifies that the returned array holds DOM nodes +// (which is not the case in the Blackberry browser) +try { + Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType; + +// Provide a fallback method if it does not work +} catch( e ) { + makeArray = function( array, results ) { + var i = 0, + ret = results || []; + + if ( toString.call(array) === "[object Array]" ) { + Array.prototype.push.apply( ret, array ); + + } else { + if ( typeof array.length === "number" ) { + for ( var l = array.length; i < l; i++ ) { + ret.push( array[i] ); + } + + } else { + for ( ; array[i]; i++ ) { + ret.push( array[i] ); + } + } + } + + return ret; + }; +} + +var sortOrder, siblingCheck; + +if ( document.documentElement.compareDocumentPosition ) { + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { + return a.compareDocumentPosition ? -1 : 1; + } + + return a.compareDocumentPosition(b) & 4 ? -1 : 1; + }; + +} else { + sortOrder = function( a, b ) { + var al, bl, + ap = [], + bp = [], + aup = a.parentNode, + bup = b.parentNode, + cur = aup; + + // The nodes are identical, we can exit early + if ( a === b ) { + hasDuplicate = true; + return 0; + + // If the nodes are siblings (or identical) we can do a quick check + } else if ( aup === bup ) { + return siblingCheck( a, b ); + + // If no parents were found then the nodes are disconnected + } else if ( !aup ) { + return -1; + + } else if ( !bup ) { + return 1; + } + + // Otherwise they're somewhere else in the tree so we need + // to build up a full list of the parentNodes for comparison + while ( cur ) { + ap.unshift( cur ); + cur = cur.parentNode; + } + + cur = bup; + + while ( cur ) { + bp.unshift( cur ); + cur = cur.parentNode; + } + + al = ap.length; + bl = bp.length; + + // Start walking down the tree looking for a discrepancy + for ( var i = 0; i < al && i < bl; i++ ) { + if ( ap[i] !== bp[i] ) { + return siblingCheck( ap[i], bp[i] ); + } + } + + // We ended someplace up the tree so do a sibling check + return i === al ? + siblingCheck( a, bp[i], -1 ) : + siblingCheck( ap[i], b, 1 ); + }; + + siblingCheck = function( a, b, ret ) { + if ( a === b ) { + return ret; + } + + var cur = a.nextSibling; + + while ( cur ) { + if ( cur === b ) { + return -1; + } + + cur = cur.nextSibling; + } + + return 1; + }; +} + +// Utility function for retreiving the text value of an array of DOM nodes +Sizzle.getText = function( elems ) { + var ret = "", elem; + + for ( var i = 0; elems[i]; i++ ) { + elem = elems[i]; + + // Get the text from text nodes and CDATA nodes + if ( elem.nodeType === 3 || elem.nodeType === 4 ) { + ret += elem.nodeValue; + + // Traverse everything else, except comment nodes + } else if ( elem.nodeType !== 8 ) { + ret += Sizzle.getText( elem.childNodes ); + } + } + + return ret; +}; + +// Check to see if the browser returns elements by name when +// querying by getElementById (and provide a workaround) +(function(){ + // We're going to inject a fake input element with a specified name + var form = document.createElement("div"), + id = "script" + (new Date()).getTime(), + root = document.documentElement; + + form.innerHTML = "<a name='" + id + "'/>"; + + // Inject it into the root element, check its status, and remove it quickly + root.insertBefore( form, root.firstChild ); + + // The workaround has to do additional checks after a getElementById + // Which slows things down for other browsers (hence the branching) + if ( document.getElementById( id ) ) { + Expr.find.ID = function( match, context, isXML ) { + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + + return m ? + m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? + [m] : + undefined : + []; + } + }; + + Expr.filter.ID = function( elem, match ) { + var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); + + return elem.nodeType === 1 && node && node.nodeValue === match; + }; + } + + root.removeChild( form ); + + // release memory in IE + root = form = null; +})(); + +(function(){ + // Check to see if the browser returns only elements + // when doing getElementsByTagName("*") + + // Create a fake element + var div = document.createElement("div"); + div.appendChild( document.createComment("") ); + + // Make sure no comments are found + if ( div.getElementsByTagName("*").length > 0 ) { + Expr.find.TAG = function( match, context ) { + var results = context.getElementsByTagName( match[1] ); + + // Filter out possible comments + if ( match[1] === "*" ) { + var tmp = []; + + for ( var i = 0; results[i]; i++ ) { + if ( results[i].nodeType === 1 ) { + tmp.push( results[i] ); + } + } + + results = tmp; + } + + return results; + }; + } + + // Check to see if an attribute returns normalized href attributes + div.innerHTML = "<a href='#'></a>"; + + if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && + div.firstChild.getAttribute("href") !== "#" ) { + + Expr.attrHandle.href = function( elem ) { + return elem.getAttribute( "href", 2 ); + }; + } + + // release memory in IE + div = null; +})(); + +if ( document.querySelectorAll ) { + (function(){ + var oldSizzle = Sizzle, + div = document.createElement("div"), + id = "__sizzle__"; + + div.innerHTML = "<p class='TEST'></p>"; + + // Safari can't handle uppercase or unicode characters when + // in quirks mode. + if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { + return; + } + + Sizzle = function( query, context, extra, seed ) { + context = context || document; + + // Make sure that attribute selectors are quoted + query = query.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']"); + + // Only use querySelectorAll on non-XML documents + // (ID selectors don't work in non-HTML documents) + if ( !seed && !Sizzle.isXML(context) ) { + if ( context.nodeType === 9 ) { + try { + return makeArray( context.querySelectorAll(query), extra ); + } catch(qsaError) {} + + // qSA works strangely on Element-rooted queries + // We can work around this by specifying an extra ID on the root + // and working up from there (Thanks to Andrew Dupont for the technique) + // IE 8 doesn't work on object elements + } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { + var old = context.getAttribute( "id" ), + nid = old || id; + + if ( !old ) { + context.setAttribute( "id", nid ); + } + + try { + return makeArray( context.querySelectorAll( "#" + nid + " " + query ), extra ); + + } catch(pseudoError) { + } finally { + if ( !old ) { + context.removeAttribute( "id" ); + } + } + } + } + + return oldSizzle(query, context, extra, seed); + }; + + for ( var prop in oldSizzle ) { + Sizzle[ prop ] = oldSizzle[ prop ]; + } + + // release memory in IE + div = null; + })(); +} + +(function(){ + var html = document.documentElement, + matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector, + pseudoWorks = false; + + try { + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( document.documentElement, "[test!='']:sizzle" ); + + } catch( pseudoError ) { + pseudoWorks = true; + } + + if ( matches ) { + Sizzle.matchesSelector = function( node, expr ) { + // Make sure that attribute selectors are quoted + expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']"); + + if ( !Sizzle.isXML( node ) ) { + try { + if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) { + return matches.call( node, expr ); + } + } catch(e) {} + } + + return Sizzle(expr, null, null, [node]).length > 0; + }; + } +})(); + +(function(){ + var div = document.createElement("div"); + + div.innerHTML = "<div class='test e'></div><div class='test'></div>"; + + // Opera can't find a second classname (in 9.6) + // Also, make sure that getElementsByClassName actually exists + if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) { + return; + } + + // Safari caches class attributes, doesn't catch changes (in 3.2) + div.lastChild.className = "e"; + + if ( div.getElementsByClassName("e").length === 1 ) { + return; + } + + Expr.order.splice(1, 0, "CLASS"); + Expr.find.CLASS = function( match, context, isXML ) { + if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { + return context.getElementsByClassName(match[1]); + } + }; + + // release memory in IE + div = null; +})(); + +function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + + if ( elem ) { + var match = false; + + elem = elem[dir]; + + while ( elem ) { + if ( elem.sizcache === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 && !isXML ){ + elem.sizcache = doneName; + elem.sizset = i; + } + + if ( elem.nodeName.toLowerCase() === cur ) { + match = elem; + break; + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + + if ( elem ) { + var match = false; + + elem = elem[dir]; + + while ( elem ) { + if ( elem.sizcache === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 ) { + if ( !isXML ) { + elem.sizcache = doneName; + elem.sizset = i; + } + + if ( typeof cur !== "string" ) { + if ( elem === cur ) { + match = true; + break; + } + + } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { + match = elem; + break; + } + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +if ( document.documentElement.contains ) { + Sizzle.contains = function( a, b ) { + return a !== b && (a.contains ? a.contains(b) : true); + }; + +} else if ( document.documentElement.compareDocumentPosition ) { + Sizzle.contains = function( a, b ) { + return !!(a.compareDocumentPosition(b) & 16); + }; + +} else { + Sizzle.contains = function() { + return false; + }; +} + +Sizzle.isXML = function( elem ) { + // documentElement is verified for cases where it doesn't yet exist + // (such as loading iframes in IE - #4833) + var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement; + + return documentElement ? documentElement.nodeName !== "HTML" : false; +}; + +var posProcess = function( selector, context ) { + var match, + tmpSet = [], + later = "", + root = context.nodeType ? [context] : context; + + // Position selectors must be done after the filter + // And so must :not(positional) so we move all PSEUDOs to the end + while ( (match = Expr.match.PSEUDO.exec( selector )) ) { + later += match[0]; + selector = selector.replace( Expr.match.PSEUDO, "" ); + } + + selector = Expr.relative[selector] ? selector + "*" : selector; + + for ( var i = 0, l = root.length; i < l; i++ ) { + Sizzle( selector, root[i], tmpSet ); + } + + return Sizzle.filter( later, tmpSet ); +}; + +// EXPOSE +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; +jQuery.expr[":"] = jQuery.expr.filters; +jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; + + +})(); + + +var runtil = /Until$/, + rparentsprev = /^(?:parents|prevUntil|prevAll)/, + // Note: This RegExp should be improved, or likely pulled from Sizzle + rmultiselector = /,/, + isSimple = /^.[^:#\[\.,]*$/, + slice = Array.prototype.slice, + POS = jQuery.expr.match.POS; + +jQuery.fn.extend({ + find: function( selector ) { + var ret = this.pushStack( "", "find", selector ), + length = 0; + + for ( var i = 0, l = this.length; i < l; i++ ) { + length = ret.length; + jQuery.find( selector, this[i], ret ); + + if ( i > 0 ) { + // Make sure that the results are unique + for ( var n = length; n < ret.length; n++ ) { + for ( var r = 0; r < length; r++ ) { + if ( ret[r] === ret[n] ) { + ret.splice(n--, 1); + break; + } + } + } + } + } + + return ret; + }, + + has: function( target ) { + var targets = jQuery( target ); + return this.filter(function() { + for ( var i = 0, l = targets.length; i < l; i++ ) { + if ( jQuery.contains( this, targets[i] ) ) { + return true; + } + } + }); + }, + + not: function( selector ) { + return this.pushStack( winnow(this, selector, false), "not", selector); + }, + + filter: function( selector ) { + return this.pushStack( winnow(this, selector, true), "filter", selector ); + }, + + is: function( selector ) { + return !!selector && jQuery.filter( selector, this ).length > 0; + }, + + closest: function( selectors, context ) { + var ret = [], i, l, cur = this[0]; + + if ( jQuery.isArray( selectors ) ) { + var match, selector, + matches = {}, + level = 1; + + if ( cur && selectors.length ) { + for ( i = 0, l = selectors.length; i < l; i++ ) { + selector = selectors[i]; + + if ( !matches[selector] ) { + matches[selector] = jQuery.expr.match.POS.test( selector ) ? + jQuery( selector, context || this.context ) : + selector; + } + } + + while ( cur && cur.ownerDocument && cur !== context ) { + for ( selector in matches ) { + match = matches[selector]; + + if ( match.jquery ? match.index(cur) > -1 : jQuery(cur).is(match) ) { + ret.push({ selector: selector, elem: cur, level: level }); + } + } + + cur = cur.parentNode; + level++; + } + } + + return ret; + } + + var pos = POS.test( selectors ) ? + jQuery( selectors, context || this.context ) : null; + + for ( i = 0, l = this.length; i < l; i++ ) { + cur = this[i]; + + while ( cur ) { + if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) { + ret.push( cur ); + break; + + } else { + cur = cur.parentNode; + if ( !cur || !cur.ownerDocument || cur === context ) { + break; + } + } + } + } + + ret = ret.length > 1 ? jQuery.unique(ret) : ret; + + return this.pushStack( ret, "closest", selectors ); + }, + + // Determine the position of an element within + // the matched set of elements + index: function( elem ) { + if ( !elem || typeof elem === "string" ) { + return jQuery.inArray( this[0], + // If it receives a string, the selector is used + // If it receives nothing, the siblings are used + elem ? jQuery( elem ) : this.parent().children() ); + } + // Locate the position of the desired element + return jQuery.inArray( + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[0] : elem, this ); + }, + + add: function( selector, context ) { + var set = typeof selector === "string" ? + jQuery( selector, context || this.context ) : + jQuery.makeArray( selector ), + all = jQuery.merge( this.get(), set ); + + return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ? + all : + jQuery.unique( all ) ); + }, + + andSelf: function() { + return this.add( this.prevObject ); + } +}); + +// A painfully simple check to see if an element is disconnected +// from a document (should be improved, where feasible). +function isDisconnected( node ) { + return !node || !node.parentNode || node.parentNode.nodeType === 11; +} + +jQuery.each({ + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return jQuery.dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, i, until ) { + return jQuery.dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return jQuery.nth( elem, 2, "nextSibling" ); + }, + prev: function( elem ) { + return jQuery.nth( elem, 2, "previousSibling" ); + }, + nextAll: function( elem ) { + return jQuery.dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return jQuery.dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, i, until ) { + return jQuery.dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, i, until ) { + return jQuery.dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return jQuery.sibling( elem.parentNode.firstChild, elem ); + }, + children: function( elem ) { + return jQuery.sibling( elem.firstChild ); + }, + contents: function( elem ) { + return jQuery.nodeName( elem, "iframe" ) ? + elem.contentDocument || elem.contentWindow.document : + jQuery.makeArray( elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var ret = jQuery.map( this, fn, until ); + + if ( !runtil.test( name ) ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + ret = jQuery.filter( selector, ret ); + } + + ret = this.length > 1 ? jQuery.unique( ret ) : ret; + + if ( (this.length > 1 || rmultiselector.test( selector )) && rparentsprev.test( name ) ) { + ret = ret.reverse(); + } + + return this.pushStack( ret, name, slice.call(arguments).join(",") ); + }; +}); + +jQuery.extend({ + filter: function( expr, elems, not ) { + if ( not ) { + expr = ":not(" + expr + ")"; + } + + return elems.length === 1 ? + jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] : + jQuery.find.matches(expr, elems); + }, + + dir: function( elem, dir, until ) { + var matched = [], + cur = elem[ dir ]; + + while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) { + if ( cur.nodeType === 1 ) { + matched.push( cur ); + } + cur = cur[dir]; + } + return matched; + }, + + nth: function( cur, result, dir, elem ) { + result = result || 1; + var num = 0; + + for ( ; cur; cur = cur[dir] ) { + if ( cur.nodeType === 1 && ++num === result ) { + break; + } + } + + return cur; + }, + + sibling: function( n, elem ) { + var r = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + r.push( n ); + } + } + + return r; + } +}); + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, keep ) { + if ( jQuery.isFunction( qualifier ) ) { + return jQuery.grep(elements, function( elem, i ) { + var retVal = !!qualifier.call( elem, i, elem ); + return retVal === keep; + }); + + } else if ( qualifier.nodeType ) { + return jQuery.grep(elements, function( elem, i ) { + return (elem === qualifier) === keep; + }); + + } else if ( typeof qualifier === "string" ) { + var filtered = jQuery.grep(elements, function( elem ) { + return elem.nodeType === 1; + }); + + if ( isSimple.test( qualifier ) ) { + return jQuery.filter(qualifier, filtered, !keep); + } else { + qualifier = jQuery.filter( qualifier, filtered ); + } + } + + return jQuery.grep(elements, function( elem, i ) { + return (jQuery.inArray( elem, qualifier ) >= 0) === keep; + }); +} + + + + +var rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g, + rleadingWhitespace = /^\s+/, + rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig, + rtagName = /<([\w:]+)/, + rtbody = /<tbody/i, + rhtml = /<|&#?\w+;/, + rnocache = /<(?:script|object|embed|option|style)/i, + // checked="checked" or checked (html5) + rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i, + raction = /\=([^="'>\s]+\/)>/g, + wrapMap = { + option: [ 1, "<select multiple='multiple'>", "</select>" ], + legend: [ 1, "<fieldset>", "</fieldset>" ], + thead: [ 1, "<table>", "</table>" ], + tr: [ 2, "<table><tbody>", "</tbody></table>" ], + td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ], + col: [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ], + area: [ 1, "<map>", "</map>" ], + _default: [ 0, "", "" ] + }; + +wrapMap.optgroup = wrapMap.option; +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +// IE can't serialize <link> and <script> tags normally +if ( !jQuery.support.htmlSerialize ) { + wrapMap._default = [ 1, "div<div>", "</div>" ]; +} + +jQuery.fn.extend({ + text: function( text ) { + if ( jQuery.isFunction(text) ) { + return this.each(function(i) { + var self = jQuery( this ); + + self.text( text.call(this, i, self.text()) ); + }); + } + + if ( typeof text !== "object" && text !== undefined ) { + return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) ); + } + + return jQuery.text( this ); + }, + + wrapAll: function( html ) { + if ( jQuery.isFunction( html ) ) { + return this.each(function(i) { + jQuery(this).wrapAll( html.call(this, i) ); + }); + } + + if ( this[0] ) { + // The elements to wrap the target around + var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true); + + if ( this[0].parentNode ) { + wrap.insertBefore( this[0] ); + } + + wrap.map(function() { + var elem = this; + + while ( elem.firstChild && elem.firstChild.nodeType === 1 ) { + elem = elem.firstChild; + } + + return elem; + }).append(this); + } + + return this; + }, + + wrapInner: function( html ) { + if ( jQuery.isFunction( html ) ) { + return this.each(function(i) { + jQuery(this).wrapInner( html.call(this, i) ); + }); + } + + return this.each(function() { + var self = jQuery( this ), + contents = self.contents(); + + if ( contents.length ) { + contents.wrapAll( html ); + + } else { + self.append( html ); + } + }); + }, + + wrap: function( html ) { + return this.each(function() { + jQuery( this ).wrapAll( html ); + }); + }, + + unwrap: function() { + return this.parent().each(function() { + if ( !jQuery.nodeName( this, "body" ) ) { + jQuery( this ).replaceWith( this.childNodes ); + } + }).end(); + }, + + append: function() { + return this.domManip(arguments, true, function( elem ) { + if ( this.nodeType === 1 ) { + this.appendChild( elem ); + } + }); + }, + + prepend: function() { + return this.domManip(arguments, true, function( elem ) { + if ( this.nodeType === 1 ) { + this.insertBefore( elem, this.firstChild ); + } + }); + }, + + before: function() { + if ( this[0] && this[0].parentNode ) { + return this.domManip(arguments, false, function( elem ) { + this.parentNode.insertBefore( elem, this ); + }); + } else if ( arguments.length ) { + var set = jQuery(arguments[0]); + set.push.apply( set, this.toArray() ); + return this.pushStack( set, "before", arguments ); + } + }, + + after: function() { + if ( this[0] && this[0].parentNode ) { + return this.domManip(arguments, false, function( elem ) { + this.parentNode.insertBefore( elem, this.nextSibling ); + }); + } else if ( arguments.length ) { + var set = this.pushStack( this, "after", arguments ); + set.push.apply( set, jQuery(arguments[0]).toArray() ); + return set; + } + }, + + // keepData is for internal use only--do not document + remove: function( selector, keepData ) { + for ( var i = 0, elem; (elem = this[i]) != null; i++ ) { + if ( !selector || jQuery.filter( selector, [ elem ] ).length ) { + if ( !keepData && elem.nodeType === 1 ) { + jQuery.cleanData( elem.getElementsByTagName("*") ); + jQuery.cleanData( [ elem ] ); + } + + if ( elem.parentNode ) { + elem.parentNode.removeChild( elem ); + } + } + } + + return this; + }, + + empty: function() { + for ( var i = 0, elem; (elem = this[i]) != null; i++ ) { + // Remove element nodes and prevent memory leaks + if ( elem.nodeType === 1 ) { + jQuery.cleanData( elem.getElementsByTagName("*") ); + } + + // Remove any remaining nodes + while ( elem.firstChild ) { + elem.removeChild( elem.firstChild ); + } + } + + return this; + }, + + clone: function( events ) { + // Do the clone + var ret = this.map(function() { + if ( !jQuery.support.noCloneEvent && !jQuery.isXMLDoc(this) ) { + // IE copies events bound via attachEvent when + // using cloneNode. Calling detachEvent on the + // clone will also remove the events from the orignal + // In order to get around this, we use innerHTML. + // Unfortunately, this means some modifications to + // attributes in IE that are actually only stored + // as properties will not be copied (such as the + // the name attribute on an input). + var html = this.outerHTML, + ownerDocument = this.ownerDocument; + + if ( !html ) { + var div = ownerDocument.createElement("div"); + div.appendChild( this.cloneNode(true) ); + html = div.innerHTML; + } + + return jQuery.clean([html.replace(rinlinejQuery, "") + // Handle the case in IE 8 where action=/test/> self-closes a tag + .replace(raction, '="$1">') + .replace(rleadingWhitespace, "")], ownerDocument)[0]; + } else { + return this.cloneNode(true); + } + }); + + // Copy the events from the original to the clone + if ( events === true ) { + cloneCopyEvent( this, ret ); + cloneCopyEvent( this.find("*"), ret.find("*") ); + } + + // Return the cloned set + return ret; + }, + + html: function( value ) { + if ( value === undefined ) { + return this[0] && this[0].nodeType === 1 ? + this[0].innerHTML.replace(rinlinejQuery, "") : + null; + + // See if we can take a shortcut and just use innerHTML + } else if ( typeof value === "string" && !rnocache.test( value ) && + (jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value )) && + !wrapMap[ (rtagName.exec( value ) || ["", ""])[1].toLowerCase() ] ) { + + value = value.replace(rxhtmlTag, "<$1></$2>"); + + try { + for ( var i = 0, l = this.length; i < l; i++ ) { + // Remove element nodes and prevent memory leaks + if ( this[i].nodeType === 1 ) { + jQuery.cleanData( this[i].getElementsByTagName("*") ); + this[i].innerHTML = value; + } + } + + // If using innerHTML throws an exception, use the fallback method + } catch(e) { + this.empty().append( value ); + } + + } else if ( jQuery.isFunction( value ) ) { + this.each(function(i){ + var self = jQuery( this ); + + self.html( value.call(this, i, self.html()) ); + }); + + } else { + this.empty().append( value ); + } + + return this; + }, + + replaceWith: function( value ) { + if ( this[0] && this[0].parentNode ) { + // Make sure that the elements are removed from the DOM before they are inserted + // this can help fix replacing a parent with child elements + if ( jQuery.isFunction( value ) ) { + return this.each(function(i) { + var self = jQuery(this), old = self.html(); + self.replaceWith( value.call( this, i, old ) ); + }); + } + + if ( typeof value !== "string" ) { + value = jQuery( value ).detach(); + } + + return this.each(function() { + var next = this.nextSibling, + parent = this.parentNode; + + jQuery( this ).remove(); + + if ( next ) { + jQuery(next).before( value ); + } else { + jQuery(parent).append( value ); + } + }); + } else { + return this.pushStack( jQuery(jQuery.isFunction(value) ? value() : value), "replaceWith", value ); + } + }, + + detach: function( selector ) { + return this.remove( selector, true ); + }, + + domManip: function( args, table, callback ) { + var results, first, fragment, parent, + value = args[0], + scripts = []; + + // We can't cloneNode fragments that contain checked, in WebKit + if ( !jQuery.support.checkClone && arguments.length === 3 && typeof value === "string" && rchecked.test( value ) ) { + return this.each(function() { + jQuery(this).domManip( args, table, callback, true ); + }); + } + + if ( jQuery.isFunction(value) ) { + return this.each(function(i) { + var self = jQuery(this); + args[0] = value.call(this, i, table ? self.html() : undefined); + self.domManip( args, table, callback ); + }); + } + + if ( this[0] ) { + parent = value && value.parentNode; + + // If we're in a fragment, just use that instead of building a new one + if ( jQuery.support.parentNode && parent && parent.nodeType === 11 && parent.childNodes.length === this.length ) { + results = { fragment: parent }; + + } else { + results = jQuery.buildFragment( args, this, scripts ); + } + + fragment = results.fragment; + + if ( fragment.childNodes.length === 1 ) { + first = fragment = fragment.firstChild; + } else { + first = fragment.firstChild; + } + + if ( first ) { + table = table && jQuery.nodeName( first, "tr" ); + + for ( var i = 0, l = this.length; i < l; i++ ) { + callback.call( + table ? + root(this[i], first) : + this[i], + i > 0 || results.cacheable || this.length > 1 ? + fragment.cloneNode(true) : + fragment + ); + } + } + + if ( scripts.length ) { + jQuery.each( scripts, evalScript ); + } + } + + return this; + } +}); + +function root( elem, cur ) { + return jQuery.nodeName(elem, "table") ? + (elem.getElementsByTagName("tbody")[0] || + elem.appendChild(elem.ownerDocument.createElement("tbody"))) : + elem; +} + +function cloneCopyEvent(orig, ret) { + var i = 0; + + ret.each(function() { + if ( this.nodeName !== (orig[i] && orig[i].nodeName) ) { + return; + } + + var oldData = jQuery.data( orig[i++] ), + curData = jQuery.data( this, oldData ), + events = oldData && oldData.events; + + if ( events ) { + delete curData.handle; + curData.events = {}; + + for ( var type in events ) { + for ( var handler in events[ type ] ) { + jQuery.event.add( this, type, events[ type ][ handler ], events[ type ][ handler ].data ); + } + } + } + }); +} + +jQuery.buildFragment = function( args, nodes, scripts ) { + var fragment, cacheable, cacheresults, + doc = (nodes && nodes[0] ? nodes[0].ownerDocument || nodes[0] : document); + + // Only cache "small" (1/2 KB) strings that are associated with the main document + // Cloning options loses the selected state, so don't cache them + // IE 6 doesn't like it when you put <object> or <embed> elements in a fragment + // Also, WebKit does not clone 'checked' attributes on cloneNode, so don't cache + if ( args.length === 1 && typeof args[0] === "string" && args[0].length < 512 && doc === document && + !rnocache.test( args[0] ) && (jQuery.support.checkClone || !rchecked.test( args[0] )) ) { + + cacheable = true; + cacheresults = jQuery.fragments[ args[0] ]; + if ( cacheresults ) { + if ( cacheresults !== 1 ) { + fragment = cacheresults; + } + } + } + + if ( !fragment ) { + fragment = doc.createDocumentFragment(); + jQuery.clean( args, doc, fragment, scripts ); + } + + if ( cacheable ) { + jQuery.fragments[ args[0] ] = cacheresults ? fragment : 1; + } + + return { fragment: fragment, cacheable: cacheable }; +}; + +jQuery.fragments = {}; + +jQuery.each({ + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var ret = [], + insert = jQuery( selector ), + parent = this.length === 1 && this[0].parentNode; + + if ( parent && parent.nodeType === 11 && parent.childNodes.length === 1 && insert.length === 1 ) { + insert[ original ]( this[0] ); + return this; + + } else { + for ( var i = 0, l = insert.length; i < l; i++ ) { + var elems = (i > 0 ? this.clone(true) : this).get(); + jQuery( insert[i] )[ original ]( elems ); + ret = ret.concat( elems ); + } + + return this.pushStack( ret, name, insert.selector ); + } + }; +}); + +jQuery.extend({ + clean: function( elems, context, fragment, scripts ) { + context = context || document; + + // !context.createElement fails in IE with an error but returns typeof 'object' + if ( typeof context.createElement === "undefined" ) { + context = context.ownerDocument || context[0] && context[0].ownerDocument || document; + } + + var ret = []; + + for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { + if ( typeof elem === "number" ) { + elem += ""; + } + + if ( !elem ) { + continue; + } + + // Convert html string into DOM nodes + if ( typeof elem === "string" && !rhtml.test( elem ) ) { + elem = context.createTextNode( elem ); + + } else if ( typeof elem === "string" ) { + // Fix "XHTML"-style tags in all browsers + elem = elem.replace(rxhtmlTag, "<$1></$2>"); + + // Trim whitespace, otherwise indexOf won't work as expected + var tag = (rtagName.exec( elem ) || ["", ""])[1].toLowerCase(), + wrap = wrapMap[ tag ] || wrapMap._default, + depth = wrap[0], + div = context.createElement("div"); + + // Go to html and back, then peel off extra wrappers + div.innerHTML = wrap[1] + elem + wrap[2]; + + // Move to the right depth + while ( depth-- ) { + div = div.lastChild; + } + + // Remove IE's autoinserted <tbody> from table fragments + if ( !jQuery.support.tbody ) { + + // String was a <table>, *may* have spurious <tbody> + var hasBody = rtbody.test(elem), + tbody = tag === "table" && !hasBody ? + div.firstChild && div.firstChild.childNodes : + + // String was a bare <thead> or <tfoot> + wrap[1] === "<table>" && !hasBody ? + div.childNodes : + []; + + for ( var j = tbody.length - 1; j >= 0 ; --j ) { + if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length ) { + tbody[ j ].parentNode.removeChild( tbody[ j ] ); + } + } + + } + + // IE completely kills leading whitespace when innerHTML is used + if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) { + div.insertBefore( context.createTextNode( rleadingWhitespace.exec(elem)[0] ), div.firstChild ); + } + + elem = div.childNodes; + } + + if ( elem.nodeType ) { + ret.push( elem ); + } else { + ret = jQuery.merge( ret, elem ); + } + } + + if ( fragment ) { + for ( i = 0; ret[i]; i++ ) { + if ( scripts && jQuery.nodeName( ret[i], "script" ) && (!ret[i].type || ret[i].type.toLowerCase() === "text/javascript") ) { + scripts.push( ret[i].parentNode ? ret[i].parentNode.removeChild( ret[i] ) : ret[i] ); + + } else { + if ( ret[i].nodeType === 1 ) { + ret.splice.apply( ret, [i + 1, 0].concat(jQuery.makeArray(ret[i].getElementsByTagName("script"))) ); + } + fragment.appendChild( ret[i] ); + } + } + } + + return ret; + }, + + cleanData: function( elems ) { + var data, id, cache = jQuery.cache, + special = jQuery.event.special, + deleteExpando = jQuery.support.deleteExpando; + + for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { + if ( elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()] ) { + continue; + } + + id = elem[ jQuery.expando ]; + + if ( id ) { + data = cache[ id ]; + + if ( data && data.events ) { + for ( var type in data.events ) { + if ( special[ type ] ) { + jQuery.event.remove( elem, type ); + + } else { + jQuery.removeEvent( elem, type, data.handle ); + } + } + } + + if ( deleteExpando ) { + delete elem[ jQuery.expando ]; + + } else if ( elem.removeAttribute ) { + elem.removeAttribute( jQuery.expando ); + } + + delete cache[ id ]; + } + } + } +}); + +function evalScript( i, elem ) { + if ( elem.src ) { + jQuery.ajax({ + url: elem.src, + async: false, + dataType: "script" + }); + } else { + jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" ); + } + + if ( elem.parentNode ) { + elem.parentNode.removeChild( elem ); + } +} + + + + +var ralpha = /alpha\([^)]*\)/i, + ropacity = /opacity=([^)]*)/, + rdashAlpha = /-([a-z])/ig, + rupper = /([A-Z])/g, + rnumpx = /^-?\d+(?:px)?$/i, + rnum = /^-?\d/, + + cssShow = { position: "absolute", visibility: "hidden", display: "block" }, + cssWidth = [ "Left", "Right" ], + cssHeight = [ "Top", "Bottom" ], + curCSS, + + getComputedStyle, + currentStyle, + + fcamelCase = function( all, letter ) { + return letter.toUpperCase(); + }; + +jQuery.fn.css = function( name, value ) { + // Setting 'undefined' is a no-op + if ( arguments.length === 2 && value === undefined ) { + return this; + } + + return jQuery.access( this, name, value, true, function( elem, name, value ) { + return value !== undefined ? + jQuery.style( elem, name, value ) : + jQuery.css( elem, name ); + }); +}; + +jQuery.extend({ + // Add in style property hooks for overriding the default + // behavior of getting and setting a style property + cssHooks: { + opacity: { + get: function( elem, computed ) { + if ( computed ) { + // We should always get a number back from opacity + var ret = curCSS( elem, "opacity", "opacity" ); + return ret === "" ? "1" : ret; + + } else { + return elem.style.opacity; + } + } + } + }, + + // Exclude the following css properties to add px + cssNumber: { + "zIndex": true, + "fontWeight": true, + "opacity": true, + "zoom": true, + "lineHeight": true + }, + + // Add in properties whose names you wish to fix before + // setting or getting the value + cssProps: { + // normalize float css property + "float": jQuery.support.cssFloat ? "cssFloat" : "styleFloat" + }, + + // Get and set the style property on a DOM Node + style: function( elem, name, value, extra ) { + // Don't set styles on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { + return; + } + + // Make sure that we're working with the right name + var ret, origName = jQuery.camelCase( name ), + style = elem.style, hooks = jQuery.cssHooks[ origName ]; + + name = jQuery.cssProps[ origName ] || origName; + + // Check if we're setting a value + if ( value !== undefined ) { + // Make sure that NaN and null values aren't set. See: #7116 + if ( typeof value === "number" && isNaN( value ) || value == null ) { + return; + } + + // If a number was passed in, add 'px' to the (except for certain CSS properties) + if ( typeof value === "number" && !jQuery.cssNumber[ origName ] ) { + value += "px"; + } + + // If a hook was provided, use that value, otherwise just set the specified value + if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value )) !== undefined ) { + // Wrapped to prevent IE from throwing errors when 'invalid' values are provided + // Fixes bug #5509 + try { + style[ name ] = value; + } catch(e) {} + } + + } else { + // If a hook was provided get the non-computed value from there + if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) { + return ret; + } + + // Otherwise just get the value from the style object + return style[ name ]; + } + }, + + css: function( elem, name, extra ) { + // Make sure that we're working with the right name + var ret, origName = jQuery.camelCase( name ), + hooks = jQuery.cssHooks[ origName ]; + + name = jQuery.cssProps[ origName ] || origName; + + // If a hook was provided get the computed value from there + if ( hooks && "get" in hooks && (ret = hooks.get( elem, true, extra )) !== undefined ) { + return ret; + + // Otherwise, if a way to get the computed value exists, use that + } else if ( curCSS ) { + return curCSS( elem, name, origName ); + } + }, + + // A method for quickly swapping in/out CSS properties to get correct calculations + swap: function( elem, options, callback ) { + var old = {}; + + // Remember the old values, and insert the new ones + for ( var name in options ) { + old[ name ] = elem.style[ name ]; + elem.style[ name ] = options[ name ]; + } + + callback.call( elem ); + + // Revert the old values + for ( name in options ) { + elem.style[ name ] = old[ name ]; + } + }, + + camelCase: function( string ) { + return string.replace( rdashAlpha, fcamelCase ); + } +}); + +// DEPRECATED, Use jQuery.css() instead +jQuery.curCSS = jQuery.css; + +jQuery.each(["height", "width"], function( i, name ) { + jQuery.cssHooks[ name ] = { + get: function( elem, computed, extra ) { + var val; + + if ( computed ) { + if ( elem.offsetWidth !== 0 ) { + val = getWH( elem, name, extra ); + + } else { + jQuery.swap( elem, cssShow, function() { + val = getWH( elem, name, extra ); + }); + } + + if ( val <= 0 ) { + val = curCSS( elem, name, name ); + + if ( val === "0px" && currentStyle ) { + val = currentStyle( elem, name, name ); + } + + if ( val != null ) { + // Should return "auto" instead of 0, use 0 for + // temporary backwards-compat + return val === "" || val === "auto" ? "0px" : val; + } + } + + if ( val < 0 || val == null ) { + val = elem.style[ name ]; + + // Should return "auto" instead of 0, use 0 for + // temporary backwards-compat + return val === "" || val === "auto" ? "0px" : val; + } + + return typeof val === "string" ? val : val + "px"; + } + }, + + set: function( elem, value ) { + if ( rnumpx.test( value ) ) { + // ignore negative width and height values #1599 + value = parseFloat(value); + + if ( value >= 0 ) { + return value + "px"; + } + + } else { + return value; + } + } + }; +}); + +if ( !jQuery.support.opacity ) { + jQuery.cssHooks.opacity = { + get: function( elem, computed ) { + // IE uses filters for opacity + return ropacity.test((computed && elem.currentStyle ? elem.currentStyle.filter : elem.style.filter) || "") ? + (parseFloat(RegExp.$1) / 100) + "" : + computed ? "1" : ""; + }, + + set: function( elem, value ) { + var style = elem.style; + + // IE has trouble with opacity if it does not have layout + // Force it by setting the zoom level + style.zoom = 1; + + // Set the alpha filter to set the opacity + var opacity = jQuery.isNaN(value) ? + "" : + "alpha(opacity=" + value * 100 + ")", + filter = style.filter || ""; + + style.filter = ralpha.test(filter) ? + filter.replace(ralpha, opacity) : + style.filter + ' ' + opacity; + } + }; +} + +if ( document.defaultView && document.defaultView.getComputedStyle ) { + getComputedStyle = function( elem, newName, name ) { + var ret, defaultView, computedStyle; + + name = name.replace( rupper, "-$1" ).toLowerCase(); + + if ( !(defaultView = elem.ownerDocument.defaultView) ) { + return undefined; + } + + if ( (computedStyle = defaultView.getComputedStyle( elem, null )) ) { + ret = computedStyle.getPropertyValue( name ); + if ( ret === "" && !jQuery.contains( elem.ownerDocument.documentElement, elem ) ) { + ret = jQuery.style( elem, name ); + } + } + + return ret; + }; +} + +if ( document.documentElement.currentStyle ) { + currentStyle = function( elem, name ) { + var left, rsLeft, + ret = elem.currentStyle && elem.currentStyle[ name ], + style = elem.style; + + // From the awesome hack by Dean Edwards + // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 + + // If we're not dealing with a regular pixel number + // but a number that has a weird ending, we need to convert it to pixels + if ( !rnumpx.test( ret ) && rnum.test( ret ) ) { + // Remember the original values + left = style.left; + rsLeft = elem.runtimeStyle.left; + + // Put in the new values to get a computed value out + elem.runtimeStyle.left = elem.currentStyle.left; + style.left = name === "fontSize" ? "1em" : (ret || 0); + ret = style.pixelLeft + "px"; + + // Revert the changed values + style.left = left; + elem.runtimeStyle.left = rsLeft; + } + + return ret === "" ? "auto" : ret; + }; +} + +curCSS = getComputedStyle || currentStyle; + +function getWH( elem, name, extra ) { + var which = name === "width" ? cssWidth : cssHeight, + val = name === "width" ? elem.offsetWidth : elem.offsetHeight; + + if ( extra === "border" ) { + return val; + } + + jQuery.each( which, function() { + if ( !extra ) { + val -= parseFloat(jQuery.css( elem, "padding" + this )) || 0; + } + + if ( extra === "margin" ) { + val += parseFloat(jQuery.css( elem, "margin" + this )) || 0; + + } else { + val -= parseFloat(jQuery.css( elem, "border" + this + "Width" )) || 0; + } + }); + + return val; +} + +if ( jQuery.expr && jQuery.expr.filters ) { + jQuery.expr.filters.hidden = function( elem ) { + var width = elem.offsetWidth, + height = elem.offsetHeight; + + return (width === 0 && height === 0) || (!jQuery.support.reliableHiddenOffsets && (elem.style.display || jQuery.css( elem, "display" )) === "none"); + }; + + jQuery.expr.filters.visible = function( elem ) { + return !jQuery.expr.filters.hidden( elem ); + }; +} + + + + +var jsc = jQuery.now(), + rscript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, + rselectTextarea = /^(?:select|textarea)/i, + rinput = /^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i, + rnoContent = /^(?:GET|HEAD)$/, + rbracket = /\[\]$/, + jsre = /\=\?(&|$)/, + rquery = /\?/, + rts = /([?&])_=[^&]*/, + rurl = /^(\w+:)?\/\/([^\/?#]+)/, + r20 = /%20/g, + rhash = /#.*$/, + + // Keep a copy of the old load method + _load = jQuery.fn.load; + +jQuery.fn.extend({ + load: function( url, params, callback ) { + if ( typeof url !== "string" && _load ) { + return _load.apply( this, arguments ); + + // Don't do a request if no elements are being requested + } else if ( !this.length ) { + return this; + } + + var off = url.indexOf(" "); + if ( off >= 0 ) { + var selector = url.slice(off, url.length); + url = url.slice(0, off); + } + + // Default to a GET request + var type = "GET"; + + // If the second parameter was provided + if ( params ) { + // If it's a function + if ( jQuery.isFunction( params ) ) { + // We assume that it's the callback + callback = params; + params = null; + + // Otherwise, build a param string + } else if ( typeof params === "object" ) { + params = jQuery.param( params, jQuery.ajaxSettings.traditional ); + type = "POST"; + } + } + + var self = this; + + // Request the remote document + jQuery.ajax({ + url: url, + type: type, + dataType: "html", + data: params, + complete: function( res, status ) { + // If successful, inject the HTML into all the matched elements + if ( status === "success" || status === "notmodified" ) { + // See if a selector was specified + self.html( selector ? + // Create a dummy div to hold the results + jQuery("<div>") + // inject the contents of the document in, removing the scripts + // to avoid any 'Permission Denied' errors in IE + .append(res.responseText.replace(rscript, "")) + + // Locate the specified elements + .find(selector) : + + // If not, just inject the full result + res.responseText ); + } + + if ( callback ) { + self.each( callback, [res.responseText, status, res] ); + } + } + }); + + return this; + }, + + serialize: function() { + return jQuery.param(this.serializeArray()); + }, + + serializeArray: function() { + return this.map(function() { + return this.elements ? jQuery.makeArray(this.elements) : this; + }) + .filter(function() { + return this.name && !this.disabled && + (this.checked || rselectTextarea.test(this.nodeName) || + rinput.test(this.type)); + }) + .map(function( i, elem ) { + var val = jQuery(this).val(); + + return val == null ? + null : + jQuery.isArray(val) ? + jQuery.map( val, function( val, i ) { + return { name: elem.name, value: val }; + }) : + { name: elem.name, value: val }; + }).get(); + } +}); + +// Attach a bunch of functions for handling common AJAX events +jQuery.each( "ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "), function( i, o ) { + jQuery.fn[o] = function( f ) { + return this.bind(o, f); + }; +}); + +jQuery.extend({ + get: function( url, data, callback, type ) { + // shift arguments if data argument was omited + if ( jQuery.isFunction( data ) ) { + type = type || callback; + callback = data; + data = null; + } + + return jQuery.ajax({ + type: "GET", + url: url, + data: data, + success: callback, + dataType: type + }); + }, + + getScript: function( url, callback ) { + return jQuery.get(url, null, callback, "script"); + }, + + getJSON: function( url, data, callback ) { + return jQuery.get(url, data, callback, "json"); + }, + + post: function( url, data, callback, type ) { + // shift arguments if data argument was omited + if ( jQuery.isFunction( data ) ) { + type = type || callback; + callback = data; + data = {}; + } + + return jQuery.ajax({ + type: "POST", + url: url, + data: data, + success: callback, + dataType: type + }); + }, + + ajaxSetup: function( settings ) { + jQuery.extend( jQuery.ajaxSettings, settings ); + }, + + ajaxSettings: { + url: location.href, + global: true, + type: "GET", + contentType: "application/x-www-form-urlencoded", + processData: true, + async: true, + /* + timeout: 0, + data: null, + username: null, + password: null, + traditional: false, + */ + // This function can be overriden by calling jQuery.ajaxSetup + xhr: function() { + return new window.XMLHttpRequest(); + }, + accepts: { + xml: "application/xml, text/xml", + html: "text/html", + script: "text/javascript, application/javascript", + json: "application/json, text/javascript", + text: "text/plain", + _default: "*/*" + } + }, + + ajax: function( origSettings ) { + var s = jQuery.extend(true, {}, jQuery.ajaxSettings, origSettings), + jsonp, status, data, type = s.type.toUpperCase(), noContent = rnoContent.test(type); + + s.url = s.url.replace( rhash, "" ); + + // Use original (not extended) context object if it was provided + s.context = origSettings && origSettings.context != null ? origSettings.context : s; + + // convert data if not already a string + if ( s.data && s.processData && typeof s.data !== "string" ) { + s.data = jQuery.param( s.data, s.traditional ); + } + + // Handle JSONP Parameter Callbacks + if ( s.dataType === "jsonp" ) { + if ( type === "GET" ) { + if ( !jsre.test( s.url ) ) { + s.url += (rquery.test( s.url ) ? "&" : "?") + (s.jsonp || "callback") + "=?"; + } + } else if ( !s.data || !jsre.test(s.data) ) { + s.data = (s.data ? s.data + "&" : "") + (s.jsonp || "callback") + "=?"; + } + s.dataType = "json"; + } + + // Build temporary JSONP function + if ( s.dataType === "json" && (s.data && jsre.test(s.data) || jsre.test(s.url)) ) { + jsonp = s.jsonpCallback || ("jsonp" + jsc++); + + // Replace the =? sequence both in the query string and the data + if ( s.data ) { + s.data = (s.data + "").replace(jsre, "=" + jsonp + "$1"); + } + + s.url = s.url.replace(jsre, "=" + jsonp + "$1"); + + // We need to make sure + // that a JSONP style response is executed properly + s.dataType = "script"; + + // Handle JSONP-style loading + var customJsonp = window[ jsonp ]; + + window[ jsonp ] = function( tmp ) { + if ( jQuery.isFunction( customJsonp ) ) { + customJsonp( tmp ); + + } else { + // Garbage collect + window[ jsonp ] = undefined; + + try { + delete window[ jsonp ]; + } catch( jsonpError ) {} + } + + data = tmp; + jQuery.handleSuccess( s, xhr, status, data ); + jQuery.handleComplete( s, xhr, status, data ); + + if ( head ) { + head.removeChild( script ); + } + }; + } + + if ( s.dataType === "script" && s.cache === null ) { + s.cache = false; + } + + if ( s.cache === false && noContent ) { + var ts = jQuery.now(); + + // try replacing _= if it is there + var ret = s.url.replace(rts, "$1_=" + ts); + + // if nothing was replaced, add timestamp to the end + s.url = ret + ((ret === s.url) ? (rquery.test(s.url) ? "&" : "?") + "_=" + ts : ""); + } + + // If data is available, append data to url for GET/HEAD requests + if ( s.data && noContent ) { + s.url += (rquery.test(s.url) ? "&" : "?") + s.data; + } + + // Watch for a new set of requests + if ( s.global && jQuery.active++ === 0 ) { + jQuery.event.trigger( "ajaxStart" ); + } + + // Matches an absolute URL, and saves the domain + var parts = rurl.exec( s.url ), + remote = parts && (parts[1] && parts[1].toLowerCase() !== location.protocol || parts[2].toLowerCase() !== location.host); + + // If we're requesting a remote document + // and trying to load JSON or Script with a GET + if ( s.dataType === "script" && type === "GET" && remote ) { + var head = document.getElementsByTagName("head")[0] || document.documentElement; + var script = document.createElement("script"); + if ( s.scriptCharset ) { + script.charset = s.scriptCharset; + } + script.src = s.url; + + // Handle Script loading + if ( !jsonp ) { + var done = false; + + // Attach handlers for all browsers + script.onload = script.onreadystatechange = function() { + if ( !done && (!this.readyState || + this.readyState === "loaded" || this.readyState === "complete") ) { + done = true; + jQuery.handleSuccess( s, xhr, status, data ); + jQuery.handleComplete( s, xhr, status, data ); + + // Handle memory leak in IE + script.onload = script.onreadystatechange = null; + if ( head && script.parentNode ) { + head.removeChild( script ); + } + } + }; + } + + // Use insertBefore instead of appendChild to circumvent an IE6 bug. + // This arises when a base node is used (#2709 and #4378). + head.insertBefore( script, head.firstChild ); + + // We handle everything using the script element injection + return undefined; + } + + var requestDone = false; + + // Create the request object + var xhr = s.xhr(); + + if ( !xhr ) { + return; + } + + // Open the socket + // Passing null username, generates a login popup on Opera (#2865) + if ( s.username ) { + xhr.open(type, s.url, s.async, s.username, s.password); + } else { + xhr.open(type, s.url, s.async); + } + + // Need an extra try/catch for cross domain requests in Firefox 3 + try { + // Set content-type if data specified and content-body is valid for this type + if ( (s.data != null && !noContent) || (origSettings && origSettings.contentType) ) { + xhr.setRequestHeader("Content-Type", s.contentType); + } + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + if ( jQuery.lastModified[s.url] ) { + xhr.setRequestHeader("If-Modified-Since", jQuery.lastModified[s.url]); + } + + if ( jQuery.etag[s.url] ) { + xhr.setRequestHeader("If-None-Match", jQuery.etag[s.url]); + } + } + + // Set header so the called script knows that it's an XMLHttpRequest + // Only send the header if it's not a remote XHR + if ( !remote ) { + xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest"); + } + + // Set the Accepts header for the server, depending on the dataType + xhr.setRequestHeader("Accept", s.dataType && s.accepts[ s.dataType ] ? + s.accepts[ s.dataType ] + ", */*; q=0.01" : + s.accepts._default ); + } catch( headerError ) {} + + // Allow custom headers/mimetypes and early abort + if ( s.beforeSend && s.beforeSend.call(s.context, xhr, s) === false ) { + // Handle the global AJAX counter + if ( s.global && jQuery.active-- === 1 ) { + jQuery.event.trigger( "ajaxStop" ); + } + + // close opended socket + xhr.abort(); + return false; + } + + if ( s.global ) { + jQuery.triggerGlobal( s, "ajaxSend", [xhr, s] ); + } + + // Wait for a response to come back + var onreadystatechange = xhr.onreadystatechange = function( isTimeout ) { + // The request was aborted + if ( !xhr || xhr.readyState === 0 || isTimeout === "abort" ) { + // Opera doesn't call onreadystatechange before this point + // so we simulate the call + if ( !requestDone ) { + jQuery.handleComplete( s, xhr, status, data ); + } + + requestDone = true; + if ( xhr ) { + xhr.onreadystatechange = jQuery.noop; + } + + // The transfer is complete and the data is available, or the request timed out + } else if ( !requestDone && xhr && (xhr.readyState === 4 || isTimeout === "timeout") ) { + requestDone = true; + xhr.onreadystatechange = jQuery.noop; + + status = isTimeout === "timeout" ? + "timeout" : + !jQuery.httpSuccess( xhr ) ? + "error" : + s.ifModified && jQuery.httpNotModified( xhr, s.url ) ? + "notmodified" : + "success"; + + var errMsg; + + if ( status === "success" ) { + // Watch for, and catch, XML document parse errors + try { + // process the data (runs the xml through httpData regardless of callback) + data = jQuery.httpData( xhr, s.dataType, s ); + } catch( parserError ) { + status = "parsererror"; + errMsg = parserError; + } + } + + // Make sure that the request was successful or notmodified + if ( status === "success" || status === "notmodified" ) { + // JSONP handles its own success callback + if ( !jsonp ) { + jQuery.handleSuccess( s, xhr, status, data ); + } + } else { + jQuery.handleError( s, xhr, status, errMsg ); + } + + // Fire the complete handlers + if ( !jsonp ) { + jQuery.handleComplete( s, xhr, status, data ); + } + + if ( isTimeout === "timeout" ) { + xhr.abort(); + } + + // Stop memory leaks + if ( s.async ) { + xhr = null; + } + } + }; + + // Override the abort handler, if we can (IE 6 doesn't allow it, but that's OK) + // Opera doesn't fire onreadystatechange at all on abort + try { + var oldAbort = xhr.abort; + xhr.abort = function() { + if ( xhr ) { + // oldAbort has no call property in IE7 so + // just do it this way, which works in all + // browsers + Function.prototype.call.call( oldAbort, xhr ); + } + + onreadystatechange( "abort" ); + }; + } catch( abortError ) {} + + // Timeout checker + if ( s.async && s.timeout > 0 ) { + setTimeout(function() { + // Check to see if the request is still happening + if ( xhr && !requestDone ) { + onreadystatechange( "timeout" ); + } + }, s.timeout); + } + + // Send the data + try { + xhr.send( noContent || s.data == null ? null : s.data ); + + } catch( sendError ) { + jQuery.handleError( s, xhr, null, sendError ); + + // Fire the complete handlers + jQuery.handleComplete( s, xhr, status, data ); + } + + // firefox 1.5 doesn't fire statechange for sync requests + if ( !s.async ) { + onreadystatechange(); + } + + // return XMLHttpRequest to allow aborting the request etc. + return xhr; + }, + + // Serialize an array of form elements or a set of + // key/values into a query string + param: function( a, traditional ) { + var s = [], + add = function( key, value ) { + // If value is a function, invoke it and return its value + value = jQuery.isFunction(value) ? value() : value; + s[ s.length ] = encodeURIComponent(key) + "=" + encodeURIComponent(value); + }; + + // Set traditional to true for jQuery <= 1.3.2 behavior. + if ( traditional === undefined ) { + traditional = jQuery.ajaxSettings.traditional; + } + + // If an array was passed in, assume that it is an array of form elements. + if ( jQuery.isArray(a) || a.jquery ) { + // Serialize the form elements + jQuery.each( a, function() { + add( this.name, this.value ); + }); + + } else { + // If traditional, encode the "old" way (the way 1.3.2 or older + // did it), otherwise encode params recursively. + for ( var prefix in a ) { + buildParams( prefix, a[prefix], traditional, add ); + } + } + + // Return the resulting serialization + return s.join("&").replace(r20, "+"); + } +}); + +function buildParams( prefix, obj, traditional, add ) { + if ( jQuery.isArray(obj) && obj.length ) { + // Serialize array item. + jQuery.each( obj, function( i, v ) { + if ( traditional || rbracket.test( prefix ) ) { + // Treat each array item as a scalar. + add( prefix, v ); + + } else { + // If array item is non-scalar (array or object), encode its + // numeric index to resolve deserialization ambiguity issues. + // Note that rack (as of 1.0.0) can't currently deserialize + // nested arrays properly, and attempting to do so may cause + // a server error. Possible fixes are to modify rack's + // deserialization algorithm or to provide an option or flag + // to force array serialization to be shallow. + buildParams( prefix + "[" + ( typeof v === "object" || jQuery.isArray(v) ? i : "" ) + "]", v, traditional, add ); + } + }); + + } else if ( !traditional && obj != null && typeof obj === "object" ) { + if ( jQuery.isEmptyObject( obj ) ) { + add( prefix, "" ); + + // Serialize object item. + } else { + jQuery.each( obj, function( k, v ) { + buildParams( prefix + "[" + k + "]", v, traditional, add ); + }); + } + + } else { + // Serialize scalar item. + add( prefix, obj ); + } +} + +// This is still on the jQuery object... for now +// Want to move this to jQuery.ajax some day +jQuery.extend({ + + // Counter for holding the number of active queries + active: 0, + + // Last-Modified header cache for next request + lastModified: {}, + etag: {}, + + handleError: function( s, xhr, status, e ) { + // If a local callback was specified, fire it + if ( s.error ) { + s.error.call( s.context, xhr, status, e ); + } + + // Fire the global callback + if ( s.global ) { + jQuery.triggerGlobal( s, "ajaxError", [xhr, s, e] ); + } + }, + + handleSuccess: function( s, xhr, status, data ) { + // If a local callback was specified, fire it and pass it the data + if ( s.success ) { + s.success.call( s.context, data, status, xhr ); + } + + // Fire the global callback + if ( s.global ) { + jQuery.triggerGlobal( s, "ajaxSuccess", [xhr, s] ); + } + }, + + handleComplete: function( s, xhr, status ) { + // Process result + if ( s.complete ) { + s.complete.call( s.context, xhr, status ); + } + + // The request was completed + if ( s.global ) { + jQuery.triggerGlobal( s, "ajaxComplete", [xhr, s] ); + } + + // Handle the global AJAX counter + if ( s.global && jQuery.active-- === 1 ) { + jQuery.event.trigger( "ajaxStop" ); + } + }, + + triggerGlobal: function( s, type, args ) { + (s.context && s.context.url == null ? jQuery(s.context) : jQuery.event).trigger(type, args); + }, + + // Determines if an XMLHttpRequest was successful or not + httpSuccess: function( xhr ) { + try { + // IE error sometimes returns 1223 when it should be 204 so treat it as success, see #1450 + return !xhr.status && location.protocol === "file:" || + xhr.status >= 200 && xhr.status < 300 || + xhr.status === 304 || xhr.status === 1223; + } catch(e) {} + + return false; + }, + + // Determines if an XMLHttpRequest returns NotModified + httpNotModified: function( xhr, url ) { + var lastModified = xhr.getResponseHeader("Last-Modified"), + etag = xhr.getResponseHeader("Etag"); + + if ( lastModified ) { + jQuery.lastModified[url] = lastModified; + } + + if ( etag ) { + jQuery.etag[url] = etag; + } + + return xhr.status === 304; + }, + + httpData: function( xhr, type, s ) { + var ct = xhr.getResponseHeader("content-type") || "", + xml = type === "xml" || !type && ct.indexOf("xml") >= 0, + data = xml ? xhr.responseXML : xhr.responseText; + + if ( xml && data.documentElement.nodeName === "parsererror" ) { + jQuery.error( "parsererror" ); + } + + // Allow a pre-filtering function to sanitize the response + // s is checked to keep backwards compatibility + if ( s && s.dataFilter ) { + data = s.dataFilter( data, type ); + } + + // The filter can actually parse the response + if ( typeof data === "string" ) { + // Get the JavaScript object, if JSON is used. + if ( type === "json" || !type && ct.indexOf("json") >= 0 ) { + data = jQuery.parseJSON( data ); + + // If the type is "script", eval it in global context + } else if ( type === "script" || !type && ct.indexOf("javascript") >= 0 ) { + jQuery.globalEval( data ); + } + } + + return data; + } + +}); + +/* + * Create the request object; Microsoft failed to properly + * implement the XMLHttpRequest in IE7 (can't request local files), + * so we use the ActiveXObject when it is available + * Additionally XMLHttpRequest can be disabled in IE7/IE8 so + * we need a fallback. + */ +if ( window.ActiveXObject ) { + jQuery.ajaxSettings.xhr = function() { + if ( window.location.protocol !== "file:" ) { + try { + return new window.XMLHttpRequest(); + } catch(xhrError) {} + } + + try { + return new window.ActiveXObject("Microsoft.XMLHTTP"); + } catch(activeError) {} + }; +} + +// Does this browser support XHR requests? +jQuery.support.ajax = !!jQuery.ajaxSettings.xhr(); + + + + +var elemdisplay = {}, + rfxtypes = /^(?:toggle|show|hide)$/, + rfxnum = /^([+\-]=)?([\d+.\-]+)(.*)$/, + timerId, + fxAttrs = [ + // height animations + [ "height", "marginTop", "marginBottom", "paddingTop", "paddingBottom" ], + // width animations + [ "width", "marginLeft", "marginRight", "paddingLeft", "paddingRight" ], + // opacity animations + [ "opacity" ] + ]; + +jQuery.fn.extend({ + show: function( speed, easing, callback ) { + var elem, display; + + if ( speed || speed === 0 ) { + return this.animate( genFx("show", 3), speed, easing, callback); + + } else { + for ( var i = 0, j = this.length; i < j; i++ ) { + elem = this[i]; + display = elem.style.display; + + // Reset the inline display of this element to learn if it is + // being hidden by cascaded rules or not + if ( !jQuery.data(elem, "olddisplay") && display === "none" ) { + display = elem.style.display = ""; + } + + // Set elements which have been overridden with display: none + // in a stylesheet to whatever the default browser style is + // for such an element + if ( display === "" && jQuery.css( elem, "display" ) === "none" ) { + jQuery.data(elem, "olddisplay", defaultDisplay(elem.nodeName)); + } + } + + // Set the display of most of the elements in a second loop + // to avoid the constant reflow + for ( i = 0; i < j; i++ ) { + elem = this[i]; + display = elem.style.display; + + if ( display === "" || display === "none" ) { + elem.style.display = jQuery.data(elem, "olddisplay") || ""; + } + } + + return this; + } + }, + + hide: function( speed, easing, callback ) { + if ( speed || speed === 0 ) { + return this.animate( genFx("hide", 3), speed, easing, callback); + + } else { + for ( var i = 0, j = this.length; i < j; i++ ) { + var display = jQuery.css( this[i], "display" ); + + if ( display !== "none" ) { + jQuery.data( this[i], "olddisplay", display ); + } + } + + // Set the display of the elements in a second loop + // to avoid the constant reflow + for ( i = 0; i < j; i++ ) { + this[i].style.display = "none"; + } + + return this; + } + }, + + // Save the old toggle function + _toggle: jQuery.fn.toggle, + + toggle: function( fn, fn2, callback ) { + var bool = typeof fn === "boolean"; + + if ( jQuery.isFunction(fn) && jQuery.isFunction(fn2) ) { + this._toggle.apply( this, arguments ); + + } else if ( fn == null || bool ) { + this.each(function() { + var state = bool ? fn : jQuery(this).is(":hidden"); + jQuery(this)[ state ? "show" : "hide" ](); + }); + + } else { + this.animate(genFx("toggle", 3), fn, fn2, callback); + } + + return this; + }, + + fadeTo: function( speed, to, easing, callback ) { + return this.filter(":hidden").css("opacity", 0).show().end() + .animate({opacity: to}, speed, easing, callback); + }, + + animate: function( prop, speed, easing, callback ) { + var optall = jQuery.speed(speed, easing, callback); + + if ( jQuery.isEmptyObject( prop ) ) { + return this.each( optall.complete ); + } + + return this[ optall.queue === false ? "each" : "queue" ](function() { + // XXX 'this' does not always have a nodeName when running the + // test suite + + var opt = jQuery.extend({}, optall), p, + isElement = this.nodeType === 1, + hidden = isElement && jQuery(this).is(":hidden"), + self = this; + + for ( p in prop ) { + var name = jQuery.camelCase( p ); + + if ( p !== name ) { + prop[ name ] = prop[ p ]; + delete prop[ p ]; + p = name; + } + + if ( prop[p] === "hide" && hidden || prop[p] === "show" && !hidden ) { + return opt.complete.call(this); + } + + if ( isElement && ( p === "height" || p === "width" ) ) { + // Make sure that nothing sneaks out + // Record all 3 overflow attributes because IE does not + // change the overflow attribute when overflowX and + // overflowY are set to the same value + opt.overflow = [ this.style.overflow, this.style.overflowX, this.style.overflowY ]; + + // Set display property to inline-block for height/width + // animations on inline elements that are having width/height + // animated + if ( jQuery.css( this, "display" ) === "inline" && + jQuery.css( this, "float" ) === "none" ) { + if ( !jQuery.support.inlineBlockNeedsLayout ) { + this.style.display = "inline-block"; + + } else { + var display = defaultDisplay(this.nodeName); + + // inline-level elements accept inline-block; + // block-level elements need to be inline with layout + if ( display === "inline" ) { + this.style.display = "inline-block"; + + } else { + this.style.display = "inline"; + this.style.zoom = 1; + } + } + } + } + + if ( jQuery.isArray( prop[p] ) ) { + // Create (if needed) and add to specialEasing + (opt.specialEasing = opt.specialEasing || {})[p] = prop[p][1]; + prop[p] = prop[p][0]; + } + } + + if ( opt.overflow != null ) { + this.style.overflow = "hidden"; + } + + opt.curAnim = jQuery.extend({}, prop); + + jQuery.each( prop, function( name, val ) { + var e = new jQuery.fx( self, opt, name ); + + if ( rfxtypes.test(val) ) { + e[ val === "toggle" ? hidden ? "show" : "hide" : val ]( prop ); + + } else { + var parts = rfxnum.exec(val), + start = e.cur() || 0; + + if ( parts ) { + var end = parseFloat( parts[2] ), + unit = parts[3] || "px"; + + // We need to compute starting value + if ( unit !== "px" ) { + jQuery.style( self, name, (end || 1) + unit); + start = ((end || 1) / e.cur()) * start; + jQuery.style( self, name, start + unit); + } + + // If a +=/-= token was provided, we're doing a relative animation + if ( parts[1] ) { + end = ((parts[1] === "-=" ? -1 : 1) * end) + start; + } + + e.custom( start, end, unit ); + + } else { + e.custom( start, val, "" ); + } + } + }); + + // For JS strict compliance + return true; + }); + }, + + stop: function( clearQueue, gotoEnd ) { + var timers = jQuery.timers; + + if ( clearQueue ) { + this.queue([]); + } + + this.each(function() { + // go in reverse order so anything added to the queue during the loop is ignored + for ( var i = timers.length - 1; i >= 0; i-- ) { + if ( timers[i].elem === this ) { + if (gotoEnd) { + // force the next step to be the last + timers[i](true); + } + + timers.splice(i, 1); + } + } + }); + + // start the next in the queue if the last step wasn't forced + if ( !gotoEnd ) { + this.dequeue(); + } + + return this; + } + +}); + +function genFx( type, num ) { + var obj = {}; + + jQuery.each( fxAttrs.concat.apply([], fxAttrs.slice(0,num)), function() { + obj[ this ] = type; + }); + + return obj; +} + +// Generate shortcuts for custom animations +jQuery.each({ + slideDown: genFx("show", 1), + slideUp: genFx("hide", 1), + slideToggle: genFx("toggle", 1), + fadeIn: { opacity: "show" }, + fadeOut: { opacity: "hide" }, + fadeToggle: { opacity: "toggle" } +}, function( name, props ) { + jQuery.fn[ name ] = function( speed, easing, callback ) { + return this.animate( props, speed, easing, callback ); + }; +}); + +jQuery.extend({ + speed: function( speed, easing, fn ) { + var opt = speed && typeof speed === "object" ? jQuery.extend({}, speed) : { + complete: fn || !fn && easing || + jQuery.isFunction( speed ) && speed, + duration: speed, + easing: fn && easing || easing && !jQuery.isFunction(easing) && easing + }; + + opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration : + opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[opt.duration] : jQuery.fx.speeds._default; + + // Queueing + opt.old = opt.complete; + opt.complete = function() { + if ( opt.queue !== false ) { + jQuery(this).dequeue(); + } + if ( jQuery.isFunction( opt.old ) ) { + opt.old.call( this ); + } + }; + + return opt; + }, + + easing: { + linear: function( p, n, firstNum, diff ) { + return firstNum + diff * p; + }, + swing: function( p, n, firstNum, diff ) { + return ((-Math.cos(p*Math.PI)/2) + 0.5) * diff + firstNum; + } + }, + + timers: [], + + fx: function( elem, options, prop ) { + this.options = options; + this.elem = elem; + this.prop = prop; + + if ( !options.orig ) { + options.orig = {}; + } + } + +}); + +jQuery.fx.prototype = { + // Simple function for setting a style value + update: function() { + if ( this.options.step ) { + this.options.step.call( this.elem, this.now, this ); + } + + (jQuery.fx.step[this.prop] || jQuery.fx.step._default)( this ); + }, + + // Get the current size + cur: function() { + if ( this.elem[this.prop] != null && (!this.elem.style || this.elem.style[this.prop] == null) ) { + return this.elem[ this.prop ]; + } + + var r = parseFloat( jQuery.css( this.elem, this.prop ) ); + return r && r > -10000 ? r : 0; + }, + + // Start an animation from one number to another + custom: function( from, to, unit ) { + var self = this, + fx = jQuery.fx; + + this.startTime = jQuery.now(); + this.start = from; + this.end = to; + this.unit = unit || this.unit || "px"; + this.now = this.start; + this.pos = this.state = 0; + + function t( gotoEnd ) { + return self.step(gotoEnd); + } + + t.elem = this.elem; + + if ( t() && jQuery.timers.push(t) && !timerId ) { + timerId = setInterval(fx.tick, fx.interval); + } + }, + + // Simple 'show' function + show: function() { + // Remember where we started, so that we can go back to it later + this.options.orig[this.prop] = jQuery.style( this.elem, this.prop ); + this.options.show = true; + + // Begin the animation + // Make sure that we start at a small width/height to avoid any + // flash of content + this.custom(this.prop === "width" || this.prop === "height" ? 1 : 0, this.cur()); + + // Start by showing the element + jQuery( this.elem ).show(); + }, + + // Simple 'hide' function + hide: function() { + // Remember where we started, so that we can go back to it later + this.options.orig[this.prop] = jQuery.style( this.elem, this.prop ); + this.options.hide = true; + + // Begin the animation + this.custom(this.cur(), 0); + }, + + // Each step of an animation + step: function( gotoEnd ) { + var t = jQuery.now(), done = true; + + if ( gotoEnd || t >= this.options.duration + this.startTime ) { + this.now = this.end; + this.pos = this.state = 1; + this.update(); + + this.options.curAnim[ this.prop ] = true; + + for ( var i in this.options.curAnim ) { + if ( this.options.curAnim[i] !== true ) { + done = false; + } + } + + if ( done ) { + // Reset the overflow + if ( this.options.overflow != null && !jQuery.support.shrinkWrapBlocks ) { + var elem = this.elem, + options = this.options; + + jQuery.each( [ "", "X", "Y" ], function (index, value) { + elem.style[ "overflow" + value ] = options.overflow[index]; + } ); + } + + // Hide the element if the "hide" operation was done + if ( this.options.hide ) { + jQuery(this.elem).hide(); + } + + // Reset the properties, if the item has been hidden or shown + if ( this.options.hide || this.options.show ) { + for ( var p in this.options.curAnim ) { + jQuery.style( this.elem, p, this.options.orig[p] ); + } + } + + // Execute the complete function + this.options.complete.call( this.elem ); + } + + return false; + + } else { + var n = t - this.startTime; + this.state = n / this.options.duration; + + // Perform the easing function, defaults to swing + var specialEasing = this.options.specialEasing && this.options.specialEasing[this.prop]; + var defaultEasing = this.options.easing || (jQuery.easing.swing ? "swing" : "linear"); + this.pos = jQuery.easing[specialEasing || defaultEasing](this.state, n, 0, 1, this.options.duration); + this.now = this.start + ((this.end - this.start) * this.pos); + + // Perform the next step of the animation + this.update(); + } + + return true; + } +}; + +jQuery.extend( jQuery.fx, { + tick: function() { + var timers = jQuery.timers; + + for ( var i = 0; i < timers.length; i++ ) { + if ( !timers[i]() ) { + timers.splice(i--, 1); + } + } + + if ( !timers.length ) { + jQuery.fx.stop(); + } + }, + + interval: 13, + + stop: function() { + clearInterval( timerId ); + timerId = null; + }, + + speeds: { + slow: 600, + fast: 200, + // Default speed + _default: 400 + }, + + step: { + opacity: function( fx ) { + jQuery.style( fx.elem, "opacity", fx.now ); + }, + + _default: function( fx ) { + if ( fx.elem.style && fx.elem.style[ fx.prop ] != null ) { + fx.elem.style[ fx.prop ] = (fx.prop === "width" || fx.prop === "height" ? Math.max(0, fx.now) : fx.now) + fx.unit; + } else { + fx.elem[ fx.prop ] = fx.now; + } + } + } +}); + +if ( jQuery.expr && jQuery.expr.filters ) { + jQuery.expr.filters.animated = function( elem ) { + return jQuery.grep(jQuery.timers, function( fn ) { + return elem === fn.elem; + }).length; + }; +} + +function defaultDisplay( nodeName ) { + if ( !elemdisplay[ nodeName ] ) { + var elem = jQuery("<" + nodeName + ">").appendTo("body"), + display = elem.css("display"); + + elem.remove(); + + if ( display === "none" || display === "" ) { + display = "block"; + } + + elemdisplay[ nodeName ] = display; + } + + return elemdisplay[ nodeName ]; +} + + + + +var rtable = /^t(?:able|d|h)$/i, + rroot = /^(?:body|html)$/i; + +if ( "getBoundingClientRect" in document.documentElement ) { + jQuery.fn.offset = function( options ) { + var elem = this[0], box; + + if ( options ) { + return this.each(function( i ) { + jQuery.offset.setOffset( this, options, i ); + }); + } + + if ( !elem || !elem.ownerDocument ) { + return null; + } + + if ( elem === elem.ownerDocument.body ) { + return jQuery.offset.bodyOffset( elem ); + } + + try { + box = elem.getBoundingClientRect(); + } catch(e) {} + + var doc = elem.ownerDocument, + docElem = doc.documentElement; + + // Make sure we're not dealing with a disconnected DOM node + if ( !box || !jQuery.contains( docElem, elem ) ) { + return box || { top: 0, left: 0 }; + } + + var body = doc.body, + win = getWindow(doc), + clientTop = docElem.clientTop || body.clientTop || 0, + clientLeft = docElem.clientLeft || body.clientLeft || 0, + scrollTop = (win.pageYOffset || jQuery.support.boxModel && docElem.scrollTop || body.scrollTop ), + scrollLeft = (win.pageXOffset || jQuery.support.boxModel && docElem.scrollLeft || body.scrollLeft), + top = box.top + scrollTop - clientTop, + left = box.left + scrollLeft - clientLeft; + + return { top: top, left: left }; + }; + +} else { + jQuery.fn.offset = function( options ) { + var elem = this[0]; + + if ( options ) { + return this.each(function( i ) { + jQuery.offset.setOffset( this, options, i ); + }); + } + + if ( !elem || !elem.ownerDocument ) { + return null; + } + + if ( elem === elem.ownerDocument.body ) { + return jQuery.offset.bodyOffset( elem ); + } + + jQuery.offset.initialize(); + + var computedStyle, + offsetParent = elem.offsetParent, + prevOffsetParent = elem, + doc = elem.ownerDocument, + docElem = doc.documentElement, + body = doc.body, + defaultView = doc.defaultView, + prevComputedStyle = defaultView ? defaultView.getComputedStyle( elem, null ) : elem.currentStyle, + top = elem.offsetTop, + left = elem.offsetLeft; + + while ( (elem = elem.parentNode) && elem !== body && elem !== docElem ) { + if ( jQuery.offset.supportsFixedPosition && prevComputedStyle.position === "fixed" ) { + break; + } + + computedStyle = defaultView ? defaultView.getComputedStyle(elem, null) : elem.currentStyle; + top -= elem.scrollTop; + left -= elem.scrollLeft; + + if ( elem === offsetParent ) { + top += elem.offsetTop; + left += elem.offsetLeft; + + if ( jQuery.offset.doesNotAddBorder && !(jQuery.offset.doesAddBorderForTableAndCells && rtable.test(elem.nodeName)) ) { + top += parseFloat( computedStyle.borderTopWidth ) || 0; + left += parseFloat( computedStyle.borderLeftWidth ) || 0; + } + + prevOffsetParent = offsetParent; + offsetParent = elem.offsetParent; + } + + if ( jQuery.offset.subtractsBorderForOverflowNotVisible && computedStyle.overflow !== "visible" ) { + top += parseFloat( computedStyle.borderTopWidth ) || 0; + left += parseFloat( computedStyle.borderLeftWidth ) || 0; + } + + prevComputedStyle = computedStyle; + } + + if ( prevComputedStyle.position === "relative" || prevComputedStyle.position === "static" ) { + top += body.offsetTop; + left += body.offsetLeft; + } + + if ( jQuery.offset.supportsFixedPosition && prevComputedStyle.position === "fixed" ) { + top += Math.max( docElem.scrollTop, body.scrollTop ); + left += Math.max( docElem.scrollLeft, body.scrollLeft ); + } + + return { top: top, left: left }; + }; +} + +jQuery.offset = { + initialize: function() { + var body = document.body, container = document.createElement("div"), innerDiv, checkDiv, table, td, bodyMarginTop = parseFloat( jQuery.css(body, "marginTop") ) || 0, + html = "<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>"; + + jQuery.extend( container.style, { position: "absolute", top: 0, left: 0, margin: 0, border: 0, width: "1px", height: "1px", visibility: "hidden" } ); + + container.innerHTML = html; + body.insertBefore( container, body.firstChild ); + innerDiv = container.firstChild; + checkDiv = innerDiv.firstChild; + td = innerDiv.nextSibling.firstChild.firstChild; + + this.doesNotAddBorder = (checkDiv.offsetTop !== 5); + this.doesAddBorderForTableAndCells = (td.offsetTop === 5); + + checkDiv.style.position = "fixed"; + checkDiv.style.top = "20px"; + + // safari subtracts parent border width here which is 5px + this.supportsFixedPosition = (checkDiv.offsetTop === 20 || checkDiv.offsetTop === 15); + checkDiv.style.position = checkDiv.style.top = ""; + + innerDiv.style.overflow = "hidden"; + innerDiv.style.position = "relative"; + + this.subtractsBorderForOverflowNotVisible = (checkDiv.offsetTop === -5); + + this.doesNotIncludeMarginInBodyOffset = (body.offsetTop !== bodyMarginTop); + + body.removeChild( container ); + body = container = innerDiv = checkDiv = table = td = null; + jQuery.offset.initialize = jQuery.noop; + }, + + bodyOffset: function( body ) { + var top = body.offsetTop, + left = body.offsetLeft; + + jQuery.offset.initialize(); + + if ( jQuery.offset.doesNotIncludeMarginInBodyOffset ) { + top += parseFloat( jQuery.css(body, "marginTop") ) || 0; + left += parseFloat( jQuery.css(body, "marginLeft") ) || 0; + } + + return { top: top, left: left }; + }, + + setOffset: function( elem, options, i ) { + var position = jQuery.css( elem, "position" ); + + // set position first, in-case top/left are set even on static elem + if ( position === "static" ) { + elem.style.position = "relative"; + } + + var curElem = jQuery( elem ), + curOffset = curElem.offset(), + curCSSTop = jQuery.css( elem, "top" ), + curCSSLeft = jQuery.css( elem, "left" ), + calculatePosition = (position === "absolute" && jQuery.inArray('auto', [curCSSTop, curCSSLeft]) > -1), + props = {}, curPosition = {}, curTop, curLeft; + + // need to be able to calculate position if either top or left is auto and position is absolute + if ( calculatePosition ) { + curPosition = curElem.position(); + } + + curTop = calculatePosition ? curPosition.top : parseInt( curCSSTop, 10 ) || 0; + curLeft = calculatePosition ? curPosition.left : parseInt( curCSSLeft, 10 ) || 0; + + if ( jQuery.isFunction( options ) ) { + options = options.call( elem, i, curOffset ); + } + + if (options.top != null) { + props.top = (options.top - curOffset.top) + curTop; + } + if (options.left != null) { + props.left = (options.left - curOffset.left) + curLeft; + } + + if ( "using" in options ) { + options.using.call( elem, props ); + } else { + curElem.css( props ); + } + } +}; + + +jQuery.fn.extend({ + position: function() { + if ( !this[0] ) { + return null; + } + + var elem = this[0], + + // Get *real* offsetParent + offsetParent = this.offsetParent(), + + // Get correct offsets + offset = this.offset(), + parentOffset = rroot.test(offsetParent[0].nodeName) ? { top: 0, left: 0 } : offsetParent.offset(); + + // Subtract element margins + // note: when an element has margin: auto the offsetLeft and marginLeft + // are the same in Safari causing offset.left to incorrectly be 0 + offset.top -= parseFloat( jQuery.css(elem, "marginTop") ) || 0; + offset.left -= parseFloat( jQuery.css(elem, "marginLeft") ) || 0; + + // Add offsetParent borders + parentOffset.top += parseFloat( jQuery.css(offsetParent[0], "borderTopWidth") ) || 0; + parentOffset.left += parseFloat( jQuery.css(offsetParent[0], "borderLeftWidth") ) || 0; + + // Subtract the two offsets + return { + top: offset.top - parentOffset.top, + left: offset.left - parentOffset.left + }; + }, + + offsetParent: function() { + return this.map(function() { + var offsetParent = this.offsetParent || document.body; + while ( offsetParent && (!rroot.test(offsetParent.nodeName) && jQuery.css(offsetParent, "position") === "static") ) { + offsetParent = offsetParent.offsetParent; + } + return offsetParent; + }); + } +}); + + +// Create scrollLeft and scrollTop methods +jQuery.each( ["Left", "Top"], function( i, name ) { + var method = "scroll" + name; + + jQuery.fn[ method ] = function(val) { + var elem = this[0], win; + + if ( !elem ) { + return null; + } + + if ( val !== undefined ) { + // Set the scroll offset + return this.each(function() { + win = getWindow( this ); + + if ( win ) { + win.scrollTo( + !i ? val : jQuery(win).scrollLeft(), + i ? val : jQuery(win).scrollTop() + ); + + } else { + this[ method ] = val; + } + }); + } else { + win = getWindow( elem ); + + // Return the scroll offset + return win ? ("pageXOffset" in win) ? win[ i ? "pageYOffset" : "pageXOffset" ] : + jQuery.support.boxModel && win.document.documentElement[ method ] || + win.document.body[ method ] : + elem[ method ]; + } + }; +}); + +function getWindow( elem ) { + return jQuery.isWindow( elem ) ? + elem : + elem.nodeType === 9 ? + elem.defaultView || elem.parentWindow : + false; +} + + + + +// Create innerHeight, innerWidth, outerHeight and outerWidth methods +jQuery.each([ "Height", "Width" ], function( i, name ) { + + var type = name.toLowerCase(); + + // innerHeight and innerWidth + jQuery.fn["inner" + name] = function() { + return this[0] ? + parseFloat( jQuery.css( this[0], type, "padding" ) ) : + null; + }; + + // outerHeight and outerWidth + jQuery.fn["outer" + name] = function( margin ) { + return this[0] ? + parseFloat( jQuery.css( this[0], type, margin ? "margin" : "border" ) ) : + null; + }; + + jQuery.fn[ type ] = function( size ) { + // Get window width or height + var elem = this[0]; + if ( !elem ) { + return size == null ? null : this; + } + + if ( jQuery.isFunction( size ) ) { + return this.each(function( i ) { + var self = jQuery( this ); + self[ type ]( size.call( this, i, self[ type ]() ) ); + }); + } + + if ( jQuery.isWindow( elem ) ) { + // Everyone else use document.documentElement or document.body depending on Quirks vs Standards mode + return elem.document.compatMode === "CSS1Compat" && elem.document.documentElement[ "client" + name ] || + elem.document.body[ "client" + name ]; + + // Get document width or height + } else if ( elem.nodeType === 9 ) { + // Either scroll[Width/Height] or offset[Width/Height], whichever is greater + return Math.max( + elem.documentElement["client" + name], + elem.body["scroll" + name], elem.documentElement["scroll" + name], + elem.body["offset" + name], elem.documentElement["offset" + name] + ); + + // Get or set width or height on the element + } else if ( size === undefined ) { + var orig = jQuery.css( elem, type ), + ret = parseFloat( orig ); + + return jQuery.isNaN( ret ) ? orig : ret; + + // Set the width or height on the element (default to pixels if value is unitless) + } else { + return this.css( type, typeof size === "string" ? size : size + "px" ); + } + }; + +}); + + +})(window); diff --git a/browserid/static/funcunit/resources/json.js b/browserid/static/funcunit/resources/json.js new file mode 100644 index 0000000000000000000000000000000000000000..5f2e79833f64a0d171fec19cb14f702426ae2388 --- /dev/null +++ b/browserid/static/funcunit/resources/json.js @@ -0,0 +1,201 @@ +/* + * jQuery JSON Plugin + * version: 2.1 (2009-08-14) + * + * This document is licensed as free software under the terms of the + * MIT License: http://www.opensource.org/licenses/mit-license.php + * + * Brantley Harris wrote this plugin. It is based somewhat on the JSON.org + * website's http://www.json.org/json2.js, which proclaims: + * "NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.", a sentiment that + * I uphold. + * + * It is also influenced heavily by MochiKit's serializeJSON, which is + * copyrighted 2005 by Bob Ippolito. + */ + steal('jquery').then(function(){ +(function($) { + /** jQuery.toJSON( json-serializble ) + Converts the given argument into a JSON respresentation. + + If an object has a "toJSON" function, that will be used to get the representation. + Non-integer/string keys are skipped in the object, as are keys that point to a function. + + json-serializble: + The *thing* to be converted. + **/ + $.toJSON = function(o, replacer, space, recurse) + { + if (typeof(JSON) == 'object' && JSON.stringify) + return JSON.stringify(o, replacer, space); + + if (!recurse && $.isFunction(replacer)) + o = replacer("", o); + + if (typeof space == "number") + space = " ".substring(0, space); + space = (typeof space == "string") ? space.substring(0, 10) : ""; + + var type = typeof(o); + + if (o === null) + return "null"; + + if (type == "undefined" || type == "function") + return undefined; + + if (type == "number" || type == "boolean") + return o + ""; + + if (type == "string") + return $.quoteString(o); + + if (type == 'object') + { + if (typeof o.toJSON == "function") + return $.toJSON( o.toJSON(), replacer, space, true ); + + if (o.constructor === Date) + { + var month = o.getUTCMonth() + 1; + if (month < 10) month = '0' + month; + + var day = o.getUTCDate(); + if (day < 10) day = '0' + day; + + var year = o.getUTCFullYear(); + + var hours = o.getUTCHours(); + if (hours < 10) hours = '0' + hours; + + var minutes = o.getUTCMinutes(); + if (minutes < 10) minutes = '0' + minutes; + + var seconds = o.getUTCSeconds(); + if (seconds < 10) seconds = '0' + seconds; + + var milli = o.getUTCMilliseconds(); + if (milli < 100) milli = '0' + milli; + if (milli < 10) milli = '0' + milli; + + return '"' + year + '-' + month + '-' + day + 'T' + + hours + ':' + minutes + ':' + seconds + + '.' + milli + 'Z"'; + } + + var process = ($.isFunction(replacer)) ? + function (k, v) { return replacer(k, v); } : + function (k, v) { return v; }, + nl = (space) ? "\n" : "", + sp = (space) ? " " : ""; + + if (o.constructor === Array) + { + var ret = []; + for (var i = 0; i < o.length; i++) + ret.push(( $.toJSON( process(i, o[i]), replacer, space, true ) || "null" ).replace(/^/gm, space)); + + return "[" + nl + ret.join("," + nl) + nl + "]"; + } + + var pairs = [], proplist; + if ($.isArray(replacer)) { + proplist = $.map(replacer, function (v) { + return (typeof v == "string" || typeof v == "number") ? + v + "" : + null; + }); + } + for (var k in o) { + var name, val, type = typeof k; + + if (proplist && $.inArray(k + "", proplist) == -1) + continue; + + if (type == "number") + name = '"' + k + '"'; + else if (type == "string") + name = $.quoteString(k); + else + continue; //skip non-string or number keys + + val = $.toJSON( process(k, o[k]), replacer, space, true ); + + if (typeof val == "undefined") + continue; //skip pairs where the value is a function. + + pairs.push((name + ":" + sp + val).replace(/^/gm, space)); + } + + return "{" + nl + pairs.join("," + nl) + nl + "}"; + } + }; + + /** jQuery.evalJSON(src) + Evaluates a given piece of json source. + **/ + $.evalJSON = function(src) + { + if (typeof(JSON) == 'object' && JSON.parse) + return JSON.parse(src); + return eval("(" + src + ")"); + }; + + /** jQuery.secureEvalJSON(src) + Evals JSON in a way that is *more* secure. + **/ + $.secureEvalJSON = function(src) + { + if (typeof(JSON) == 'object' && JSON.parse) + return JSON.parse(src); + + var filtered = src; + filtered = filtered.replace(/\\["\\\/bfnrtu]/g, '@'); + filtered = filtered.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']'); + filtered = filtered.replace(/(?:^|:|,)(?:\s*\[)+/g, ''); + + if (/^[\],:{}\s]*$/.test(filtered)) + return eval("(" + src + ")"); + else + throw new SyntaxError("Error parsing JSON, source is not valid."); + }; + + /** jQuery.quoteString(string) + Returns a string-repr of a string, escaping quotes intelligently. + Mostly a support function for toJSON. + + Examples: + >>> jQuery.quoteString("apple") + "apple" + + >>> jQuery.quoteString('"Where are we going?", she asked.') + "\"Where are we going?\", she asked." + **/ + $.quoteString = function(string) + { + if (string.match(_escapeable)) + { + return '"' + string.replace(_escapeable, function (a) + { + var c = _meta[a]; + if (typeof c === 'string') return c; + c = a.charCodeAt(); + return '\\u00' + Math.floor(c / 16).toString(16) + (c % 16).toString(16); + }) + '"'; + } + return '"' + string + '"'; + }; + + var _escapeable = /["\\\x00-\x1f\x7f-\x9f]/g; + + var _meta = { + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '"' : '\\"', + '\\': '\\\\' + }; +})(jQuery); +}) \ No newline at end of file diff --git a/browserid/static/funcunit/resources/selector.js b/browserid/static/funcunit/resources/selector.js new file mode 100644 index 0000000000000000000000000000000000000000..4afb184c72cae292364b52853b1ad3744c859b78 --- /dev/null +++ b/browserid/static/funcunit/resources/selector.js @@ -0,0 +1,56 @@ +steal('jquery').then(function(){ + +(function($){ + var getWindow = function( element ) { + return element.ownerDocument.defaultView || element.ownerDocument.parentWindow + } + +/** + * Returns a unique selector for the matched element. + * @param {Object} target + */ +$.fn.prettySelector= function() { + var target = this[0]; + if(!target){ + return null + } + var selector = target.nodeName.toLowerCase(); + //always try to get an id + if(target.id){ + return "#"+target.id; + }else{ + var parent = target.parentNode; + while(parent){ + if(parent.id){ + selector = "#"+parent.id+" "+selector; + break; + }else{ + parent = parent.parentNode + } + } + } + if(target.className){ + selector += "."+target.className.split(" ")[0] + } + var others = $(selector, getWindow(target).document); //jquery should take care of the #foo if there + + if(others.length > 1){ + return selector+":eq("+others.index(target)+")"; + }else{ + return selector; + } +}; +$.each(["closest","find","next","prev","siblings","last","first"], function(i, name){ + $.fn[name+"Selector"] = function(selector){ + return this[name](selector).prettySelector(); + } +}); + + + + + +}(window.jQuery || window.FuncUnit.jQuery)); + + +}) \ No newline at end of file diff --git a/browserid/static/funcunit/resources/selenium_start.js b/browserid/static/funcunit/resources/selenium_start.js new file mode 100644 index 0000000000000000000000000000000000000000..3077674ed90bd7b79ae48cf3205c35e646e9c80e --- /dev/null +++ b/browserid/static/funcunit/resources/selenium_start.js @@ -0,0 +1,48 @@ +steal.then(function(){ + +FuncUnit.startSelenium = function(){ + importClass(Packages.com.thoughtworks.selenium.DefaultSelenium); + + //first lets ping and make sure the server is up + var addr = java.net.InetAddress.getByName(FuncUnit.serverHost) + try { + var s = new java.net.Socket(addr, FuncUnit.serverPort) + } + catch (ex) { + spawn(function(){ + var jarCommand = 'java -jar '+ + 'funcunit/java/selenium-server-standalone-2.0b3.jar'+ + ' -userExtensions '+ + 'funcunit/java/user-extensions.js'; + if (java.lang.System.getProperty("os.name").indexOf("Windows") != -1) { + var command = 'start "selenium" ' + jarCommand; + runCommand("cmd", "/C", command.replace(/\//g, "\\")) + } + else { + var command = jarCommand + " > selenium.log 2> selenium.log &"; + runCommand("sh", "-c", command); + } + }) + var timeouts = 0, + started = false; + var pollSeleniumServer = function(){ + try { + var s = new java.net.Socket(addr, FuncUnit.serverPort) + started = true; + } + catch (ex) { + if (timeouts > 3) { + print("Selenium is not running. Please use steal/js -selenium to start it.") + quit(); + } else { + timeouts++; + } + } + } + while(!started){ + java.lang.Thread.currentThread().sleep(1000); + pollSeleniumServer(); + } + } +} +}) diff --git a/browserid/static/funcunit/scripts/run.js b/browserid/static/funcunit/scripts/run.js new file mode 100644 index 0000000000000000000000000000000000000000..fc6855977c1ed3e0197aa02c4113107b707ee33d --- /dev/null +++ b/browserid/static/funcunit/scripts/run.js @@ -0,0 +1,5 @@ +// used to 'run' a funcunit/envjs command: +load(java.lang.System.getProperty("basepath")+"../steal/rhino/utils.js") +load('steal/rhino/steal.js'); +load('funcunit/loader.js'); +FuncUnit.load(_args.shift()) \ No newline at end of file diff --git a/browserid/static/funcunit/settings.js b/browserid/static/funcunit/settings.js new file mode 100644 index 0000000000000000000000000000000000000000..ba2185dc56b148caed23d4cd571ae134558bfd57 --- /dev/null +++ b/browserid/static/funcunit/settings.js @@ -0,0 +1,10 @@ +FuncUnit = { + // the list of browsers that selenium runs tests on + browsers: null, //["*firefox", "*iexplore"], + + // the root for all paths in the tests, defaults to filesystem + jmvcRoot: null, // "http://localhost:8000/", + + // the number of milliseconds between Selenium commands, "slow" is 500 ms + speed: null, //"slow" +} \ No newline at end of file diff --git a/browserid/static/funcunit/summary.ejs b/browserid/static/funcunit/summary.ejs new file mode 100644 index 0000000000000000000000000000000000000000..98aeb2abfa13f269b874437196133246acdcf56a --- /dev/null +++ b/browserid/static/funcunit/summary.ejs @@ -0,0 +1,63 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" + "http://www.w3.org/TR/html4/strict.dtd"> +<html lang="en"> + <head> + <title>JavaScriptMVC</title> + <link rel="stylesheet" type='text/css' href='<%= pathToRoot %>/documentjs/jmvcdoc/style.css' /> + <link rel="shortcut icon" href="<%= pathToRoot %>/documentjs/jmvcdoc/images/favicon.ico" /> + </head> + <body> + + <div id='documentation'> + <div id='top'> + <div class="topCorner"><div> </div></div> + <div class="content"> + <div id="searchRoundCorners"> + <input id='search' type='input' disabled='true'/> + </div> + <div id='defaults'> + <ul id="menu" class="ui-menu"> + <li class="ui-menu-item"> + <a class="menuLink" href="#&search=*&who=index"><span class="menuSpan">Home</span></a> + </li> + <li class="ui-menu-item"> + <a class="menuLink" href="#favorites"><span class="menuSpan">Favorites</span></a> + </li> + <li class="ui-menu-item"> + <a class="menuLink" href="#&who=follow" title="Follow"><span class="menuSpan red">Follow</span></a> + </li> + <li class="ui-menu-item"> + <a class="menuLink" href="http://github.com/jupiterjs/funcunit" title="Code Repositories"><span class="menuSpan red">Code Repo</span></a> + </li> + </ul> + </div> + <div class="logo-text">FuncUnit <img src='../jmvc/images/funcunit_small.png' class="logo-image"/></div> + </div> + <div class="bottomCorner"><div> </div></div> + </div> + + <div id='bottom'> + + + <div id='left'> + <a>Loading ... </a> + </div> + <div id='doc_container'> + <div id='doc'> + <%= this.indexPage ? this.indexPage.real_comment : "Add a page named 'index' to see something here." %> + </div> + <div id="disqus_thread"></div> + </div> + </div> + </div> + <div id='low'> + <a href="http://jupiterit.com">© Jupiter IT - FuncUnit Training and Support</a> + </div> + <script type='text/javascript'> + DOCS_LOCATION = "docs/" //adds searchData to this + </script> + <script type='text/javascript' + src='<%= pathToRoot %>/steal/steal.js?steal[app]=documentjs/jmvcdoc&steal[env]=production'> + </script> + </body> +</html> \ No newline at end of file diff --git a/browserid/static/funcunit/syn/.gitignore b/browserid/static/funcunit/syn/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..53c37a16608c014b2cf0bd2d5dfcafe953cdd857 --- /dev/null +++ b/browserid/static/funcunit/syn/.gitignore @@ -0,0 +1 @@ +dist \ No newline at end of file diff --git a/browserid/static/funcunit/syn/README b/browserid/static/funcunit/syn/README new file mode 100644 index 0000000000000000000000000000000000000000..661e2fa6a287f602420b357e836c8a6990625884 --- /dev/null +++ b/browserid/static/funcunit/syn/README @@ -0,0 +1,34 @@ +What It Is + +Syn is a synthetic event library that pretty much handles typing, clicking, moving, and +dragging exactly how a real user would perform those actions. + +Relevant Links + +1. http://jupiterjs.com/news/syn-a-standalone-synthetic-event-library +2. http://javascriptmvc.com/#&who=Syn +3. http://javascriptmvc.com/funcunit/syn/synthetic.html + +Using Syn + +You'd use syn to perform functional testing on a JavaScript application. Check out the demo +page (3rd link above) for some examples. To add syn to your page, you have two options: + +1. Add each script with script tags + +There are 5 scripts to add to your page in the following order: + +1. synthetic.js +2. mouse.js +3. browsers.js +4. key.js +5. drag/drag.js + +2. Use steal + +If you are using StealJS as your dependency loader, just steal.plugins('syn'), and the dependencies +will be loaded for you. + +Running Syn Tests + +Load syn/qunit.html in any browser to run all the tests. Load syn/drag/qunit.html to run the drag tests. \ No newline at end of file diff --git a/browserid/static/funcunit/syn/browsers.js b/browserid/static/funcunit/syn/browsers.js new file mode 100644 index 0000000000000000000000000000000000000000..be54fd27f9c53b0756957ba622274cbdbff5a142 --- /dev/null +++ b/browserid/static/funcunit/syn/browsers.js @@ -0,0 +1,154 @@ +steal.then(function(){ +//steal("synthetic") +// .then("mouse") +// .then(function() { + Syn.key.browsers = { + webkit : { + 'prevent': + {"keyup":[],"keydown":["char","keypress"],"keypress":["char"]}, + 'character': + {"keydown":[0,"key"],"keypress":["char","char"],"keyup":[0,"key"]}, + 'specialChars': + {"keydown":[0,"char"],"keyup":[0,"char"]}, + 'navigation': + {"keydown":[0,"key"],"keyup":[0,"key"]}, + 'special': + {"keydown":[0,"key"],"keyup":[0,"key"]}, + 'tab': + {"keydown":[0,"char"],"keyup":[0,"char"]}, + 'pause-break': + {"keydown":[0,"key"],"keyup":[0,"key"]}, + 'caps': + {"keydown":[0,"key"],"keyup":[0,"key"]}, + 'escape': + {"keydown":[0,"key"],"keyup":[0,"key"]}, + 'num-lock': + {"keydown":[0,"key"],"keyup":[0,"key"]}, + 'scroll-lock': + {"keydown":[0,"key"],"keyup":[0,"key"]}, + 'print': + {"keyup":[0,"key"]}, + 'function': + {"keydown":[0,"key"],"keyup":[0,"key"]}, + '\r': + {"keydown":[0,"key"],"keypress":["char","key"],"keyup":[0,"key"]} + }, + gecko : { + 'prevent': + {"keyup":[],"keydown":["char"],"keypress":["char"]}, + 'character': + {"keydown":[0,"key"],"keypress":["char",0],"keyup":[0,"key"]}, + 'specialChars': + {"keydown":[0,"key"],"keypress":[0,"key"],"keyup":[0,"key"]}, + 'navigation': + {"keydown":[0,"key"],"keypress":[0,"key"],"keyup":[0,"key"]}, + 'special': + {"keydown":[0,"key"],"keyup":[0,"key"]}, + '\t': + {"keydown":[0,"key"],"keypress":[0,"key"],"keyup":[0,"key"]}, + 'pause-break': + {"keydown":[0,"key"],"keypress":[0,"key"],"keyup":[0,"key"]}, + 'caps': + {"keydown":[0,"key"],"keyup":[0,"key"]}, + 'escape': + {"keydown":[0,"key"],"keypress":[0,"key"],"keyup":[0,"key"]}, + 'num-lock': + {"keydown":[0,"key"],"keyup":[0,"key"]}, + 'scroll-lock': + {"keydown":[0,"key"],"keyup":[0,"key"]}, + 'print': + {"keyup":[0,"key"]}, + 'function': + {"keydown":[0,"key"],"keyup":[0,"key"]}, + '\r': + {"keydown":[0,"key"],"keypress":[0,"key"],"keyup":[0,"key"]} + }, + msie : { + 'prevent':{"keyup":[],"keydown":["char","keypress"],"keypress":["char"]}, + 'character':{"keydown":[null,"key"],"keypress":[null,"char"],"keyup":[null,"key"]}, + 'specialChars':{"keydown":[null,"char"],"keyup":[null,"char"]}, + 'navigation':{"keydown":[null,"key"],"keyup":[null,"key"]}, + 'special':{"keydown":[null,"key"],"keyup":[null,"key"]}, + 'tab':{"keydown":[null,"char"],"keyup":[null,"char"]}, + 'pause-break':{"keydown":[null,"key"],"keyup":[null,"key"]}, + 'caps':{"keydown":[null,"key"],"keyup":[null,"key"]}, + 'escape':{"keydown":[null,"key"],"keypress":[null,"key"],"keyup":[null,"key"]}, + 'num-lock':{"keydown":[null,"key"],"keyup":[null,"key"]}, + 'scroll-lock':{"keydown":[null,"key"],"keyup":[null,"key"]}, + 'print':{"keyup":[null,"key"]}, + 'function':{"keydown":[null,"key"],"keyup":[null,"key"]}, + '\r':{"keydown":[null,"key"],"keypress":[null,"key"],"keyup":[null,"key"]} + }, + opera : { + 'prevent': + {"keyup":[],"keydown":[],"keypress":["char"]}, + 'character': + {"keydown":[null,"key"],"keypress":[null,"char"],"keyup":[null,"key"]}, + 'specialChars': + {"keydown":[null,"char"],"keypress":[null,"char"],"keyup":[null,"char"]}, + 'navigation': + {"keydown":[null,"key"],"keypress":[null,"key"]}, + 'special': + {"keydown":[null,"key"],"keypress":[null,"key"],"keyup":[null,"key"]}, + 'tab': + {"keydown":[null,"char"],"keypress":[null,"char"],"keyup":[null,"char"]}, + 'pause-break': + {"keydown":[null,"key"],"keypress":[null,"key"],"keyup":[null,"key"]}, + 'caps': + {"keydown":[null,"key"],"keyup":[null,"key"]}, + 'escape': + {"keydown":[null,"key"],"keypress":[null,"key"]}, + 'num-lock': + {"keyup":[null,"key"],"keydown":[null,"key"],"keypress":[null,"key"]}, + 'scroll-lock': + {"keydown":[null,"key"],"keypress":[null,"key"],"keyup":[null,"key"]}, + 'print': + {}, + 'function': + {"keydown":[null,"key"],"keypress":[null,"key"],"keyup":[null,"key"]}, + '\r': + {"keydown":[null,"key"],"keypress":[null,"key"],"keyup":[null,"key"]} + } + }; + + Syn.mouse.browsers = { + webkit : {"right":{"mousedown":{"button":2,"which":3},"mouseup":{"button":2,"which":3},"contextmenu":{"button":2,"which":3}}, + "left":{"mousedown":{"button":0,"which":1},"mouseup":{"button":0,"which":1},"click":{"button":0,"which":1}}}, + opera: {"right":{"mousedown":{"button":2,"which":3},"mouseup":{"button":2,"which":3}}, + "left":{"mousedown":{"button":0,"which":1},"mouseup":{"button":0,"which":1},"click":{"button":0,"which":1}}}, + msie: { "right":{"mousedown":{"button":2},"mouseup":{"button":2},"contextmenu":{"button":0}}, + "left":{"mousedown":{"button":1},"mouseup":{"button":1},"click":{"button":0}}}, + chrome : {"right":{"mousedown":{"button":2,"which":3},"mouseup":{"button":2,"which":3},"contextmenu":{"button":2,"which":3}}, + "left":{"mousedown":{"button":0,"which":1},"mouseup":{"button":0,"which":1},"click":{"button":0,"which":1}}}, + gecko: {"left":{"mousedown":{"button":0,"which":1},"mouseup":{"button":0,"which":1},"click":{"button":0,"which":1}}, + "right":{"mousedown":{"button":2,"which":3},"mouseup":{"button":2,"which":3},"contextmenu":{"button":2,"which":3}}} + } + + //set browser + Syn.key.browser = + (function(){ + if(Syn.key.browsers[window.navigator.userAgent]){ + return Syn.key.browsers[window.navigator.userAgent]; + } + for(var browser in Syn.browser){ + if(Syn.browser[browser] && Syn.key.browsers[browser]){ + return Syn.key.browsers[browser] + } + } + return Syn.key.browsers.gecko; + })(); + + Syn.mouse.browser = + (function(){ + if(Syn.mouse.browsers[window.navigator.userAgent]){ + return Syn.mouse.browsers[window.navigator.userAgent]; + } + for(var browser in Syn.browser){ + if(Syn.browser[browser] && Syn.mouse.browsers[browser]){ + return Syn.mouse.browsers[browser] + } + } + return Syn.mouse.browsers.gecko; + })(); +//}); +}) \ No newline at end of file diff --git a/browserid/static/funcunit/syn/build.js b/browserid/static/funcunit/syn/build.js new file mode 100644 index 0000000000000000000000000000000000000000..5e42a2f203de834914d5f9bd73383a692ed81e18 --- /dev/null +++ b/browserid/static/funcunit/syn/build.js @@ -0,0 +1,28 @@ +load('steal/rhino/steal.js') + +/** + * Build syn, funcunit, user-extensions + */ +steal.File('funcunit/syn/dist').mkdir() +steal('//steal/build/pluginify/pluginify', function(s){ + steal.build.pluginify("funcunit/syn",{ + global: "true", + nojquery: true, + destination: "funcunit/syn/dist/syn.js" + }) +}) +// add drag/drop + +var copyToDist = function(path){ + var fileNameArr = path.split("/"), + fileName = fileNameArr[fileNameArr.length - 1]; + print("copying to "+fileName) + steal.File(path).copyTo("funcunit/syn/resources/"+fileName); +} +var filesToCopy = [ + "funcunit/resources/jquery.js" +] + +for(var i = 0; i < filesToCopy.length; i++) { + copyToDist(filesToCopy[i]) +} \ No newline at end of file diff --git a/browserid/static/funcunit/syn/demo.html b/browserid/static/funcunit/syn/demo.html new file mode 100644 index 0000000000000000000000000000000000000000..4077f826f7aaabb37db28dd630d59bf304a6c156 --- /dev/null +++ b/browserid/static/funcunit/syn/demo.html @@ -0,0 +1,150 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" + "http://www.w3.org/TR/html4/strict.dtd"> +<html lang="en"> + <head> + <title>Demo Page</title> + <link type="text/css" href="../../mxui/smoothness/jquery-ui-1.7.2.custom.css" rel="stylesheet" /> + <link type="text/css" href="../../mxui/resizable/resizable.css" rel="stylesheet" /> + <style type='text/css'> + body {font-family: verdana} + .error {border: solid 1px red;} + .error_text { color: red; font-size: 10px;} + td {padding: 3px;} + + #first { + border: solid 1px; + height: 200px;width: 200px; + } + .ui-widget { + font-size: 1em; + } + .ui-tabs .ui-tabs-nav li a { + padding: 0.1em 0.2em + } + .ui-tabs .ui-tabs-panel { + padding: 0.5em + } + #uitabs a:focus { + outline: dashed; + } + #submitform { + border-left: dotted 1px gray; + padding: 10px; + margin-left: 10px; + } + </style> + </head> + <body> + <div class='tabarea'> + <ul id="uitabs"> + <li><a href="javascript://"><span>Instructions</span></a></li> + <li><a href="javascript://"><span>Drag Drop</span></a></li> + <li><a href="javascript://"><span>Typing</span></a></li> + <li><a href="javascript://"><span>Form</span></a></li> + </ul> + + <div> + <p>Welcome to the Syn demo!</p> + <p>Click, drag, and type in this page. + Your actions will be recorded as a syn-script. + You can playback this script by pressing "Replay" below. + </p> + <p>Try clicking and tabbing between tab-buttons.</p> + <div> + <ul id='uitabs2'> + <li><a href="javascript://">Alpha</a></li> + <li><a href="javascript://">Beta</a></li> + <li><a href="javascript://">Gamma</a></li> + </ul> + <div>Content Alpha</div> + <div>Content Beta</div> + <div>Content Gamma</div> + </div> + </div> + <div> + <p>Drag the gray dot.</p> + <div class='wrap' id='first'>Here is some swell content</div> + </div> + <div> + <p>Test the numeric field. It only accepts numbers. + </p> + <p><input type='text' id='numeric' /></p> + <p>You can copy and paste if you select text with the keyboard like: + [shift][left][shift-up][ctrl]c[ctrl-up][right][ctrl]v[ctrl-up]. It's + possible to make mouse text selection work, but not with this recording demo.</p> + </div> + <div> + <form> + <form action='' id='submitform'> + + <p id='indicator'>I turn green when this form is submitted. + </p> + <p id='changeIndicator'>I turn green when an element is changed.</p> + <div> + <label>Type enter to submit the form: </label> + <input type='text' id='clearme'/> + </div> + <div> + <label>Click to submit: </label> + <input type='submit'/> + </div> + <div> + <label>Click to toggle: </label> + <input type='checkbox' /> + </div> + <div> + <label>Click to toggle: </label> + <input type='radio' name='radio' value='1'/> + <input type='radio' name='radio' value='2'/> + <input type='radio' name='radio' value='3'/> + </div> + <div> + <label>Click to change: </label> + <select> + <option>1</option><option>2</option><option>3</option> + </select> + </div> + + + </form> + </form> + + </div> + </div> + + + + + <script type='text/javascript' src='../../steal/steal.js'></script> + + + <script type='text/javascript'> + steal.plugins('mxui/tabs','mxui/resizable','mxui/key_validator').then(function(){ + + $("#first").mxui_resizable(); + $("#uitabs, #uitabs2").mxui_ui_tabs(); + + + $(window).bind('load', function(){ + $("select")[0].selectedIndex = 0; + + $("#numeric").mxui_key_validator({test: /^[-+]?[0-9]*\.?[0-9]*$/}).val("") + }) + $("#submitform").submit(function(ev){ + ev.preventDefault(); + $("#indicator").css("backgroundColor","#00ff00") + setTimeout(function(){ + $("#indicator").css("backgroundColor","") + },300) + + }).change(function(ev){ + $("#changeIndicator").css("backgroundColor","#00ff00") + setTimeout(function(){ + $("#changeIndicator").css("backgroundColor","") + },300) + }) + //this is to handle all click events in the form + }) + </script> + </body> +</html> \ No newline at end of file diff --git a/browserid/static/funcunit/syn/demo/record.js b/browserid/static/funcunit/syn/demo/record.js new file mode 100644 index 0000000000000000000000000000000000000000..eb6228de97a24c1f793d7f10a1d233cb3a7b4c53 --- /dev/null +++ b/browserid/static/funcunit/syn/demo/record.js @@ -0,0 +1,254 @@ +//Safari doesn't send mouse/click on option + +$(function(){ + Syn.autoDelay = true; + REPLAY = false; + ADD = true; + Recorder ={ + cb: function( i ) { + return function(){ + $("#code div:nth("+(i)+")").css("color","black").css("font-weight","") + $("#code div:nth("+(i+1)+")").css("color","orange").css("font-weight","bold") + if(i == commands.length - 1){ + Recorder.done(); + } + } + + }, + done: function() { + ADD = true; + $("#code div").css("color","black") + } + } + var commands =[], + downKeys = [], + keytarget = null, + current = [], + mousedown, + mousemove, + mouseup, + h ={ + commandsText: function( func ) { + var text = [], + command, + prev, + args; + for(var i=0; i < commands.length; i++){ + command = commands[i]; + args = []; + command.options && args.push(command.options); + (!prev || prev.selector !== command.selector) && args.push("$('"+command.selector+"')"); + func && args.push("Recorder.cb("+i+")"); + + text.push(func ? "":"<div>", + i > 0 ? " ." : "Syn.", + command.type, + "(", + args.join(", "), + ")\n", + func ? "":"</div>" + ) + prev = command; + } + return text.join("") + }, + addCode: function( type, options, target ) { + if(!ADD){ + return; + } + + var selector = h.selector(target), + last = commands[commands.length - 1] || {}; + + if(last.type == type && type == 'type' && last.selector == selector){ + //change options + last.options = options + }else if(last.type == 'type' + && type == 'click' + && last.options.lastIndexOf("\\r") == last.options.length -3 + && last.selector == selector){ + } + else{ + commands.push({ + type : type, + options: options, + selector: selector + }) + } + + $("#code").html(h.commandsText()) + }, + getKey: function( code ) { + for(var key in Syn.keycodes){ + if(Syn.keycodes[key] == code){ + return key + } + } + }, + addKey: function( key ) { + + }, + showChar: function( character, target ) { + var convert = { + "\r" : "\\r", + "\t" :"\\t", + "\b" : "\\b" + } + if(convert[character]){ + character = convert[character] + }else if(character == "[" || character.length > 1){ + character = "["+character+"]" + } + current.push(character); + + h.addCode("type",'"'+current.join("")+'"', target) + }, + keydown: function( ev ) { + var key = h.getKey(ev.keyCode); + + if(keytarget != ev.target){ + current = []; + keytarget = ev.target; + } + if($.inArray(key, downKeys) == -1){ + downKeys.push(key); + h.showChar(key, ev.target); + } + }, + keyup: function( ev ) { + var key = h.getKey(ev.keyCode); + if(Syn.key.isSpecial(ev.keyCode)){ + h.showChar(key+"-up", ev.target); + } + + var location = $.inArray(key, downKeys); + downKeys.splice(location,1); + justKey = true; + setTimeout(function(){ + justKey = false; + },20) + }, + // returns a selector + selector: function( target ) { + var selector = target.nodeName.toLowerCase(); + if(target.id){ + return "#"+target.id + }else{ + var parent = target.parentNode; + while(parent){ + if(parent.id){ + selector = "#"+parent.id+" "+selector; + break; + }else{ + parent = parent.parentNode + } + } + } + if(target.className){ + selector += "."+target.className.split(" ")[0] + } + var others = jQuery(selector, Syn.helpers.getWindow(target).document) + if(others.length > 1){ + return selector+":eq("+others.index(target)+")"; + }else{ + return selector; + } + + + } + }, + lastX, + lastY, + justKey = false, + mousedownH = function(ev){ + mousedown = ev.target; + mousemove = 0 + lastX = ev.pageX + lastY = ev.pageY; + }, + mouseupH = function(ev){ + if(/option/i.test(ev.target.nodeName)){ + + }else if(!mousemove || (lastX == ev.pageX && lastY == ev.pageY)){ + h.addCode("click",undefined,ev.target) + }else if(mousemove > 2 && mousedown){ + h.addCode("drag","'"+ev.clientX+"X"+ev.clientY+"'",mousedown) + } + + mousedown = null; + mousemove = 0; + lastY = lastX = null; + + }, + mousemoveH = function(ev){ + mousemove++; + + }, + changeH = function(ev){ + //if we changed without a previous keypress + if(!justKey && ev.target.nodeName.toLowerCase() == "select"){ + + var el = $("option:eq("+ev.target.selectedIndex+")", ev.target); + h.addCode("click",undefined, el[0]) + } + }; + + $("<iframe src='demo.html'></iframe>").load(function(){ + //cant uses handled b/c it doesn't bubble + var iframe = $('iframe'); + //iframe.mxui_filler({parent: $("#fill")}); + var frameWindow = iframe[0].contentWindow; + + var oldHandle = frameWindow.jQuery.event.handle; + + frameWindow.jQuery.event.handle = function(ev){ + if(! ev[ frameWindow.jQuery.expando ]){ + //add + //if(code[ev.type]){ + // code[ev.type].call(ev.target||ev.srcElement, ev) + //} + } + oldHandle.apply(this,arguments); + } + keytarget = null; + $(frameWindow.document).keydown(h.keydown).keyup(h.keyup) + .mousedown(mousedownH).mousemove(mousemoveH).mouseup(mouseupH) + .change(changeH) + .click(function(ev){ + //if(ev.target.nodeName.toLowerCase() == 'option'){ + // h.addCode("click",undefined,ev.target) + //} + }) + + + + if(REPLAY){ + REPLAY = false; + setTimeout(function(){ + var text = h.commandsText(true) + ADD = false; + $("#code div:first").css("color","red") + eval("with(frameWindow){"+ text +"}" ); + },500) + + } + + }).appendTo($("#app")) + + $(function(){ + $("#code, #clearme").val(""); + }) + $('#app').mxui_layout_fill({parent: document.body}); + + $("#run").click(function(){ + REPLAY = true; + $('iframe')[0].contentWindow.location.reload(true); + $("#code div").css("color","gray") + + }) + + +}); + + + diff --git a/browserid/static/funcunit/syn/drag/drag.html b/browserid/static/funcunit/syn/drag/drag.html new file mode 100644 index 0000000000000000000000000000000000000000..5f07f72f89e5020484fb8bd2c214fe3edd38df9a --- /dev/null +++ b/browserid/static/funcunit/syn/drag/drag.html @@ -0,0 +1,41 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" + "http://www.w3.org/TR/html4/strict.dtd"> +<html lang="en"> + <head> + <title>Synthetic Typing Test</title> + <style type='text/css'> + body {font-family: verdana} + td {text-align: right} + td:nth-child(even) {background: #CCd} + #key th:nth-child(even) {background: #CCf} + #who th:nth-child(even) {background: #fCb} + .hover {background-color: blue} + </style> + </head> + <body> + <p id='start'>start</p> + <div class='over'>Move over me</div> + <div class='over'>Move over me</div> + <p id='end'>end</p> +<script type='text/javascript' src='../../../steal/steal.js'></script> +<script type='text/javascript'> + steal.plugins('funcunit/synthetic', 'funcunit/synthetic/drag','jquery').start(); +</script> +<script type='text/javascript' id="demo-source"> + setTimeout(function(){ + Syn("move",{ + from: "#start", + to: "#end", + duration: 1000 + },"start") + },1000) + + + $(".over").bind('mouseover',function(){ + $(this).addClass('hover') + }).bind('mouseout',function(){ + $(this).removeClass('hover') + }) +</script> + </body> +</html> \ No newline at end of file diff --git a/browserid/static/funcunit/syn/drag/drag.js b/browserid/static/funcunit/syn/drag/drag.js new file mode 100644 index 0000000000000000000000000000000000000000..c6691a0c502c15a7d14b401f2cbd0551091a6380 --- /dev/null +++ b/browserid/static/funcunit/syn/drag/drag.js @@ -0,0 +1,322 @@ +(function() { + + // check if elementFromPageExists + (function() { + + // document body has to exists for this test + if (!document.body ) { + setTimeout(arguments.callee, 1) + return; + } + var div = document.createElement('div') + document.body.appendChild(div); + Syn.helpers.extend(div.style, { + width: "100px", + height: "10000px", + backgroundColor: "blue", + position: "absolute", + top: "10px", + left: "0px", + zIndex: 19999 + }); + document.body.scrollTop = 11; + if (!document.elementFromPoint ) { + return; + } + var el = document.elementFromPoint(3, 1) + if ( el == div ) { + Syn.support.elementFromClient = true; + } + else { + Syn.support.elementFromPage = true; + } + document.body.removeChild(div); + document.body.scrollTop = 0; + })(); + + + //gets an element from a point + var elementFromPoint = function( point, element ) { + var clientX = point.clientX, + clientY = point.clientY, + win = Syn.helpers.getWindow(element), + el; + + + + if ( Syn.support.elementFromPage ) { + var off = Syn.helpers.scrollOffset(win); + clientX = clientX + off.left; //convert to pageX + clientY = clientY + off.top; //convert to pageY + } + el = win.document.elementFromPoint ? win.document.elementFromPoint(clientX, clientY) : element; + if ( el === win.document.documentElement && (point.clientY < 0 || point.clientX < 0) ) { + return element; + } else { + return el; + } + }, + //creates an event at a certain point + createEventAtPoint = function( event, point, element ) { + var el = elementFromPoint(point, element) + Syn.trigger(event, point, el || element) + return el; + }, + // creates a mousemove event, but first triggering mouseout / mouseover if appropriate + mouseMove = function( point, element, last ) { + var el = elementFromPoint(point, element) + if ( last != el && el && last ) { + var options = Syn.helpers.extend({}, point); + options.relatedTarget = el; + Syn.trigger("mouseout", options, last); + options.relatedTarget = last; + Syn.trigger("mouseover", options, el); + } + + Syn.trigger("mousemove", point, el || element) + return el; + }, + // start and end are in clientX, clientY + startMove = function( start, end, duration, element, callback ) { + var startTime = new Date(), + distX = end.clientX - start.clientX, + distY = end.clientY - start.clientY, + win = Syn.helpers.getWindow(element), + current = elementFromPoint(start, element), + cursor = win.document.createElement('div'), + calls = 0; + move = function() { + //get what fraction we are at + var now = new Date(), + scrollOffset = Syn.helpers.scrollOffset(win), + fraction = (calls == 0 ? 0 : now - startTime) / duration, + options = { + clientX: distX * fraction + start.clientX, + clientY: distY * fraction + start.clientY + }; + calls++; + if ( fraction < 1 ) { + Syn.helpers.extend(cursor.style, { + left: (options.clientX + scrollOffset.left + 2) + "px", + top: (options.clientY + scrollOffset.top + 2) + "px" + }) + current = mouseMove(options, element, current) + setTimeout(arguments.callee, 15) + } + else { + current = mouseMove(end, element, current); + win.document.body.removeChild(cursor) + callback(); + } + } + Syn.helpers.extend(cursor.style, { + height: "5px", + width: "5px", + backgroundColor: "red", + position: "absolute", + zIndex: 19999, + fontSize: "1px" + }) + win.document.body.appendChild(cursor) + move(); + }, + startDrag = function( start, end, duration, element, callback ) { + createEventAtPoint("mousedown", start, element); + startMove(start, end, duration, element, function() { + createEventAtPoint("mouseup", end, element); + callback(); + }) + }, + center = function( el ) { + var j = Syn.jquery()(el), + o = j.offset(); + return { + pageX: o.left + (j.width() / 2), + pageY: o.top + (j.height() / 2) + } + }, + convertOption = function( option, win, from ) { + var page = /(\d+)[x ](\d+)/, + client = /(\d+)X(\d+)/, + relative = /([+-]\d+)[xX ]([+-]\d+)/ + //check relative "+22x-44" + if ( typeof option == 'string' && relative.test(option) && from ) { + var cent = center(from), + parts = option.match(relative); + option = { + pageX: cent.pageX + parseInt(parts[1]), + pageY: cent.pageY + parseInt(parts[2]) + } + } + if ( typeof option == 'string' && page.test(option) ) { + var parts = option.match(page) + option = { + pageX: parseInt(parts[1]), + pageY: parseInt(parts[2]) + } + } + if ( typeof option == 'string' && client.test(option) ) { + var parts = option.match(client) + option = { + clientX: parseInt(parts[1]), + clientY: parseInt(parts[2]) + } + } + if ( typeof option == 'string' ) { + option = Syn.jquery()(option, win.document)[0]; + } + if ( option.nodeName ) { + option = center(option) + } + if ( option.pageX ) { + var off = Syn.helpers.scrollOffset(win); + option = { + clientX: option.pageX - off.left, + clientY: option.pageY - off.top + } + } + return option; + }, + // if the client chords are not going to be visible ... scroll the page so they will be ... + adjust = function(from, to, win){ + if(from.clientY < 0){ + var off = Syn.helpers.scrollOffset(win); + var dimensions = Syn.helpers.scrollDimensions(win), + top = off.top + (from.clientY) - 100, + diff = top - off.top + + // first, lets see if we can scroll 100 px + if( top > 0){ + + } else { + top =0; + diff = -off.top; + } + console.log("moving", from.clientY,from.clientY - diff, off.top, top ) + from.clientY = from.clientY - diff; + to.clientY = to.clientY - diff; + Syn.helpers.scrollOffset(win,{top: top, left: off.left}); + + //throw "out of bounds!" + } + } + /** + * @add Syn prototype + */ + Syn.helpers.extend(Syn.init.prototype, { + /** + * @function move + * Moves the cursor from one point to another. + * + * ### Quick Example + * + * The following moves the cursor from (0,0) in + * the window to (100,100) in 1 second. + * + * Syn.move( + * { + * from: {clientX: 0, clientY: 0}, + * to: {clientX: 100, clientY: 100}, + * duration: 1000 + * }, + * document.document) + * + * ## Options + * + * There are many ways to configure the endpoints of the move. + * + * ### PageX and PageY + * + * If you pass pageX or pageY, these will get converted + * to client coordinates. + * + * Syn.move( + * { + * from: {pageX: 0, pageY: 0}, + * to: {pageX: 100, pageY: 100} + * }, + * document.document) + * + * ### String Coordinates + * + * You can set the pageX and pageY as strings like: + * + * Syn.move( + * { + * from: "0x0", + * to: "100x100" + * }, + * document.document) + * + * ### Element Coordinates + * + * If jQuery is present, you can pass an element as the from or to option + * and the coordinate will be set as the center of the element. + + * Syn.move( + * { + * from: $(".recipe")[0], + * to: $("#trash")[0] + * }, + * document.document) + * + * ### Query Strings + * + * If jQuery is present, you can pass a query string as the from or to option. + * + * Syn.move( + * { + * from: ".recipe", + * to: "#trash" + * }, + * document.document) + * + * ### No From + * + * If you don't provide a from, the element argument passed to Syn is used. + * + * Syn.move( + * { to: "#trash" }, + * 'myrecipe') + * + * ### Relative + * + * You can move the drag relative to the center of the from element. + * + * Syn.move("+20 +30", "myrecipe"); + * + * @param {Object} options options to configure the drag + * @param {HTMLElement} from the element to move + * @param {Function} callback a callback that happens after the drag motion has completed + */ + _move: function( options, from, callback ) { + //need to convert if elements + var win = Syn.helpers.getWindow(from), + fro = convertOption(options.from || from, win, from), + to = convertOption(options.to || options, win, from); + + options.adjust !== false && adjust(fro, to, win); + startMove(fro, to, options.duration || 500, from, callback); + + }, + /** + * @function drag + * Creates a mousedown and drags from one point to another. + * Check out [Syn.prototype.move move] for API details. + * + * @param {Object} options + * @param {Object} from + * @param {Object} callback + */ + _drag: function( options, from, callback ) { + //need to convert if elements + var win = Syn.helpers.getWindow(from), + fro = convertOption(options.from || from, win, from), + to = convertOption(options.to || options, win, from); + + options.adjust !== false && adjust(fro, to, win); + startDrag(fro, to, options.duration || 500, from, callback); + + } + }) +}()); \ No newline at end of file diff --git a/browserid/static/funcunit/syn/drag/qunit.html b/browserid/static/funcunit/syn/drag/qunit.html new file mode 100644 index 0000000000000000000000000000000000000000..e534604dc0921d014478769b1b520a2c509bb508 --- /dev/null +++ b/browserid/static/funcunit/syn/drag/qunit.html @@ -0,0 +1,30 @@ +<html> + <head> + <link rel="stylesheet" type="text/css" href="../resources/qunit/qunit.css" /> + <title>QUnit Test</title> + <script type='text/javascript'> + steal = {ignoreControllers: true} + </script> + <script type='text/javascript' src='../../../steal/steal.js?funcunit/synthetic/drag/test/qunit'></script> + </head> + <body> + + <h1 id="qunit-header">drag Test Suite</h1> + <h2 id="qunit-banner"></h2> + <div id="qunit-testrunner-toolbar"></div> + <h2 id="qunit-userAgent"></h2> + <div id="test-content"></div> + <ol id="qunit-tests"></ol> + <div id="qunit-test-area"></div> + <script type='text/javascript' src='../resources/jquery.js'></script> + <script type='text/javascript' src='../resources/jquery.event.drag.js'></script> + <script type='text/javascript' src='../resources/jquery.event.drop.js'></script> + <script type='text/javascript' src='../resources/qunit/qunit.js'></script> + <script type='text/javascript' src='../synthetic.js'></script> + <script type='text/javascript' src='../mouse.js'></script> + <script type='text/javascript' src='../browsers.js'></script> + <script type='text/javascript' src='../key.js'></script> + <script type='text/javascript' src='drag.js'></script> + <script type='text/javascript' src='test/qunit/drag_test.js'></script> + </body> +</html> \ No newline at end of file diff --git a/browserid/static/funcunit/syn/drag/test/qunit/drag_test.js b/browserid/static/funcunit/syn/drag/test/qunit/drag_test.js new file mode 100644 index 0000000000000000000000000000000000000000..30c305b5db36b6af18c8808decba4cc6f860d0fa --- /dev/null +++ b/browserid/static/funcunit/syn/drag/test/qunit/drag_test.js @@ -0,0 +1,195 @@ +module("funcunit/syn/drag"); + + +test("dragging off the page", function(){ + var drags = ( {}), + drops = ({}); + + + + var div = $("<div>"+ + "<div id='drag'></div>"+ + "<div id='drop'></div>"+ + "</div>"); + + div.appendTo($("#qunit-test-area")); + var basicCss = { + width: "20px", + height: "20px", + border: "solid 1px black" + } + $("#drag").css(basicCss).css({top: "300px", left: "0px", backgroundColor: "green", zIndex: 99}) + $("#drop").css(basicCss).css({top: "300px", marginTop: "1000px", left: "30px", backgroundColor: "yellow"}); + + + $('#drag') + .live("draginit", function(){}) + + $('#drop') + .live("dropinit", function(){ }) + .live("dropover", function(){ + drops.dropover = true; + }) + + stop(); + + Syn.drag( {to: "#drop", duration: 700}, $("#drag")[0], function(){ + ok(drops.dropover,"dropover fired correctly") + $("#qunit-test-area").innerHTML = ""; + start(); + }) +}) + +test("move", function(){ + + var drags = {}, drops ={}; + var div = $("<div id='wrap'>"+ + "<div id='left'></div>"+ + "<div id='right'></div>"+ + "</div>"); + + div.appendTo($("#qunit-test-area")); + var basicCss = { + width: "90px", + height: "100px", + position: "absolute", + border: "solid 1px black" + } + $('#wrap').css({position: "absolute",top: "0px",left: "0px", width: "200px", height: "100px",backgroundColor: "yellow"}) + $("#left").css(basicCss).css({top: "0px", left: "10px", backgroundColor: "green"}) + $("#right").css(basicCss).css({top: "0px", left: "100px", backgroundColor: "blue"}) + + var clientX=-1, + clientY=-1, + els = [$('#wrap')[0], $('#left')[0], $('#right')[0], $('#wrap')[0] ], + targets = []; + + var move = function(ev){ + if(ev.clientX < clientX){ + ok(false, "mouse isn't moving right") + } + clientX = ev.clientX; + if(ev.clientY < clientY){ + ok(false, "mouse isn't moving right") + } + clientY = ev.clientY; + if(!targets.length || targets[targets.length - 1] !== ev.target){ + targets.push(ev.target) + } + } + $(document.documentElement).bind('mousemove',move ) + + stop(); + Syn.move({ + from : {pageX: 2, pageY: 50}, + to : {pageX: 199, pageY: 50}, + duration: 1000 + }, "wrap", function(){ + + equals(clientX, 199) + equals(clientY, 50) + $(document.documentElement).unbind('mousemove',move ) + for(var i=0; i < els.length;i++){ + ok(els[i] == targets[i], "target is right") + } + + $("#qunit-test-area").html("") + start(); + }) + +}) + +test("dragging an element with duration", function(){ + var drags = ( {}), + drops = ({}); + + + + var div = $("<div>"+ + "<div id='drag'></div>"+ + "<div id='midpoint'></div>"+ + "<div id='drop'></div>"+ + "</div>"); + + div.appendTo($("#qunit-test-area")); + var basicCss = { + width: "20px", + height: "20px", + border: "solid 1px black", + position: "absolute" + } + $("#drag").css(basicCss).css({top: "300px", left: "0px", backgroundColor: "green", zIndex: 99}) + $("#midpoint").css(basicCss).css({top: "300px", left: "30px", backgroundColor: "blue"}) + $("#drop").css(basicCss).css({top: "330px", left: "30px", backgroundColor: "yellow"}); + + + $('#drag') + .live("dragdown", function(){ + drags.dragdown = true; + }) + .live("draginit", function(){ + drags.draginit = true; + }) + .live("dragmove", function(){ + drags.dragmove = true; + }) + .live("dragend", function(){ + drags.dragend = true; + }) + .live("dragover", function(){ + drags.dragover = true; + }) + .live("dragout", function(){ + drags.dragout = true; + }); + + $('#drop') + .live("dropinit", function(){ + drops.dropinit = true; + }) + .live("dropover", function(){ + drops.dropover = true; + }) + .live("dropout", function(){ + drops.dropout = true; + }) + .live("dropmove", function(){ + drops.dropmove = true; + }) + .live("dropon", function(){ + drops.dropon = true; + }) + .live("dropend", function(){ + drops.dropend = true; + }) + + + + stop(); + + Syn.drag( {to: "#midpoint", duration: 700}, $("#drag")[0], function(){ + + ok(drags.draginit, "draginit fired correctly") + ok(drags.dragmove, "dragmove fired correctly") + ok(!drags.dragover,"dragover not fired yet") + + ok(!drops.dropover,"dropover fired correctly") + ok(!drops.dropon, "dropon not fired yet") + ok(drops.dropend, "dropend fired"); + + Syn.drag( {to: "#drop", duration: 700}, $("#drag")[0], function(){ + ok(drops.dropinit, "dropinit fired correctly") + ok(drops.dropover, "dropover fired correctly") + ok(drops.dropmove, "dropmove fired correctly") + ok(drops.dropon, "dropon fired correctly") + + Syn.drag( {to: "#midpoint", duration: 700}, $("#drag")[0], function(){ + ok(drags.dragout, "dragout fired correctly") + ok(drops.dropout, "dropout fired correctly") + $("#qunit-test-area").innerHTML = ""; + start(); + }) + + }); + }) +}) \ No newline at end of file diff --git a/browserid/static/funcunit/syn/drag/test/qunit/qunit.js b/browserid/static/funcunit/syn/drag/test/qunit/qunit.js new file mode 100644 index 0000000000000000000000000000000000000000..2682012f30680e28f31f5fefcc7732beb39c04ed --- /dev/null +++ b/browserid/static/funcunit/syn/drag/test/qunit/qunit.js @@ -0,0 +1,7 @@ +steal + .plugins("funcunit/qunit", + "funcunit/syn", + "funcunit/syn/drag" + ) + .plugins("jquery", "jquery/event/drag", "jquery/event/drop") + .then("drag_test") \ No newline at end of file diff --git a/browserid/static/funcunit/syn/key.js b/browserid/static/funcunit/syn/key.js new file mode 100644 index 0000000000000000000000000000000000000000..0a86158ecefba2135c77dafb9fe9daefc3a162c9 --- /dev/null +++ b/browserid/static/funcunit/syn/key.js @@ -0,0 +1,907 @@ +steal.then(function(){ +(function() { + var h = Syn.helpers, + S = Syn, + + // gets the selection of an input or textarea + getSelection = function( el ) { + // use selectionStart if we can + if ( el.selectionStart !== undefined ) { + // this is for opera, so we don't have to focus to type how we think we would + if ( document.activeElement && document.activeElement != el && el.selectionStart == el.selectionEnd && el.selectionStart == 0 ) { + return { + start: el.value.length, + end: el.value.length + }; + } + return { + start: el.selectionStart, + end: el.selectionEnd + } + } else { + //check if we aren't focused + try { + //try 2 different methods that work differently (IE breaks depending on type) + if ( el.nodeName.toLowerCase() == 'input' ) { + var real = h.getWindow(el).document.selection.createRange(), + r = el.createTextRange(); + r.setEndPoint("EndToStart", real); + + var start = r.text.length + return { + start: start, + end: start + real.text.length + } + } + else { + var real = h.getWindow(el).document.selection.createRange(), + r = real.duplicate(), + r2 = real.duplicate(), + r3 = real.duplicate(); + r2.collapse(); + r3.collapse(false); + r2.moveStart('character', -1) + r3.moveStart('character', -1) + //select all of our element + r.moveToElementText(el) + //now move our endpoint to the end of our real range + r.setEndPoint('EndToEnd', real); + var start = r.text.length - real.text.length, + end = r.text.length; + if ( start != 0 && r2.text == "" ) { + start += 2; + } + if ( end != 0 && r3.text == "" ) { + end += 2; + } + //if we aren't at the start, but previous is empty, we are at start of newline + return { + start: start, + end: end + } + } + } catch (e) { + return { + start: el.value.length, + end: el.value.length + }; + } + } + }, + // gets all focusable elements + getFocusable = function( el ) { + var document = h.getWindow(el).document, + res = []; + + var els = document.getElementsByTagName('*'), + len = els.length; + + for ( var i = 0; i < len; i++ ) { + Syn.isFocusable(els[i]) && els[i] != document.documentElement && res.push(els[i]) + } + return res; + + + }; + + /** + * @add Syn static + */ + h.extend(Syn, { + /** + * @attribute + * A list of the keys and their keycodes codes you can type. + * You can add type keys with + * @codestart + * Syn('key','delete','title'); + * + * //or + * + * Syn('type','One Two Three[left][left][delete]','title') + * @codeend + * + * The following are a list of keys you can type: + * @codestart text + * \b - backspace + * \t - tab + * \r - enter + * ' ' - space + * a-Z 0-9 - normal characters + * /!@#$*,.? - All other typeable characters + * page-up - scrolls up + * page-down - scrolls down + * end - scrolls to bottom + * home - scrolls to top + * insert - changes how keys are entered + * delete - deletes the next character + * left - moves cursor left + * right - moves cursor right + * up - moves the cursor up + * down - moves the cursor down + * f1-12 - function buttons + * shift, ctrl, alt - special keys + * pause-break - the pause button + * scroll-lock - locks scrolling + * caps - makes caps + * escape - escape button + * num-lock - allows numbers on keypad + * print - screen capture + * @codeend + */ + keycodes: { + //backspace + '\b': '8', + + //tab + '\t': '9', + + //enter + '\r': '13', + + //special + 'shift': '16', + 'ctrl': '17', + 'alt': '18', + + //weird + 'pause-break': '19', + 'caps': '20', + 'escape': '27', + 'num-lock': '144', + 'scroll-lock': '145', + 'print': '44', + + //navigation + 'page-up': '33', + 'page-down': '34', + 'end': '35', + 'home': '36', + 'left': '37', + 'up': '38', + 'right': '39', + 'down': '40', + 'insert': '45', + 'delete': '46', + + //normal characters + ' ': '32', + '0': '48', + '1': '49', + '2': '50', + '3': '51', + '4': '52', + '5': '53', + '6': '54', + '7': '55', + '8': '56', + '9': '57', + 'a': '65', + 'b': '66', + 'c': '67', + 'd': '68', + 'e': '69', + 'f': '70', + 'g': '71', + 'h': '72', + 'i': '73', + 'j': '74', + 'k': '75', + 'l': '76', + 'm': '77', + 'n': '78', + 'o': '79', + 'p': '80', + 'q': '81', + 'r': '82', + 's': '83', + 't': '84', + 'u': '85', + 'v': '86', + 'w': '87', + 'x': '88', + 'y': '89', + 'z': '90', + //normal-characters, numpad + 'num0': '96', + 'num1': '97', + 'num2': '98', + 'num3': '99', + 'num4': '100', + 'num5': '101', + 'num6': '102', + 'num7': '103', + 'num8': '104', + 'num9': '105', + '*': '106', + '+': '107', + '-': '109', + '.': '110', + //normal-characters, others + '/': '111', + ';': '186', + '=': '187', + ',': '188', + '-': '189', + '.': '190', + '/': '191', + '`': '192', + '[': '219', + '\\': '220', + ']': '221', + "'": '222', + + //ignore these, you shouldn't use them + 'left window key': '91', + 'right window key': '92', + 'select key': '93', + + + 'f1': '112', + 'f2': '113', + 'f3': '114', + 'f4': '115', + 'f5': '116', + 'f6': '117', + 'f7': '118', + 'f8': '119', + 'f9': '120', + 'f10': '121', + 'f11': '122', + 'f12': '123' + }, + + // what we can type in + typeable: /input|textarea/i, + + // selects text on an element + selectText: function( el, start, end ) { + if ( el.setSelectionRange ) { + if (!end ) { + el.focus(); + el.setSelectionRange(start, start); + } else { + el.selectionStart = start; + el.selectionEnd = end; + } + } else if ( el.createTextRange ) { + //el.focus(); + var r = el.createTextRange(); + r.moveStart('character', start); + end = end || start; + r.moveEnd('character', end - el.value.length); + + r.select(); + } + }, + getText: function( el ) { + //first check if the el has anything selected .. + if ( Syn.typeable.test(el.nodeName) ) { + var sel = getSelection(el); + return el.value.substring(sel.start, sel.end) + } + //otherwise get from page + var win = Syn.helpers.getWindow(el); + if ( win.getSelection ) { + return win.getSelection().toString(); + } + else if ( win.document.getSelection ) { + return win.document.getSelection().toString() + } + else { + return win.document.selection.createRange().text; + } + }, + getSelection: getSelection + }); + + h.extend(Syn.key, { + // retrieves a description of what events for this character should look like + data: function( key ) { + //check if it is described directly + if ( S.key.browser[key] ) { + return S.key.browser[key]; + } + for ( var kind in S.key.kinds ) { + if ( h.inArray(key, S.key.kinds[kind]) > -1 ) { + return S.key.browser[kind] + } + } + return S.key.browser.character + }, + + //returns the special key if special + isSpecial: function( keyCode ) { + var specials = S.key.kinds.special; + for ( var i = 0; i < specials.length; i++ ) { + if ( Syn.keycodes[specials[i]] == keyCode ) { + return specials[i]; + } + } + }, + /** + * @hide + * gets the options for a key and event type ... + * @param {Object} key + * @param {Object} event + */ + options: function( key, event ) { + var keyData = Syn.key.data(key); + + if (!keyData[event] ) { + //we shouldn't be creating this event + return null; + } + + var charCode = keyData[event][0], + keyCode = keyData[event][1], + result = {}; + + if ( keyCode == 'key' ) { + result.keyCode = Syn.keycodes[key] + } else if ( keyCode == 'char' ) { + result.keyCode = key.charCodeAt(0) + } else { + result.keyCode = keyCode; + } + + if ( charCode == 'char' ) { + result.charCode = key.charCodeAt(0) + } else if ( charCode !== null ) { + result.charCode = charCode; + } + + + return result + }, + //types of event keys + kinds: { + special: ["shift", 'ctrl', 'alt', 'caps'], + specialChars: ["\b"], + navigation: ["page-up", 'page-down', 'end', 'home', 'left', 'up', 'right', 'down', 'insert', 'delete'], + 'function': ['f1', 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9', 'f10', 'f11', 'f12'] + }, + //returns the default function + getDefault: function( key ) { + //check if it is described directly + if ( Syn.key.defaults[key] ) { + return Syn.key.defaults[key]; + } + for ( var kind in Syn.key.kinds ) { + if ( h.inArray(key, Syn.key.kinds[kind]) > -1 && Syn.key.defaults[kind] ) { + return Syn.key.defaults[kind]; + } + } + return Syn.key.defaults.character + }, + // default behavior when typing + defaults: { + 'character': function( options, scope, key, force, sel ) { + if (/num\d+/.test(key) ) { + key = key.match(/\d+/)[0] + } + + if ( force || (!S.support.keyCharacters && Syn.typeable.test(this.nodeName)) ) { + var current = this.value, + before = current.substr(0, sel.start), + after = current.substr(sel.end), + character = key; + + this.value = before + character + after; + //handle IE inserting \r\n + var charLength = character == "\n" && S.support.textareaCarriage ? 2 : character.length; + Syn.selectText(this, before.length + charLength) + } + }, + 'c': function( options, scope, key, force, sel ) { + if ( Syn.key.ctrlKey ) { + Syn.key.clipboard = Syn.getText(this) + } else { + Syn.key.defaults.character.apply(this, arguments); + } + }, + 'v': function( options, scope, key, force, sel ) { + if ( Syn.key.ctrlKey ) { + Syn.key.defaults.character.call(this, options, scope, Syn.key.clipboard, true, sel); + } else { + Syn.key.defaults.character.apply(this, arguments); + } + }, + 'a': function( options, scope, key, force, sel ) { + if ( Syn.key.ctrlKey ) { + Syn.selectText(this, 0, this.value.length) + } else { + Syn.key.defaults.character.apply(this, arguments); + } + }, + 'home': function() { + Syn.onParents(this, function( el ) { + if ( el.scrollHeight != el.clientHeight ) { + el.scrollTop = 0; + return false; + } + }) + }, + 'end': function() { + Syn.onParents(this, function( el ) { + if ( el.scrollHeight != el.clientHeight ) { + el.scrollTop = el.scrollHeight; + return false; + } + }) + }, + 'page-down': function() { + //find the first parent we can scroll + Syn.onParents(this, function( el ) { + if ( el.scrollHeight != el.clientHeight ) { + var ch = el.clientHeight + el.scrollTop += ch; + return false; + } + }) + }, + 'page-up': function() { + Syn.onParents(this, function( el ) { + if ( el.scrollHeight != el.clientHeight ) { + var ch = el.clientHeight + el.scrollTop -= ch; + return false; + } + }) + }, + '\b': function( options, scope, key, force, sel ) { + //this assumes we are deleting from the end + if (!S.support.backspaceWorks && Syn.typeable.test(this.nodeName) ) { + var current = this.value, + before = current.substr(0, sel.start), + after = current.substr(sel.end); + + if ( sel.start == sel.end && sel.start > 0 ) { + //remove a character + this.value = before.substring(0, before.length - 1) + after + Syn.selectText(this, sel.start - 1) + } else { + this.value = before + after; + Syn.selectText(this, sel.start) + } + + //set back the selection + } + }, + 'delete': function( options, scope, key, force, sel ) { + if (!S.support.backspaceWorks && Syn.typeable.test(this.nodeName) ) { + var current = this.value, + before = current.substr(0, sel.start), + after = current.substr(sel.end); + if ( sel.start == sel.end && sel.start <= this.value.length - 1 ) { + this.value = before + after.substring(1) + } else { + this.value = before + after; + + } + Syn.selectText(this, sel.start) + } + }, + '\r': function( options, scope, key, force, sel ) { + + var nodeName = this.nodeName.toLowerCase() + // submit a form + if (!S.support.keypressSubmits && nodeName == 'input' ) { + var form = Syn.closest(this, "form"); + if ( form ) { + Syn.trigger("submit", {}, form); + } + + } + //newline in textarea + if (!S.support.keyCharacters && nodeName == 'textarea' ) { + Syn.key.defaults.character.call(this, options, scope, "\n", undefined, sel) + } + // 'click' hyperlinks + if (!S.support.keypressOnAnchorClicks && nodeName == 'a' ) { + Syn.trigger("click", {}, this); + } + }, + // + // Gets all focusable elements. If the element (this) + // doesn't have a tabindex, finds the next element after. + // If the element (this) has a tabindex finds the element + // with the next higher tabindex OR the element with the same + // tabindex after it in the document. + // @return the next element + // + '\t': function( options, scope ) { + // focusable elements + var focusEls = getFocusable(this), + // the current element's tabindex + tabIndex = Syn.tabIndex(this), + // will be set to our guess for the next element + current = null, + // the next index we care about + currentIndex = 1000000000, + // set to true once we found 'this' element + found = false, + i = 0, + el, + //the tabindex of the tabable element we are looking at + elIndex, firstNotIndexed, prev; + orders = []; + for (; i < focusEls.length; i++ ) { + orders.push([focusEls[i], i]); + } + var sort = function( order1, order2 ) { + var el1 = order1[0], + el2 = order2[0], + tab1 = Syn.tabIndex(el1) || 0, + tab2 = Syn.tabIndex(el2) || 0; + if ( tab1 == tab2 ) { + return order1[1] - order2[1] + } else { + if ( tab1 == 0 ) { + return 1; + } else if ( tab2 == 0 ) { + return -1; + } else { + return tab1 - tab2; + } + } + } + orders.sort(sort); + //now find current + for ( i = 0; i < orders.length; i++ ) { + el = orders[i][0]; + if ( this == el ) { + if (!Syn.key.shiftKey ) { + current = orders[i + 1][0]; + if (!current ) { + current = orders[0][0] + } + } else { + current = orders[i - 1][0]; + if (!current ) { + current = orders[focusEls.length - 1][0] + } + } + + } + } + + //restart if we didn't find anything + if (!current ) { + current = firstNotIndexed; + } + current && current.focus(); + return current; + }, + 'left': function( options, scope, key, force, sel ) { + if ( Syn.typeable.test(this.nodeName) ) { + if ( Syn.key.shiftKey ) { + Syn.selectText(this, sel.start == 0 ? 0 : sel.start - 1, sel.end) + } else { + Syn.selectText(this, sel.start == 0 ? 0 : sel.start - 1) + } + } + }, + 'right': function( options, scope, key, force, sel ) { + if ( Syn.typeable.test(this.nodeName) ) { + if ( Syn.key.shiftKey ) { + Syn.selectText(this, sel.start, sel.end + 1 > this.value.length ? this.value.length : sel.end + 1) + } else { + Syn.selectText(this, sel.end + 1 > this.value.length ? this.value.length : sel.end + 1) + } + } + }, + 'up': function() { + if (/select/i.test(this.nodeName) ) { + + this.selectedIndex = this.selectedIndex ? this.selectedIndex - 1 : 0; + //set this to change on blur? + } + }, + 'down': function() { + if (/select/i.test(this.nodeName) ) { + Syn.changeOnBlur(this, "selectedIndex", this.selectedIndex) + this.selectedIndex = this.selectedIndex + 1; + //set this to change on blur? + } + }, + 'shift': function() { + return null; + } + } + }); + + + h.extend(Syn.create, { + keydown: { + setup: function( type, options, element ) { + if ( h.inArray(options, Syn.key.kinds.special) != -1 ) { + Syn.key[options + "Key"] = element; + } + } + }, + keypress: { + setup: function( type, options, element ) { + // if this browsers supports writing keys on events + // but doesn't write them if the element isn't focused + // focus on the element (ignored if already focused) + if ( S.support.keyCharacters && !S.support.keysOnNotFocused ) { + element.focus() + } + } + }, + keyup: { + setup: function( type, options, element ) { + if ( h.inArray(options, Syn.key.kinds.special) != -1 ) { + Syn.key[options + "Key"] = null; + } + } + }, + key: { + // return the options for a key event + options: function( type, options, element ) { + //check if options is character or has character + options = typeof options != "object" ? { + character: options + } : options; + + //don't change the orignial + options = h.extend({}, options) + if ( options.character ) { + h.extend(options, S.key.options(options.character, type)); + delete options.character; + } + + options = h.extend({ + ctrlKey: !! Syn.key.ctrlKey, + altKey: !! Syn.key.altKey, + shiftKey: !! Syn.key.shiftKey, + metaKey: !! Syn.key.metaKey + }, options) + + return options; + }, + // creates a key event + event: function( type, options, element ) { //Everyone Else + var doc = h.getWindow(element).document || document; + if ( doc.createEvent ) { + var event; + + try { + + event = doc.createEvent("KeyEvents"); + event.initKeyEvent(type, true, true, window, options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, options.keyCode, options.charCode); + } + catch (e) { + event = h.createBasicStandardEvent(type, options, doc); + } + event.synthetic = true; + return event; + } + else { + var event; + try { + event = h.createEventObject.apply(this, arguments); + h.extend(event, options) + } + catch (e) {} + + return event; + } + } + } + }); + + var convert = { + "enter": "\r", + "backspace": "\b", + "tab": "\t", + "space": " " + } + + /** + * @add Syn prototype + */ + h.extend(Syn.init.prototype, { + /** + * @function key + * Types a single key. The key should be + * a string that matches a + * [Syn.static.keycodes]. + * + * The following sends a carridge return + * to the 'name' element. + * @codestart + * Syn.key('\r','name') + * @codeend + * For each character, a keydown, keypress, and keyup is triggered if + * appropriate. + * @param {String} options + * @param {HTMLElement} [element] + * @param {Function} [callback] + * @return {HTMLElement} the element currently focused. + */ + _key: function( options, element, callback ) { + //first check if it is a special up + if (/-up$/.test(options) && h.inArray(options.replace("-up", ""), Syn.key.kinds.special) != -1 ) { + Syn.trigger('keyup', options.replace("-up", ""), element) + callback(true, element); + return; + } + + + var caret = Syn.typeable.test(element.nodeName) && getSelection(element), + key = convert[options] || options, + // should we run default events + runDefaults = Syn.trigger('keydown', key, element), + + // a function that gets the default behavior for a key + getDefault = Syn.key.getDefault, + + // how this browser handles preventing default events + prevent = Syn.key.browser.prevent, + + // the result of the default event + defaultResult, + + // options for keypress + keypressOptions = Syn.key.options(key, 'keypress') + + + if ( runDefaults ) { + //if the browser doesn't create keypresses for this key, run default + if (!keypressOptions ) { + defaultResult = getDefault(key).call(element, keypressOptions, h.getWindow(element), key, undefined, caret) + } else { + //do keypress + runDefaults = Syn.trigger('keypress', keypressOptions, element) + if ( runDefaults ) { + defaultResult = getDefault(key).call(element, keypressOptions, h.getWindow(element), key, undefined, caret) + } + } + } else { + //canceled ... possibly don't run keypress + if ( keypressOptions && h.inArray('keypress', prevent.keydown) == -1 ) { + Syn.trigger('keypress', keypressOptions, element) + } + } + if ( defaultResult && defaultResult.nodeName ) { + element = defaultResult + } + + if ( defaultResult !== null ) { + setTimeout(function() { + Syn.trigger('keyup', Syn.key.options(key, 'keyup'), element) + callback(runDefaults, element) + }, 1) + } else { + callback(runDefaults, element) + } + + + //do mouseup + return element; + // is there a keypress? .. if not , run default + // yes -> did we prevent it?, if not run ... + }, + /** + * @function type + * Types sequence of [Syn.key key actions]. Each + * character is typed, one at a type. + * Multi-character keys like 'left' should be + * enclosed in square brackents. + * + * The following types 'JavaScript MVC' then deletes the space. + * @codestart + * Syn.type('JavaScript MVC[left][left][left]\b','name') + * @codeend + * + * Type is able to handle (and move with) tabs (\t). + * The following simulates tabing and entering values in a form and + * eventually submitting the form. + * @codestart + * Syn.type("Justin\tMeyer\t27\tjustinbmeyer@gmail.com\r") + * @codeend + * @param {String} options the text to type + * @param {HTMLElement} [element] an element or an id of an element + * @param {Function} [callback] a function to callback + */ + _type: function( options, element, callback ) { + //break it up into parts ... + //go through each type and run + var parts = options.match(/(\[[^\]]+\])|([^\[])/g), + self = this, + runNextPart = function( runDefaults, el ) { + var part = parts.shift(); + if (!part ) { + callback(runDefaults, el); + return; + } + el = el || element; + if ( part.length > 1 ) { + part = part.substr(1, part.length - 2) + } + self._key(part, el, runNextPart) + } + + runNextPart(); + + } + }); + + + //do support code + (function() { + if (!document.body ) { + setTimeout(arguments.callee, 1) + return; + } + + var div = document.createElement("div"), + checkbox, submit, form, input, submitted = false, + anchor, textarea, inputter; + + div.innerHTML = "<form id='outer'>" + + "<input name='checkbox' type='checkbox'/>" + + "<input name='radio' type='radio' />" + + "<input type='submit' name='submitter'/>" + + "<input type='input' name='inputter'/>" + + "<input name='one'>" + + "<input name='two'/>" + + "<a href='#abc'></a>" + + "<textarea>1\n2</textarea>" + + "</form>"; + + document.documentElement.appendChild(div); + form = div.firstChild; + checkbox = form.childNodes[0]; + submit = form.childNodes[2]; + anchor = form.getElementsByTagName("a")[0]; + textarea = form.getElementsByTagName("textarea")[0]; + inputter = form.childNodes[3]; + + form.onsubmit = function( ev ) { + if ( ev.preventDefault ) ev.preventDefault(); + S.support.keypressSubmits = true; + ev.returnValue = false; + return false; + }; + // Firefox 4 won't write key events if the element isn't focused + inputter.focus(); + Syn.trigger("keypress", "\r", inputter); + + + Syn.trigger("keypress", "a", inputter); + S.support.keyCharacters = inputter.value == "a"; + + + inputter.value = "a"; + Syn.trigger("keypress", "\b", inputter); + S.support.backspaceWorks = inputter.value == ""; + + + + inputter.onchange = function() { + S.support.focusChanges = true; + } + inputter.focus(); + Syn.trigger("keypress", "a", inputter); + form.childNodes[5].focus(); // this will throw a change event + Syn.trigger("keypress", "b", inputter); + S.support.keysOnNotFocused = inputter.value == "ab"; + + //test keypress \r on anchor submits + S.bind(anchor, "click", function( ev ) { + if ( ev.preventDefault ) ev.preventDefault(); + S.support.keypressOnAnchorClicks = true; + ev.returnValue = false; + return false; + }) + Syn.trigger("keypress", "\r", anchor); + + S.support.textareaCarriage = textarea.value.length == 4 + document.documentElement.removeChild(div); + + S.support.ready++; + })(); +}()); +}) \ No newline at end of file diff --git a/browserid/static/funcunit/syn/mouse.js b/browserid/static/funcunit/syn/mouse.js new file mode 100644 index 0000000000000000000000000000000000000000..b35ce676c20d2146d9375d88e43d0d2c97f7ce02 --- /dev/null +++ b/browserid/static/funcunit/syn/mouse.js @@ -0,0 +1,290 @@ +steal.then(function(){ +//steal("synthetic").then(function() { +//handles mosue events +(function() { + + var h = Syn.helpers, + getWin = h.getWindow; + + Syn.mouse = {}; + h.extend(Syn.defaults, { + mousedown: function( options ) { + Syn.trigger("focus", {}, this) + }, + click: function() { + // prevents the access denied issue in IE if the click causes the element to be destroyed + var element = this; + try { + element.nodeType; + } catch (e) { + return; + } + //get old values + var href, radioChanged = Syn.data(element, "radioChanged"), + scope = getWin(element), + nodeName = element.nodeName.toLowerCase(); + + //this code was for restoring the href attribute to prevent popup opening + //if ((href = Syn.data(element, "href"))) { + // element.setAttribute('href', href) + //} + + //run href javascript + if (!Syn.support.linkHrefJS && /^\s*javascript:/.test(element.href) ) { + //eval js + var code = element.href.replace(/^\s*javascript:/, "") + + //try{ + if ( code != "//" && code.indexOf("void(0)") == -1 ) { + if ( window.selenium ) { + eval("with(selenium.browserbot.getCurrentWindow()){" + code + "}") + } else { + eval("with(scope){" + code + "}") + } + } + } + + //submit a form + if (!(Syn.support.clickSubmits) && (nodeName == "input" && element.type == "submit") || nodeName == 'button' ) { + + var form = Syn.closest(element, "form"); + if ( form ) { + Syn.trigger("submit", {}, form) + } + + } + //follow a link, probably needs to check if in an a. + if ( nodeName == "a" && element.href && !/^\s*javascript:/.test(element.href) ) { + + scope.location.href = element.href; + + } + + //change a checkbox + if ( nodeName == "input" && element.type == "checkbox" ) { + + //if(!Syn.support.clickChecks && !Syn.support.changeChecks){ + // element.checked = !element.checked; + //} + if (!Syn.support.clickChanges ) { + Syn.trigger("change", {}, element); + } + } + + //change a radio button + if ( nodeName == "input" && element.type == "radio" ) { // need to uncheck others if not checked + if ( radioChanged && !Syn.support.radioClickChanges ) { + Syn.trigger("change", {}, element); + } + } + // change options + if ( nodeName == "option" && Syn.data(element, "createChange") ) { + Syn.trigger("change", {}, element.parentNode); //does not bubble + Syn.data(element, "createChange", false) + } + } + }) + + //add create and setup behavior for mosue events + h.extend(Syn.create, { + mouse: { + options: function( type, options, element ) { + var doc = document.documentElement, + body = document.body, + center = [options.pageX || 0, options.pageY || 0], + //browser might not be loaded yet (doing support code) + left = Syn.mouse.browser && Syn.mouse.browser.left[type], + right = Syn.mouse.browser && Syn.mouse.browser.right[type]; + return h.extend({ + bubbles: true, + cancelable: true, + view: window, + detail: 1, + screenX: 1, + screenY: 1, + clientX: options.clientX || center[0] - (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc.clientLeft || 0), + clientY: options.clientY || center[1] - (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc.clientTop || 0), + ctrlKey: !! Syn.key.ctrlKey, + altKey: !! Syn.key.altKey, + shiftKey: !! Syn.key.shiftKey, + metaKey: !! Syn.key.metaKey, + button: left && left.button != null ? left.button : right && right.button || (type == 'contextmenu' ? 2 : 0), + relatedTarget: document.documentElement + }, options); + }, + event: function( type, defaults, element ) { //Everyone Else + var doc = getWin(element).document || document + if ( doc.createEvent ) { + var event; + + try { + event = doc.createEvent('MouseEvents'); + event.initMouseEvent(type, defaults.bubbles, defaults.cancelable, defaults.view, defaults.detail, defaults.screenX, defaults.screenY, defaults.clientX, defaults.clientY, defaults.ctrlKey, defaults.altKey, defaults.shiftKey, defaults.metaKey, defaults.button, defaults.relatedTarget); + } catch (e) { + event = h.createBasicStandardEvent(type, defaults, doc) + } + event.synthetic = true; + return event; + } else { + var event; + try { + event = h.createEventObject(type, defaults, element) + } + catch (e) {} + + return event; + } + + } + }, + click: { + setup: function( type, options, element ) { + var nodeName = element.nodeName.toLowerCase(), + type; + + //we need to manually 'check' in browser that can't check + //so checked has the right value + if (!Syn.support.clickChecks && !Syn.support.changeChecks && nodeName === "input" ) { + type = element.type.toLowerCase(); //pretty sure lowercase isn't needed + if ( type === 'checkbox' ) { + element.checked = !element.checked; + } + if ( type === "radio" ) { + //do the checks manually + if (!element.checked ) { //do nothing, no change + try { + Syn.data(element, "radioChanged", true); + } catch (e) {} + element.checked = true; + } + } + } + + if ( nodeName == "a" && element.href && !/^\s*javascript:/.test(element.href) ) { + + //save href + Syn.data(element, "href", element.href) + + //remove b/c safari/opera will open a new tab instead of changing the page + // this has been removed because newer versions don't have this problem + //element.setAttribute('href', 'javascript://') + //however this breaks scripts using the href + //we need to listen to this and prevent the default behavior + //and run the default behavior ourselves. Boo! + } + //if select or option, save old value and mark to change + if (/option/i.test(element.nodeName) ) { + var child = element.parentNode.firstChild, + i = -1; + while ( child ) { + if ( child.nodeType == 1 ) { + i++; + if ( child == element ) break; + } + child = child.nextSibling; + } + if ( i !== element.parentNode.selectedIndex ) { + //shouldn't this wait on triggering + //change? + element.parentNode.selectedIndex = i; + Syn.data(element, "createChange", true) + } + } + + } + }, + mousedown: { + setup: function( type, options, element ) { + var nn = element.nodeName.toLowerCase(); + //we have to auto prevent default to prevent freezing error in safari + if ( Syn.browser.safari && (nn == "select" || nn == "option") ) { + options._autoPrevent = true; + } + } + } + }); + //do support code + (function() { + if (!document.body ) { + setTimeout(arguments.callee, 1) + return; + } + var oldSynth = window.__synthTest; + window.__synthTest = function() { + Syn.support.linkHrefJS = true; + } + var div = document.createElement("div"), + checkbox, submit, form, input, select; + + div.innerHTML = "<form id='outer'>" + "<input name='checkbox' type='checkbox'/>" + "<input name='radio' type='radio' />" + "<input type='submit' name='submitter'/>" + "<input type='input' name='inputter'/>" + "<input name='one'>" + "<input name='two'/>" + "<a href='javascript:__synthTest()' id='synlink'></a>" + "<select><option></option></select>" + "</form>"; + document.documentElement.appendChild(div); + form = div.firstChild + checkbox = form.childNodes[0]; + submit = form.childNodes[2]; + select = form.getElementsByTagName('select')[0] + + checkbox.checked = false; + checkbox.onchange = function() { + Syn.support.clickChanges = true; + } + + Syn.trigger("click", {}, checkbox) + Syn.support.clickChecks = checkbox.checked; + + checkbox.checked = false; + + Syn.trigger("change", {}, checkbox); + + Syn.support.changeChecks = checkbox.checked; + + form.onsubmit = function( ev ) { + if ( ev.preventDefault ) ev.preventDefault(); + Syn.support.clickSubmits = true; + return false; + } + Syn.trigger("click", {}, submit) + + + + form.childNodes[1].onchange = function() { + Syn.support.radioClickChanges = true; + } + Syn.trigger("click", {}, form.childNodes[1]) + + + Syn.bind(div, 'click', function() { + Syn.support.optionClickBubbles = true; + Syn.unbind(div, 'click', arguments.callee) + }) + Syn.trigger("click", {}, select.firstChild) + + + Syn.support.changeBubbles = Syn.eventSupported('change'); + + //test if mousedown followed by mouseup causes click (opera), make sure there are no clicks after this + var clicksCount = 0 + div.onclick = function() { + Syn.support.mouseDownUpClicks = true; + //we should use this to check for opera potentially, but would + //be difficult to remove element correctly + //Syn.support.mouseDownUpRepeatClicks = (2 == (++clicksCount)) + } + Syn.trigger("mousedown", {}, div) + Syn.trigger("mouseup", {}, div) + + //setTimeout(function(){ + // Syn.trigger("mousedown",{},div) + // Syn.trigger("mouseup",{},div) + //},1) + + document.documentElement.removeChild(div); + + //check stuff + window.__synthTest = oldSynth; + Syn.support.ready++; + })(); + + +})() +//}); +}) \ No newline at end of file diff --git a/browserid/static/funcunit/syn/qunit.html b/browserid/static/funcunit/syn/qunit.html new file mode 100644 index 0000000000000000000000000000000000000000..440787facb8222c4b20b81026a9b07bff93b417f --- /dev/null +++ b/browserid/static/funcunit/syn/qunit.html @@ -0,0 +1,31 @@ +<html> + <head> + <link rel="stylesheet" type="text/css" href="resources/qunit/qunit.css" /> + <title>QUnit Test</title> + <style> + + </style> + + </head> + <body> + + <h1 id="qunit-header">Syn Test Suite</h1> + <h2 id="qunit-banner"></h2> + <div id="qunit-testrunner-toolbar"></div> + <h2 id="qunit-userAgent"></h2> + <div id="test-content"></div> + <ol id="qunit-tests"></ol> + <div id='syntheticSupport'></div> + <div id="qunit-test-area"></div> + <div id="mlog"></div> + <script type='text/javascript' src='resources/jquery.js'></script> + <script type='text/javascript' src='resources/qunit/qunit.js'></script> + <script type='text/javascript' src='synthetic.js'></script> + <script type='text/javascript' src='mouse.js'></script> + <script type='text/javascript' src='browsers.js'></script> + <script type='text/javascript' src='key.js'></script> + <script type='text/javascript' src='test/qunit/syn_test.js'></script> + <script type='text/javascript' src='test/qunit/mouse_test.js'></script> + <script type='text/javascript' src='test/qunit/key_test.js'></script> + </body> +</html> \ No newline at end of file diff --git a/browserid/static/funcunit/syn/recorder.html b/browserid/static/funcunit/syn/recorder.html new file mode 100644 index 0000000000000000000000000000000000000000..dd084d80dba64518a309a5c314fd17e54a8e8ac6 --- /dev/null +++ b/browserid/static/funcunit/syn/recorder.html @@ -0,0 +1,303 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" + "http://www.w3.org/TR/html4/strict.dtd"> +<html lang="en"> + <head> + <title>Synthetic Typing Test</title> + <style type='text/css'> + body {font-family: verdana} + td {text-align: right} + td:nth-child(even) {background: #CCd} + #key th:nth-child(even) {background: #CCf} + #who th:nth-child(even) {background: #fCb} + </style> + </head> + <body> +<h1>Key Recorder</h1> +<p id='ua' tabindex='2'></p> +<div> + Type 'a' + <input id='t_a' /> +</div> +<div> + Type [backspace] + <input id='t_\\b' /> - specialChars +</div> +<div> + Type [Enter] + <input id='t_\\r' /> - function +</div> +<div> + Type and hold [down] + <input id='t_down' /> - navigation +</div> +<div> + Type and hold [shift] + <input id='t_shift' /> - special +</div> +<div> + Type [b] + <input id='keyUpPrevent' /> - preventDefault keydown +</div> +<div> + Type [c] + <input id='keyPressPrevent' /> - preventDefault keypress +</div> +<div> + Type [tab] + <input id='t_\\t' /><input id='t_tab2' /> +</div> +<div> + Type [pause-break] + <input id='t_pause-break' /> +</div> +<div> + Type [caps] + <input id='t_caps' /> +</div> +<div> + Type [escape] + <input id='t_escape' /> +</div> +<div> + Type [num-lock] + <input id='t_num-lock' /> +</div> +<div> + Type [scrolllock] + <input id='t_scroll-lock' /> +</div> +<div> + Type [print] + <input id='t_print' /> +</div> +<div> + Type [f8] + <input id='t_f8' /> - function +</div> + +<div id='leftClick'> + Left Click Me +</div> +<div id='rightClick'> + Right Click Me +</div> +<div id='dblClick'> + Double Click Me +</div> +<h3>Key Data</h3> +<pre id='out'></pre> +<h3>Click Data</h3> +<pre id='clickdata'></pre> +<script type='text/javascript' src='../../steal/steal.js'></script> +<script type='text/javascript'> + steal.plugins('funcunit/syn','jquery','jquery/lang/json').start(); +</script> +<script type='text/javascript' id="demo-source"> + var gId = function(id){ + return document.getElementById(id) + }, + bind = function(el, ev, func){ + Syn.bind(gId(el.replace("\\","\\\\")), ev,func); + }; + $(function(){ + $("#ua").html(window.navigator.userAgent) + }); + + var keycodes = Syn.keycodes, + special = { + backspace : "\b", + tab : "\t", + enter : "\r" + }, + convert = { + "\\r" : "\r", + "\\t" :"\t", + "\\b" : "\b" + } + + isKey = function(str, val){ + str = convert[str] || str; + return keycodes[str] === String(val) + } + isChar = function(str, val){ + str = convert[str] || str; + return val && (special[str] || str).charCodeAt(0) == val + } + keyOrChar = function(str, val){ + return isKey(str,val) ? "key" : + (isChar(str, val) ? "char" : val) + } + tests= ['prevent'] + data= { + prevent :{ + keyup : [], + keydown : [], + keypress :[] + } + }; + + binder = function(name,test){ + //listen for events + test = test || name; + var testData = (data[test] = {}), + keydown = 0, + keypress= 0 + keyup = 0; + + bind('t_'+name,'keydown', function(ev){ + testData.keydown = [ (isChar(name, ev.charCode) ? "char" : ev.charCode), + keyOrChar(name, ev.keyCode)]; + keydown++ + if(keydown ==2 ){ + //testData.repeat.push('keydown') + } + }) + + bind('t_'+name,'keypress', function(ev){ + testData.keypress = [ (isChar(name, ev.charCode) ? "char" : ev.charCode), + keyOrChar(name, ev.keyCode)] + + keypress++; + + if(keypress ==2 ){ + //testData.repeat.push('keypress') + } + update(); + }) + + bind('t_'+name,'keyup', function(ev){ + testData.keyup = [ (isChar(name, ev.charCode) ? "char" : ev.charCode), + keyOrChar(name, ev.keyCode)] + keyup++; + if(keyup ==2 ){ + //testData.repeat.push('keyup') + } + + update(); + + }) + + + + $('#t_'+name).val("") + tests.push(test) + } + binder('a','character') + binder('\\b','specialChars') + binder('down','navigation') + binder('shift','special') + binder('\\t') + bind('t_tab2','keyup', function(ev){ + data["\\t"].keyup = [ (isChar("\\t", ev.charCode) ? "char" : ev.charCode), + keyOrChar("\\t", ev.keyCode)] + update(); + }) + binder('pause-break') + binder('caps') + binder('escape') + binder('num-lock') + binder('scroll-lock') + binder('print') + binder('f8','function') + binder('\\r') + var pd = function(ev){ + if(ev.preventDefault) + ev.preventDefault(); + + ev.returnValue = false; + } + var keyDown_keyPressed = false + bind('keyUpPrevent','keydown', function(ev){ + pd(ev); + setTimeout(function(){ + if($('#keyUpPrevent').val() !== 'b'){ + data.prevent.keydown.push('char') + } + if(!keyDown_keyPressed){ + data.prevent.keydown.push('keypress') + } + update(); + },1000) + }) + bind('keyUpPrevent','keypress', function(ev){ + keyDown_keyPressed = true; + }) + + + var keyPress_keyUp = false + bind('keyPressPrevent','keypress', function(ev){ + pd(ev); + setTimeout(function(){ + if($('#keyPressPrevent').val() !== 'c'){ + data.prevent.keypress.push('char') + } + if(!keyPress_keyUp){ + data.prevent.keypress.push('keyup') + } + update(); + },1000) + }) + bind('keyPressPrevent','keyup', function(ev){ + keyPress_keyUp = true; + }) + + + var clickData = { + right : {}, + left : {}, + dblclick :{} + } + $.each(['right','left'], function(i, name){ + + bind(name+'Click','mousedown',function(ev){ + clickData[name].mousedown = { + button: ev.button, + which: ev.which + } + }) + bind(name+'Click','mouseup',function(ev){ + clickData[name].mouseup = { + button: ev.button, + which: ev.which + } + setTimeout(function(){ + $('#clickdata').html($.toJSON(clickData)) + },100) + }) + bind(name+'Click','click',function(ev){ + clickData[name].click = { + button: ev.button, + which: ev.which + } + }) + bind(name+'Click','contextmenu',function(ev){ + clickData[name].contextmenu = { + button: ev.button, + which: ev.which + } + }) + + }) + var dblClicks = 0 + bind('dblClick','click',function(ev){ + clickData.dblclick.click = (++dblClicks) + }) + bind('dblClick','dblclick',function(ev){ + clickData.dblclick.dblclick = (1) + setTimeout(function(){ + $('#clickdata').html($.toJSON(clickData)) + },100) + }) + update = function(){ + var str = []; + $.each(tests, function(i, sub){ + str.push("'"+sub+ "':<br/> "+$.toJSON(data[sub])+",") + }) + + $('#out').html(str.join("<br/>")) + } + + +</script> + </body> +</html> \ No newline at end of file diff --git a/browserid/static/funcunit/syn/resources/jquery.event.drag.js b/browserid/static/funcunit/syn/resources/jquery.event.drag.js new file mode 100644 index 0000000000000000000000000000000000000000..877a0f2854975b8eb5e779ed2b5a9e0f775b4497 --- /dev/null +++ b/browserid/static/funcunit/syn/resources/jquery.event.drag.js @@ -0,0 +1,823 @@ +(function( $ ) { + var getSetZero = function( v ) { + return v !== undefined ? (this.array[0] = v) : this.array[0]; + }, + getSetOne = function( v ) { + return v !== undefined ? (this.array[1] = v) : this.array[1]; + }; + /** + * @class jQuery.Vector + * A vector class + * @constructor creates a new vector instance from the arguments. Example: + * @codestart + * new jQuery.Vector(1,2) + * @codeend + * + */ + $.Vector = function() { + this.update($.makeArray(arguments)); + }; + $.Vector.prototype = + /* @Prototype*/ + { + /** + * Applys the function to every item in the vector. Returns the new vector. + * @param {Function} f + * @return {jQuery.Vector} new vector class. + */ + app: function( f ) { + var i, vec, newArr = []; + + for ( i = 0; i < this.array.length; i++ ) { + newArr.push(f(this.array[i])); + } + vec = new $.Vector(); + return vec.update(newArr); + }, + /** + * Adds two vectors together. Example: + * @codestart + * new Vector(1,2).plus(2,3) //-> <3,5> + * new Vector(3,5).plus(new Vector(4,5)) //-> <7,10> + * @codeend + * @return {$.Vector} + */ + plus: function() { + var i, args = arguments[0] instanceof $.Vector ? arguments[0].array : $.makeArray(arguments), + arr = this.array.slice(0), + vec = new $.Vector(); + for ( i = 0; i < args.length; i++ ) { + arr[i] = (arr[i] ? arr[i] : 0) + args[i]; + } + return vec.update(arr); + }, + /** + * Like plus but subtracts 2 vectors + * @return {jQuery.Vector} + */ + minus: function() { + var i, args = arguments[0] instanceof $.Vector ? arguments[0].array : $.makeArray(arguments), + arr = this.array.slice(0), + vec = new $.Vector(); + for ( i = 0; i < args.length; i++ ) { + arr[i] = (arr[i] ? arr[i] : 0) - args[i]; + } + return vec.update(arr); + }, + /** + * Returns the current vector if it is equal to the vector passed in. + * False if otherwise. + * @return {jQuery.Vector} + */ + equals: function() { + var i, args = arguments[0] instanceof $.Vector ? arguments[0].array : $.makeArray(arguments), + arr = this.array.slice(0), + vec = new $.Vector(); + for ( i = 0; i < args.length; i++ ) { + if ( arr[i] != args[i] ) { + return null; + } + } + return vec.update(arr); + }, +/* + * Returns the 2nd value of the vector + * @return {Number} + */ + x: getSetZero, + width: getSetZero, + /** + * Returns the first value of the vector + * @return {Number} + */ + y: getSetOne, + height: getSetOne, + /** + * Same as x() + * @return {Number} + */ + top: getSetOne, + /** + * same as y() + * @return {Number} + */ + left: getSetZero, + /** + * returns (x,y) + * @return {String} + */ + toString: function() { + return "(" + this.array[0] + "," + this.array[1] + ")"; + }, + /** + * Replaces the vectors contents + * @param {Object} array + */ + update: function( array ) { + var i; + if ( this.array ) { + for ( i = 0; i < this.array.length; i++ ) { + delete this.array[i]; + } + } + this.array = array; + for ( i = 0; i < array.length; i++ ) { + this[i] = this.array[i]; + } + return this; + } + }; + + $.Event.prototype.vector = function() { + if ( this.originalEvent.synthetic ) { + var doc = document.documentElement, + body = document.body; + return new $.Vector(this.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc.clientLeft || 0), this.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc.clientTop || 0)); + } else { + return new $.Vector(this.pageX, this.pageY); + } + }; + + $.fn.offsetv = function() { + if ( this[0] == window ) { + return new $.Vector(window.pageXOffset ? window.pageXOffset : document.documentElement.scrollLeft, window.pageYOffset ? window.pageYOffset : document.documentElement.scrollTop); + } else { + var offset = this.offset(); + return new $.Vector(offset.left, offset.top); + } + }; + + $.fn.dimensionsv = function( which ) { + if ( this[0] == window || !which ) { + return new $.Vector(this.width(), this.height()); + } + else { + return new $.Vector(this[which + "Width"](), this[which + "Height"]()); + } + }; +})(jQuery); +(function() { + + var event = jQuery.event, + + //helper that finds handlers by type and calls back a function, this is basically handle + findHelper = function( events, types, callback ) { + var t, type, typeHandlers, all, h, handle, namespaces, namespace; + for ( t = 0; t < types.length; t++ ) { + type = types[t]; + all = type.indexOf(".") < 0; + if (!all ) { + namespaces = type.split("."); + type = namespaces.shift(); + namespace = new RegExp("(^|\\.)" + namespaces.slice(0).sort().join("\\.(?:.*\\.)?") + "(\\.|$)"); + } + typeHandlers = (events[type] || []).slice(0); + + for ( h = 0; h < typeHandlers.length; h++ ) { + handle = typeHandlers[h]; + if (!handle.selector && (all || namespace.test(handle.namespace)) ) { + callback(type, handle.origHandler || handle.handler); + } + } + } + }; + + /** + * Finds event handlers of a given type on an element. + * @param {HTMLElement} el + * @param {Array} types an array of event names + * @param {String} [selector] optional selector + * @return {Array} an array of event handlers + */ + event.find = function( el, types, selector ) { + var events = $.data(el, "events"), + handlers = [], + t, liver, live; + + if (!events ) { + return handlers; + } + + if ( selector ) { + if (!events.live ) { + return []; + } + live = events.live; + + for ( t = 0; t < live.length; t++ ) { + liver = live[t]; + if ( liver.selector === selector && $.inArray(liver.origType, types) !== -1 ) { + handlers.push(liver.origHandler || liver.handler); + } + } + } else { + // basically re-create handler's logic + findHelper(events, types, function( type, handler ) { + handlers.push(handler); + }); + } + return handlers; + }; + /** + * Finds + * @param {HTMLElement} el + * @param {Array} types + */ + event.findBySelector = function( el, types ) { + var events = $.data(el, "events"), + selectors = {}, + //adds a handler for a given selector and event + add = function( selector, event, handler ) { + var select = selectors[selector] || (selectors[selector] = {}), + events = select[event] || (select[event] = []); + events.push(handler); + }; + + if (!events ) { + return selectors; + } + //first check live: + $.each(events.live || [], function( i, live ) { + if ( $.inArray(live.origType, types) !== -1 ) { + add(live.selector, live.origType, live.origHandler || live.handler); + } + }); + //then check straight binds + findHelper(events, types, function( type, handler ) { + add("", type, handler); + }); + + return selectors; + }; + $.fn.respondsTo = function( events ) { + if (!this.length ) { + return false; + } else { + //add default ? + return event.find(this[0], $.isArray(events) ? events : [events]).length > 0; + } + }; + $.fn.triggerHandled = function( event, data ) { + event = (typeof event == "string" ? $.Event(event) : event); + this.trigger(event, data); + return event.handled; + }; + /** + * Only attaches one event handler for all types ... + * @param {Array} types llist of types that will delegate here + * @param {Object} startingEvent the first event to start listening to + * @param {Object} onFirst a function to call + */ + event.setupHelper = function( types, startingEvent, onFirst ) { + if (!onFirst ) { + onFirst = startingEvent; + startingEvent = null; + } + var add = function( handleObj ) { + + var bySelector, selector = handleObj.selector || ""; + if ( selector ) { + bySelector = event.find(this, types, selector); + if (!bySelector.length ) { + $(this).delegate(selector, startingEvent, onFirst); + } + } + else { + //var bySelector = event.find(this, types, selector); + if (!event.find(this, types, selector).length ) { + event.add(this, startingEvent, onFirst, { + selector: selector, + delegate: this + }); + } + + } + + }, + remove = function( handleObj ) { + var bySelector, selector = handleObj.selector || ""; + if ( selector ) { + bySelector = event.find(this, types, selector); + if (!bySelector.length ) { + $(this).undelegate(selector, startingEvent, onFirst); + } + } + else { + if (!event.find(this, types, selector).length ) { + event.remove(this, startingEvent, onFirst, { + selector: selector, + delegate: this + }); + } + } + }; + $.each(types, function() { + event.special[this] = { + add: add, + remove: remove, + setup: function() {}, + teardown: function() {} + }; + }); + }; +})(jQuery); +(function( $ ) { + //modify live + //steal the live handler .... + var bind = function( object, method ) { + var args = Array.prototype.slice.call(arguments, 2); + return function() { + var args2 = [this].concat(args, $.makeArray(arguments)); + return method.apply(object, args2); + }; + }, + event = $.event; + // var handle = event.handle; //unused + /** + * @class jQuery.Drag + * @parent specialevents + * @plugin jquery/event/drag + * @download http://jmvcsite.heroku.com/pluginify?plugins[]=jquery/event/drag/drag.js + * @test jquery/event/drag/qunit.html + * Provides drag events as a special events to jQuery. + * A jQuery.Drag instance is created on a drag and passed + * as a parameter to the drag event callbacks. By calling + * methods on the drag event, you can alter the drag's + * behavior. + * <h2>Drag Events</h2> + * The drag plugin allows you to listen to the following events: + * <ul> + * <li><code>dragdown</code> - the mouse cursor is pressed down</li> + * <li><code>draginit</code> - the drag motion is started</li> + * <li><code>dragmove</code> - the drag is moved</li> + * <li><code>dragend</code> - the drag has ended</li> + * <li><code>dragover</code> - the drag is over a drop point</li> + * <li><code>dragout</code> - the drag moved out of a drop point</li> + * </ul> + * <p>Just by binding or delegating on one of these events, you make + * the element dragable. You can change the behavior of the drag + * by calling methods on the drag object passed to the callback. + * <h3>Example</h3> + * Here's a quick example: + * @codestart + * //makes the drag vertical + * $(".drags").live("draginit", function(event, drag){ + * drag.vertical(); + * }) + * //gets the position of the drag and uses that to set the width + * //of an element + * $(".resize").live("dragmove",function(event, drag){ + * $(this).width(drag.position.left() - $(this).offset().left ) + * }) + * @codeend + * <h2>Drag Object</h2> + * <p>The drag object is passed after the event to drag + * event callback functions. By calling methods + * and changing the properties of the drag object, + * you can alter how the drag behaves. + * </p> + * <p>The drag properties and methods:</p> + * <ul> + * <li><code>[jQuery.Drag.prototype.cancel cancel]</code> - stops the drag motion from happening</li> + * <li><code>[jQuery.Drag.prototype.ghost ghost]</code> - copys the draggable and drags the cloned element</li> + * <li><code>[jQuery.Drag.prototype.horizontal horizontal]</code> - limits the scroll to horizontal movement</li> + * <li><code>[jQuery.Drag.prototype.location location]</code> - where the drag should be on the screen</li> + * <li><code>[jQuery.Drag.prototype.mouseElementPosition mouseElementPosition]</code> - where the mouse should be on the drag</li> + * <li><code>[jQuery.Drag.prototype.only only]</code> - only have drags, no drops</li> + * <li><code>[jQuery.Drag.prototype.representative representative]</code> - move another element in place of this element</li> + * <li><code>[jQuery.Drag.prototype.revert revert]</code> - animate the drag back to its position</li> + * <li><code>[jQuery.Drag.prototype.vertical vertical]</code> - limit the drag to vertical movement</li> + * <li><code>[jQuery.Drag.prototype.limit limit]</code> - limit the drag within an element (*limit plugin)</li> + * <li><code>[jQuery.Drag.prototype.scrolls scrolls]</code> - scroll scrollable areas when dragging near their boundries (*scroll plugin)</li> + * </ul> + * <h2>Demo</h2> + * Now lets see some examples: + * @demo jquery/event/drag/drag.html 1000 + * @constructor + * The constructor is never called directly. + */ + $.Drag = function() {}; + + /** + * @Static + */ + $.extend($.Drag, { + lowerName: "drag", + current: null, + /** + * Called when someone mouses down on a draggable object. + * Gathers all callback functions and creates a new Draggable. + * @hide + */ + mousedown: function( ev, element ) { + var isLeftButton = ev.button === 0 || ev.button == 1; + if (!isLeftButton || this.current ) { + return; + } //only allows 1 drag at a time, but in future could allow more + //ev.preventDefault(); + //create Drag + var drag = new $.Drag(), + delegate = ev.liveFired || element, + selector = ev.handleObj.selector, + self = this; + this.current = drag; + + drag.setup({ + element: element, + delegate: ev.liveFired || element, + selector: ev.handleObj.selector, + moved: false, + callbacks: { + dragdown: event.find(delegate, ["dragdown"], selector), + draginit: event.find(delegate, ["draginit"], selector), + dragover: event.find(delegate, ["dragover"], selector), + dragmove: event.find(delegate, ["dragmove"], selector), + dragout: event.find(delegate, ["dragout"], selector), + dragend: event.find(delegate, ["dragend"], selector) + }, + destroyed: function() { + self.current = null; + } + }, ev); + } + }); + + + + + + /** + * @Prototype + */ + $.extend($.Drag.prototype, { + setup: function( options, ev ) { + //this.noSelection(); + $.extend(this, options); + this.element = $(this.element); + this.event = ev; + this.moved = false; + this.allowOtherDrags = false; + var mousemove = bind(this, this.mousemove), + mouseup = bind(this, this.mouseup); + this._mousemove = mousemove; + this._mouseup = mouseup; + $(document).bind('mousemove', mousemove); + $(document).bind('mouseup', mouseup); + + if (!this.callEvents('down', this.element, ev) ) { + ev.preventDefault(); + } + }, + /** + * Unbinds listeners and allows other drags ... + * @hide + */ + destroy: function() { + $(document).unbind('mousemove', this._mousemove); + $(document).unbind('mouseup', this._mouseup); + if (!this.moved ) { + this.event = this.element = null; + } + //this.selection(); + this.destroyed(); + }, + mousemove: function( docEl, ev ) { + if (!this.moved ) { + this.init(this.element, ev); + this.moved = true; + } + + var pointer = ev.vector(); + if ( this._start_position && this._start_position.equals(pointer) ) { + return; + } + //e.preventDefault(); + this.draw(pointer, ev); + }, + mouseup: function( docEl, event ) { + //if there is a current, we should call its dragstop + if ( this.moved ) { + this.end(event); + } + this.destroy(); + }, + noSelection: function() { + document.documentElement.onselectstart = function() { + return false; + }; + document.documentElement.unselectable = "on"; + $(document.documentElement).css('-moz-user-select', 'none'); + }, + selection: function() { + document.documentElement.onselectstart = function() {}; + document.documentElement.unselectable = "off"; + $(document.documentElement).css('-moz-user-select', ''); + }, + init: function( element, event ) { + element = $(element); + var startElement = (this.movingElement = (this.element = $(element))); //the element that has been clicked on + //if a mousemove has come after the click + this._cancelled = false; //if the drag has been cancelled + this.event = event; + this.mouseStartPosition = event.vector(); //where the mouse is located + /** + * @attribute mouseElementPosition + * The position of start of the cursor on the element + */ + this.mouseElementPosition = this.mouseStartPosition.minus(this.element.offsetv()); //where the mouse is on the Element + //this.callStart(element, event); + this.callEvents('init', element, event); + + //Check what they have set and respond accordingly + // if they canceled + if ( this._cancelled === true ) { + return; + } + //if they set something else as the element + this.startPosition = startElement != this.movingElement ? this.movingElement.offsetv() : this.currentDelta(); + + this.makePositioned(this.movingElement); + this.oldZIndex = this.movingElement.css('zIndex'); + this.movingElement.css('zIndex', 1000); + if (!this._only && this.constructor.responder ) { + this.constructor.responder.compile(event, this); + } + }, + makePositioned: function( that ) { + var style, pos = that.css('position'); + + if (!pos || pos == 'static' ) { + style = { + position: 'relative' + }; + + if ( window.opera ) { + style.top = '0px'; + style.left = '0px'; + } + that.css(style); + } + }, + callEvents: function( type, element, event, drop ) { + var i, cbs = this.callbacks[this.constructor.lowerName + type]; + for ( i = 0; i < cbs.length; i++ ) { + cbs[i].call(element, event, this, drop); + } + return cbs.length; + }, + /** + * Returns the position of the movingElement by taking its top and left. + * @hide + * @return {Vector} + */ + currentDelta: function() { + return new $.Vector(parseInt(this.movingElement.css('left'), 10) || 0, parseInt(this.movingElement.css('top'), 10) || 0); + }, + //draws the position of the dragmove object + draw: function( pointer, event ) { + // only drag if we haven't been cancelled; + if ( this._cancelled ) { + return; + } + /** + * @attribute location + * The location of where the element should be in the page. This + * takes into account the start position of the cursor on the element. + */ + this.location = pointer.minus(this.mouseElementPosition); // the offset between the mouse pointer and the representative that the user asked for + // position = mouse - (dragOffset - dragTopLeft) - mousePosition + this.move(event); + if ( this._cancelled ) { + return; + } + if (!event.isDefaultPrevented() ) { + this.position(this.location); + } + + //fill in + if (!this._only && this.constructor.responder ) { + this.constructor.responder.show(pointer, this, event); + } + }, + /** + * Sets the position of this drag. + * + * The limit and scroll plugins + * overwrite this to make sure the drag follows a particular path. + * + * @param {jQuery.Vector} newOffsetv the position of the element (not the mouse) + */ + position: function( newOffsetv ) { //should draw it on the page + var style, dragged_element_css_offset = this.currentDelta(), + // the drag element's current left + top css attributes + dragged_element_position_vector = // the vector between the movingElement's page and css positions + this.movingElement.offsetv().minus(dragged_element_css_offset); // this can be thought of as the original offset + this.required_css_position = newOffsetv.minus(dragged_element_position_vector); + + this.offsetv = newOffsetv; + //dragged_element vector can probably be cached. + style = this.movingElement[0].style; + if (!this._cancelled && !this._horizontal ) { + style.top = this.required_css_position.top() + "px"; + } + if (!this._cancelled && !this._vertical ) { + style.left = this.required_css_position.left() + "px"; + } + }, + move: function( event ) { + this.callEvents('move', this.element, event); + }, + over: function( event, drop ) { + this.callEvents('over', this.element, event, drop); + }, + out: function( event, drop ) { + this.callEvents('out', this.element, event, drop); + }, + /** + * Called on drag up + * @hide + * @param {Event} event a mouseup event signalling drag/drop has completed + */ + end: function( event ) { + if ( this._cancelled ) { + return; + } + if (!this._only && this.constructor.responder ) { + this.constructor.responder.end(event, this); + } + + this.callEvents('end', this.element, event); + + if ( this._revert ) { + var self = this; + this.movingElement.animate({ + top: this.startPosition.top() + "px", + left: this.startPosition.left() + "px" + }, function() { + self.cleanup.apply(self, arguments); + }); + } + else { + this.cleanup(); + } + this.event = null; + }, + /** + * Cleans up drag element after drag drop. + * @hide + */ + cleanup: function() { + this.movingElement.css({ + zIndex: this.oldZIndex + }); + if ( this.movingElement[0] !== this.element[0] ) { + this.movingElement.css({ + display: 'none' + }); + } + if ( this._removeMovingElement ) { + this.movingElement.remove(); + } + + this.movingElement = this.element = this.event = null; + }, + /** + * Stops drag drop from running. + */ + cancel: function() { + this._cancelled = true; + //this.end(this.event); + if (!this._only && this.constructor.responder ) { + this.constructor.responder.clear(this.event.vector(), this, this.event); + } + this.destroy(); + + }, + /** + * Clones the element and uses it as the moving element. + * @return {jQuery.fn} the ghost + */ + ghost: function( loc ) { + // create a ghost by cloning the source element and attach the clone to the dom after the source element + var ghost = this.movingElement.clone().css('position', 'absolute'); + (loc ? $(loc) : this.movingElement).after(ghost); + ghost.width(this.movingElement.width()).height(this.movingElement.height()); + + // store the original element and make the ghost the dragged element + this.movingElement = ghost; + this._removeMovingElement = true; + return ghost; + }, + /** + * Use a representative element, instead of the movingElement. + * @param {HTMLElement} element the element you want to actually drag + * @param {Number} offsetX the x position where you want your mouse on the object + * @param {Number} offsetY the y position where you want your mouse on the object + */ + representative: function( element, offsetX, offsetY ) { + this._offsetX = offsetX || 0; + this._offsetY = offsetY || 0; + + var p = this.mouseStartPosition; + + this.movingElement = $(element); + this.movingElement.css({ + top: (p.y() - this._offsetY) + "px", + left: (p.x() - this._offsetX) + "px", + display: 'block', + position: 'absolute' + }).show(); + + this.mouseElementPosition = new $.Vector(this._offsetX, this._offsetY); + }, + /** + * Makes the movingElement go back to its original position after drop. + * @codestart + * ".handle dragend" : function( el, ev, drag ) { + * drag.revert() + * } + * @codeend + * @param {Boolean} [val] optional, set to false if you don't want to revert. + */ + revert: function( val ) { + this._revert = val === null ? true : val; + }, + /** + * Isolates the drag to vertical movement. + */ + vertical: function() { + this._vertical = true; + }, + /** + * Isolates the drag to horizontal movement. + */ + horizontal: function() { + this._horizontal = true; + }, + + + /** + * Respondables will not be alerted to this drag. + */ + only: function( only ) { + return (this._only = (only === undefined ? true : only)); + } + }); + + /** + * @add jQuery.event.special + */ + event.setupHelper([ + /** + * @attribute dragdown + * <p>Listens for when a drag movement has started on a mousedown. + * If you listen to this, the mousedown's default event (preventing + * text selection) is not prevented. You are responsible for calling it + * if you want it (you probably do). </p> + * <p><b>Why might you not want it?</b></p> + * <p>You might want it if you want to allow text selection on element + * within the drag element. Typically these are input elements.</p> + * <p>Drag events are covered in more detail in [jQuery.Drag].</p> + * @codestart + * $(".handles").live("dragdown", function(ev, drag){}) + * @codeend + */ + 'dragdown', + /** + * @attribute draginit + * Called when the drag starts. + * <p>Drag events are covered in more detail in [jQuery.Drag].</p> + */ + 'draginit', + /** + * @attribute dragover + * Called when the drag is over a drop. + * <p>Drag events are covered in more detail in [jQuery.Drag].</p> + */ + 'dragover', + /** + * @attribute dragmove + * Called when the drag is moved. + * <p>Drag events are covered in more detail in [jQuery.Drag].</p> + */ + 'dragmove', + /** + * @attribute dragout + * When the drag leaves a drop point. + * <p>Drag events are covered in more detail in [jQuery.Drag].</p> + */ + 'dragout', + /** + * @attribute dragend + * Called when the drag is done. + * <p>Drag events are covered in more detail in [jQuery.Drag].</p> + */ + 'dragend'], "mousedown", function( e ) { + $.Drag.mousedown.call($.Drag, e, this); + + }); + + + + +})(jQuery) \ No newline at end of file diff --git a/browserid/static/funcunit/syn/resources/jquery.event.drop.js b/browserid/static/funcunit/syn/resources/jquery.event.drop.js new file mode 100644 index 0000000000000000000000000000000000000000..f686674bbebf0c9bd0cffc402bfd44baf41c79da --- /dev/null +++ b/browserid/static/funcunit/syn/resources/jquery.event.drop.js @@ -0,0 +1,429 @@ +(function($){ + var withinBox = function(x, y, left, top, width, height ){ + return (y >= top && + y < top + height && + x >= left && + x < left + width); + } +/** + * @function within + * @parent dom + * Returns if the elements are within the position + * @param {Object} x + * @param {Object} y + * @param {Object} cache + */ +$.fn.within= function(x, y, cache) { + var ret = [] + this.each(function(){ + var q = jQuery(this); + + if(this == document.documentElement) return ret.push(this); + + var offset = cache ? jQuery.data(this,"offset", q.offset()) : q.offset(); + + var res = withinBox(x, y, + offset.left, offset.top, + this.offsetWidth, this.offsetHeight ); + + if(res) ret.push(this); + }); + + return this.pushStack( jQuery.unique( ret ), "within", x+","+y ); +} + + +/** + * @function withinBox + * returns if elements are within the box + * @param {Object} left + * @param {Object} top + * @param {Object} width + * @param {Object} height + * @param {Object} cache + */ +$.fn.withinBox = function(left, top, width, height, cache){ + var ret = [] + this.each(function(){ + var q = jQuery(this); + + if(this == document.documentElement) return this.ret.push(this); + + var offset = cache ? jQuery.data(this,"offset", q.offset()) : q.offset(); + + var ew = q.width(), eh = q.height(); + + res = !( (offset.top > top+height) || (offset.top +eh < top) || (offset.left > left+width ) || (offset.left+ew < left)); + + if(res) + ret.push(this); + }); + return this.pushStack( jQuery.unique( ret ), "withinBox", jQuery.makeArray(arguments).join(",") ); +} + +})(jQuery); +(function($){ +/** + * @function compare + * @parent dom + * @download http://jmvcsite.heroku.com/pluginify?plugins[]=jquery/dom/compare/compare.js + * Compares the position of two nodes and returns a bitmask detailing how they are positioned + * relative to each other. You can expect it to return the same results as + * [http://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-compareDocumentPosition | compareDocumentPosition]. + * Parts of this documentation and source come from [http://ejohn.org/blog/comparing-document-position | John Resig]. + * <h2>Demo</h2> + * @demo jquery/dom/compare/compare.html + * @test jquery/dom/compare/qunit.html + * @plugin dom/compare + * @param {HTMLElement} a the first node + * @param {HTMLElement} b the second node + * @return {Number} A bitmap with the following digit values: + * <table class='options'> + * <tr><th>Bits</th><th>Number</th><th>Meaning</th></tr> + * <tr><td>000000</td><td>0</td><td>Elements are identical.</td></tr> + * <tr><td>000001</td><td>1</td><td>The nodes are in different documents (or one is outside of a document).</td></tr> + * <tr><td>000010</td><td>2</td><td>Node B precedes Node A.</td></tr> + * <tr><td>000100</td><td>4</td><td>Node A precedes Node B.</td></tr> + * <tr><td>001000</td><td>8</td><td>Node B contains Node A.</td></tr> + * <tr><td>010000</td><td>16</td><td>Node A contains Node B.</td></tr> + * </table> + */ +jQuery.fn.compare = function(b){ //usually + //b is usually a relatedTarget, but b/c it is we have to avoid a few FF errors + + try{ //FF3 freaks out with XUL + b = b.jquery ? b[0] : b; + }catch(e){ + return null; + } + if (window.HTMLElement) { //make sure we aren't coming from XUL element + var s = HTMLElement.prototype.toString.call(b) + if (s == '[xpconnect wrapped native prototype]' || s == '[object XULElement]') return null; + } + if(this[0].compareDocumentPosition){ + return this[0].compareDocumentPosition(b); + } + if(this[0] == document && b != document) return 8; + var number = (this[0] !== b && this[0].contains(b) && 16) + (this[0] != b && b.contains(this[0]) && 8), + docEl = document.documentElement; + if(this[0].sourceIndex){ + number += (this[0].sourceIndex < b.sourceIndex && 4) + number += (this[0].sourceIndex > b.sourceIndex && 2) + number += (this[0].ownerDocument !== b.ownerDocument || + (this[0] != docEl && this[0].sourceIndex <= 0 ) || + (b != docEl && b.sourceIndex <= 0 )) && 1 + }else{ + var range = document.createRange(), + sourceRange = document.createRange(), + compare; + range.selectNode(this[0]); + sourceRange.selectNode(b); + compare = range.compareBoundaryPoints(Range.START_TO_START, sourceRange); + + } + + return number; +} + +})(jQuery); +(function($){ + var event = $.event, + callHanders = function(){ + + }; + //somehow need to keep track of elements with selectors on them. When element is removed, somehow we need to know that + // + /** + * @add jQuery.event.special + */ + var eventNames = [ + /** + * @attribute dropover + * Called when a drag is first moved over this drop element. + * <p>Drop events are covered in more detail in [jQuery.Drop].</p> + */ + "dropover", + /** + * @attribute dropon + * Called when a drag is dropped on a drop element. + * <p>Drop events are covered in more detail in [jQuery.Drop].</p> + */ + "dropon", + /** + * @attribute dropout + * Called when a drag is moved out of this drop. + * <p>Drop events are covered in more detail in [jQuery.Drop].</p> + */ + "dropout", + /** + * @attribute dropinit + * Called when a drag motion starts and the drop elements are initialized. + * <p>Drop events are covered in more detail in [jQuery.Drop].</p> + */ + "dropinit", + /** + * @attribute dropmove + * Called repeatedly when a drag is moved over a drop. + * <p>Drop events are covered in more detail in [jQuery.Drop].</p> + */ + "dropmove", + /** + * @attribute dropend + * Called when the drag is done for this drop. + * <p>Drop events are covered in more detail in [jQuery.Drop].</p> + */ + "dropend"]; + + + + /** + * @class jQuery.Drop + * @parent specialevents + * @plugin jquery/event/drop + * @download http://jmvcsite.heroku.com/pluginify?plugins[]=jquery/event/drop/drop.js + * @test jquery/event/drag/qunit.html + * + * Provides drop events as a special event to jQuery. + * By binding to a drop event, the your callback functions will be + * called during the corresponding phase of drag. + * <h2>Drop Events</h2> + * All drop events are called with the native event, an instance of drop, and the drag. Here are the available drop + * events: + * <ul> + * <li><code>dropinit</code> - the drag motion is started, drop positions are calculated.</li> + * <li><code>dropover</code> - a drag moves over a drop element, called once as the drop is dragged over the element.</li> + * <li><code>dropout</code> - a drag moves out of the drop element.</li> + * <li><code>dropmove</code> - a drag is moved over a drop element, called repeatedly as the element is moved.</li> + * <li><code>dropon</code> - a drag is released over a drop element.</li> + * <li><code>dropend</code> - the drag motion has completed.</li> + * </ul> + * <h2>Examples</h2> + * Here's how to listen for when a drag moves over a drop: + * @codestart + * $('.drop').live("dropover", function(ev, drop, drag){ + * $(this).addClass("drop-over") + * }) + * @codeend + * A bit more complex example: + * @demo jquery/event/drop/drop.html 1000 + * @constructor + * The constructor is never called directly. + */ + $.Drop = function(callbacks, element){ + jQuery.extend(this,callbacks); + this.element = element; + } + $.each(eventNames, function(){ + event.special[this] = { + add: function( handleObj ) { + //add this element to the compiles list + var el = $(this), current = (el.data("dropEventCount") || 0); + el.data("dropEventCount", current+1 ) + if(current==0){ + $.Drop.addElement(this); + } + }, + remove: function() { + var el = $(this), current = (el.data("dropEventCount") || 0); + el.data("dropEventCount", current-1 ) + if(current<=1){ + $.Drop.removeElement(this); + } + } + } + }) + $.extend($.Drop,{ + lowerName: "drop", + _elements: [], //elements that are listening for drops + _responders: [], //potential drop points + last_active: [], + endName: "dropon", + addElement: function( el ) { + //check other elements + for(var i =0; i < this._elements.length ; i++ ){ + if(el ==this._elements[i]) return; + } + this._elements.push(el); + }, + removeElement: function( el ) { + for(var i =0; i < this._elements.length ; i++ ){ + if(el == this._elements[i]){ + this._elements.splice(i,1) + return; + } + } + }, + /** + * @hide + * For a list of affected drops, sorts them by which is deepest in the DOM first. + */ + sortByDeepestChild: function( a, b ) { + var compare = a.element.compare(b.element); + if(compare & 16 || compare & 4) return 1; + if(compare & 8 || compare & 2) return -1; + return 0; + }, + /** + * @hide + * Tests if a drop is within the point. + */ + isAffected: function( point, moveable, responder ) { + return ((responder.element != moveable.element) && (responder.element.within(point[0], point[1], responder).length == 1)); + }, + /** + * @hide + * Calls dropout and sets last active to null + * @param {Object} drop + * @param {Object} drag + * @param {Object} event + */ + deactivate: function( responder, mover, event ) { + mover.out(event, responder) + responder.callHandlers(this.lowerName+'out',responder.element[0], event, mover) + }, + /** + * @hide + * Calls dropover + * @param {Object} drop + * @param {Object} drag + * @param {Object} event + */ + activate: function( responder, mover, event ) { //this is where we should call over + mover.over(event, responder) + //this.last_active = responder; + responder.callHandlers(this.lowerName+'over',responder.element[0], event, mover); + }, + move: function( responder, mover, event ) { + responder.callHandlers(this.lowerName+'move',responder.element[0], event, mover) + }, + /** + * Gets all elements that are droppable, adds them + */ + compile: function( event, drag ) { + var el, drops, selector, sels; + this.last_active = []; + for(var i=0; i < this._elements.length; i++){ //for each element + el = this._elements[i] + var drops = $.event.findBySelector(el, eventNames) + + for(selector in drops){ //find the selectors + sels = selector ? jQuery(selector, el) : [el]; + for(var e= 0; e < sels.length; e++){ //for each found element, create a drop point + jQuery.removeData(sels[e],"offset"); + this.add(sels[e], new this(drops[selector]), event, drag); + } + } + } + + }, + add: function( element, callbacks, event, drag ) { + element = jQuery(element); + var responder = new $.Drop(callbacks, element); + responder.callHandlers(this.lowerName+'init', element[0], event, drag) + if(!responder._canceled){ + this._responders.push(responder); + } + }, + show: function( point, moveable, event ) { + var element = moveable.element; + if(!this._responders.length) return; + + var respondable, + affected = [], + propagate = true, + i,j, la, toBeActivated, aff, + oldLastActive = this.last_active; + + for(var d =0 ; d < this._responders.length; d++ ){ + + if(this.isAffected(point, moveable, this._responders[d])){ + affected.push(this._responders[d]); + } + + } + + affected.sort(this.sortByDeepestChild); //we should only trigger on lowest children + event.stopRespondPropagate = function(){ + propagate = false; + } + //deactivate everything in last_active that isn't active + toBeActivated = affected.slice(); + this.last_active = affected; + for (j = 0; j < oldLastActive.length; j++) { + la = oldLastActive[j] + i = 0; + while((aff = toBeActivated[i])){ + if(la == aff){ + toBeActivated.splice(i,1);break; + }else{ + i++; + } + } + if(!aff){ + this.deactivate(la, moveable, event); + } + if(!propagate) return; + } + for(var i =0; i < toBeActivated.length; i++){ + this.activate(toBeActivated[i], moveable, event); + if(!propagate) return; + } + //activate everything in affected that isn't in last_active + + for (i = 0; i < affected.length; i++) { + this.move(affected[i], moveable, event); + + if(!propagate) return; + } + }, + end: function( event, moveable ) { + var responder, la; + for(var r =0; r<this._responders.length; r++){ + this._responders[r].callHandlers(this.lowerName+'end', null, event, moveable); + } + //go through the actives ... if you are over one, call dropped on it + for(var i = 0; i < this.last_active.length; i++){ + la = this.last_active[i] + if( this.isAffected(event.vector(), moveable, la) && la[this.endName]){ + la.callHandlers(this.endName, null, event, moveable); + } + } + + + this.clear(); + }, + /** + * Called after dragging has stopped. + * @hide + */ + clear: function() { + + this._responders = []; + } + }) + $.Drag.responder = $.Drop; + + $.extend($.Drop.prototype,{ + callHandlers: function( method, el, ev, drag ) { + var length = this[method] ? this[method].length : 0 + for(var i =0; i < length; i++){ + this[method][i].call(el || this.element[0], ev, this, drag) + } + }, + /** + * Caches positions of draggable elements. This should be called in dropinit. For example: + * @codestart + * dropinit: function( el, ev, drop ) { drop.cache_position() } + * @codeend + */ + cache: function( value ) { + this._cache = value != null ? value : true; + }, + /** + * Prevents this drop from being dropped on. + */ + cancel: function() { + this._canceled = true; + } + } ) +})(jQuery) \ No newline at end of file diff --git a/browserid/static/funcunit/syn/resources/jquery.js b/browserid/static/funcunit/syn/resources/jquery.js new file mode 100644 index 0000000000000000000000000000000000000000..a4f114586ce4468aae29f8f241f145556377307f --- /dev/null +++ b/browserid/static/funcunit/syn/resources/jquery.js @@ -0,0 +1,7179 @@ +/*! + * jQuery JavaScript Library v1.4.4 + * http://jquery.com/ + * + * Copyright 2010, John Resig + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * Copyright 2010, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * + * Date: Thu Nov 11 19:04:53 2010 -0500 + */ +(function( window, undefined ) { + +// Use the correct document accordingly with window argument (sandbox) +var document = window.document; +var jQuery = (function() { + +// Define a local copy of jQuery +var jQuery = function( selector, context ) { + // The jQuery object is actually just the init constructor 'enhanced' + return new jQuery.fn.init( selector, context ); + }, + + // Map over jQuery in case of overwrite + _jQuery = window.jQuery, + + // Map over the $ in case of overwrite + _$ = window.$, + + // A central reference to the root jQuery(document) + rootjQuery, + + // A simple way to check for HTML strings or ID strings + // (both of which we optimize for) + quickExpr = /^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]+)$)/, + + // Is it a simple selector + isSimple = /^.[^:#\[\.,]*$/, + + // Check if a string has a non-whitespace character in it + rnotwhite = /\S/, + rwhite = /\s/, + + // Used for trimming whitespace + trimLeft = /^\s+/, + trimRight = /\s+$/, + + // Check for non-word characters + rnonword = /\W/, + + // Check for digits + rdigit = /\d/, + + // Match a standalone tag + rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/, + + // JSON RegExp + rvalidchars = /^[\],:{}\s]*$/, + rvalidescape = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, + rvalidtokens = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, + rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g, + + // Useragent RegExp + rwebkit = /(webkit)[ \/]([\w.]+)/, + ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/, + rmsie = /(msie) ([\w.]+)/, + rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/, + + // Keep a UserAgent string for use with jQuery.browser + userAgent = navigator.userAgent, + + // For matching the engine and version of the browser + browserMatch, + + // Has the ready events already been bound? + readyBound = false, + + // The functions to execute on DOM ready + readyList = [], + + // The ready event handler + DOMContentLoaded, + + // Save a reference to some core methods + toString = Object.prototype.toString, + hasOwn = Object.prototype.hasOwnProperty, + push = Array.prototype.push, + slice = Array.prototype.slice, + trim = String.prototype.trim, + indexOf = Array.prototype.indexOf, + + // [[Class]] -> type pairs + class2type = {}; + +jQuery.fn = jQuery.prototype = { + init: function( selector, context ) { + var match, elem, ret, doc; + + // Handle $(""), $(null), or $(undefined) + if ( !selector ) { + return this; + } + + // Handle $(DOMElement) + if ( selector.nodeType ) { + this.context = this[0] = selector; + this.length = 1; + return this; + } + + // The body element only exists once, optimize finding it + if ( selector === "body" && !context && document.body ) { + this.context = document; + this[0] = document.body; + this.selector = "body"; + this.length = 1; + return this; + } + + // Handle HTML strings + if ( typeof selector === "string" ) { + // Are we dealing with HTML string or an ID? + match = quickExpr.exec( selector ); + + // Verify a match, and that no context was specified for #id + if ( match && (match[1] || !context) ) { + + // HANDLE: $(html) -> $(array) + if ( match[1] ) { + doc = (context ? context.ownerDocument || context : document); + + // If a single string is passed in and it's a single tag + // just do a createElement and skip the rest + ret = rsingleTag.exec( selector ); + + if ( ret ) { + if ( jQuery.isPlainObject( context ) ) { + selector = [ document.createElement( ret[1] ) ]; + jQuery.fn.attr.call( selector, context, true ); + + } else { + selector = [ doc.createElement( ret[1] ) ]; + } + + } else { + ret = jQuery.buildFragment( [ match[1] ], [ doc ] ); + selector = (ret.cacheable ? ret.fragment.cloneNode(true) : ret.fragment).childNodes; + } + + return jQuery.merge( this, selector ); + + // HANDLE: $("#id") + } else { + elem = document.getElementById( match[2] ); + + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + if ( elem && elem.parentNode ) { + // Handle the case where IE and Opera return items + // by name instead of ID + if ( elem.id !== match[2] ) { + return rootjQuery.find( selector ); + } + + // Otherwise, we inject the element directly into the jQuery object + this.length = 1; + this[0] = elem; + } + + this.context = document; + this.selector = selector; + return this; + } + + // HANDLE: $("TAG") + } else if ( !context && !rnonword.test( selector ) ) { + this.selector = selector; + this.context = document; + selector = document.getElementsByTagName( selector ); + return jQuery.merge( this, selector ); + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return (context || rootjQuery).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return jQuery( context ).find( selector ); + } + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( jQuery.isFunction( selector ) ) { + return rootjQuery.ready( selector ); + } + + if (selector.selector !== undefined) { + this.selector = selector.selector; + this.context = selector.context; + } + + return jQuery.makeArray( selector, this ); + }, + + // Start with an empty selector + selector: "", + + // The current version of jQuery being used + jquery: "1.4.4", + + // The default length of a jQuery object is 0 + length: 0, + + // The number of elements contained in the matched element set + size: function() { + return this.length; + }, + + toArray: function() { + return slice.call( this, 0 ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + return num == null ? + + // Return a 'clean' array + this.toArray() : + + // Return just the object + ( num < 0 ? this.slice(num)[ 0 ] : this[ num ] ); + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems, name, selector ) { + // Build a new jQuery matched element set + var ret = jQuery(); + + if ( jQuery.isArray( elems ) ) { + push.apply( ret, elems ); + + } else { + jQuery.merge( ret, elems ); + } + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + ret.context = this.context; + + if ( name === "find" ) { + ret.selector = this.selector + (this.selector ? " " : "") + selector; + } else if ( name ) { + ret.selector = this.selector + "." + name + "(" + selector + ")"; + } + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + // (You can seed the arguments with an array of args, but this is + // only used internally.) + each: function( callback, args ) { + return jQuery.each( this, callback, args ); + }, + + ready: function( fn ) { + // Attach the listeners + jQuery.bindReady(); + + // If the DOM is already ready + if ( jQuery.isReady ) { + // Execute the function immediately + fn.call( document, jQuery ); + + // Otherwise, remember the function for later + } else if ( readyList ) { + // Add the function to the wait list + readyList.push( fn ); + } + + return this; + }, + + eq: function( i ) { + return i === -1 ? + this.slice( i ) : + this.slice( i, +i + 1 ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ), + "slice", slice.call(arguments).join(",") ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map(this, function( elem, i ) { + return callback.call( elem, i, elem ); + })); + }, + + end: function() { + return this.prevObject || jQuery(null); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: push, + sort: [].sort, + splice: [].splice +}; + +// Give the init function the jQuery prototype for later instantiation +jQuery.fn.init.prototype = jQuery.fn; + +jQuery.extend = jQuery.fn.extend = function() { + var options, name, src, copy, copyIsArray, clone, + target = arguments[0] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + target = arguments[1] || {}; + // skip the boolean and the target + i = 2; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !jQuery.isFunction(target) ) { + target = {}; + } + + // extend jQuery itself if only one argument is passed + if ( length === i ) { + target = this; + --i; + } + + for ( ; i < length; i++ ) { + // Only deal with non-null/undefined values + if ( (options = arguments[ i ]) != null ) { + // Extend the base object + for ( name in options ) { + src = target[ name ]; + copy = options[ name ]; + + // Prevent never-ending loop + if ( target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { + if ( copyIsArray ) { + copyIsArray = false; + clone = src && jQuery.isArray(src) ? src : []; + + } else { + clone = src && jQuery.isPlainObject(src) ? src : {}; + } + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend({ + noConflict: function( deep ) { + window.$ = _$; + + if ( deep ) { + window.jQuery = _jQuery; + } + + return jQuery; + }, + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Handle when the DOM is ready + ready: function( wait ) { + // A third-party is pushing the ready event forwards + if ( wait === true ) { + jQuery.readyWait--; + } + + // Make sure that the DOM is not already loaded + if ( !jQuery.readyWait || (wait !== true && !jQuery.isReady) ) { + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if ( !document.body ) { + return setTimeout( jQuery.ready, 1 ); + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + if ( readyList ) { + // Execute all of them + var fn, + i = 0, + ready = readyList; + + // Reset the list of functions + readyList = null; + + while ( (fn = ready[ i++ ]) ) { + fn.call( document, jQuery ); + } + + // Trigger any bound ready events + if ( jQuery.fn.trigger ) { + jQuery( document ).trigger( "ready" ).unbind( "ready" ); + } + } + } + }, + + bindReady: function() { + if ( readyBound ) { + return; + } + + readyBound = true; + + // Catch cases where $(document).ready() is called after the + // browser event has already occurred. + if ( document.readyState === "complete" ) { + // Handle it asynchronously to allow scripts the opportunity to delay ready + return setTimeout( jQuery.ready, 1 ); + } + + // Mozilla, Opera and webkit nightlies currently support this event + if ( document.addEventListener ) { + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", jQuery.ready, false ); + + // If IE event model is used + } else if ( document.attachEvent ) { + // ensure firing before onload, + // maybe late but safe also for iframes + document.attachEvent("onreadystatechange", DOMContentLoaded); + + // A fallback to window.onload, that will always work + window.attachEvent( "onload", jQuery.ready ); + + // If IE and not a frame + // continually check to see if the document is ready + var toplevel = false; + + try { + toplevel = window.frameElement == null; + } catch(e) {} + + if ( document.documentElement.doScroll && toplevel ) { + doScrollCheck(); + } + } + }, + + // See test/unit/core.js for details concerning isFunction. + // Since version 1.3, DOM methods and functions like alert + // aren't supported. They return false on IE (#2968). + isFunction: function( obj ) { + return jQuery.type(obj) === "function"; + }, + + isArray: Array.isArray || function( obj ) { + return jQuery.type(obj) === "array"; + }, + + // A crude way of determining if an object is a window + isWindow: function( obj ) { + return obj && typeof obj === "object" && "setInterval" in obj; + }, + + isNaN: function( obj ) { + return obj == null || !rdigit.test( obj ) || isNaN( obj ); + }, + + type: function( obj ) { + return obj == null ? + String( obj ) : + class2type[ toString.call(obj) ] || "object"; + }, + + isPlainObject: function( obj ) { + // Must be an Object. + // Because of IE, we also have to check the presence of the constructor property. + // Make sure that DOM nodes and window objects don't pass through, as well + if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) { + return false; + } + + // Not own constructor property must be Object + if ( obj.constructor && + !hasOwn.call(obj, "constructor") && + !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) { + return false; + } + + // Own properties are enumerated firstly, so to speed up, + // if last one is own, then all properties are own. + + var key; + for ( key in obj ) {} + + return key === undefined || hasOwn.call( obj, key ); + }, + + isEmptyObject: function( obj ) { + for ( var name in obj ) { + return false; + } + return true; + }, + + error: function( msg ) { + throw msg; + }, + + parseJSON: function( data ) { + if ( typeof data !== "string" || !data ) { + return null; + } + + // Make sure leading/trailing whitespace is removed (IE can't handle it) + data = jQuery.trim( data ); + + // Make sure the incoming data is actual JSON + // Logic borrowed from http://json.org/json2.js + if ( rvalidchars.test(data.replace(rvalidescape, "@") + .replace(rvalidtokens, "]") + .replace(rvalidbraces, "")) ) { + + // Try to use the native JSON parser first + return window.JSON && window.JSON.parse ? + window.JSON.parse( data ) : + (new Function("return " + data))(); + + } else { + jQuery.error( "Invalid JSON: " + data ); + } + }, + + noop: function() {}, + + // Evalulates a script in a global context + globalEval: function( data ) { + if ( data && rnotwhite.test(data) ) { + // Inspired by code by Andrea Giammarchi + // http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html + var head = document.getElementsByTagName("head")[0] || document.documentElement, + script = document.createElement("script"); + + script.type = "text/javascript"; + + if ( jQuery.support.scriptEval ) { + script.appendChild( document.createTextNode( data ) ); + } else { + script.text = data; + } + + // Use insertBefore instead of appendChild to circumvent an IE6 bug. + // This arises when a base node is used (#2709). + head.insertBefore( script, head.firstChild ); + head.removeChild( script ); + } + }, + + nodeName: function( elem, name ) { + return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase(); + }, + + // args is for internal usage only + each: function( object, callback, args ) { + var name, i = 0, + length = object.length, + isObj = length === undefined || jQuery.isFunction(object); + + if ( args ) { + if ( isObj ) { + for ( name in object ) { + if ( callback.apply( object[ name ], args ) === false ) { + break; + } + } + } else { + for ( ; i < length; ) { + if ( callback.apply( object[ i++ ], args ) === false ) { + break; + } + } + } + + // A special, fast, case for the most common use of each + } else { + if ( isObj ) { + for ( name in object ) { + if ( callback.call( object[ name ], name, object[ name ] ) === false ) { + break; + } + } + } else { + for ( var value = object[0]; + i < length && callback.call( value, i, value ) !== false; value = object[++i] ) {} + } + } + + return object; + }, + + // Use native String.trim function wherever possible + trim: trim ? + function( text ) { + return text == null ? + "" : + trim.call( text ); + } : + + // Otherwise use our own trimming functionality + function( text ) { + return text == null ? + "" : + text.toString().replace( trimLeft, "" ).replace( trimRight, "" ); + }, + + // results is for internal usage only + makeArray: function( array, results ) { + var ret = results || []; + + if ( array != null ) { + // The window, strings (and functions) also have 'length' + // The extra typeof function check is to prevent crashes + // in Safari 2 (See: #3039) + // Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930 + var type = jQuery.type(array); + + if ( array.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( array ) ) { + push.call( ret, array ); + } else { + jQuery.merge( ret, array ); + } + } + + return ret; + }, + + inArray: function( elem, array ) { + if ( array.indexOf ) { + return array.indexOf( elem ); + } + + for ( var i = 0, length = array.length; i < length; i++ ) { + if ( array[ i ] === elem ) { + return i; + } + } + + return -1; + }, + + merge: function( first, second ) { + var i = first.length, + j = 0; + + if ( typeof second.length === "number" ) { + for ( var l = second.length; j < l; j++ ) { + first[ i++ ] = second[ j ]; + } + + } else { + while ( second[j] !== undefined ) { + first[ i++ ] = second[ j++ ]; + } + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, inv ) { + var ret = [], retVal; + inv = !!inv; + + // Go through the array, only saving the items + // that pass the validator function + for ( var i = 0, length = elems.length; i < length; i++ ) { + retVal = !!callback( elems[ i ], i ); + if ( inv !== retVal ) { + ret.push( elems[ i ] ); + } + } + + return ret; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var ret = [], value; + + // Go through the array, translating each of the items to their + // new value (or values). + for ( var i = 0, length = elems.length; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret[ ret.length ] = value; + } + } + + return ret.concat.apply( [], ret ); + }, + + // A global GUID counter for objects + guid: 1, + + proxy: function( fn, proxy, thisObject ) { + if ( arguments.length === 2 ) { + if ( typeof proxy === "string" ) { + thisObject = fn; + fn = thisObject[ proxy ]; + proxy = undefined; + + } else if ( proxy && !jQuery.isFunction( proxy ) ) { + thisObject = proxy; + proxy = undefined; + } + } + + if ( !proxy && fn ) { + proxy = function() { + return fn.apply( thisObject || this, arguments ); + }; + } + + // Set the guid of unique handler to the same of original handler, so it can be removed + if ( fn ) { + proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++; + } + + // So proxy can be declared as an argument + return proxy; + }, + + // Mutifunctional method to get and set values to a collection + // The value/s can be optionally by executed if its a function + access: function( elems, key, value, exec, fn, pass ) { + var length = elems.length; + + // Setting many attributes + if ( typeof key === "object" ) { + for ( var k in key ) { + jQuery.access( elems, k, key[k], exec, fn, value ); + } + return elems; + } + + // Setting one attribute + if ( value !== undefined ) { + // Optionally, function values get executed if exec is true + exec = !pass && exec && jQuery.isFunction(value); + + for ( var i = 0; i < length; i++ ) { + fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass ); + } + + return elems; + } + + // Getting an attribute + return length ? fn( elems[0], key ) : undefined; + }, + + now: function() { + return (new Date()).getTime(); + }, + + // Use of jQuery.browser is frowned upon. + // More details: http://docs.jquery.com/Utilities/jQuery.browser + uaMatch: function( ua ) { + ua = ua.toLowerCase(); + + var match = rwebkit.exec( ua ) || + ropera.exec( ua ) || + rmsie.exec( ua ) || + ua.indexOf("compatible") < 0 && rmozilla.exec( ua ) || + []; + + return { browser: match[1] || "", version: match[2] || "0" }; + }, + + browser: {} +}); + +// Populate the class2type map +jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +}); + +browserMatch = jQuery.uaMatch( userAgent ); +if ( browserMatch.browser ) { + jQuery.browser[ browserMatch.browser ] = true; + jQuery.browser.version = browserMatch.version; +} + +// Deprecated, use jQuery.browser.webkit instead +if ( jQuery.browser.webkit ) { + jQuery.browser.safari = true; +} + +if ( indexOf ) { + jQuery.inArray = function( elem, array ) { + return indexOf.call( array, elem ); + }; +} + +// Verify that \s matches non-breaking spaces +// (IE fails on this test) +if ( !rwhite.test( "\xA0" ) ) { + trimLeft = /^[\s\xA0]+/; + trimRight = /[\s\xA0]+$/; +} + +// All jQuery objects should point back to these +rootjQuery = jQuery(document); + +// Cleanup functions for the document ready method +if ( document.addEventListener ) { + DOMContentLoaded = function() { + document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false ); + jQuery.ready(); + }; + +} else if ( document.attachEvent ) { + DOMContentLoaded = function() { + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if ( document.readyState === "complete" ) { + document.detachEvent( "onreadystatechange", DOMContentLoaded ); + jQuery.ready(); + } + }; +} + +// The DOM ready check for Internet Explorer +function doScrollCheck() { + if ( jQuery.isReady ) { + return; + } + + try { + // If IE is used, use the trick by Diego Perini + // http://javascript.nwbox.com/IEContentLoaded/ + document.documentElement.doScroll("left"); + } catch(e) { + setTimeout( doScrollCheck, 1 ); + return; + } + + // and execute any waiting functions + jQuery.ready(); +} + +// Expose jQuery to the global object +return (window.jQuery = window.$ = jQuery); + +})(); + + +(function() { + + jQuery.support = {}; + + var root = document.documentElement, + script = document.createElement("script"), + div = document.createElement("div"), + id = "script" + jQuery.now(); + + div.style.display = "none"; + div.innerHTML = " <link/><table></table><a href='/a' style='color:red;float:left;opacity:.55;'>a</a><input type='checkbox'/>"; + + var all = div.getElementsByTagName("*"), + a = div.getElementsByTagName("a")[0], + select = document.createElement("select"), + opt = select.appendChild( document.createElement("option") ); + + // Can't get basic test support + if ( !all || !all.length || !a ) { + return; + } + + jQuery.support = { + // IE strips leading whitespace when .innerHTML is used + leadingWhitespace: div.firstChild.nodeType === 3, + + // Make sure that tbody elements aren't automatically inserted + // IE will insert them into empty tables + tbody: !div.getElementsByTagName("tbody").length, + + // Make sure that link elements get serialized correctly by innerHTML + // This requires a wrapper element in IE + htmlSerialize: !!div.getElementsByTagName("link").length, + + // Get the style information from getAttribute + // (IE uses .cssText insted) + style: /red/.test( a.getAttribute("style") ), + + // Make sure that URLs aren't manipulated + // (IE normalizes it by default) + hrefNormalized: a.getAttribute("href") === "/a", + + // Make sure that element opacity exists + // (IE uses filter instead) + // Use a regex to work around a WebKit issue. See #5145 + opacity: /^0.55$/.test( a.style.opacity ), + + // Verify style float existence + // (IE uses styleFloat instead of cssFloat) + cssFloat: !!a.style.cssFloat, + + // Make sure that if no value is specified for a checkbox + // that it defaults to "on". + // (WebKit defaults to "" instead) + checkOn: div.getElementsByTagName("input")[0].value === "on", + + // Make sure that a selected-by-default option has a working selected property. + // (WebKit defaults to false instead of true, IE too, if it's in an optgroup) + optSelected: opt.selected, + + // Will be defined later + deleteExpando: true, + optDisabled: false, + checkClone: false, + scriptEval: false, + noCloneEvent: true, + boxModel: null, + inlineBlockNeedsLayout: false, + shrinkWrapBlocks: false, + reliableHiddenOffsets: true + }; + + // Make sure that the options inside disabled selects aren't marked as disabled + // (WebKit marks them as diabled) + select.disabled = true; + jQuery.support.optDisabled = !opt.disabled; + + script.type = "text/javascript"; + try { + script.appendChild( document.createTextNode( "window." + id + "=1;" ) ); + } catch(e) {} + + root.insertBefore( script, root.firstChild ); + + // Make sure that the execution of code works by injecting a script + // tag with appendChild/createTextNode + // (IE doesn't support this, fails, and uses .text instead) + if ( window[ id ] ) { + jQuery.support.scriptEval = true; + delete window[ id ]; + } + + // Test to see if it's possible to delete an expando from an element + // Fails in Internet Explorer + try { + delete script.test; + + } catch(e) { + jQuery.support.deleteExpando = false; + } + + root.removeChild( script ); + + if ( div.attachEvent && div.fireEvent ) { + div.attachEvent("onclick", function click() { + // Cloning a node shouldn't copy over any + // bound event handlers (IE does this) + jQuery.support.noCloneEvent = false; + div.detachEvent("onclick", click); + }); + div.cloneNode(true).fireEvent("onclick"); + } + + div = document.createElement("div"); + div.innerHTML = "<input type='radio' name='radiotest' checked='checked'/>"; + + var fragment = document.createDocumentFragment(); + fragment.appendChild( div.firstChild ); + + // WebKit doesn't clone checked state correctly in fragments + jQuery.support.checkClone = fragment.cloneNode(true).cloneNode(true).lastChild.checked; + + // Figure out if the W3C box model works as expected + // document.body must exist before we can do this + jQuery(function() { + var div = document.createElement("div"); + div.style.width = div.style.paddingLeft = "1px"; + + document.body.appendChild( div ); + jQuery.boxModel = jQuery.support.boxModel = div.offsetWidth === 2; + + if ( "zoom" in div.style ) { + // Check if natively block-level elements act like inline-block + // elements when setting their display to 'inline' and giving + // them layout + // (IE < 8 does this) + div.style.display = "inline"; + div.style.zoom = 1; + jQuery.support.inlineBlockNeedsLayout = div.offsetWidth === 2; + + // Check if elements with layout shrink-wrap their children + // (IE 6 does this) + div.style.display = ""; + div.innerHTML = "<div style='width:4px;'></div>"; + jQuery.support.shrinkWrapBlocks = div.offsetWidth !== 2; + } + + div.innerHTML = "<table><tr><td style='padding:0;display:none'></td><td>t</td></tr></table>"; + var tds = div.getElementsByTagName("td"); + + // Check if table cells still have offsetWidth/Height when they are set + // to display:none and there are still other visible table cells in a + // table row; if so, offsetWidth/Height are not reliable for use when + // determining if an element has been hidden directly using + // display:none (it is still safe to use offsets if a parent element is + // hidden; don safety goggles and see bug #4512 for more information). + // (only IE 8 fails this test) + jQuery.support.reliableHiddenOffsets = tds[0].offsetHeight === 0; + + tds[0].style.display = ""; + tds[1].style.display = "none"; + + // Check if empty table cells still have offsetWidth/Height + // (IE < 8 fail this test) + jQuery.support.reliableHiddenOffsets = jQuery.support.reliableHiddenOffsets && tds[0].offsetHeight === 0; + div.innerHTML = ""; + + document.body.removeChild( div ).style.display = "none"; + div = tds = null; + }); + + // Technique from Juriy Zaytsev + // http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/ + var eventSupported = function( eventName ) { + var el = document.createElement("div"); + eventName = "on" + eventName; + + var isSupported = (eventName in el); + if ( !isSupported ) { + el.setAttribute(eventName, "return;"); + isSupported = typeof el[eventName] === "function"; + } + el = null; + + return isSupported; + }; + + jQuery.support.submitBubbles = eventSupported("submit"); + jQuery.support.changeBubbles = eventSupported("change"); + + // release memory in IE + root = script = div = all = a = null; +})(); + + + +var windowData = {}, + rbrace = /^(?:\{.*\}|\[.*\])$/; + +jQuery.extend({ + cache: {}, + + // Please use with caution + uuid: 0, + + // Unique for each copy of jQuery on the page + expando: "jQuery" + jQuery.now(), + + // The following elements throw uncatchable exceptions if you + // attempt to add expando properties to them. + noData: { + "embed": true, + // Ban all objects except for Flash (which handle expandos) + "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000", + "applet": true + }, + + data: function( elem, name, data ) { + if ( !jQuery.acceptData( elem ) ) { + return; + } + + elem = elem == window ? + windowData : + elem; + + var isNode = elem.nodeType, + id = isNode ? elem[ jQuery.expando ] : null, + cache = jQuery.cache, thisCache; + + if ( isNode && !id && typeof name === "string" && data === undefined ) { + return; + } + + // Get the data from the object directly + if ( !isNode ) { + cache = elem; + + // Compute a unique ID for the element + } else if ( !id ) { + elem[ jQuery.expando ] = id = ++jQuery.uuid; + } + + // Avoid generating a new cache unless none exists and we + // want to manipulate it. + if ( typeof name === "object" ) { + if ( isNode ) { + cache[ id ] = jQuery.extend(cache[ id ], name); + + } else { + jQuery.extend( cache, name ); + } + + } else if ( isNode && !cache[ id ] ) { + cache[ id ] = {}; + } + + thisCache = isNode ? cache[ id ] : cache; + + // Prevent overriding the named cache with undefined values + if ( data !== undefined ) { + thisCache[ name ] = data; + } + + return typeof name === "string" ? thisCache[ name ] : thisCache; + }, + + removeData: function( elem, name ) { + if ( !jQuery.acceptData( elem ) ) { + return; + } + + elem = elem == window ? + windowData : + elem; + + var isNode = elem.nodeType, + id = isNode ? elem[ jQuery.expando ] : elem, + cache = jQuery.cache, + thisCache = isNode ? cache[ id ] : id; + + // If we want to remove a specific section of the element's data + if ( name ) { + if ( thisCache ) { + // Remove the section of cache data + delete thisCache[ name ]; + + // If we've removed all the data, remove the element's cache + if ( isNode && jQuery.isEmptyObject(thisCache) ) { + jQuery.removeData( elem ); + } + } + + // Otherwise, we want to remove all of the element's data + } else { + if ( isNode && jQuery.support.deleteExpando ) { + delete elem[ jQuery.expando ]; + + } else if ( elem.removeAttribute ) { + elem.removeAttribute( jQuery.expando ); + + // Completely remove the data cache + } else if ( isNode ) { + delete cache[ id ]; + + // Remove all fields from the object + } else { + for ( var n in elem ) { + delete elem[ n ]; + } + } + } + }, + + // A method for determining if a DOM node can handle the data expando + acceptData: function( elem ) { + if ( elem.nodeName ) { + var match = jQuery.noData[ elem.nodeName.toLowerCase() ]; + + if ( match ) { + return !(match === true || elem.getAttribute("classid") !== match); + } + } + + return true; + } +}); + +jQuery.fn.extend({ + data: function( key, value ) { + var data = null; + + if ( typeof key === "undefined" ) { + if ( this.length ) { + var attr = this[0].attributes, name; + data = jQuery.data( this[0] ); + + for ( var i = 0, l = attr.length; i < l; i++ ) { + name = attr[i].name; + + if ( name.indexOf( "data-" ) === 0 ) { + name = name.substr( 5 ); + dataAttr( this[0], name, data[ name ] ); + } + } + } + + return data; + + } else if ( typeof key === "object" ) { + return this.each(function() { + jQuery.data( this, key ); + }); + } + + var parts = key.split("."); + parts[1] = parts[1] ? "." + parts[1] : ""; + + if ( value === undefined ) { + data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]); + + // Try to fetch any internally stored data first + if ( data === undefined && this.length ) { + data = jQuery.data( this[0], key ); + data = dataAttr( this[0], key, data ); + } + + return data === undefined && parts[1] ? + this.data( parts[0] ) : + data; + + } else { + return this.each(function() { + var $this = jQuery( this ), + args = [ parts[0], value ]; + + $this.triggerHandler( "setData" + parts[1] + "!", args ); + jQuery.data( this, key, value ); + $this.triggerHandler( "changeData" + parts[1] + "!", args ); + }); + } + }, + + removeData: function( key ) { + return this.each(function() { + jQuery.removeData( this, key ); + }); + } +}); + +function dataAttr( elem, key, data ) { + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + data = elem.getAttribute( "data-" + key ); + + if ( typeof data === "string" ) { + try { + data = data === "true" ? true : + data === "false" ? false : + data === "null" ? null : + !jQuery.isNaN( data ) ? parseFloat( data ) : + rbrace.test( data ) ? jQuery.parseJSON( data ) : + data; + } catch( e ) {} + + // Make sure we set the data so it isn't changed later + jQuery.data( elem, key, data ); + + } else { + data = undefined; + } + } + + return data; +} + + + + +jQuery.extend({ + queue: function( elem, type, data ) { + if ( !elem ) { + return; + } + + type = (type || "fx") + "queue"; + var q = jQuery.data( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( !data ) { + return q || []; + } + + if ( !q || jQuery.isArray(data) ) { + q = jQuery.data( elem, type, jQuery.makeArray(data) ); + + } else { + q.push( data ); + } + + return q; + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + fn = queue.shift(); + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + } + + if ( fn ) { + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift("inprogress"); + } + + fn.call(elem, function() { + jQuery.dequeue(elem, type); + }); + } + } +}); + +jQuery.fn.extend({ + queue: function( type, data ) { + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + } + + if ( data === undefined ) { + return jQuery.queue( this[0], type ); + } + return this.each(function( i ) { + var queue = jQuery.queue( this, type, data ); + + if ( type === "fx" && queue[0] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + }); + }, + dequeue: function( type ) { + return this.each(function() { + jQuery.dequeue( this, type ); + }); + }, + + // Based off of the plugin by Clint Helfers, with permission. + // http://blindsignals.com/index.php/2009/07/jquery-delay/ + delay: function( time, type ) { + time = jQuery.fx ? jQuery.fx.speeds[time] || time : time; + type = type || "fx"; + + return this.queue( type, function() { + var elem = this; + setTimeout(function() { + jQuery.dequeue( elem, type ); + }, time ); + }); + }, + + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + } +}); + + + + +var rclass = /[\n\t]/g, + rspaces = /\s+/, + rreturn = /\r/g, + rspecialurl = /^(?:href|src|style)$/, + rtype = /^(?:button|input)$/i, + rfocusable = /^(?:button|input|object|select|textarea)$/i, + rclickable = /^a(?:rea)?$/i, + rradiocheck = /^(?:radio|checkbox)$/i; + +jQuery.props = { + "for": "htmlFor", + "class": "className", + readonly: "readOnly", + maxlength: "maxLength", + cellspacing: "cellSpacing", + rowspan: "rowSpan", + colspan: "colSpan", + tabindex: "tabIndex", + usemap: "useMap", + frameborder: "frameBorder" +}; + +jQuery.fn.extend({ + attr: function( name, value ) { + return jQuery.access( this, name, value, true, jQuery.attr ); + }, + + removeAttr: function( name, fn ) { + return this.each(function(){ + jQuery.attr( this, name, "" ); + if ( this.nodeType === 1 ) { + this.removeAttribute( name ); + } + }); + }, + + addClass: function( value ) { + if ( jQuery.isFunction(value) ) { + return this.each(function(i) { + var self = jQuery(this); + self.addClass( value.call(this, i, self.attr("class")) ); + }); + } + + if ( value && typeof value === "string" ) { + var classNames = (value || "").split( rspaces ); + + for ( var i = 0, l = this.length; i < l; i++ ) { + var elem = this[i]; + + if ( elem.nodeType === 1 ) { + if ( !elem.className ) { + elem.className = value; + + } else { + var className = " " + elem.className + " ", + setClass = elem.className; + + for ( var c = 0, cl = classNames.length; c < cl; c++ ) { + if ( className.indexOf( " " + classNames[c] + " " ) < 0 ) { + setClass += " " + classNames[c]; + } + } + elem.className = jQuery.trim( setClass ); + } + } + } + } + + return this; + }, + + removeClass: function( value ) { + if ( jQuery.isFunction(value) ) { + return this.each(function(i) { + var self = jQuery(this); + self.removeClass( value.call(this, i, self.attr("class")) ); + }); + } + + if ( (value && typeof value === "string") || value === undefined ) { + var classNames = (value || "").split( rspaces ); + + for ( var i = 0, l = this.length; i < l; i++ ) { + var elem = this[i]; + + if ( elem.nodeType === 1 && elem.className ) { + if ( value ) { + var className = (" " + elem.className + " ").replace(rclass, " "); + for ( var c = 0, cl = classNames.length; c < cl; c++ ) { + className = className.replace(" " + classNames[c] + " ", " "); + } + elem.className = jQuery.trim( className ); + + } else { + elem.className = ""; + } + } + } + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + var type = typeof value, + isBool = typeof stateVal === "boolean"; + + if ( jQuery.isFunction( value ) ) { + return this.each(function(i) { + var self = jQuery(this); + self.toggleClass( value.call(this, i, self.attr("class"), stateVal), stateVal ); + }); + } + + return this.each(function() { + if ( type === "string" ) { + // toggle individual class names + var className, + i = 0, + self = jQuery( this ), + state = stateVal, + classNames = value.split( rspaces ); + + while ( (className = classNames[ i++ ]) ) { + // check each className given, space seperated list + state = isBool ? state : !self.hasClass( className ); + self[ state ? "addClass" : "removeClass" ]( className ); + } + + } else if ( type === "undefined" || type === "boolean" ) { + if ( this.className ) { + // store className if set + jQuery.data( this, "__className__", this.className ); + } + + // toggle whole className + this.className = this.className || value === false ? "" : jQuery.data( this, "__className__" ) || ""; + } + }); + }, + + hasClass: function( selector ) { + var className = " " + selector + " "; + for ( var i = 0, l = this.length; i < l; i++ ) { + if ( (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) { + return true; + } + } + + return false; + }, + + val: function( value ) { + if ( !arguments.length ) { + var elem = this[0]; + + if ( elem ) { + if ( jQuery.nodeName( elem, "option" ) ) { + // attributes.value is undefined in Blackberry 4.7 but + // uses .value. See #6932 + var val = elem.attributes.value; + return !val || val.specified ? elem.value : elem.text; + } + + // We need to handle select boxes special + if ( jQuery.nodeName( elem, "select" ) ) { + var index = elem.selectedIndex, + values = [], + options = elem.options, + one = elem.type === "select-one"; + + // Nothing was selected + if ( index < 0 ) { + return null; + } + + // Loop through all the selected options + for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) { + var option = options[ i ]; + + // Don't return options that are disabled or in a disabled optgroup + if ( option.selected && (jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) && + (!option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" )) ) { + + // Get the specific value for the option + value = jQuery(option).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } + } + + return values; + } + + // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified + if ( rradiocheck.test( elem.type ) && !jQuery.support.checkOn ) { + return elem.getAttribute("value") === null ? "on" : elem.value; + } + + + // Everything else, we just grab the value + return (elem.value || "").replace(rreturn, ""); + + } + + return undefined; + } + + var isFunction = jQuery.isFunction(value); + + return this.each(function(i) { + var self = jQuery(this), val = value; + + if ( this.nodeType !== 1 ) { + return; + } + + if ( isFunction ) { + val = value.call(this, i, self.val()); + } + + // Treat null/undefined as ""; convert numbers to string + if ( val == null ) { + val = ""; + } else if ( typeof val === "number" ) { + val += ""; + } else if ( jQuery.isArray(val) ) { + val = jQuery.map(val, function (value) { + return value == null ? "" : value + ""; + }); + } + + if ( jQuery.isArray(val) && rradiocheck.test( this.type ) ) { + this.checked = jQuery.inArray( self.val(), val ) >= 0; + + } else if ( jQuery.nodeName( this, "select" ) ) { + var values = jQuery.makeArray(val); + + jQuery( "option", this ).each(function() { + this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0; + }); + + if ( !values.length ) { + this.selectedIndex = -1; + } + + } else { + this.value = val; + } + }); + } +}); + +jQuery.extend({ + attrFn: { + val: true, + css: true, + html: true, + text: true, + data: true, + width: true, + height: true, + offset: true + }, + + attr: function( elem, name, value, pass ) { + // don't set attributes on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 ) { + return undefined; + } + + if ( pass && name in jQuery.attrFn ) { + return jQuery(elem)[name](value); + } + + var notxml = elem.nodeType !== 1 || !jQuery.isXMLDoc( elem ), + // Whether we are setting (or getting) + set = value !== undefined; + + // Try to normalize/fix the name + name = notxml && jQuery.props[ name ] || name; + + // These attributes require special treatment + var special = rspecialurl.test( name ); + + // Safari mis-reports the default selected property of an option + // Accessing the parent's selectedIndex property fixes it + if ( name === "selected" && !jQuery.support.optSelected ) { + var parent = elem.parentNode; + if ( parent ) { + parent.selectedIndex; + + // Make sure that it also works with optgroups, see #5701 + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + } + + // If applicable, access the attribute via the DOM 0 way + // 'in' checks fail in Blackberry 4.7 #6931 + if ( (name in elem || elem[ name ] !== undefined) && notxml && !special ) { + if ( set ) { + // We can't allow the type property to be changed (since it causes problems in IE) + if ( name === "type" && rtype.test( elem.nodeName ) && elem.parentNode ) { + jQuery.error( "type property can't be changed" ); + } + + if ( value === null ) { + if ( elem.nodeType === 1 ) { + elem.removeAttribute( name ); + } + + } else { + elem[ name ] = value; + } + } + + // browsers index elements by id/name on forms, give priority to attributes. + if ( jQuery.nodeName( elem, "form" ) && elem.getAttributeNode(name) ) { + return elem.getAttributeNode( name ).nodeValue; + } + + // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set + // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + if ( name === "tabIndex" ) { + var attributeNode = elem.getAttributeNode( "tabIndex" ); + + return attributeNode && attributeNode.specified ? + attributeNode.value : + rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? + 0 : + undefined; + } + + return elem[ name ]; + } + + if ( !jQuery.support.style && notxml && name === "style" ) { + if ( set ) { + elem.style.cssText = "" + value; + } + + return elem.style.cssText; + } + + if ( set ) { + // convert the value to a string (all browsers do this but IE) see #1070 + elem.setAttribute( name, "" + value ); + } + + // Ensure that missing attributes return undefined + // Blackberry 4.7 returns "" from getAttribute #6938 + if ( !elem.attributes[ name ] && (elem.hasAttribute && !elem.hasAttribute( name )) ) { + return undefined; + } + + var attr = !jQuery.support.hrefNormalized && notxml && special ? + // Some attributes require a special call on IE + elem.getAttribute( name, 2 ) : + elem.getAttribute( name ); + + // Non-existent attributes return null, we normalize to undefined + return attr === null ? undefined : attr; + } +}); + + + + +var rnamespaces = /\.(.*)$/, + rformElems = /^(?:textarea|input|select)$/i, + rperiod = /\./g, + rspace = / /g, + rescape = /[^\w\s.|`]/g, + fcleanup = function( nm ) { + return nm.replace(rescape, "\\$&"); + }, + focusCounts = { focusin: 0, focusout: 0 }; + +/* + * A number of helper functions used for managing events. + * Many of the ideas behind this code originated from + * Dean Edwards' addEvent library. + */ +jQuery.event = { + + // Bind an event to an element + // Original by Dean Edwards + add: function( elem, types, handler, data ) { + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // For whatever reason, IE has trouble passing the window object + // around, causing it to be cloned in the process + if ( jQuery.isWindow( elem ) && ( elem !== window && !elem.frameElement ) ) { + elem = window; + } + + if ( handler === false ) { + handler = returnFalse; + } else if ( !handler ) { + // Fixes bug #7229. Fix recommended by jdalton + return; + } + + var handleObjIn, handleObj; + + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + } + + // Make sure that the function being executed has a unique ID + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure + var elemData = jQuery.data( elem ); + + // If no elemData is found then we must be trying to bind to one of the + // banned noData elements + if ( !elemData ) { + return; + } + + // Use a key less likely to result in collisions for plain JS objects. + // Fixes bug #7150. + var eventKey = elem.nodeType ? "events" : "__events__", + events = elemData[ eventKey ], + eventHandle = elemData.handle; + + if ( typeof events === "function" ) { + // On plain objects events is a fn that holds the the data + // which prevents this data from being JSON serialized + // the function does not need to be called, it just contains the data + eventHandle = events.handle; + events = events.events; + + } else if ( !events ) { + if ( !elem.nodeType ) { + // On plain objects, create a fn that acts as the holder + // of the values to avoid JSON serialization of event data + elemData[ eventKey ] = elemData = function(){}; + } + + elemData.events = events = {}; + } + + if ( !eventHandle ) { + elemData.handle = eventHandle = function() { + // Handle the second event of a trigger and when + // an event is called after a page has unloaded + return typeof jQuery !== "undefined" && !jQuery.event.triggered ? + jQuery.event.handle.apply( eventHandle.elem, arguments ) : + undefined; + }; + } + + // Add elem as a property of the handle function + // This is to prevent a memory leak with non-native events in IE. + eventHandle.elem = elem; + + // Handle multiple events separated by a space + // jQuery(...).bind("mouseover mouseout", fn); + types = types.split(" "); + + var type, i = 0, namespaces; + + while ( (type = types[ i++ ]) ) { + handleObj = handleObjIn ? + jQuery.extend({}, handleObjIn) : + { handler: handler, data: data }; + + // Namespaced event handlers + if ( type.indexOf(".") > -1 ) { + namespaces = type.split("."); + type = namespaces.shift(); + handleObj.namespace = namespaces.slice(0).sort().join("."); + + } else { + namespaces = []; + handleObj.namespace = ""; + } + + handleObj.type = type; + if ( !handleObj.guid ) { + handleObj.guid = handler.guid; + } + + // Get the current list of functions bound to this event + var handlers = events[ type ], + special = jQuery.event.special[ type ] || {}; + + // Init the event handler queue + if ( !handlers ) { + handlers = events[ type ] = []; + + // Check for a special event handler + // Only use addEventListener/attachEvent if the special + // events handler returns false + if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + // Bind the global event handler to the element + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle, false ); + + } else if ( elem.attachEvent ) { + elem.attachEvent( "on" + type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add the function to the element's handler list + handlers.push( handleObj ); + + // Keep track of which events have been used, for global triggering + jQuery.event.global[ type ] = true; + } + + // Nullify elem to prevent memory leaks in IE + elem = null; + }, + + global: {}, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, pos ) { + // don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + if ( handler === false ) { + handler = returnFalse; + } + + var ret, type, fn, j, i = 0, all, namespaces, namespace, special, eventType, handleObj, origType, + eventKey = elem.nodeType ? "events" : "__events__", + elemData = jQuery.data( elem ), + events = elemData && elemData[ eventKey ]; + + if ( !elemData || !events ) { + return; + } + + if ( typeof events === "function" ) { + elemData = events; + events = events.events; + } + + // types is actually an event object here + if ( types && types.type ) { + handler = types.handler; + types = types.type; + } + + // Unbind all events for the element + if ( !types || typeof types === "string" && types.charAt(0) === "." ) { + types = types || ""; + + for ( type in events ) { + jQuery.event.remove( elem, type + types ); + } + + return; + } + + // Handle multiple events separated by a space + // jQuery(...).unbind("mouseover mouseout", fn); + types = types.split(" "); + + while ( (type = types[ i++ ]) ) { + origType = type; + handleObj = null; + all = type.indexOf(".") < 0; + namespaces = []; + + if ( !all ) { + // Namespaced event handlers + namespaces = type.split("."); + type = namespaces.shift(); + + namespace = new RegExp("(^|\\.)" + + jQuery.map( namespaces.slice(0).sort(), fcleanup ).join("\\.(?:.*\\.)?") + "(\\.|$)"); + } + + eventType = events[ type ]; + + if ( !eventType ) { + continue; + } + + if ( !handler ) { + for ( j = 0; j < eventType.length; j++ ) { + handleObj = eventType[ j ]; + + if ( all || namespace.test( handleObj.namespace ) ) { + jQuery.event.remove( elem, origType, handleObj.handler, j ); + eventType.splice( j--, 1 ); + } + } + + continue; + } + + special = jQuery.event.special[ type ] || {}; + + for ( j = pos || 0; j < eventType.length; j++ ) { + handleObj = eventType[ j ]; + + if ( handler.guid === handleObj.guid ) { + // remove the given handler for the given type + if ( all || namespace.test( handleObj.namespace ) ) { + if ( pos == null ) { + eventType.splice( j--, 1 ); + } + + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + + if ( pos != null ) { + break; + } + } + } + + // remove generic event handler if no more handlers exist + if ( eventType.length === 0 || pos != null && eventType.length === 1 ) { + if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) { + jQuery.removeEvent( elem, type, elemData.handle ); + } + + ret = null; + delete events[ type ]; + } + } + + // Remove the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + var handle = elemData.handle; + if ( handle ) { + handle.elem = null; + } + + delete elemData.events; + delete elemData.handle; + + if ( typeof elemData === "function" ) { + jQuery.removeData( elem, eventKey ); + + } else if ( jQuery.isEmptyObject( elemData ) ) { + jQuery.removeData( elem ); + } + } + }, + + // bubbling is internal + trigger: function( event, data, elem /*, bubbling */ ) { + // Event object or event type + var type = event.type || event, + bubbling = arguments[3]; + + if ( !bubbling ) { + event = typeof event === "object" ? + // jQuery.Event object + event[ jQuery.expando ] ? event : + // Object literal + jQuery.extend( jQuery.Event(type), event ) : + // Just the event type (string) + jQuery.Event(type); + + if ( type.indexOf("!") >= 0 ) { + event.type = type = type.slice(0, -1); + event.exclusive = true; + } + + // Handle a global trigger + if ( !elem ) { + // Don't bubble custom events when global (to avoid too much overhead) + event.stopPropagation(); + + // Only trigger if we've ever bound an event for it + if ( jQuery.event.global[ type ] ) { + jQuery.each( jQuery.cache, function() { + if ( this.events && this.events[type] ) { + jQuery.event.trigger( event, data, this.handle.elem ); + } + }); + } + } + + // Handle triggering a single element + + // don't do events on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 ) { + return undefined; + } + + // Clean up in case it is reused + event.result = undefined; + event.target = elem; + + // Clone the incoming data, if any + data = jQuery.makeArray( data ); + data.unshift( event ); + } + + event.currentTarget = elem; + + // Trigger the event, it is assumed that "handle" is a function + var handle = elem.nodeType ? + jQuery.data( elem, "handle" ) : + (jQuery.data( elem, "__events__" ) || {}).handle; + + if ( handle ) { + handle.apply( elem, data ); + } + + var parent = elem.parentNode || elem.ownerDocument; + + // Trigger an inline bound script + try { + if ( !(elem && elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()]) ) { + if ( elem[ "on" + type ] && elem[ "on" + type ].apply( elem, data ) === false ) { + event.result = false; + event.preventDefault(); + } + } + + // prevent IE from throwing an error for some elements with some event types, see #3533 + } catch (inlineError) {} + + if ( !event.isPropagationStopped() && parent ) { + jQuery.event.trigger( event, data, parent, true ); + + } else if ( !event.isDefaultPrevented() ) { + var old, + target = event.target, + targetType = type.replace( rnamespaces, "" ), + isClick = jQuery.nodeName( target, "a" ) && targetType === "click", + special = jQuery.event.special[ targetType ] || {}; + + if ( (!special._default || special._default.call( elem, event ) === false) && + !isClick && !(target && target.nodeName && jQuery.noData[target.nodeName.toLowerCase()]) ) { + + try { + if ( target[ targetType ] ) { + // Make sure that we don't accidentally re-trigger the onFOO events + old = target[ "on" + targetType ]; + + if ( old ) { + target[ "on" + targetType ] = null; + } + + jQuery.event.triggered = true; + target[ targetType ](); + } + + // prevent IE from throwing an error for some elements with some event types, see #3533 + } catch (triggerError) {} + + if ( old ) { + target[ "on" + targetType ] = old; + } + + jQuery.event.triggered = false; + } + } + }, + + handle: function( event ) { + var all, handlers, namespaces, namespace_re, events, + namespace_sort = [], + args = jQuery.makeArray( arguments ); + + event = args[0] = jQuery.event.fix( event || window.event ); + event.currentTarget = this; + + // Namespaced event handlers + all = event.type.indexOf(".") < 0 && !event.exclusive; + + if ( !all ) { + namespaces = event.type.split("."); + event.type = namespaces.shift(); + namespace_sort = namespaces.slice(0).sort(); + namespace_re = new RegExp("(^|\\.)" + namespace_sort.join("\\.(?:.*\\.)?") + "(\\.|$)"); + } + + event.namespace = event.namespace || namespace_sort.join("."); + + events = jQuery.data(this, this.nodeType ? "events" : "__events__"); + + if ( typeof events === "function" ) { + events = events.events; + } + + handlers = (events || {})[ event.type ]; + + if ( events && handlers ) { + // Clone the handlers to prevent manipulation + handlers = handlers.slice(0); + + for ( var j = 0, l = handlers.length; j < l; j++ ) { + var handleObj = handlers[ j ]; + + // Filter the functions by class + if ( all || namespace_re.test( handleObj.namespace ) ) { + // Pass in a reference to the handler function itself + // So that we can later remove it + event.handler = handleObj.handler; + event.data = handleObj.data; + event.handleObj = handleObj; + + var ret = handleObj.handler.apply( this, args ); + + if ( ret !== undefined ) { + event.result = ret; + if ( ret === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + + if ( event.isImmediatePropagationStopped() ) { + break; + } + } + } + } + + return event.result; + }, + + props: "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "), + + fix: function( event ) { + if ( event[ jQuery.expando ] ) { + return event; + } + + // store a copy of the original event object + // and "clone" to set read-only properties + var originalEvent = event; + event = jQuery.Event( originalEvent ); + + for ( var i = this.props.length, prop; i; ) { + prop = this.props[ --i ]; + event[ prop ] = originalEvent[ prop ]; + } + + // Fix target property, if necessary + if ( !event.target ) { + // Fixes #1925 where srcElement might not be defined either + event.target = event.srcElement || document; + } + + // check if target is a textnode (safari) + if ( event.target.nodeType === 3 ) { + event.target = event.target.parentNode; + } + + // Add relatedTarget, if necessary + if ( !event.relatedTarget && event.fromElement ) { + event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement; + } + + // Calculate pageX/Y if missing and clientX/Y available + if ( event.pageX == null && event.clientX != null ) { + var doc = document.documentElement, + body = document.body; + + event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0); + event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0); + } + + // Add which for key events + if ( event.which == null && (event.charCode != null || event.keyCode != null) ) { + event.which = event.charCode != null ? event.charCode : event.keyCode; + } + + // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs) + if ( !event.metaKey && event.ctrlKey ) { + event.metaKey = event.ctrlKey; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + // Note: button is not normalized, so don't use it + if ( !event.which && event.button !== undefined ) { + event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) )); + } + + return event; + }, + + // Deprecated, use jQuery.guid instead + guid: 1E8, + + // Deprecated, use jQuery.proxy instead + proxy: jQuery.proxy, + + special: { + ready: { + // Make sure the ready event is setup + setup: jQuery.bindReady, + teardown: jQuery.noop + }, + + live: { + add: function( handleObj ) { + jQuery.event.add( this, + liveConvert( handleObj.origType, handleObj.selector ), + jQuery.extend({}, handleObj, {handler: liveHandler, guid: handleObj.handler.guid}) ); + }, + + remove: function( handleObj ) { + jQuery.event.remove( this, liveConvert( handleObj.origType, handleObj.selector ), handleObj ); + } + }, + + beforeunload: { + setup: function( data, namespaces, eventHandle ) { + // We only want to do this special case on windows + if ( jQuery.isWindow( this ) ) { + this.onbeforeunload = eventHandle; + } + }, + + teardown: function( namespaces, eventHandle ) { + if ( this.onbeforeunload === eventHandle ) { + this.onbeforeunload = null; + } + } + } + } +}; + +jQuery.removeEvent = document.removeEventListener ? + function( elem, type, handle ) { + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle, false ); + } + } : + function( elem, type, handle ) { + if ( elem.detachEvent ) { + elem.detachEvent( "on" + type, handle ); + } + }; + +jQuery.Event = function( src ) { + // Allow instantiation without the 'new' keyword + if ( !this.preventDefault ) { + return new jQuery.Event( src ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + // Event type + } else { + this.type = src; + } + + // timeStamp is buggy for some events on Firefox(#3843) + // So we won't rely on the native value + this.timeStamp = jQuery.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +function returnFalse() { + return false; +} +function returnTrue() { + return true; +} + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + preventDefault: function() { + this.isDefaultPrevented = returnTrue; + + var e = this.originalEvent; + if ( !e ) { + return; + } + + // if preventDefault exists run it on the original event + if ( e.preventDefault ) { + e.preventDefault(); + + // otherwise set the returnValue property of the original event to false (IE) + } else { + e.returnValue = false; + } + }, + stopPropagation: function() { + this.isPropagationStopped = returnTrue; + + var e = this.originalEvent; + if ( !e ) { + return; + } + // if stopPropagation exists run it on the original event + if ( e.stopPropagation ) { + e.stopPropagation(); + } + // otherwise set the cancelBubble property of the original event to true (IE) + e.cancelBubble = true; + }, + stopImmediatePropagation: function() { + this.isImmediatePropagationStopped = returnTrue; + this.stopPropagation(); + }, + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse +}; + +// Checks if an event happened on an element within another element +// Used in jQuery.event.special.mouseenter and mouseleave handlers +var withinElement = function( event ) { + // Check if mouse(over|out) are still within the same parent element + var parent = event.relatedTarget; + + // Firefox sometimes assigns relatedTarget a XUL element + // which we cannot access the parentNode property of + try { + // Traverse up the tree + while ( parent && parent !== this ) { + parent = parent.parentNode; + } + + if ( parent !== this ) { + // set the correct event type + event.type = event.data; + + // handle event if we actually just moused on to a non sub-element + jQuery.event.handle.apply( this, arguments ); + } + + // assuming we've left the element since we most likely mousedover a xul element + } catch(e) { } +}, + +// In case of event delegation, we only need to rename the event.type, +// liveHandler will take care of the rest. +delegate = function( event ) { + event.type = event.data; + jQuery.event.handle.apply( this, arguments ); +}; + +// Create mouseenter and mouseleave events +jQuery.each({ + mouseenter: "mouseover", + mouseleave: "mouseout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + setup: function( data ) { + jQuery.event.add( this, fix, data && data.selector ? delegate : withinElement, orig ); + }, + teardown: function( data ) { + jQuery.event.remove( this, fix, data && data.selector ? delegate : withinElement ); + } + }; +}); + +// submit delegation +if ( !jQuery.support.submitBubbles ) { + + jQuery.event.special.submit = { + setup: function( data, namespaces ) { + if ( this.nodeName.toLowerCase() !== "form" ) { + jQuery.event.add(this, "click.specialSubmit", function( e ) { + var elem = e.target, + type = elem.type; + + if ( (type === "submit" || type === "image") && jQuery( elem ).closest("form").length ) { + e.liveFired = undefined; + return trigger( "submit", this, arguments ); + } + }); + + jQuery.event.add(this, "keypress.specialSubmit", function( e ) { + var elem = e.target, + type = elem.type; + + if ( (type === "text" || type === "password") && jQuery( elem ).closest("form").length && e.keyCode === 13 ) { + e.liveFired = undefined; + return trigger( "submit", this, arguments ); + } + }); + + } else { + return false; + } + }, + + teardown: function( namespaces ) { + jQuery.event.remove( this, ".specialSubmit" ); + } + }; + +} + +// change delegation, happens here so we have bind. +if ( !jQuery.support.changeBubbles ) { + + var changeFilters, + + getVal = function( elem ) { + var type = elem.type, val = elem.value; + + if ( type === "radio" || type === "checkbox" ) { + val = elem.checked; + + } else if ( type === "select-multiple" ) { + val = elem.selectedIndex > -1 ? + jQuery.map( elem.options, function( elem ) { + return elem.selected; + }).join("-") : + ""; + + } else if ( elem.nodeName.toLowerCase() === "select" ) { + val = elem.selectedIndex; + } + + return val; + }, + + testChange = function testChange( e ) { + var elem = e.target, data, val; + + if ( !rformElems.test( elem.nodeName ) || elem.readOnly ) { + return; + } + + data = jQuery.data( elem, "_change_data" ); + val = getVal(elem); + + // the current data will be also retrieved by beforeactivate + if ( e.type !== "focusout" || elem.type !== "radio" ) { + jQuery.data( elem, "_change_data", val ); + } + + if ( data === undefined || val === data ) { + return; + } + + if ( data != null || val ) { + e.type = "change"; + e.liveFired = undefined; + return jQuery.event.trigger( e, arguments[1], elem ); + } + }; + + jQuery.event.special.change = { + filters: { + focusout: testChange, + + beforedeactivate: testChange, + + click: function( e ) { + var elem = e.target, type = elem.type; + + if ( type === "radio" || type === "checkbox" || elem.nodeName.toLowerCase() === "select" ) { + return testChange.call( this, e ); + } + }, + + // Change has to be called before submit + // Keydown will be called before keypress, which is used in submit-event delegation + keydown: function( e ) { + var elem = e.target, type = elem.type; + + if ( (e.keyCode === 13 && elem.nodeName.toLowerCase() !== "textarea") || + (e.keyCode === 32 && (type === "checkbox" || type === "radio")) || + type === "select-multiple" ) { + return testChange.call( this, e ); + } + }, + + // Beforeactivate happens also before the previous element is blurred + // with this event you can't trigger a change event, but you can store + // information + beforeactivate: function( e ) { + var elem = e.target; + jQuery.data( elem, "_change_data", getVal(elem) ); + } + }, + + setup: function( data, namespaces ) { + if ( this.type === "file" ) { + return false; + } + + for ( var type in changeFilters ) { + jQuery.event.add( this, type + ".specialChange", changeFilters[type] ); + } + + return rformElems.test( this.nodeName ); + }, + + teardown: function( namespaces ) { + jQuery.event.remove( this, ".specialChange" ); + + return rformElems.test( this.nodeName ); + } + }; + + changeFilters = jQuery.event.special.change.filters; + + // Handle when the input is .focus()'d + changeFilters.focus = changeFilters.beforeactivate; +} + +function trigger( type, elem, args ) { + args[0].type = type; + return jQuery.event.handle.apply( elem, args ); +} + +// Create "bubbling" focus and blur events +if ( document.addEventListener ) { + jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { + jQuery.event.special[ fix ] = { + setup: function() { + if ( focusCounts[fix]++ === 0 ) { + document.addEventListener( orig, handler, true ); + } + }, + teardown: function() { + if ( --focusCounts[fix] === 0 ) { + document.removeEventListener( orig, handler, true ); + } + } + }; + + function handler( e ) { + e = jQuery.event.fix( e ); + e.type = fix; + return jQuery.event.trigger( e, null, e.target ); + } + }); +} + +jQuery.each(["bind", "one"], function( i, name ) { + jQuery.fn[ name ] = function( type, data, fn ) { + // Handle object literals + if ( typeof type === "object" ) { + for ( var key in type ) { + this[ name ](key, data, type[key], fn); + } + return this; + } + + if ( jQuery.isFunction( data ) || data === false ) { + fn = data; + data = undefined; + } + + var handler = name === "one" ? jQuery.proxy( fn, function( event ) { + jQuery( this ).unbind( event, handler ); + return fn.apply( this, arguments ); + }) : fn; + + if ( type === "unload" && name !== "one" ) { + this.one( type, data, fn ); + + } else { + for ( var i = 0, l = this.length; i < l; i++ ) { + jQuery.event.add( this[i], type, handler, data ); + } + } + + return this; + }; +}); + +jQuery.fn.extend({ + unbind: function( type, fn ) { + // Handle object literals + if ( typeof type === "object" && !type.preventDefault ) { + for ( var key in type ) { + this.unbind(key, type[key]); + } + + } else { + for ( var i = 0, l = this.length; i < l; i++ ) { + jQuery.event.remove( this[i], type, fn ); + } + } + + return this; + }, + + delegate: function( selector, types, data, fn ) { + return this.live( types, data, fn, selector ); + }, + + undelegate: function( selector, types, fn ) { + if ( arguments.length === 0 ) { + return this.unbind( "live" ); + + } else { + return this.die( types, null, fn, selector ); + } + }, + + trigger: function( type, data ) { + return this.each(function() { + jQuery.event.trigger( type, data, this ); + }); + }, + + triggerHandler: function( type, data ) { + if ( this[0] ) { + var event = jQuery.Event( type ); + event.preventDefault(); + event.stopPropagation(); + jQuery.event.trigger( event, data, this[0] ); + return event.result; + } + }, + + toggle: function( fn ) { + // Save reference to arguments for access in closure + var args = arguments, + i = 1; + + // link all the functions, so any of them can unbind this click handler + while ( i < args.length ) { + jQuery.proxy( fn, args[ i++ ] ); + } + + return this.click( jQuery.proxy( fn, function( event ) { + // Figure out which function to execute + var lastToggle = ( jQuery.data( this, "lastToggle" + fn.guid ) || 0 ) % i; + jQuery.data( this, "lastToggle" + fn.guid, lastToggle + 1 ); + + // Make sure that clicks stop + event.preventDefault(); + + // and execute the function + return args[ lastToggle ].apply( this, arguments ) || false; + })); + }, + + hover: function( fnOver, fnOut ) { + return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); + } +}); + +var liveMap = { + focus: "focusin", + blur: "focusout", + mouseenter: "mouseover", + mouseleave: "mouseout" +}; + +jQuery.each(["live", "die"], function( i, name ) { + jQuery.fn[ name ] = function( types, data, fn, origSelector /* Internal Use Only */ ) { + var type, i = 0, match, namespaces, preType, + selector = origSelector || this.selector, + context = origSelector ? this : jQuery( this.context ); + + if ( typeof types === "object" && !types.preventDefault ) { + for ( var key in types ) { + context[ name ]( key, data, types[key], selector ); + } + + return this; + } + + if ( jQuery.isFunction( data ) ) { + fn = data; + data = undefined; + } + + types = (types || "").split(" "); + + while ( (type = types[ i++ ]) != null ) { + match = rnamespaces.exec( type ); + namespaces = ""; + + if ( match ) { + namespaces = match[0]; + type = type.replace( rnamespaces, "" ); + } + + if ( type === "hover" ) { + types.push( "mouseenter" + namespaces, "mouseleave" + namespaces ); + continue; + } + + preType = type; + + if ( type === "focus" || type === "blur" ) { + types.push( liveMap[ type ] + namespaces ); + type = type + namespaces; + + } else { + type = (liveMap[ type ] || type) + namespaces; + } + + if ( name === "live" ) { + // bind live handler + for ( var j = 0, l = context.length; j < l; j++ ) { + jQuery.event.add( context[j], "live." + liveConvert( type, selector ), + { data: data, selector: selector, handler: fn, origType: type, origHandler: fn, preType: preType } ); + } + + } else { + // unbind live handler + context.unbind( "live." + liveConvert( type, selector ), fn ); + } + } + + return this; + }; +}); + +function liveHandler( event ) { + var stop, maxLevel, related, match, handleObj, elem, j, i, l, data, close, namespace, ret, + elems = [], + selectors = [], + events = jQuery.data( this, this.nodeType ? "events" : "__events__" ); + + if ( typeof events === "function" ) { + events = events.events; + } + + // Make sure we avoid non-left-click bubbling in Firefox (#3861) + if ( event.liveFired === this || !events || !events.live || event.button && event.type === "click" ) { + return; + } + + if ( event.namespace ) { + namespace = new RegExp("(^|\\.)" + event.namespace.split(".").join("\\.(?:.*\\.)?") + "(\\.|$)"); + } + + event.liveFired = this; + + var live = events.live.slice(0); + + for ( j = 0; j < live.length; j++ ) { + handleObj = live[j]; + + if ( handleObj.origType.replace( rnamespaces, "" ) === event.type ) { + selectors.push( handleObj.selector ); + + } else { + live.splice( j--, 1 ); + } + } + + match = jQuery( event.target ).closest( selectors, event.currentTarget ); + + for ( i = 0, l = match.length; i < l; i++ ) { + close = match[i]; + + for ( j = 0; j < live.length; j++ ) { + handleObj = live[j]; + + if ( close.selector === handleObj.selector && (!namespace || namespace.test( handleObj.namespace )) ) { + elem = close.elem; + related = null; + + // Those two events require additional checking + if ( handleObj.preType === "mouseenter" || handleObj.preType === "mouseleave" ) { + event.type = handleObj.preType; + related = jQuery( event.relatedTarget ).closest( handleObj.selector )[0]; + } + + if ( !related || related !== elem ) { + elems.push({ elem: elem, handleObj: handleObj, level: close.level }); + } + } + } + } + + for ( i = 0, l = elems.length; i < l; i++ ) { + match = elems[i]; + + if ( maxLevel && match.level > maxLevel ) { + break; + } + + event.currentTarget = match.elem; + event.data = match.handleObj.data; + event.handleObj = match.handleObj; + + ret = match.handleObj.origHandler.apply( match.elem, arguments ); + + if ( ret === false || event.isPropagationStopped() ) { + maxLevel = match.level; + + if ( ret === false ) { + stop = false; + } + if ( event.isImmediatePropagationStopped() ) { + break; + } + } + } + + return stop; +} + +function liveConvert( type, selector ) { + return (type && type !== "*" ? type + "." : "") + selector.replace(rperiod, "`").replace(rspace, "&"); +} + +jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " + + "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + + "change select submit keydown keypress keyup error").split(" "), function( i, name ) { + + // Handle event binding + jQuery.fn[ name ] = function( data, fn ) { + if ( fn == null ) { + fn = data; + data = null; + } + + return arguments.length > 0 ? + this.bind( name, data, fn ) : + this.trigger( name ); + }; + + if ( jQuery.attrFn ) { + jQuery.attrFn[ name ] = true; + } +}); + +// Prevent memory leaks in IE +// Window isn't included so as not to unbind existing unload events +// More info: +// - http://isaacschlueter.com/2006/10/msie-memory-leaks/ +if ( window.attachEvent && !window.addEventListener ) { + jQuery(window).bind("unload", function() { + for ( var id in jQuery.cache ) { + if ( jQuery.cache[ id ].handle ) { + // Try/Catch is to handle iframes being unloaded, see #4280 + try { + jQuery.event.remove( jQuery.cache[ id ].handle.elem ); + } catch(e) {} + } + } + }); +} + + +/*! + * Sizzle CSS Selector Engine - v1.0 + * Copyright 2009, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * More information: http://sizzlejs.com/ + */ +(function(){ + +var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, + done = 0, + toString = Object.prototype.toString, + hasDuplicate = false, + baseHasDuplicate = true; + +// Here we check if the JavaScript engine is using some sort of +// optimization where it does not always call our comparision +// function. If that is the case, discard the hasDuplicate value. +// Thus far that includes Google Chrome. +[0, 0].sort(function() { + baseHasDuplicate = false; + return 0; +}); + +var Sizzle = function( selector, context, results, seed ) { + results = results || []; + context = context || document; + + var origContext = context; + + if ( context.nodeType !== 1 && context.nodeType !== 9 ) { + return []; + } + + if ( !selector || typeof selector !== "string" ) { + return results; + } + + var m, set, checkSet, extra, ret, cur, pop, i, + prune = true, + contextXML = Sizzle.isXML( context ), + parts = [], + soFar = selector; + + // Reset the position of the chunker regexp (start from head) + do { + chunker.exec( "" ); + m = chunker.exec( soFar ); + + if ( m ) { + soFar = m[3]; + + parts.push( m[1] ); + + if ( m[2] ) { + extra = m[3]; + break; + } + } + } while ( m ); + + if ( parts.length > 1 && origPOS.exec( selector ) ) { + + if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { + set = posProcess( parts[0] + parts[1], context ); + + } else { + set = Expr.relative[ parts[0] ] ? + [ context ] : + Sizzle( parts.shift(), context ); + + while ( parts.length ) { + selector = parts.shift(); + + if ( Expr.relative[ selector ] ) { + selector += parts.shift(); + } + + set = posProcess( selector, set ); + } + } + + } else { + // Take a shortcut and set the context if the root selector is an ID + // (but not if it'll be faster if the inner selector is an ID) + if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && + Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { + + ret = Sizzle.find( parts.shift(), context, contextXML ); + context = ret.expr ? + Sizzle.filter( ret.expr, ret.set )[0] : + ret.set[0]; + } + + if ( context ) { + ret = seed ? + { expr: parts.pop(), set: makeArray(seed) } : + Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); + + set = ret.expr ? + Sizzle.filter( ret.expr, ret.set ) : + ret.set; + + if ( parts.length > 0 ) { + checkSet = makeArray( set ); + + } else { + prune = false; + } + + while ( parts.length ) { + cur = parts.pop(); + pop = cur; + + if ( !Expr.relative[ cur ] ) { + cur = ""; + } else { + pop = parts.pop(); + } + + if ( pop == null ) { + pop = context; + } + + Expr.relative[ cur ]( checkSet, pop, contextXML ); + } + + } else { + checkSet = parts = []; + } + } + + if ( !checkSet ) { + checkSet = set; + } + + if ( !checkSet ) { + Sizzle.error( cur || selector ); + } + + if ( toString.call(checkSet) === "[object Array]" ) { + if ( !prune ) { + results.push.apply( results, checkSet ); + + } else if ( context && context.nodeType === 1 ) { + for ( i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) { + results.push( set[i] ); + } + } + + } else { + for ( i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && checkSet[i].nodeType === 1 ) { + results.push( set[i] ); + } + } + } + + } else { + makeArray( checkSet, results ); + } + + if ( extra ) { + Sizzle( extra, origContext, results, seed ); + Sizzle.uniqueSort( results ); + } + + return results; +}; + +Sizzle.uniqueSort = function( results ) { + if ( sortOrder ) { + hasDuplicate = baseHasDuplicate; + results.sort( sortOrder ); + + if ( hasDuplicate ) { + for ( var i = 1; i < results.length; i++ ) { + if ( results[i] === results[ i - 1 ] ) { + results.splice( i--, 1 ); + } + } + } + } + + return results; +}; + +Sizzle.matches = function( expr, set ) { + return Sizzle( expr, null, null, set ); +}; + +Sizzle.matchesSelector = function( node, expr ) { + return Sizzle( expr, null, null, [node] ).length > 0; +}; + +Sizzle.find = function( expr, context, isXML ) { + var set; + + if ( !expr ) { + return []; + } + + for ( var i = 0, l = Expr.order.length; i < l; i++ ) { + var match, + type = Expr.order[i]; + + if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { + var left = match[1]; + match.splice( 1, 1 ); + + if ( left.substr( left.length - 1 ) !== "\\" ) { + match[1] = (match[1] || "").replace(/\\/g, ""); + set = Expr.find[ type ]( match, context, isXML ); + + if ( set != null ) { + expr = expr.replace( Expr.match[ type ], "" ); + break; + } + } + } + } + + if ( !set ) { + set = context.getElementsByTagName( "*" ); + } + + return { set: set, expr: expr }; +}; + +Sizzle.filter = function( expr, set, inplace, not ) { + var match, anyFound, + old = expr, + result = [], + curLoop = set, + isXMLFilter = set && set[0] && Sizzle.isXML( set[0] ); + + while ( expr && set.length ) { + for ( var type in Expr.filter ) { + if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) { + var found, item, + filter = Expr.filter[ type ], + left = match[1]; + + anyFound = false; + + match.splice(1,1); + + if ( left.substr( left.length - 1 ) === "\\" ) { + continue; + } + + if ( curLoop === result ) { + result = []; + } + + if ( Expr.preFilter[ type ] ) { + match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); + + if ( !match ) { + anyFound = found = true; + + } else if ( match === true ) { + continue; + } + } + + if ( match ) { + for ( var i = 0; (item = curLoop[i]) != null; i++ ) { + if ( item ) { + found = filter( item, match, i, curLoop ); + var pass = not ^ !!found; + + if ( inplace && found != null ) { + if ( pass ) { + anyFound = true; + + } else { + curLoop[i] = false; + } + + } else if ( pass ) { + result.push( item ); + anyFound = true; + } + } + } + } + + if ( found !== undefined ) { + if ( !inplace ) { + curLoop = result; + } + + expr = expr.replace( Expr.match[ type ], "" ); + + if ( !anyFound ) { + return []; + } + + break; + } + } + } + + // Improper expression + if ( expr === old ) { + if ( anyFound == null ) { + Sizzle.error( expr ); + + } else { + break; + } + } + + old = expr; + } + + return curLoop; +}; + +Sizzle.error = function( msg ) { + throw "Syntax error, unrecognized expression: " + msg; +}; + +var Expr = Sizzle.selectors = { + order: [ "ID", "NAME", "TAG" ], + + match: { + ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, + CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, + NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/, + ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/, + TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/, + CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+\-]*)\))?/, + POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/, + PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/ + }, + + leftMatch: {}, + + attrMap: { + "class": "className", + "for": "htmlFor" + }, + + attrHandle: { + href: function( elem ) { + return elem.getAttribute( "href" ); + } + }, + + relative: { + "+": function(checkSet, part){ + var isPartStr = typeof part === "string", + isTag = isPartStr && !/\W/.test( part ), + isPartStrNotTag = isPartStr && !isTag; + + if ( isTag ) { + part = part.toLowerCase(); + } + + for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { + if ( (elem = checkSet[i]) ) { + while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} + + checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ? + elem || false : + elem === part; + } + } + + if ( isPartStrNotTag ) { + Sizzle.filter( part, checkSet, true ); + } + }, + + ">": function( checkSet, part ) { + var elem, + isPartStr = typeof part === "string", + i = 0, + l = checkSet.length; + + if ( isPartStr && !/\W/.test( part ) ) { + part = part.toLowerCase(); + + for ( ; i < l; i++ ) { + elem = checkSet[i]; + + if ( elem ) { + var parent = elem.parentNode; + checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false; + } + } + + } else { + for ( ; i < l; i++ ) { + elem = checkSet[i]; + + if ( elem ) { + checkSet[i] = isPartStr ? + elem.parentNode : + elem.parentNode === part; + } + } + + if ( isPartStr ) { + Sizzle.filter( part, checkSet, true ); + } + } + }, + + "": function(checkSet, part, isXML){ + var nodeCheck, + doneName = done++, + checkFn = dirCheck; + + if ( typeof part === "string" && !/\W/.test(part) ) { + part = part.toLowerCase(); + nodeCheck = part; + checkFn = dirNodeCheck; + } + + checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML ); + }, + + "~": function( checkSet, part, isXML ) { + var nodeCheck, + doneName = done++, + checkFn = dirCheck; + + if ( typeof part === "string" && !/\W/.test( part ) ) { + part = part.toLowerCase(); + nodeCheck = part; + checkFn = dirNodeCheck; + } + + checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML ); + } + }, + + find: { + ID: function( match, context, isXML ) { + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + return m && m.parentNode ? [m] : []; + } + }, + + NAME: function( match, context ) { + if ( typeof context.getElementsByName !== "undefined" ) { + var ret = [], + results = context.getElementsByName( match[1] ); + + for ( var i = 0, l = results.length; i < l; i++ ) { + if ( results[i].getAttribute("name") === match[1] ) { + ret.push( results[i] ); + } + } + + return ret.length === 0 ? null : ret; + } + }, + + TAG: function( match, context ) { + return context.getElementsByTagName( match[1] ); + } + }, + preFilter: { + CLASS: function( match, curLoop, inplace, result, not, isXML ) { + match = " " + match[1].replace(/\\/g, "") + " "; + + if ( isXML ) { + return match; + } + + for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { + if ( elem ) { + if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n]/g, " ").indexOf(match) >= 0) ) { + if ( !inplace ) { + result.push( elem ); + } + + } else if ( inplace ) { + curLoop[i] = false; + } + } + } + + return false; + }, + + ID: function( match ) { + return match[1].replace(/\\/g, ""); + }, + + TAG: function( match, curLoop ) { + return match[1].toLowerCase(); + }, + + CHILD: function( match ) { + if ( match[1] === "nth" ) { + // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' + var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec( + match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" || + !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); + + // calculate the numbers (first)n+(last) including if they are negative + match[2] = (test[1] + (test[2] || 1)) - 0; + match[3] = test[3] - 0; + } + + // TODO: Move to normal caching system + match[0] = done++; + + return match; + }, + + ATTR: function( match, curLoop, inplace, result, not, isXML ) { + var name = match[1].replace(/\\/g, ""); + + if ( !isXML && Expr.attrMap[name] ) { + match[1] = Expr.attrMap[name]; + } + + if ( match[2] === "~=" ) { + match[4] = " " + match[4] + " "; + } + + return match; + }, + + PSEUDO: function( match, curLoop, inplace, result, not ) { + if ( match[1] === "not" ) { + // If we're dealing with a complex expression, or a simple one + if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { + match[3] = Sizzle(match[3], null, null, curLoop); + + } else { + var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); + + if ( !inplace ) { + result.push.apply( result, ret ); + } + + return false; + } + + } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { + return true; + } + + return match; + }, + + POS: function( match ) { + match.unshift( true ); + + return match; + } + }, + + filters: { + enabled: function( elem ) { + return elem.disabled === false && elem.type !== "hidden"; + }, + + disabled: function( elem ) { + return elem.disabled === true; + }, + + checked: function( elem ) { + return elem.checked === true; + }, + + selected: function( elem ) { + // Accessing this property makes selected-by-default + // options in Safari work properly + elem.parentNode.selectedIndex; + + return elem.selected === true; + }, + + parent: function( elem ) { + return !!elem.firstChild; + }, + + empty: function( elem ) { + return !elem.firstChild; + }, + + has: function( elem, i, match ) { + return !!Sizzle( match[3], elem ).length; + }, + + header: function( elem ) { + return (/h\d/i).test( elem.nodeName ); + }, + + text: function( elem ) { + return "text" === elem.type; + }, + radio: function( elem ) { + return "radio" === elem.type; + }, + + checkbox: function( elem ) { + return "checkbox" === elem.type; + }, + + file: function( elem ) { + return "file" === elem.type; + }, + password: function( elem ) { + return "password" === elem.type; + }, + + submit: function( elem ) { + return "submit" === elem.type; + }, + + image: function( elem ) { + return "image" === elem.type; + }, + + reset: function( elem ) { + return "reset" === elem.type; + }, + + button: function( elem ) { + return "button" === elem.type || elem.nodeName.toLowerCase() === "button"; + }, + + input: function( elem ) { + return (/input|select|textarea|button/i).test( elem.nodeName ); + } + }, + setFilters: { + first: function( elem, i ) { + return i === 0; + }, + + last: function( elem, i, match, array ) { + return i === array.length - 1; + }, + + even: function( elem, i ) { + return i % 2 === 0; + }, + + odd: function( elem, i ) { + return i % 2 === 1; + }, + + lt: function( elem, i, match ) { + return i < match[3] - 0; + }, + + gt: function( elem, i, match ) { + return i > match[3] - 0; + }, + + nth: function( elem, i, match ) { + return match[3] - 0 === i; + }, + + eq: function( elem, i, match ) { + return match[3] - 0 === i; + } + }, + filter: { + PSEUDO: function( elem, match, i, array ) { + var name = match[1], + filter = Expr.filters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + + } else if ( name === "contains" ) { + return (elem.textContent || elem.innerText || Sizzle.getText([ elem ]) || "").indexOf(match[3]) >= 0; + + } else if ( name === "not" ) { + var not = match[3]; + + for ( var j = 0, l = not.length; j < l; j++ ) { + if ( not[j] === elem ) { + return false; + } + } + + return true; + + } else { + Sizzle.error( "Syntax error, unrecognized expression: " + name ); + } + }, + + CHILD: function( elem, match ) { + var type = match[1], + node = elem; + + switch ( type ) { + case "only": + case "first": + while ( (node = node.previousSibling) ) { + if ( node.nodeType === 1 ) { + return false; + } + } + + if ( type === "first" ) { + return true; + } + + node = elem; + + case "last": + while ( (node = node.nextSibling) ) { + if ( node.nodeType === 1 ) { + return false; + } + } + + return true; + + case "nth": + var first = match[2], + last = match[3]; + + if ( first === 1 && last === 0 ) { + return true; + } + + var doneName = match[0], + parent = elem.parentNode; + + if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) { + var count = 0; + + for ( node = parent.firstChild; node; node = node.nextSibling ) { + if ( node.nodeType === 1 ) { + node.nodeIndex = ++count; + } + } + + parent.sizcache = doneName; + } + + var diff = elem.nodeIndex - last; + + if ( first === 0 ) { + return diff === 0; + + } else { + return ( diff % first === 0 && diff / first >= 0 ); + } + } + }, + + ID: function( elem, match ) { + return elem.nodeType === 1 && elem.getAttribute("id") === match; + }, + + TAG: function( elem, match ) { + return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match; + }, + + CLASS: function( elem, match ) { + return (" " + (elem.className || elem.getAttribute("class")) + " ") + .indexOf( match ) > -1; + }, + + ATTR: function( elem, match ) { + var name = match[1], + result = Expr.attrHandle[ name ] ? + Expr.attrHandle[ name ]( elem ) : + elem[ name ] != null ? + elem[ name ] : + elem.getAttribute( name ), + value = result + "", + type = match[2], + check = match[4]; + + return result == null ? + type === "!=" : + type === "=" ? + value === check : + type === "*=" ? + value.indexOf(check) >= 0 : + type === "~=" ? + (" " + value + " ").indexOf(check) >= 0 : + !check ? + value && result !== false : + type === "!=" ? + value !== check : + type === "^=" ? + value.indexOf(check) === 0 : + type === "$=" ? + value.substr(value.length - check.length) === check : + type === "|=" ? + value === check || value.substr(0, check.length + 1) === check + "-" : + false; + }, + + POS: function( elem, match, i, array ) { + var name = match[2], + filter = Expr.setFilters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + } + } + } +}; + +var origPOS = Expr.match.POS, + fescape = function(all, num){ + return "\\" + (num - 0 + 1); + }; + +for ( var type in Expr.match ) { + Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) ); + Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) ); +} + +var makeArray = function( array, results ) { + array = Array.prototype.slice.call( array, 0 ); + + if ( results ) { + results.push.apply( results, array ); + return results; + } + + return array; +}; + +// Perform a simple check to determine if the browser is capable of +// converting a NodeList to an array using builtin methods. +// Also verifies that the returned array holds DOM nodes +// (which is not the case in the Blackberry browser) +try { + Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType; + +// Provide a fallback method if it does not work +} catch( e ) { + makeArray = function( array, results ) { + var i = 0, + ret = results || []; + + if ( toString.call(array) === "[object Array]" ) { + Array.prototype.push.apply( ret, array ); + + } else { + if ( typeof array.length === "number" ) { + for ( var l = array.length; i < l; i++ ) { + ret.push( array[i] ); + } + + } else { + for ( ; array[i]; i++ ) { + ret.push( array[i] ); + } + } + } + + return ret; + }; +} + +var sortOrder, siblingCheck; + +if ( document.documentElement.compareDocumentPosition ) { + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { + return a.compareDocumentPosition ? -1 : 1; + } + + return a.compareDocumentPosition(b) & 4 ? -1 : 1; + }; + +} else { + sortOrder = function( a, b ) { + var al, bl, + ap = [], + bp = [], + aup = a.parentNode, + bup = b.parentNode, + cur = aup; + + // The nodes are identical, we can exit early + if ( a === b ) { + hasDuplicate = true; + return 0; + + // If the nodes are siblings (or identical) we can do a quick check + } else if ( aup === bup ) { + return siblingCheck( a, b ); + + // If no parents were found then the nodes are disconnected + } else if ( !aup ) { + return -1; + + } else if ( !bup ) { + return 1; + } + + // Otherwise they're somewhere else in the tree so we need + // to build up a full list of the parentNodes for comparison + while ( cur ) { + ap.unshift( cur ); + cur = cur.parentNode; + } + + cur = bup; + + while ( cur ) { + bp.unshift( cur ); + cur = cur.parentNode; + } + + al = ap.length; + bl = bp.length; + + // Start walking down the tree looking for a discrepancy + for ( var i = 0; i < al && i < bl; i++ ) { + if ( ap[i] !== bp[i] ) { + return siblingCheck( ap[i], bp[i] ); + } + } + + // We ended someplace up the tree so do a sibling check + return i === al ? + siblingCheck( a, bp[i], -1 ) : + siblingCheck( ap[i], b, 1 ); + }; + + siblingCheck = function( a, b, ret ) { + if ( a === b ) { + return ret; + } + + var cur = a.nextSibling; + + while ( cur ) { + if ( cur === b ) { + return -1; + } + + cur = cur.nextSibling; + } + + return 1; + }; +} + +// Utility function for retreiving the text value of an array of DOM nodes +Sizzle.getText = function( elems ) { + var ret = "", elem; + + for ( var i = 0; elems[i]; i++ ) { + elem = elems[i]; + + // Get the text from text nodes and CDATA nodes + if ( elem.nodeType === 3 || elem.nodeType === 4 ) { + ret += elem.nodeValue; + + // Traverse everything else, except comment nodes + } else if ( elem.nodeType !== 8 ) { + ret += Sizzle.getText( elem.childNodes ); + } + } + + return ret; +}; + +// Check to see if the browser returns elements by name when +// querying by getElementById (and provide a workaround) +(function(){ + // We're going to inject a fake input element with a specified name + var form = document.createElement("div"), + id = "script" + (new Date()).getTime(), + root = document.documentElement; + + form.innerHTML = "<a name='" + id + "'/>"; + + // Inject it into the root element, check its status, and remove it quickly + root.insertBefore( form, root.firstChild ); + + // The workaround has to do additional checks after a getElementById + // Which slows things down for other browsers (hence the branching) + if ( document.getElementById( id ) ) { + Expr.find.ID = function( match, context, isXML ) { + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + + return m ? + m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? + [m] : + undefined : + []; + } + }; + + Expr.filter.ID = function( elem, match ) { + var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); + + return elem.nodeType === 1 && node && node.nodeValue === match; + }; + } + + root.removeChild( form ); + + // release memory in IE + root = form = null; +})(); + +(function(){ + // Check to see if the browser returns only elements + // when doing getElementsByTagName("*") + + // Create a fake element + var div = document.createElement("div"); + div.appendChild( document.createComment("") ); + + // Make sure no comments are found + if ( div.getElementsByTagName("*").length > 0 ) { + Expr.find.TAG = function( match, context ) { + var results = context.getElementsByTagName( match[1] ); + + // Filter out possible comments + if ( match[1] === "*" ) { + var tmp = []; + + for ( var i = 0; results[i]; i++ ) { + if ( results[i].nodeType === 1 ) { + tmp.push( results[i] ); + } + } + + results = tmp; + } + + return results; + }; + } + + // Check to see if an attribute returns normalized href attributes + div.innerHTML = "<a href='#'></a>"; + + if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && + div.firstChild.getAttribute("href") !== "#" ) { + + Expr.attrHandle.href = function( elem ) { + return elem.getAttribute( "href", 2 ); + }; + } + + // release memory in IE + div = null; +})(); + +if ( document.querySelectorAll ) { + (function(){ + var oldSizzle = Sizzle, + div = document.createElement("div"), + id = "__sizzle__"; + + div.innerHTML = "<p class='TEST'></p>"; + + // Safari can't handle uppercase or unicode characters when + // in quirks mode. + if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { + return; + } + + Sizzle = function( query, context, extra, seed ) { + context = context || document; + + // Make sure that attribute selectors are quoted + query = query.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']"); + + // Only use querySelectorAll on non-XML documents + // (ID selectors don't work in non-HTML documents) + if ( !seed && !Sizzle.isXML(context) ) { + if ( context.nodeType === 9 ) { + try { + return makeArray( context.querySelectorAll(query), extra ); + } catch(qsaError) {} + + // qSA works strangely on Element-rooted queries + // We can work around this by specifying an extra ID on the root + // and working up from there (Thanks to Andrew Dupont for the technique) + // IE 8 doesn't work on object elements + } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { + var old = context.getAttribute( "id" ), + nid = old || id; + + if ( !old ) { + context.setAttribute( "id", nid ); + } + + try { + return makeArray( context.querySelectorAll( "#" + nid + " " + query ), extra ); + + } catch(pseudoError) { + } finally { + if ( !old ) { + context.removeAttribute( "id" ); + } + } + } + } + + return oldSizzle(query, context, extra, seed); + }; + + for ( var prop in oldSizzle ) { + Sizzle[ prop ] = oldSizzle[ prop ]; + } + + // release memory in IE + div = null; + })(); +} + +(function(){ + var html = document.documentElement, + matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector, + pseudoWorks = false; + + try { + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( document.documentElement, "[test!='']:sizzle" ); + + } catch( pseudoError ) { + pseudoWorks = true; + } + + if ( matches ) { + Sizzle.matchesSelector = function( node, expr ) { + // Make sure that attribute selectors are quoted + expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']"); + + if ( !Sizzle.isXML( node ) ) { + try { + if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) { + return matches.call( node, expr ); + } + } catch(e) {} + } + + return Sizzle(expr, null, null, [node]).length > 0; + }; + } +})(); + +(function(){ + var div = document.createElement("div"); + + div.innerHTML = "<div class='test e'></div><div class='test'></div>"; + + // Opera can't find a second classname (in 9.6) + // Also, make sure that getElementsByClassName actually exists + if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) { + return; + } + + // Safari caches class attributes, doesn't catch changes (in 3.2) + div.lastChild.className = "e"; + + if ( div.getElementsByClassName("e").length === 1 ) { + return; + } + + Expr.order.splice(1, 0, "CLASS"); + Expr.find.CLASS = function( match, context, isXML ) { + if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { + return context.getElementsByClassName(match[1]); + } + }; + + // release memory in IE + div = null; +})(); + +function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + + if ( elem ) { + var match = false; + + elem = elem[dir]; + + while ( elem ) { + if ( elem.sizcache === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 && !isXML ){ + elem.sizcache = doneName; + elem.sizset = i; + } + + if ( elem.nodeName.toLowerCase() === cur ) { + match = elem; + break; + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + + if ( elem ) { + var match = false; + + elem = elem[dir]; + + while ( elem ) { + if ( elem.sizcache === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 ) { + if ( !isXML ) { + elem.sizcache = doneName; + elem.sizset = i; + } + + if ( typeof cur !== "string" ) { + if ( elem === cur ) { + match = true; + break; + } + + } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { + match = elem; + break; + } + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +if ( document.documentElement.contains ) { + Sizzle.contains = function( a, b ) { + return a !== b && (a.contains ? a.contains(b) : true); + }; + +} else if ( document.documentElement.compareDocumentPosition ) { + Sizzle.contains = function( a, b ) { + return !!(a.compareDocumentPosition(b) & 16); + }; + +} else { + Sizzle.contains = function() { + return false; + }; +} + +Sizzle.isXML = function( elem ) { + // documentElement is verified for cases where it doesn't yet exist + // (such as loading iframes in IE - #4833) + var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement; + + return documentElement ? documentElement.nodeName !== "HTML" : false; +}; + +var posProcess = function( selector, context ) { + var match, + tmpSet = [], + later = "", + root = context.nodeType ? [context] : context; + + // Position selectors must be done after the filter + // And so must :not(positional) so we move all PSEUDOs to the end + while ( (match = Expr.match.PSEUDO.exec( selector )) ) { + later += match[0]; + selector = selector.replace( Expr.match.PSEUDO, "" ); + } + + selector = Expr.relative[selector] ? selector + "*" : selector; + + for ( var i = 0, l = root.length; i < l; i++ ) { + Sizzle( selector, root[i], tmpSet ); + } + + return Sizzle.filter( later, tmpSet ); +}; + +// EXPOSE +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; +jQuery.expr[":"] = jQuery.expr.filters; +jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; + + +})(); + + +var runtil = /Until$/, + rparentsprev = /^(?:parents|prevUntil|prevAll)/, + // Note: This RegExp should be improved, or likely pulled from Sizzle + rmultiselector = /,/, + isSimple = /^.[^:#\[\.,]*$/, + slice = Array.prototype.slice, + POS = jQuery.expr.match.POS; + +jQuery.fn.extend({ + find: function( selector ) { + var ret = this.pushStack( "", "find", selector ), + length = 0; + + for ( var i = 0, l = this.length; i < l; i++ ) { + length = ret.length; + jQuery.find( selector, this[i], ret ); + + if ( i > 0 ) { + // Make sure that the results are unique + for ( var n = length; n < ret.length; n++ ) { + for ( var r = 0; r < length; r++ ) { + if ( ret[r] === ret[n] ) { + ret.splice(n--, 1); + break; + } + } + } + } + } + + return ret; + }, + + has: function( target ) { + var targets = jQuery( target ); + return this.filter(function() { + for ( var i = 0, l = targets.length; i < l; i++ ) { + if ( jQuery.contains( this, targets[i] ) ) { + return true; + } + } + }); + }, + + not: function( selector ) { + return this.pushStack( winnow(this, selector, false), "not", selector); + }, + + filter: function( selector ) { + return this.pushStack( winnow(this, selector, true), "filter", selector ); + }, + + is: function( selector ) { + return !!selector && jQuery.filter( selector, this ).length > 0; + }, + + closest: function( selectors, context ) { + var ret = [], i, l, cur = this[0]; + + if ( jQuery.isArray( selectors ) ) { + var match, selector, + matches = {}, + level = 1; + + if ( cur && selectors.length ) { + for ( i = 0, l = selectors.length; i < l; i++ ) { + selector = selectors[i]; + + if ( !matches[selector] ) { + matches[selector] = jQuery.expr.match.POS.test( selector ) ? + jQuery( selector, context || this.context ) : + selector; + } + } + + while ( cur && cur.ownerDocument && cur !== context ) { + for ( selector in matches ) { + match = matches[selector]; + + if ( match.jquery ? match.index(cur) > -1 : jQuery(cur).is(match) ) { + ret.push({ selector: selector, elem: cur, level: level }); + } + } + + cur = cur.parentNode; + level++; + } + } + + return ret; + } + + var pos = POS.test( selectors ) ? + jQuery( selectors, context || this.context ) : null; + + for ( i = 0, l = this.length; i < l; i++ ) { + cur = this[i]; + + while ( cur ) { + if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) { + ret.push( cur ); + break; + + } else { + cur = cur.parentNode; + if ( !cur || !cur.ownerDocument || cur === context ) { + break; + } + } + } + } + + ret = ret.length > 1 ? jQuery.unique(ret) : ret; + + return this.pushStack( ret, "closest", selectors ); + }, + + // Determine the position of an element within + // the matched set of elements + index: function( elem ) { + if ( !elem || typeof elem === "string" ) { + return jQuery.inArray( this[0], + // If it receives a string, the selector is used + // If it receives nothing, the siblings are used + elem ? jQuery( elem ) : this.parent().children() ); + } + // Locate the position of the desired element + return jQuery.inArray( + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[0] : elem, this ); + }, + + add: function( selector, context ) { + var set = typeof selector === "string" ? + jQuery( selector, context || this.context ) : + jQuery.makeArray( selector ), + all = jQuery.merge( this.get(), set ); + + return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ? + all : + jQuery.unique( all ) ); + }, + + andSelf: function() { + return this.add( this.prevObject ); + } +}); + +// A painfully simple check to see if an element is disconnected +// from a document (should be improved, where feasible). +function isDisconnected( node ) { + return !node || !node.parentNode || node.parentNode.nodeType === 11; +} + +jQuery.each({ + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return jQuery.dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, i, until ) { + return jQuery.dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return jQuery.nth( elem, 2, "nextSibling" ); + }, + prev: function( elem ) { + return jQuery.nth( elem, 2, "previousSibling" ); + }, + nextAll: function( elem ) { + return jQuery.dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return jQuery.dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, i, until ) { + return jQuery.dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, i, until ) { + return jQuery.dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return jQuery.sibling( elem.parentNode.firstChild, elem ); + }, + children: function( elem ) { + return jQuery.sibling( elem.firstChild ); + }, + contents: function( elem ) { + return jQuery.nodeName( elem, "iframe" ) ? + elem.contentDocument || elem.contentWindow.document : + jQuery.makeArray( elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var ret = jQuery.map( this, fn, until ); + + if ( !runtil.test( name ) ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + ret = jQuery.filter( selector, ret ); + } + + ret = this.length > 1 ? jQuery.unique( ret ) : ret; + + if ( (this.length > 1 || rmultiselector.test( selector )) && rparentsprev.test( name ) ) { + ret = ret.reverse(); + } + + return this.pushStack( ret, name, slice.call(arguments).join(",") ); + }; +}); + +jQuery.extend({ + filter: function( expr, elems, not ) { + if ( not ) { + expr = ":not(" + expr + ")"; + } + + return elems.length === 1 ? + jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] : + jQuery.find.matches(expr, elems); + }, + + dir: function( elem, dir, until ) { + var matched = [], + cur = elem[ dir ]; + + while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) { + if ( cur.nodeType === 1 ) { + matched.push( cur ); + } + cur = cur[dir]; + } + return matched; + }, + + nth: function( cur, result, dir, elem ) { + result = result || 1; + var num = 0; + + for ( ; cur; cur = cur[dir] ) { + if ( cur.nodeType === 1 && ++num === result ) { + break; + } + } + + return cur; + }, + + sibling: function( n, elem ) { + var r = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + r.push( n ); + } + } + + return r; + } +}); + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, keep ) { + if ( jQuery.isFunction( qualifier ) ) { + return jQuery.grep(elements, function( elem, i ) { + var retVal = !!qualifier.call( elem, i, elem ); + return retVal === keep; + }); + + } else if ( qualifier.nodeType ) { + return jQuery.grep(elements, function( elem, i ) { + return (elem === qualifier) === keep; + }); + + } else if ( typeof qualifier === "string" ) { + var filtered = jQuery.grep(elements, function( elem ) { + return elem.nodeType === 1; + }); + + if ( isSimple.test( qualifier ) ) { + return jQuery.filter(qualifier, filtered, !keep); + } else { + qualifier = jQuery.filter( qualifier, filtered ); + } + } + + return jQuery.grep(elements, function( elem, i ) { + return (jQuery.inArray( elem, qualifier ) >= 0) === keep; + }); +} + + + + +var rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g, + rleadingWhitespace = /^\s+/, + rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig, + rtagName = /<([\w:]+)/, + rtbody = /<tbody/i, + rhtml = /<|&#?\w+;/, + rnocache = /<(?:script|object|embed|option|style)/i, + // checked="checked" or checked (html5) + rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i, + raction = /\=([^="'>\s]+\/)>/g, + wrapMap = { + option: [ 1, "<select multiple='multiple'>", "</select>" ], + legend: [ 1, "<fieldset>", "</fieldset>" ], + thead: [ 1, "<table>", "</table>" ], + tr: [ 2, "<table><tbody>", "</tbody></table>" ], + td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ], + col: [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ], + area: [ 1, "<map>", "</map>" ], + _default: [ 0, "", "" ] + }; + +wrapMap.optgroup = wrapMap.option; +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +// IE can't serialize <link> and <script> tags normally +if ( !jQuery.support.htmlSerialize ) { + wrapMap._default = [ 1, "div<div>", "</div>" ]; +} + +jQuery.fn.extend({ + text: function( text ) { + if ( jQuery.isFunction(text) ) { + return this.each(function(i) { + var self = jQuery( this ); + + self.text( text.call(this, i, self.text()) ); + }); + } + + if ( typeof text !== "object" && text !== undefined ) { + return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) ); + } + + return jQuery.text( this ); + }, + + wrapAll: function( html ) { + if ( jQuery.isFunction( html ) ) { + return this.each(function(i) { + jQuery(this).wrapAll( html.call(this, i) ); + }); + } + + if ( this[0] ) { + // The elements to wrap the target around + var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true); + + if ( this[0].parentNode ) { + wrap.insertBefore( this[0] ); + } + + wrap.map(function() { + var elem = this; + + while ( elem.firstChild && elem.firstChild.nodeType === 1 ) { + elem = elem.firstChild; + } + + return elem; + }).append(this); + } + + return this; + }, + + wrapInner: function( html ) { + if ( jQuery.isFunction( html ) ) { + return this.each(function(i) { + jQuery(this).wrapInner( html.call(this, i) ); + }); + } + + return this.each(function() { + var self = jQuery( this ), + contents = self.contents(); + + if ( contents.length ) { + contents.wrapAll( html ); + + } else { + self.append( html ); + } + }); + }, + + wrap: function( html ) { + return this.each(function() { + jQuery( this ).wrapAll( html ); + }); + }, + + unwrap: function() { + return this.parent().each(function() { + if ( !jQuery.nodeName( this, "body" ) ) { + jQuery( this ).replaceWith( this.childNodes ); + } + }).end(); + }, + + append: function() { + return this.domManip(arguments, true, function( elem ) { + if ( this.nodeType === 1 ) { + this.appendChild( elem ); + } + }); + }, + + prepend: function() { + return this.domManip(arguments, true, function( elem ) { + if ( this.nodeType === 1 ) { + this.insertBefore( elem, this.firstChild ); + } + }); + }, + + before: function() { + if ( this[0] && this[0].parentNode ) { + return this.domManip(arguments, false, function( elem ) { + this.parentNode.insertBefore( elem, this ); + }); + } else if ( arguments.length ) { + var set = jQuery(arguments[0]); + set.push.apply( set, this.toArray() ); + return this.pushStack( set, "before", arguments ); + } + }, + + after: function() { + if ( this[0] && this[0].parentNode ) { + return this.domManip(arguments, false, function( elem ) { + this.parentNode.insertBefore( elem, this.nextSibling ); + }); + } else if ( arguments.length ) { + var set = this.pushStack( this, "after", arguments ); + set.push.apply( set, jQuery(arguments[0]).toArray() ); + return set; + } + }, + + // keepData is for internal use only--do not document + remove: function( selector, keepData ) { + for ( var i = 0, elem; (elem = this[i]) != null; i++ ) { + if ( !selector || jQuery.filter( selector, [ elem ] ).length ) { + if ( !keepData && elem.nodeType === 1 ) { + jQuery.cleanData( elem.getElementsByTagName("*") ); + jQuery.cleanData( [ elem ] ); + } + + if ( elem.parentNode ) { + elem.parentNode.removeChild( elem ); + } + } + } + + return this; + }, + + empty: function() { + for ( var i = 0, elem; (elem = this[i]) != null; i++ ) { + // Remove element nodes and prevent memory leaks + if ( elem.nodeType === 1 ) { + jQuery.cleanData( elem.getElementsByTagName("*") ); + } + + // Remove any remaining nodes + while ( elem.firstChild ) { + elem.removeChild( elem.firstChild ); + } + } + + return this; + }, + + clone: function( events ) { + // Do the clone + var ret = this.map(function() { + if ( !jQuery.support.noCloneEvent && !jQuery.isXMLDoc(this) ) { + // IE copies events bound via attachEvent when + // using cloneNode. Calling detachEvent on the + // clone will also remove the events from the orignal + // In order to get around this, we use innerHTML. + // Unfortunately, this means some modifications to + // attributes in IE that are actually only stored + // as properties will not be copied (such as the + // the name attribute on an input). + var html = this.outerHTML, + ownerDocument = this.ownerDocument; + + if ( !html ) { + var div = ownerDocument.createElement("div"); + div.appendChild( this.cloneNode(true) ); + html = div.innerHTML; + } + + return jQuery.clean([html.replace(rinlinejQuery, "") + // Handle the case in IE 8 where action=/test/> self-closes a tag + .replace(raction, '="$1">') + .replace(rleadingWhitespace, "")], ownerDocument)[0]; + } else { + return this.cloneNode(true); + } + }); + + // Copy the events from the original to the clone + if ( events === true ) { + cloneCopyEvent( this, ret ); + cloneCopyEvent( this.find("*"), ret.find("*") ); + } + + // Return the cloned set + return ret; + }, + + html: function( value ) { + if ( value === undefined ) { + return this[0] && this[0].nodeType === 1 ? + this[0].innerHTML.replace(rinlinejQuery, "") : + null; + + // See if we can take a shortcut and just use innerHTML + } else if ( typeof value === "string" && !rnocache.test( value ) && + (jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value )) && + !wrapMap[ (rtagName.exec( value ) || ["", ""])[1].toLowerCase() ] ) { + + value = value.replace(rxhtmlTag, "<$1></$2>"); + + try { + for ( var i = 0, l = this.length; i < l; i++ ) { + // Remove element nodes and prevent memory leaks + if ( this[i].nodeType === 1 ) { + jQuery.cleanData( this[i].getElementsByTagName("*") ); + this[i].innerHTML = value; + } + } + + // If using innerHTML throws an exception, use the fallback method + } catch(e) { + this.empty().append( value ); + } + + } else if ( jQuery.isFunction( value ) ) { + this.each(function(i){ + var self = jQuery( this ); + + self.html( value.call(this, i, self.html()) ); + }); + + } else { + this.empty().append( value ); + } + + return this; + }, + + replaceWith: function( value ) { + if ( this[0] && this[0].parentNode ) { + // Make sure that the elements are removed from the DOM before they are inserted + // this can help fix replacing a parent with child elements + if ( jQuery.isFunction( value ) ) { + return this.each(function(i) { + var self = jQuery(this), old = self.html(); + self.replaceWith( value.call( this, i, old ) ); + }); + } + + if ( typeof value !== "string" ) { + value = jQuery( value ).detach(); + } + + return this.each(function() { + var next = this.nextSibling, + parent = this.parentNode; + + jQuery( this ).remove(); + + if ( next ) { + jQuery(next).before( value ); + } else { + jQuery(parent).append( value ); + } + }); + } else { + return this.pushStack( jQuery(jQuery.isFunction(value) ? value() : value), "replaceWith", value ); + } + }, + + detach: function( selector ) { + return this.remove( selector, true ); + }, + + domManip: function( args, table, callback ) { + var results, first, fragment, parent, + value = args[0], + scripts = []; + + // We can't cloneNode fragments that contain checked, in WebKit + if ( !jQuery.support.checkClone && arguments.length === 3 && typeof value === "string" && rchecked.test( value ) ) { + return this.each(function() { + jQuery(this).domManip( args, table, callback, true ); + }); + } + + if ( jQuery.isFunction(value) ) { + return this.each(function(i) { + var self = jQuery(this); + args[0] = value.call(this, i, table ? self.html() : undefined); + self.domManip( args, table, callback ); + }); + } + + if ( this[0] ) { + parent = value && value.parentNode; + + // If we're in a fragment, just use that instead of building a new one + if ( jQuery.support.parentNode && parent && parent.nodeType === 11 && parent.childNodes.length === this.length ) { + results = { fragment: parent }; + + } else { + results = jQuery.buildFragment( args, this, scripts ); + } + + fragment = results.fragment; + + if ( fragment.childNodes.length === 1 ) { + first = fragment = fragment.firstChild; + } else { + first = fragment.firstChild; + } + + if ( first ) { + table = table && jQuery.nodeName( first, "tr" ); + + for ( var i = 0, l = this.length; i < l; i++ ) { + callback.call( + table ? + root(this[i], first) : + this[i], + i > 0 || results.cacheable || this.length > 1 ? + fragment.cloneNode(true) : + fragment + ); + } + } + + if ( scripts.length ) { + jQuery.each( scripts, evalScript ); + } + } + + return this; + } +}); + +function root( elem, cur ) { + return jQuery.nodeName(elem, "table") ? + (elem.getElementsByTagName("tbody")[0] || + elem.appendChild(elem.ownerDocument.createElement("tbody"))) : + elem; +} + +function cloneCopyEvent(orig, ret) { + var i = 0; + + ret.each(function() { + if ( this.nodeName !== (orig[i] && orig[i].nodeName) ) { + return; + } + + var oldData = jQuery.data( orig[i++] ), + curData = jQuery.data( this, oldData ), + events = oldData && oldData.events; + + if ( events ) { + delete curData.handle; + curData.events = {}; + + for ( var type in events ) { + for ( var handler in events[ type ] ) { + jQuery.event.add( this, type, events[ type ][ handler ], events[ type ][ handler ].data ); + } + } + } + }); +} + +jQuery.buildFragment = function( args, nodes, scripts ) { + var fragment, cacheable, cacheresults, + doc = (nodes && nodes[0] ? nodes[0].ownerDocument || nodes[0] : document); + + // Only cache "small" (1/2 KB) strings that are associated with the main document + // Cloning options loses the selected state, so don't cache them + // IE 6 doesn't like it when you put <object> or <embed> elements in a fragment + // Also, WebKit does not clone 'checked' attributes on cloneNode, so don't cache + if ( args.length === 1 && typeof args[0] === "string" && args[0].length < 512 && doc === document && + !rnocache.test( args[0] ) && (jQuery.support.checkClone || !rchecked.test( args[0] )) ) { + + cacheable = true; + cacheresults = jQuery.fragments[ args[0] ]; + if ( cacheresults ) { + if ( cacheresults !== 1 ) { + fragment = cacheresults; + } + } + } + + if ( !fragment ) { + fragment = doc.createDocumentFragment(); + jQuery.clean( args, doc, fragment, scripts ); + } + + if ( cacheable ) { + jQuery.fragments[ args[0] ] = cacheresults ? fragment : 1; + } + + return { fragment: fragment, cacheable: cacheable }; +}; + +jQuery.fragments = {}; + +jQuery.each({ + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var ret = [], + insert = jQuery( selector ), + parent = this.length === 1 && this[0].parentNode; + + if ( parent && parent.nodeType === 11 && parent.childNodes.length === 1 && insert.length === 1 ) { + insert[ original ]( this[0] ); + return this; + + } else { + for ( var i = 0, l = insert.length; i < l; i++ ) { + var elems = (i > 0 ? this.clone(true) : this).get(); + jQuery( insert[i] )[ original ]( elems ); + ret = ret.concat( elems ); + } + + return this.pushStack( ret, name, insert.selector ); + } + }; +}); + +jQuery.extend({ + clean: function( elems, context, fragment, scripts ) { + context = context || document; + + // !context.createElement fails in IE with an error but returns typeof 'object' + if ( typeof context.createElement === "undefined" ) { + context = context.ownerDocument || context[0] && context[0].ownerDocument || document; + } + + var ret = []; + + for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { + if ( typeof elem === "number" ) { + elem += ""; + } + + if ( !elem ) { + continue; + } + + // Convert html string into DOM nodes + if ( typeof elem === "string" && !rhtml.test( elem ) ) { + elem = context.createTextNode( elem ); + + } else if ( typeof elem === "string" ) { + // Fix "XHTML"-style tags in all browsers + elem = elem.replace(rxhtmlTag, "<$1></$2>"); + + // Trim whitespace, otherwise indexOf won't work as expected + var tag = (rtagName.exec( elem ) || ["", ""])[1].toLowerCase(), + wrap = wrapMap[ tag ] || wrapMap._default, + depth = wrap[0], + div = context.createElement("div"); + + // Go to html and back, then peel off extra wrappers + div.innerHTML = wrap[1] + elem + wrap[2]; + + // Move to the right depth + while ( depth-- ) { + div = div.lastChild; + } + + // Remove IE's autoinserted <tbody> from table fragments + if ( !jQuery.support.tbody ) { + + // String was a <table>, *may* have spurious <tbody> + var hasBody = rtbody.test(elem), + tbody = tag === "table" && !hasBody ? + div.firstChild && div.firstChild.childNodes : + + // String was a bare <thead> or <tfoot> + wrap[1] === "<table>" && !hasBody ? + div.childNodes : + []; + + for ( var j = tbody.length - 1; j >= 0 ; --j ) { + if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length ) { + tbody[ j ].parentNode.removeChild( tbody[ j ] ); + } + } + + } + + // IE completely kills leading whitespace when innerHTML is used + if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) { + div.insertBefore( context.createTextNode( rleadingWhitespace.exec(elem)[0] ), div.firstChild ); + } + + elem = div.childNodes; + } + + if ( elem.nodeType ) { + ret.push( elem ); + } else { + ret = jQuery.merge( ret, elem ); + } + } + + if ( fragment ) { + for ( i = 0; ret[i]; i++ ) { + if ( scripts && jQuery.nodeName( ret[i], "script" ) && (!ret[i].type || ret[i].type.toLowerCase() === "text/javascript") ) { + scripts.push( ret[i].parentNode ? ret[i].parentNode.removeChild( ret[i] ) : ret[i] ); + + } else { + if ( ret[i].nodeType === 1 ) { + ret.splice.apply( ret, [i + 1, 0].concat(jQuery.makeArray(ret[i].getElementsByTagName("script"))) ); + } + fragment.appendChild( ret[i] ); + } + } + } + + return ret; + }, + + cleanData: function( elems ) { + var data, id, cache = jQuery.cache, + special = jQuery.event.special, + deleteExpando = jQuery.support.deleteExpando; + + for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { + if ( elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()] ) { + continue; + } + + id = elem[ jQuery.expando ]; + + if ( id ) { + data = cache[ id ]; + + if ( data && data.events ) { + for ( var type in data.events ) { + if ( special[ type ] ) { + jQuery.event.remove( elem, type ); + + } else { + jQuery.removeEvent( elem, type, data.handle ); + } + } + } + + if ( deleteExpando ) { + delete elem[ jQuery.expando ]; + + } else if ( elem.removeAttribute ) { + elem.removeAttribute( jQuery.expando ); + } + + delete cache[ id ]; + } + } + } +}); + +function evalScript( i, elem ) { + if ( elem.src ) { + jQuery.ajax({ + url: elem.src, + async: false, + dataType: "script" + }); + } else { + jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" ); + } + + if ( elem.parentNode ) { + elem.parentNode.removeChild( elem ); + } +} + + + + +var ralpha = /alpha\([^)]*\)/i, + ropacity = /opacity=([^)]*)/, + rdashAlpha = /-([a-z])/ig, + rupper = /([A-Z])/g, + rnumpx = /^-?\d+(?:px)?$/i, + rnum = /^-?\d/, + + cssShow = { position: "absolute", visibility: "hidden", display: "block" }, + cssWidth = [ "Left", "Right" ], + cssHeight = [ "Top", "Bottom" ], + curCSS, + + getComputedStyle, + currentStyle, + + fcamelCase = function( all, letter ) { + return letter.toUpperCase(); + }; + +jQuery.fn.css = function( name, value ) { + // Setting 'undefined' is a no-op + if ( arguments.length === 2 && value === undefined ) { + return this; + } + + return jQuery.access( this, name, value, true, function( elem, name, value ) { + return value !== undefined ? + jQuery.style( elem, name, value ) : + jQuery.css( elem, name ); + }); +}; + +jQuery.extend({ + // Add in style property hooks for overriding the default + // behavior of getting and setting a style property + cssHooks: { + opacity: { + get: function( elem, computed ) { + if ( computed ) { + // We should always get a number back from opacity + var ret = curCSS( elem, "opacity", "opacity" ); + return ret === "" ? "1" : ret; + + } else { + return elem.style.opacity; + } + } + } + }, + + // Exclude the following css properties to add px + cssNumber: { + "zIndex": true, + "fontWeight": true, + "opacity": true, + "zoom": true, + "lineHeight": true + }, + + // Add in properties whose names you wish to fix before + // setting or getting the value + cssProps: { + // normalize float css property + "float": jQuery.support.cssFloat ? "cssFloat" : "styleFloat" + }, + + // Get and set the style property on a DOM Node + style: function( elem, name, value, extra ) { + // Don't set styles on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { + return; + } + + // Make sure that we're working with the right name + var ret, origName = jQuery.camelCase( name ), + style = elem.style, hooks = jQuery.cssHooks[ origName ]; + + name = jQuery.cssProps[ origName ] || origName; + + // Check if we're setting a value + if ( value !== undefined ) { + // Make sure that NaN and null values aren't set. See: #7116 + if ( typeof value === "number" && isNaN( value ) || value == null ) { + return; + } + + // If a number was passed in, add 'px' to the (except for certain CSS properties) + if ( typeof value === "number" && !jQuery.cssNumber[ origName ] ) { + value += "px"; + } + + // If a hook was provided, use that value, otherwise just set the specified value + if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value )) !== undefined ) { + // Wrapped to prevent IE from throwing errors when 'invalid' values are provided + // Fixes bug #5509 + try { + style[ name ] = value; + } catch(e) {} + } + + } else { + // If a hook was provided get the non-computed value from there + if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) { + return ret; + } + + // Otherwise just get the value from the style object + return style[ name ]; + } + }, + + css: function( elem, name, extra ) { + // Make sure that we're working with the right name + var ret, origName = jQuery.camelCase( name ), + hooks = jQuery.cssHooks[ origName ]; + + name = jQuery.cssProps[ origName ] || origName; + + // If a hook was provided get the computed value from there + if ( hooks && "get" in hooks && (ret = hooks.get( elem, true, extra )) !== undefined ) { + return ret; + + // Otherwise, if a way to get the computed value exists, use that + } else if ( curCSS ) { + return curCSS( elem, name, origName ); + } + }, + + // A method for quickly swapping in/out CSS properties to get correct calculations + swap: function( elem, options, callback ) { + var old = {}; + + // Remember the old values, and insert the new ones + for ( var name in options ) { + old[ name ] = elem.style[ name ]; + elem.style[ name ] = options[ name ]; + } + + callback.call( elem ); + + // Revert the old values + for ( name in options ) { + elem.style[ name ] = old[ name ]; + } + }, + + camelCase: function( string ) { + return string.replace( rdashAlpha, fcamelCase ); + } +}); + +// DEPRECATED, Use jQuery.css() instead +jQuery.curCSS = jQuery.css; + +jQuery.each(["height", "width"], function( i, name ) { + jQuery.cssHooks[ name ] = { + get: function( elem, computed, extra ) { + var val; + + if ( computed ) { + if ( elem.offsetWidth !== 0 ) { + val = getWH( elem, name, extra ); + + } else { + jQuery.swap( elem, cssShow, function() { + val = getWH( elem, name, extra ); + }); + } + + if ( val <= 0 ) { + val = curCSS( elem, name, name ); + + if ( val === "0px" && currentStyle ) { + val = currentStyle( elem, name, name ); + } + + if ( val != null ) { + // Should return "auto" instead of 0, use 0 for + // temporary backwards-compat + return val === "" || val === "auto" ? "0px" : val; + } + } + + if ( val < 0 || val == null ) { + val = elem.style[ name ]; + + // Should return "auto" instead of 0, use 0 for + // temporary backwards-compat + return val === "" || val === "auto" ? "0px" : val; + } + + return typeof val === "string" ? val : val + "px"; + } + }, + + set: function( elem, value ) { + if ( rnumpx.test( value ) ) { + // ignore negative width and height values #1599 + value = parseFloat(value); + + if ( value >= 0 ) { + return value + "px"; + } + + } else { + return value; + } + } + }; +}); + +if ( !jQuery.support.opacity ) { + jQuery.cssHooks.opacity = { + get: function( elem, computed ) { + // IE uses filters for opacity + return ropacity.test((computed && elem.currentStyle ? elem.currentStyle.filter : elem.style.filter) || "") ? + (parseFloat(RegExp.$1) / 100) + "" : + computed ? "1" : ""; + }, + + set: function( elem, value ) { + var style = elem.style; + + // IE has trouble with opacity if it does not have layout + // Force it by setting the zoom level + style.zoom = 1; + + // Set the alpha filter to set the opacity + var opacity = jQuery.isNaN(value) ? + "" : + "alpha(opacity=" + value * 100 + ")", + filter = style.filter || ""; + + style.filter = ralpha.test(filter) ? + filter.replace(ralpha, opacity) : + style.filter + ' ' + opacity; + } + }; +} + +if ( document.defaultView && document.defaultView.getComputedStyle ) { + getComputedStyle = function( elem, newName, name ) { + var ret, defaultView, computedStyle; + + name = name.replace( rupper, "-$1" ).toLowerCase(); + + if ( !(defaultView = elem.ownerDocument.defaultView) ) { + return undefined; + } + + if ( (computedStyle = defaultView.getComputedStyle( elem, null )) ) { + ret = computedStyle.getPropertyValue( name ); + if ( ret === "" && !jQuery.contains( elem.ownerDocument.documentElement, elem ) ) { + ret = jQuery.style( elem, name ); + } + } + + return ret; + }; +} + +if ( document.documentElement.currentStyle ) { + currentStyle = function( elem, name ) { + var left, rsLeft, + ret = elem.currentStyle && elem.currentStyle[ name ], + style = elem.style; + + // From the awesome hack by Dean Edwards + // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 + + // If we're not dealing with a regular pixel number + // but a number that has a weird ending, we need to convert it to pixels + if ( !rnumpx.test( ret ) && rnum.test( ret ) ) { + // Remember the original values + left = style.left; + rsLeft = elem.runtimeStyle.left; + + // Put in the new values to get a computed value out + elem.runtimeStyle.left = elem.currentStyle.left; + style.left = name === "fontSize" ? "1em" : (ret || 0); + ret = style.pixelLeft + "px"; + + // Revert the changed values + style.left = left; + elem.runtimeStyle.left = rsLeft; + } + + return ret === "" ? "auto" : ret; + }; +} + +curCSS = getComputedStyle || currentStyle; + +function getWH( elem, name, extra ) { + var which = name === "width" ? cssWidth : cssHeight, + val = name === "width" ? elem.offsetWidth : elem.offsetHeight; + + if ( extra === "border" ) { + return val; + } + + jQuery.each( which, function() { + if ( !extra ) { + val -= parseFloat(jQuery.css( elem, "padding" + this )) || 0; + } + + if ( extra === "margin" ) { + val += parseFloat(jQuery.css( elem, "margin" + this )) || 0; + + } else { + val -= parseFloat(jQuery.css( elem, "border" + this + "Width" )) || 0; + } + }); + + return val; +} + +if ( jQuery.expr && jQuery.expr.filters ) { + jQuery.expr.filters.hidden = function( elem ) { + var width = elem.offsetWidth, + height = elem.offsetHeight; + + return (width === 0 && height === 0) || (!jQuery.support.reliableHiddenOffsets && (elem.style.display || jQuery.css( elem, "display" )) === "none"); + }; + + jQuery.expr.filters.visible = function( elem ) { + return !jQuery.expr.filters.hidden( elem ); + }; +} + + + + +var jsc = jQuery.now(), + rscript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, + rselectTextarea = /^(?:select|textarea)/i, + rinput = /^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i, + rnoContent = /^(?:GET|HEAD)$/, + rbracket = /\[\]$/, + jsre = /\=\?(&|$)/, + rquery = /\?/, + rts = /([?&])_=[^&]*/, + rurl = /^(\w+:)?\/\/([^\/?#]+)/, + r20 = /%20/g, + rhash = /#.*$/, + + // Keep a copy of the old load method + _load = jQuery.fn.load; + +jQuery.fn.extend({ + load: function( url, params, callback ) { + if ( typeof url !== "string" && _load ) { + return _load.apply( this, arguments ); + + // Don't do a request if no elements are being requested + } else if ( !this.length ) { + return this; + } + + var off = url.indexOf(" "); + if ( off >= 0 ) { + var selector = url.slice(off, url.length); + url = url.slice(0, off); + } + + // Default to a GET request + var type = "GET"; + + // If the second parameter was provided + if ( params ) { + // If it's a function + if ( jQuery.isFunction( params ) ) { + // We assume that it's the callback + callback = params; + params = null; + + // Otherwise, build a param string + } else if ( typeof params === "object" ) { + params = jQuery.param( params, jQuery.ajaxSettings.traditional ); + type = "POST"; + } + } + + var self = this; + + // Request the remote document + jQuery.ajax({ + url: url, + type: type, + dataType: "html", + data: params, + complete: function( res, status ) { + // If successful, inject the HTML into all the matched elements + if ( status === "success" || status === "notmodified" ) { + // See if a selector was specified + self.html( selector ? + // Create a dummy div to hold the results + jQuery("<div>") + // inject the contents of the document in, removing the scripts + // to avoid any 'Permission Denied' errors in IE + .append(res.responseText.replace(rscript, "")) + + // Locate the specified elements + .find(selector) : + + // If not, just inject the full result + res.responseText ); + } + + if ( callback ) { + self.each( callback, [res.responseText, status, res] ); + } + } + }); + + return this; + }, + + serialize: function() { + return jQuery.param(this.serializeArray()); + }, + + serializeArray: function() { + return this.map(function() { + return this.elements ? jQuery.makeArray(this.elements) : this; + }) + .filter(function() { + return this.name && !this.disabled && + (this.checked || rselectTextarea.test(this.nodeName) || + rinput.test(this.type)); + }) + .map(function( i, elem ) { + var val = jQuery(this).val(); + + return val == null ? + null : + jQuery.isArray(val) ? + jQuery.map( val, function( val, i ) { + return { name: elem.name, value: val }; + }) : + { name: elem.name, value: val }; + }).get(); + } +}); + +// Attach a bunch of functions for handling common AJAX events +jQuery.each( "ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "), function( i, o ) { + jQuery.fn[o] = function( f ) { + return this.bind(o, f); + }; +}); + +jQuery.extend({ + get: function( url, data, callback, type ) { + // shift arguments if data argument was omited + if ( jQuery.isFunction( data ) ) { + type = type || callback; + callback = data; + data = null; + } + + return jQuery.ajax({ + type: "GET", + url: url, + data: data, + success: callback, + dataType: type + }); + }, + + getScript: function( url, callback ) { + return jQuery.get(url, null, callback, "script"); + }, + + getJSON: function( url, data, callback ) { + return jQuery.get(url, data, callback, "json"); + }, + + post: function( url, data, callback, type ) { + // shift arguments if data argument was omited + if ( jQuery.isFunction( data ) ) { + type = type || callback; + callback = data; + data = {}; + } + + return jQuery.ajax({ + type: "POST", + url: url, + data: data, + success: callback, + dataType: type + }); + }, + + ajaxSetup: function( settings ) { + jQuery.extend( jQuery.ajaxSettings, settings ); + }, + + ajaxSettings: { + url: location.href, + global: true, + type: "GET", + contentType: "application/x-www-form-urlencoded", + processData: true, + async: true, + /* + timeout: 0, + data: null, + username: null, + password: null, + traditional: false, + */ + // This function can be overriden by calling jQuery.ajaxSetup + xhr: function() { + return new window.XMLHttpRequest(); + }, + accepts: { + xml: "application/xml, text/xml", + html: "text/html", + script: "text/javascript, application/javascript", + json: "application/json, text/javascript", + text: "text/plain", + _default: "*/*" + } + }, + + ajax: function( origSettings ) { + var s = jQuery.extend(true, {}, jQuery.ajaxSettings, origSettings), + jsonp, status, data, type = s.type.toUpperCase(), noContent = rnoContent.test(type); + + s.url = s.url.replace( rhash, "" ); + + // Use original (not extended) context object if it was provided + s.context = origSettings && origSettings.context != null ? origSettings.context : s; + + // convert data if not already a string + if ( s.data && s.processData && typeof s.data !== "string" ) { + s.data = jQuery.param( s.data, s.traditional ); + } + + // Handle JSONP Parameter Callbacks + if ( s.dataType === "jsonp" ) { + if ( type === "GET" ) { + if ( !jsre.test( s.url ) ) { + s.url += (rquery.test( s.url ) ? "&" : "?") + (s.jsonp || "callback") + "=?"; + } + } else if ( !s.data || !jsre.test(s.data) ) { + s.data = (s.data ? s.data + "&" : "") + (s.jsonp || "callback") + "=?"; + } + s.dataType = "json"; + } + + // Build temporary JSONP function + if ( s.dataType === "json" && (s.data && jsre.test(s.data) || jsre.test(s.url)) ) { + jsonp = s.jsonpCallback || ("jsonp" + jsc++); + + // Replace the =? sequence both in the query string and the data + if ( s.data ) { + s.data = (s.data + "").replace(jsre, "=" + jsonp + "$1"); + } + + s.url = s.url.replace(jsre, "=" + jsonp + "$1"); + + // We need to make sure + // that a JSONP style response is executed properly + s.dataType = "script"; + + // Handle JSONP-style loading + var customJsonp = window[ jsonp ]; + + window[ jsonp ] = function( tmp ) { + if ( jQuery.isFunction( customJsonp ) ) { + customJsonp( tmp ); + + } else { + // Garbage collect + window[ jsonp ] = undefined; + + try { + delete window[ jsonp ]; + } catch( jsonpError ) {} + } + + data = tmp; + jQuery.handleSuccess( s, xhr, status, data ); + jQuery.handleComplete( s, xhr, status, data ); + + if ( head ) { + head.removeChild( script ); + } + }; + } + + if ( s.dataType === "script" && s.cache === null ) { + s.cache = false; + } + + if ( s.cache === false && noContent ) { + var ts = jQuery.now(); + + // try replacing _= if it is there + var ret = s.url.replace(rts, "$1_=" + ts); + + // if nothing was replaced, add timestamp to the end + s.url = ret + ((ret === s.url) ? (rquery.test(s.url) ? "&" : "?") + "_=" + ts : ""); + } + + // If data is available, append data to url for GET/HEAD requests + if ( s.data && noContent ) { + s.url += (rquery.test(s.url) ? "&" : "?") + s.data; + } + + // Watch for a new set of requests + if ( s.global && jQuery.active++ === 0 ) { + jQuery.event.trigger( "ajaxStart" ); + } + + // Matches an absolute URL, and saves the domain + var parts = rurl.exec( s.url ), + remote = parts && (parts[1] && parts[1].toLowerCase() !== location.protocol || parts[2].toLowerCase() !== location.host); + + // If we're requesting a remote document + // and trying to load JSON or Script with a GET + if ( s.dataType === "script" && type === "GET" && remote ) { + var head = document.getElementsByTagName("head")[0] || document.documentElement; + var script = document.createElement("script"); + if ( s.scriptCharset ) { + script.charset = s.scriptCharset; + } + script.src = s.url; + + // Handle Script loading + if ( !jsonp ) { + var done = false; + + // Attach handlers for all browsers + script.onload = script.onreadystatechange = function() { + if ( !done && (!this.readyState || + this.readyState === "loaded" || this.readyState === "complete") ) { + done = true; + jQuery.handleSuccess( s, xhr, status, data ); + jQuery.handleComplete( s, xhr, status, data ); + + // Handle memory leak in IE + script.onload = script.onreadystatechange = null; + if ( head && script.parentNode ) { + head.removeChild( script ); + } + } + }; + } + + // Use insertBefore instead of appendChild to circumvent an IE6 bug. + // This arises when a base node is used (#2709 and #4378). + head.insertBefore( script, head.firstChild ); + + // We handle everything using the script element injection + return undefined; + } + + var requestDone = false; + + // Create the request object + var xhr = s.xhr(); + + if ( !xhr ) { + return; + } + + // Open the socket + // Passing null username, generates a login popup on Opera (#2865) + if ( s.username ) { + xhr.open(type, s.url, s.async, s.username, s.password); + } else { + xhr.open(type, s.url, s.async); + } + + // Need an extra try/catch for cross domain requests in Firefox 3 + try { + // Set content-type if data specified and content-body is valid for this type + if ( (s.data != null && !noContent) || (origSettings && origSettings.contentType) ) { + xhr.setRequestHeader("Content-Type", s.contentType); + } + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + if ( jQuery.lastModified[s.url] ) { + xhr.setRequestHeader("If-Modified-Since", jQuery.lastModified[s.url]); + } + + if ( jQuery.etag[s.url] ) { + xhr.setRequestHeader("If-None-Match", jQuery.etag[s.url]); + } + } + + // Set header so the called script knows that it's an XMLHttpRequest + // Only send the header if it's not a remote XHR + if ( !remote ) { + xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest"); + } + + // Set the Accepts header for the server, depending on the dataType + xhr.setRequestHeader("Accept", s.dataType && s.accepts[ s.dataType ] ? + s.accepts[ s.dataType ] + ", */*; q=0.01" : + s.accepts._default ); + } catch( headerError ) {} + + // Allow custom headers/mimetypes and early abort + if ( s.beforeSend && s.beforeSend.call(s.context, xhr, s) === false ) { + // Handle the global AJAX counter + if ( s.global && jQuery.active-- === 1 ) { + jQuery.event.trigger( "ajaxStop" ); + } + + // close opended socket + xhr.abort(); + return false; + } + + if ( s.global ) { + jQuery.triggerGlobal( s, "ajaxSend", [xhr, s] ); + } + + // Wait for a response to come back + var onreadystatechange = xhr.onreadystatechange = function( isTimeout ) { + // The request was aborted + if ( !xhr || xhr.readyState === 0 || isTimeout === "abort" ) { + // Opera doesn't call onreadystatechange before this point + // so we simulate the call + if ( !requestDone ) { + jQuery.handleComplete( s, xhr, status, data ); + } + + requestDone = true; + if ( xhr ) { + xhr.onreadystatechange = jQuery.noop; + } + + // The transfer is complete and the data is available, or the request timed out + } else if ( !requestDone && xhr && (xhr.readyState === 4 || isTimeout === "timeout") ) { + requestDone = true; + xhr.onreadystatechange = jQuery.noop; + + status = isTimeout === "timeout" ? + "timeout" : + !jQuery.httpSuccess( xhr ) ? + "error" : + s.ifModified && jQuery.httpNotModified( xhr, s.url ) ? + "notmodified" : + "success"; + + var errMsg; + + if ( status === "success" ) { + // Watch for, and catch, XML document parse errors + try { + // process the data (runs the xml through httpData regardless of callback) + data = jQuery.httpData( xhr, s.dataType, s ); + } catch( parserError ) { + status = "parsererror"; + errMsg = parserError; + } + } + + // Make sure that the request was successful or notmodified + if ( status === "success" || status === "notmodified" ) { + // JSONP handles its own success callback + if ( !jsonp ) { + jQuery.handleSuccess( s, xhr, status, data ); + } + } else { + jQuery.handleError( s, xhr, status, errMsg ); + } + + // Fire the complete handlers + if ( !jsonp ) { + jQuery.handleComplete( s, xhr, status, data ); + } + + if ( isTimeout === "timeout" ) { + xhr.abort(); + } + + // Stop memory leaks + if ( s.async ) { + xhr = null; + } + } + }; + + // Override the abort handler, if we can (IE 6 doesn't allow it, but that's OK) + // Opera doesn't fire onreadystatechange at all on abort + try { + var oldAbort = xhr.abort; + xhr.abort = function() { + if ( xhr ) { + // oldAbort has no call property in IE7 so + // just do it this way, which works in all + // browsers + Function.prototype.call.call( oldAbort, xhr ); + } + + onreadystatechange( "abort" ); + }; + } catch( abortError ) {} + + // Timeout checker + if ( s.async && s.timeout > 0 ) { + setTimeout(function() { + // Check to see if the request is still happening + if ( xhr && !requestDone ) { + onreadystatechange( "timeout" ); + } + }, s.timeout); + } + + // Send the data + try { + xhr.send( noContent || s.data == null ? null : s.data ); + + } catch( sendError ) { + jQuery.handleError( s, xhr, null, sendError ); + + // Fire the complete handlers + jQuery.handleComplete( s, xhr, status, data ); + } + + // firefox 1.5 doesn't fire statechange for sync requests + if ( !s.async ) { + onreadystatechange(); + } + + // return XMLHttpRequest to allow aborting the request etc. + return xhr; + }, + + // Serialize an array of form elements or a set of + // key/values into a query string + param: function( a, traditional ) { + var s = [], + add = function( key, value ) { + // If value is a function, invoke it and return its value + value = jQuery.isFunction(value) ? value() : value; + s[ s.length ] = encodeURIComponent(key) + "=" + encodeURIComponent(value); + }; + + // Set traditional to true for jQuery <= 1.3.2 behavior. + if ( traditional === undefined ) { + traditional = jQuery.ajaxSettings.traditional; + } + + // If an array was passed in, assume that it is an array of form elements. + if ( jQuery.isArray(a) || a.jquery ) { + // Serialize the form elements + jQuery.each( a, function() { + add( this.name, this.value ); + }); + + } else { + // If traditional, encode the "old" way (the way 1.3.2 or older + // did it), otherwise encode params recursively. + for ( var prefix in a ) { + buildParams( prefix, a[prefix], traditional, add ); + } + } + + // Return the resulting serialization + return s.join("&").replace(r20, "+"); + } +}); + +function buildParams( prefix, obj, traditional, add ) { + if ( jQuery.isArray(obj) && obj.length ) { + // Serialize array item. + jQuery.each( obj, function( i, v ) { + if ( traditional || rbracket.test( prefix ) ) { + // Treat each array item as a scalar. + add( prefix, v ); + + } else { + // If array item is non-scalar (array or object), encode its + // numeric index to resolve deserialization ambiguity issues. + // Note that rack (as of 1.0.0) can't currently deserialize + // nested arrays properly, and attempting to do so may cause + // a server error. Possible fixes are to modify rack's + // deserialization algorithm or to provide an option or flag + // to force array serialization to be shallow. + buildParams( prefix + "[" + ( typeof v === "object" || jQuery.isArray(v) ? i : "" ) + "]", v, traditional, add ); + } + }); + + } else if ( !traditional && obj != null && typeof obj === "object" ) { + if ( jQuery.isEmptyObject( obj ) ) { + add( prefix, "" ); + + // Serialize object item. + } else { + jQuery.each( obj, function( k, v ) { + buildParams( prefix + "[" + k + "]", v, traditional, add ); + }); + } + + } else { + // Serialize scalar item. + add( prefix, obj ); + } +} + +// This is still on the jQuery object... for now +// Want to move this to jQuery.ajax some day +jQuery.extend({ + + // Counter for holding the number of active queries + active: 0, + + // Last-Modified header cache for next request + lastModified: {}, + etag: {}, + + handleError: function( s, xhr, status, e ) { + // If a local callback was specified, fire it + if ( s.error ) { + s.error.call( s.context, xhr, status, e ); + } + + // Fire the global callback + if ( s.global ) { + jQuery.triggerGlobal( s, "ajaxError", [xhr, s, e] ); + } + }, + + handleSuccess: function( s, xhr, status, data ) { + // If a local callback was specified, fire it and pass it the data + if ( s.success ) { + s.success.call( s.context, data, status, xhr ); + } + + // Fire the global callback + if ( s.global ) { + jQuery.triggerGlobal( s, "ajaxSuccess", [xhr, s] ); + } + }, + + handleComplete: function( s, xhr, status ) { + // Process result + if ( s.complete ) { + s.complete.call( s.context, xhr, status ); + } + + // The request was completed + if ( s.global ) { + jQuery.triggerGlobal( s, "ajaxComplete", [xhr, s] ); + } + + // Handle the global AJAX counter + if ( s.global && jQuery.active-- === 1 ) { + jQuery.event.trigger( "ajaxStop" ); + } + }, + + triggerGlobal: function( s, type, args ) { + (s.context && s.context.url == null ? jQuery(s.context) : jQuery.event).trigger(type, args); + }, + + // Determines if an XMLHttpRequest was successful or not + httpSuccess: function( xhr ) { + try { + // IE error sometimes returns 1223 when it should be 204 so treat it as success, see #1450 + return !xhr.status && location.protocol === "file:" || + xhr.status >= 200 && xhr.status < 300 || + xhr.status === 304 || xhr.status === 1223; + } catch(e) {} + + return false; + }, + + // Determines if an XMLHttpRequest returns NotModified + httpNotModified: function( xhr, url ) { + var lastModified = xhr.getResponseHeader("Last-Modified"), + etag = xhr.getResponseHeader("Etag"); + + if ( lastModified ) { + jQuery.lastModified[url] = lastModified; + } + + if ( etag ) { + jQuery.etag[url] = etag; + } + + return xhr.status === 304; + }, + + httpData: function( xhr, type, s ) { + var ct = xhr.getResponseHeader("content-type") || "", + xml = type === "xml" || !type && ct.indexOf("xml") >= 0, + data = xml ? xhr.responseXML : xhr.responseText; + + if ( xml && data.documentElement.nodeName === "parsererror" ) { + jQuery.error( "parsererror" ); + } + + // Allow a pre-filtering function to sanitize the response + // s is checked to keep backwards compatibility + if ( s && s.dataFilter ) { + data = s.dataFilter( data, type ); + } + + // The filter can actually parse the response + if ( typeof data === "string" ) { + // Get the JavaScript object, if JSON is used. + if ( type === "json" || !type && ct.indexOf("json") >= 0 ) { + data = jQuery.parseJSON( data ); + + // If the type is "script", eval it in global context + } else if ( type === "script" || !type && ct.indexOf("javascript") >= 0 ) { + jQuery.globalEval( data ); + } + } + + return data; + } + +}); + +/* + * Create the request object; Microsoft failed to properly + * implement the XMLHttpRequest in IE7 (can't request local files), + * so we use the ActiveXObject when it is available + * Additionally XMLHttpRequest can be disabled in IE7/IE8 so + * we need a fallback. + */ +if ( window.ActiveXObject ) { + jQuery.ajaxSettings.xhr = function() { + if ( window.location.protocol !== "file:" ) { + try { + return new window.XMLHttpRequest(); + } catch(xhrError) {} + } + + try { + return new window.ActiveXObject("Microsoft.XMLHTTP"); + } catch(activeError) {} + }; +} + +// Does this browser support XHR requests? +jQuery.support.ajax = !!jQuery.ajaxSettings.xhr(); + + + + +var elemdisplay = {}, + rfxtypes = /^(?:toggle|show|hide)$/, + rfxnum = /^([+\-]=)?([\d+.\-]+)(.*)$/, + timerId, + fxAttrs = [ + // height animations + [ "height", "marginTop", "marginBottom", "paddingTop", "paddingBottom" ], + // width animations + [ "width", "marginLeft", "marginRight", "paddingLeft", "paddingRight" ], + // opacity animations + [ "opacity" ] + ]; + +jQuery.fn.extend({ + show: function( speed, easing, callback ) { + var elem, display; + + if ( speed || speed === 0 ) { + return this.animate( genFx("show", 3), speed, easing, callback); + + } else { + for ( var i = 0, j = this.length; i < j; i++ ) { + elem = this[i]; + display = elem.style.display; + + // Reset the inline display of this element to learn if it is + // being hidden by cascaded rules or not + if ( !jQuery.data(elem, "olddisplay") && display === "none" ) { + display = elem.style.display = ""; + } + + // Set elements which have been overridden with display: none + // in a stylesheet to whatever the default browser style is + // for such an element + if ( display === "" && jQuery.css( elem, "display" ) === "none" ) { + jQuery.data(elem, "olddisplay", defaultDisplay(elem.nodeName)); + } + } + + // Set the display of most of the elements in a second loop + // to avoid the constant reflow + for ( i = 0; i < j; i++ ) { + elem = this[i]; + display = elem.style.display; + + if ( display === "" || display === "none" ) { + elem.style.display = jQuery.data(elem, "olddisplay") || ""; + } + } + + return this; + } + }, + + hide: function( speed, easing, callback ) { + if ( speed || speed === 0 ) { + return this.animate( genFx("hide", 3), speed, easing, callback); + + } else { + for ( var i = 0, j = this.length; i < j; i++ ) { + var display = jQuery.css( this[i], "display" ); + + if ( display !== "none" ) { + jQuery.data( this[i], "olddisplay", display ); + } + } + + // Set the display of the elements in a second loop + // to avoid the constant reflow + for ( i = 0; i < j; i++ ) { + this[i].style.display = "none"; + } + + return this; + } + }, + + // Save the old toggle function + _toggle: jQuery.fn.toggle, + + toggle: function( fn, fn2, callback ) { + var bool = typeof fn === "boolean"; + + if ( jQuery.isFunction(fn) && jQuery.isFunction(fn2) ) { + this._toggle.apply( this, arguments ); + + } else if ( fn == null || bool ) { + this.each(function() { + var state = bool ? fn : jQuery(this).is(":hidden"); + jQuery(this)[ state ? "show" : "hide" ](); + }); + + } else { + this.animate(genFx("toggle", 3), fn, fn2, callback); + } + + return this; + }, + + fadeTo: function( speed, to, easing, callback ) { + return this.filter(":hidden").css("opacity", 0).show().end() + .animate({opacity: to}, speed, easing, callback); + }, + + animate: function( prop, speed, easing, callback ) { + var optall = jQuery.speed(speed, easing, callback); + + if ( jQuery.isEmptyObject( prop ) ) { + return this.each( optall.complete ); + } + + return this[ optall.queue === false ? "each" : "queue" ](function() { + // XXX 'this' does not always have a nodeName when running the + // test suite + + var opt = jQuery.extend({}, optall), p, + isElement = this.nodeType === 1, + hidden = isElement && jQuery(this).is(":hidden"), + self = this; + + for ( p in prop ) { + var name = jQuery.camelCase( p ); + + if ( p !== name ) { + prop[ name ] = prop[ p ]; + delete prop[ p ]; + p = name; + } + + if ( prop[p] === "hide" && hidden || prop[p] === "show" && !hidden ) { + return opt.complete.call(this); + } + + if ( isElement && ( p === "height" || p === "width" ) ) { + // Make sure that nothing sneaks out + // Record all 3 overflow attributes because IE does not + // change the overflow attribute when overflowX and + // overflowY are set to the same value + opt.overflow = [ this.style.overflow, this.style.overflowX, this.style.overflowY ]; + + // Set display property to inline-block for height/width + // animations on inline elements that are having width/height + // animated + if ( jQuery.css( this, "display" ) === "inline" && + jQuery.css( this, "float" ) === "none" ) { + if ( !jQuery.support.inlineBlockNeedsLayout ) { + this.style.display = "inline-block"; + + } else { + var display = defaultDisplay(this.nodeName); + + // inline-level elements accept inline-block; + // block-level elements need to be inline with layout + if ( display === "inline" ) { + this.style.display = "inline-block"; + + } else { + this.style.display = "inline"; + this.style.zoom = 1; + } + } + } + } + + if ( jQuery.isArray( prop[p] ) ) { + // Create (if needed) and add to specialEasing + (opt.specialEasing = opt.specialEasing || {})[p] = prop[p][1]; + prop[p] = prop[p][0]; + } + } + + if ( opt.overflow != null ) { + this.style.overflow = "hidden"; + } + + opt.curAnim = jQuery.extend({}, prop); + + jQuery.each( prop, function( name, val ) { + var e = new jQuery.fx( self, opt, name ); + + if ( rfxtypes.test(val) ) { + e[ val === "toggle" ? hidden ? "show" : "hide" : val ]( prop ); + + } else { + var parts = rfxnum.exec(val), + start = e.cur() || 0; + + if ( parts ) { + var end = parseFloat( parts[2] ), + unit = parts[3] || "px"; + + // We need to compute starting value + if ( unit !== "px" ) { + jQuery.style( self, name, (end || 1) + unit); + start = ((end || 1) / e.cur()) * start; + jQuery.style( self, name, start + unit); + } + + // If a +=/-= token was provided, we're doing a relative animation + if ( parts[1] ) { + end = ((parts[1] === "-=" ? -1 : 1) * end) + start; + } + + e.custom( start, end, unit ); + + } else { + e.custom( start, val, "" ); + } + } + }); + + // For JS strict compliance + return true; + }); + }, + + stop: function( clearQueue, gotoEnd ) { + var timers = jQuery.timers; + + if ( clearQueue ) { + this.queue([]); + } + + this.each(function() { + // go in reverse order so anything added to the queue during the loop is ignored + for ( var i = timers.length - 1; i >= 0; i-- ) { + if ( timers[i].elem === this ) { + if (gotoEnd) { + // force the next step to be the last + timers[i](true); + } + + timers.splice(i, 1); + } + } + }); + + // start the next in the queue if the last step wasn't forced + if ( !gotoEnd ) { + this.dequeue(); + } + + return this; + } + +}); + +function genFx( type, num ) { + var obj = {}; + + jQuery.each( fxAttrs.concat.apply([], fxAttrs.slice(0,num)), function() { + obj[ this ] = type; + }); + + return obj; +} + +// Generate shortcuts for custom animations +jQuery.each({ + slideDown: genFx("show", 1), + slideUp: genFx("hide", 1), + slideToggle: genFx("toggle", 1), + fadeIn: { opacity: "show" }, + fadeOut: { opacity: "hide" }, + fadeToggle: { opacity: "toggle" } +}, function( name, props ) { + jQuery.fn[ name ] = function( speed, easing, callback ) { + return this.animate( props, speed, easing, callback ); + }; +}); + +jQuery.extend({ + speed: function( speed, easing, fn ) { + var opt = speed && typeof speed === "object" ? jQuery.extend({}, speed) : { + complete: fn || !fn && easing || + jQuery.isFunction( speed ) && speed, + duration: speed, + easing: fn && easing || easing && !jQuery.isFunction(easing) && easing + }; + + opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration : + opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[opt.duration] : jQuery.fx.speeds._default; + + // Queueing + opt.old = opt.complete; + opt.complete = function() { + if ( opt.queue !== false ) { + jQuery(this).dequeue(); + } + if ( jQuery.isFunction( opt.old ) ) { + opt.old.call( this ); + } + }; + + return opt; + }, + + easing: { + linear: function( p, n, firstNum, diff ) { + return firstNum + diff * p; + }, + swing: function( p, n, firstNum, diff ) { + return ((-Math.cos(p*Math.PI)/2) + 0.5) * diff + firstNum; + } + }, + + timers: [], + + fx: function( elem, options, prop ) { + this.options = options; + this.elem = elem; + this.prop = prop; + + if ( !options.orig ) { + options.orig = {}; + } + } + +}); + +jQuery.fx.prototype = { + // Simple function for setting a style value + update: function() { + if ( this.options.step ) { + this.options.step.call( this.elem, this.now, this ); + } + + (jQuery.fx.step[this.prop] || jQuery.fx.step._default)( this ); + }, + + // Get the current size + cur: function() { + if ( this.elem[this.prop] != null && (!this.elem.style || this.elem.style[this.prop] == null) ) { + return this.elem[ this.prop ]; + } + + var r = parseFloat( jQuery.css( this.elem, this.prop ) ); + return r && r > -10000 ? r : 0; + }, + + // Start an animation from one number to another + custom: function( from, to, unit ) { + var self = this, + fx = jQuery.fx; + + this.startTime = jQuery.now(); + this.start = from; + this.end = to; + this.unit = unit || this.unit || "px"; + this.now = this.start; + this.pos = this.state = 0; + + function t( gotoEnd ) { + return self.step(gotoEnd); + } + + t.elem = this.elem; + + if ( t() && jQuery.timers.push(t) && !timerId ) { + timerId = setInterval(fx.tick, fx.interval); + } + }, + + // Simple 'show' function + show: function() { + // Remember where we started, so that we can go back to it later + this.options.orig[this.prop] = jQuery.style( this.elem, this.prop ); + this.options.show = true; + + // Begin the animation + // Make sure that we start at a small width/height to avoid any + // flash of content + this.custom(this.prop === "width" || this.prop === "height" ? 1 : 0, this.cur()); + + // Start by showing the element + jQuery( this.elem ).show(); + }, + + // Simple 'hide' function + hide: function() { + // Remember where we started, so that we can go back to it later + this.options.orig[this.prop] = jQuery.style( this.elem, this.prop ); + this.options.hide = true; + + // Begin the animation + this.custom(this.cur(), 0); + }, + + // Each step of an animation + step: function( gotoEnd ) { + var t = jQuery.now(), done = true; + + if ( gotoEnd || t >= this.options.duration + this.startTime ) { + this.now = this.end; + this.pos = this.state = 1; + this.update(); + + this.options.curAnim[ this.prop ] = true; + + for ( var i in this.options.curAnim ) { + if ( this.options.curAnim[i] !== true ) { + done = false; + } + } + + if ( done ) { + // Reset the overflow + if ( this.options.overflow != null && !jQuery.support.shrinkWrapBlocks ) { + var elem = this.elem, + options = this.options; + + jQuery.each( [ "", "X", "Y" ], function (index, value) { + elem.style[ "overflow" + value ] = options.overflow[index]; + } ); + } + + // Hide the element if the "hide" operation was done + if ( this.options.hide ) { + jQuery(this.elem).hide(); + } + + // Reset the properties, if the item has been hidden or shown + if ( this.options.hide || this.options.show ) { + for ( var p in this.options.curAnim ) { + jQuery.style( this.elem, p, this.options.orig[p] ); + } + } + + // Execute the complete function + this.options.complete.call( this.elem ); + } + + return false; + + } else { + var n = t - this.startTime; + this.state = n / this.options.duration; + + // Perform the easing function, defaults to swing + var specialEasing = this.options.specialEasing && this.options.specialEasing[this.prop]; + var defaultEasing = this.options.easing || (jQuery.easing.swing ? "swing" : "linear"); + this.pos = jQuery.easing[specialEasing || defaultEasing](this.state, n, 0, 1, this.options.duration); + this.now = this.start + ((this.end - this.start) * this.pos); + + // Perform the next step of the animation + this.update(); + } + + return true; + } +}; + +jQuery.extend( jQuery.fx, { + tick: function() { + var timers = jQuery.timers; + + for ( var i = 0; i < timers.length; i++ ) { + if ( !timers[i]() ) { + timers.splice(i--, 1); + } + } + + if ( !timers.length ) { + jQuery.fx.stop(); + } + }, + + interval: 13, + + stop: function() { + clearInterval( timerId ); + timerId = null; + }, + + speeds: { + slow: 600, + fast: 200, + // Default speed + _default: 400 + }, + + step: { + opacity: function( fx ) { + jQuery.style( fx.elem, "opacity", fx.now ); + }, + + _default: function( fx ) { + if ( fx.elem.style && fx.elem.style[ fx.prop ] != null ) { + fx.elem.style[ fx.prop ] = (fx.prop === "width" || fx.prop === "height" ? Math.max(0, fx.now) : fx.now) + fx.unit; + } else { + fx.elem[ fx.prop ] = fx.now; + } + } + } +}); + +if ( jQuery.expr && jQuery.expr.filters ) { + jQuery.expr.filters.animated = function( elem ) { + return jQuery.grep(jQuery.timers, function( fn ) { + return elem === fn.elem; + }).length; + }; +} + +function defaultDisplay( nodeName ) { + if ( !elemdisplay[ nodeName ] ) { + var elem = jQuery("<" + nodeName + ">").appendTo("body"), + display = elem.css("display"); + + elem.remove(); + + if ( display === "none" || display === "" ) { + display = "block"; + } + + elemdisplay[ nodeName ] = display; + } + + return elemdisplay[ nodeName ]; +} + + + + +var rtable = /^t(?:able|d|h)$/i, + rroot = /^(?:body|html)$/i; + +if ( "getBoundingClientRect" in document.documentElement ) { + jQuery.fn.offset = function( options ) { + var elem = this[0], box; + + if ( options ) { + return this.each(function( i ) { + jQuery.offset.setOffset( this, options, i ); + }); + } + + if ( !elem || !elem.ownerDocument ) { + return null; + } + + if ( elem === elem.ownerDocument.body ) { + return jQuery.offset.bodyOffset( elem ); + } + + try { + box = elem.getBoundingClientRect(); + } catch(e) {} + + var doc = elem.ownerDocument, + docElem = doc.documentElement; + + // Make sure we're not dealing with a disconnected DOM node + if ( !box || !jQuery.contains( docElem, elem ) ) { + return box || { top: 0, left: 0 }; + } + + var body = doc.body, + win = getWindow(doc), + clientTop = docElem.clientTop || body.clientTop || 0, + clientLeft = docElem.clientLeft || body.clientLeft || 0, + scrollTop = (win.pageYOffset || jQuery.support.boxModel && docElem.scrollTop || body.scrollTop ), + scrollLeft = (win.pageXOffset || jQuery.support.boxModel && docElem.scrollLeft || body.scrollLeft), + top = box.top + scrollTop - clientTop, + left = box.left + scrollLeft - clientLeft; + + return { top: top, left: left }; + }; + +} else { + jQuery.fn.offset = function( options ) { + var elem = this[0]; + + if ( options ) { + return this.each(function( i ) { + jQuery.offset.setOffset( this, options, i ); + }); + } + + if ( !elem || !elem.ownerDocument ) { + return null; + } + + if ( elem === elem.ownerDocument.body ) { + return jQuery.offset.bodyOffset( elem ); + } + + jQuery.offset.initialize(); + + var computedStyle, + offsetParent = elem.offsetParent, + prevOffsetParent = elem, + doc = elem.ownerDocument, + docElem = doc.documentElement, + body = doc.body, + defaultView = doc.defaultView, + prevComputedStyle = defaultView ? defaultView.getComputedStyle( elem, null ) : elem.currentStyle, + top = elem.offsetTop, + left = elem.offsetLeft; + + while ( (elem = elem.parentNode) && elem !== body && elem !== docElem ) { + if ( jQuery.offset.supportsFixedPosition && prevComputedStyle.position === "fixed" ) { + break; + } + + computedStyle = defaultView ? defaultView.getComputedStyle(elem, null) : elem.currentStyle; + top -= elem.scrollTop; + left -= elem.scrollLeft; + + if ( elem === offsetParent ) { + top += elem.offsetTop; + left += elem.offsetLeft; + + if ( jQuery.offset.doesNotAddBorder && !(jQuery.offset.doesAddBorderForTableAndCells && rtable.test(elem.nodeName)) ) { + top += parseFloat( computedStyle.borderTopWidth ) || 0; + left += parseFloat( computedStyle.borderLeftWidth ) || 0; + } + + prevOffsetParent = offsetParent; + offsetParent = elem.offsetParent; + } + + if ( jQuery.offset.subtractsBorderForOverflowNotVisible && computedStyle.overflow !== "visible" ) { + top += parseFloat( computedStyle.borderTopWidth ) || 0; + left += parseFloat( computedStyle.borderLeftWidth ) || 0; + } + + prevComputedStyle = computedStyle; + } + + if ( prevComputedStyle.position === "relative" || prevComputedStyle.position === "static" ) { + top += body.offsetTop; + left += body.offsetLeft; + } + + if ( jQuery.offset.supportsFixedPosition && prevComputedStyle.position === "fixed" ) { + top += Math.max( docElem.scrollTop, body.scrollTop ); + left += Math.max( docElem.scrollLeft, body.scrollLeft ); + } + + return { top: top, left: left }; + }; +} + +jQuery.offset = { + initialize: function() { + var body = document.body, container = document.createElement("div"), innerDiv, checkDiv, table, td, bodyMarginTop = parseFloat( jQuery.css(body, "marginTop") ) || 0, + html = "<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>"; + + jQuery.extend( container.style, { position: "absolute", top: 0, left: 0, margin: 0, border: 0, width: "1px", height: "1px", visibility: "hidden" } ); + + container.innerHTML = html; + body.insertBefore( container, body.firstChild ); + innerDiv = container.firstChild; + checkDiv = innerDiv.firstChild; + td = innerDiv.nextSibling.firstChild.firstChild; + + this.doesNotAddBorder = (checkDiv.offsetTop !== 5); + this.doesAddBorderForTableAndCells = (td.offsetTop === 5); + + checkDiv.style.position = "fixed"; + checkDiv.style.top = "20px"; + + // safari subtracts parent border width here which is 5px + this.supportsFixedPosition = (checkDiv.offsetTop === 20 || checkDiv.offsetTop === 15); + checkDiv.style.position = checkDiv.style.top = ""; + + innerDiv.style.overflow = "hidden"; + innerDiv.style.position = "relative"; + + this.subtractsBorderForOverflowNotVisible = (checkDiv.offsetTop === -5); + + this.doesNotIncludeMarginInBodyOffset = (body.offsetTop !== bodyMarginTop); + + body.removeChild( container ); + body = container = innerDiv = checkDiv = table = td = null; + jQuery.offset.initialize = jQuery.noop; + }, + + bodyOffset: function( body ) { + var top = body.offsetTop, + left = body.offsetLeft; + + jQuery.offset.initialize(); + + if ( jQuery.offset.doesNotIncludeMarginInBodyOffset ) { + top += parseFloat( jQuery.css(body, "marginTop") ) || 0; + left += parseFloat( jQuery.css(body, "marginLeft") ) || 0; + } + + return { top: top, left: left }; + }, + + setOffset: function( elem, options, i ) { + var position = jQuery.css( elem, "position" ); + + // set position first, in-case top/left are set even on static elem + if ( position === "static" ) { + elem.style.position = "relative"; + } + + var curElem = jQuery( elem ), + curOffset = curElem.offset(), + curCSSTop = jQuery.css( elem, "top" ), + curCSSLeft = jQuery.css( elem, "left" ), + calculatePosition = (position === "absolute" && jQuery.inArray('auto', [curCSSTop, curCSSLeft]) > -1), + props = {}, curPosition = {}, curTop, curLeft; + + // need to be able to calculate position if either top or left is auto and position is absolute + if ( calculatePosition ) { + curPosition = curElem.position(); + } + + curTop = calculatePosition ? curPosition.top : parseInt( curCSSTop, 10 ) || 0; + curLeft = calculatePosition ? curPosition.left : parseInt( curCSSLeft, 10 ) || 0; + + if ( jQuery.isFunction( options ) ) { + options = options.call( elem, i, curOffset ); + } + + if (options.top != null) { + props.top = (options.top - curOffset.top) + curTop; + } + if (options.left != null) { + props.left = (options.left - curOffset.left) + curLeft; + } + + if ( "using" in options ) { + options.using.call( elem, props ); + } else { + curElem.css( props ); + } + } +}; + + +jQuery.fn.extend({ + position: function() { + if ( !this[0] ) { + return null; + } + + var elem = this[0], + + // Get *real* offsetParent + offsetParent = this.offsetParent(), + + // Get correct offsets + offset = this.offset(), + parentOffset = rroot.test(offsetParent[0].nodeName) ? { top: 0, left: 0 } : offsetParent.offset(); + + // Subtract element margins + // note: when an element has margin: auto the offsetLeft and marginLeft + // are the same in Safari causing offset.left to incorrectly be 0 + offset.top -= parseFloat( jQuery.css(elem, "marginTop") ) || 0; + offset.left -= parseFloat( jQuery.css(elem, "marginLeft") ) || 0; + + // Add offsetParent borders + parentOffset.top += parseFloat( jQuery.css(offsetParent[0], "borderTopWidth") ) || 0; + parentOffset.left += parseFloat( jQuery.css(offsetParent[0], "borderLeftWidth") ) || 0; + + // Subtract the two offsets + return { + top: offset.top - parentOffset.top, + left: offset.left - parentOffset.left + }; + }, + + offsetParent: function() { + return this.map(function() { + var offsetParent = this.offsetParent || document.body; + while ( offsetParent && (!rroot.test(offsetParent.nodeName) && jQuery.css(offsetParent, "position") === "static") ) { + offsetParent = offsetParent.offsetParent; + } + return offsetParent; + }); + } +}); + + +// Create scrollLeft and scrollTop methods +jQuery.each( ["Left", "Top"], function( i, name ) { + var method = "scroll" + name; + + jQuery.fn[ method ] = function(val) { + var elem = this[0], win; + + if ( !elem ) { + return null; + } + + if ( val !== undefined ) { + // Set the scroll offset + return this.each(function() { + win = getWindow( this ); + + if ( win ) { + win.scrollTo( + !i ? val : jQuery(win).scrollLeft(), + i ? val : jQuery(win).scrollTop() + ); + + } else { + this[ method ] = val; + } + }); + } else { + win = getWindow( elem ); + + // Return the scroll offset + return win ? ("pageXOffset" in win) ? win[ i ? "pageYOffset" : "pageXOffset" ] : + jQuery.support.boxModel && win.document.documentElement[ method ] || + win.document.body[ method ] : + elem[ method ]; + } + }; +}); + +function getWindow( elem ) { + return jQuery.isWindow( elem ) ? + elem : + elem.nodeType === 9 ? + elem.defaultView || elem.parentWindow : + false; +} + + + + +// Create innerHeight, innerWidth, outerHeight and outerWidth methods +jQuery.each([ "Height", "Width" ], function( i, name ) { + + var type = name.toLowerCase(); + + // innerHeight and innerWidth + jQuery.fn["inner" + name] = function() { + return this[0] ? + parseFloat( jQuery.css( this[0], type, "padding" ) ) : + null; + }; + + // outerHeight and outerWidth + jQuery.fn["outer" + name] = function( margin ) { + return this[0] ? + parseFloat( jQuery.css( this[0], type, margin ? "margin" : "border" ) ) : + null; + }; + + jQuery.fn[ type ] = function( size ) { + // Get window width or height + var elem = this[0]; + if ( !elem ) { + return size == null ? null : this; + } + + if ( jQuery.isFunction( size ) ) { + return this.each(function( i ) { + var self = jQuery( this ); + self[ type ]( size.call( this, i, self[ type ]() ) ); + }); + } + + if ( jQuery.isWindow( elem ) ) { + // Everyone else use document.documentElement or document.body depending on Quirks vs Standards mode + return elem.document.compatMode === "CSS1Compat" && elem.document.documentElement[ "client" + name ] || + elem.document.body[ "client" + name ]; + + // Get document width or height + } else if ( elem.nodeType === 9 ) { + // Either scroll[Width/Height] or offset[Width/Height], whichever is greater + return Math.max( + elem.documentElement["client" + name], + elem.body["scroll" + name], elem.documentElement["scroll" + name], + elem.body["offset" + name], elem.documentElement["offset" + name] + ); + + // Get or set width or height on the element + } else if ( size === undefined ) { + var orig = jQuery.css( elem, type ), + ret = parseFloat( orig ); + + return jQuery.isNaN( ret ) ? orig : ret; + + // Set the width or height on the element (default to pixels if value is unitless) + } else { + return this.css( type, typeof size === "string" ? size : size + "px" ); + } + }; + +}); + + +})(window); diff --git a/browserid/static/funcunit/syn/resources/qunit/qunit.css b/browserid/static/funcunit/syn/resources/qunit/qunit.css new file mode 100644 index 0000000000000000000000000000000000000000..5714bf4a597e8d13ac34efdb7153ea3430f50f80 --- /dev/null +++ b/browserid/static/funcunit/syn/resources/qunit/qunit.css @@ -0,0 +1,119 @@ + +ol#qunit-tests { + font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial; + margin:0; + padding:0; + list-style-position:inside; + + font-size: smaller; +} +ol#qunit-tests li{ + padding:0.4em 0.5em 0.4em 2.5em; + border-bottom:1px solid #fff; + font-size:small; + list-style-position:inside; +} +ol#qunit-tests li ol{ + box-shadow: inset 0px 2px 13px #999; + -moz-box-shadow: inset 0px 2px 13px #999; + -webkit-box-shadow: inset 0px 2px 13px #999; + margin-top:0.5em; + margin-left:0; + padding:0.5em; + background-color:#fff; + border-radius:15px; + -moz-border-radius: 15px; + -webkit-border-radius: 15px; +} +ol#qunit-tests li li{ + border-bottom:none; + margin:0.5em; + background-color:#fff; + list-style-position: inside; + padding:0.4em 0.5em 0.4em 0.5em; +} + +ol#qunit-tests li li.pass{ + border-left:26px solid #C6E746; + background-color:#fff; + color:#5E740B; + } +ol#qunit-tests li li.fail{ + border-left:26px solid #EE5757; + background-color:#fff; + color:#710909; +} +ol#qunit-tests li.pass{ + background-color:#D2E0E6; + color:#528CE0; +} +ol#qunit-tests li.fail{ + background-color:#EE5757; + color:#000; +} +ol#qunit-tests li strong { + cursor:pointer; +} +h1#qunit-header{ + background-color:#0d3349; + margin:0; + padding:0.5em 0 0.5em 1em; + color:#fff; + font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial; + border-top-right-radius:15px; + border-top-left-radius:15px; + -moz-border-radius-topright:15px; + -moz-border-radius-topleft:15px; + -webkit-border-top-right-radius:15px; + -webkit-border-top-left-radius:15px; + text-shadow: rgba(0, 0, 0, 0.5) 4px 4px 1px; +} +h2#qunit-banner{ + font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial; + height:5px; + margin:0; + padding:0; +} +h2#qunit-banner.qunit-pass{ + background-color:#C6E746; +} +h2#qunit-banner.qunit-fail, #qunit-testrunner-toolbar { + background-color:#EE5757; +} +#qunit-testrunner-toolbar { + font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial; + padding:0; + /*width:80%;*/ + padding:0em 0 0.5em 2em; + font-size: small; +} +h2#qunit-userAgent { + font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial; + background-color:#2b81af; + margin:0; + padding:0; + color:#fff; + font-size: small; + padding:0.5em 0 0.5em 2.5em; + text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; +} +p#qunit-testresult{ + font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial; + margin:0; + font-size: small; + color:#2b81af; + border-bottom-right-radius:15px; + border-bottom-left-radius:15px; + -moz-border-radius-bottomright:15px; + -moz-border-radius-bottomleft:15px; + -webkit-border-bottom-right-radius:15px; + -webkit-border-bottom-left-radius:15px; + background-color:#D2E0E6; + padding:0.5em 0.5em 0.5em 2.5em; +} +strong b.fail{ + color:#710909; + } +strong b.pass{ + color:#5E740B; + } diff --git a/browserid/static/funcunit/syn/resources/qunit/qunit.js b/browserid/static/funcunit/syn/resources/qunit/qunit.js new file mode 100644 index 0000000000000000000000000000000000000000..e2466728daad645c66d10607f64cff7c2e56786a --- /dev/null +++ b/browserid/static/funcunit/syn/resources/qunit/qunit.js @@ -0,0 +1,1108 @@ +/* @documentjs-ignore + * QUnit - A JavaScript Unit Testing Framework + * + * http://docs.jquery.com/QUnit + * + * Copyright (c) 2009 John Resig, J�rn Zaefferer + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + */ + +(function(window) { + +var QUnit = { + + // Initialize the configuration options + init: function() { + extend(config, { + stats: { all: 0, bad: 0 }, + moduleStats: { all: 0, bad: 0 }, + started: +new Date, + updateRate: 1000, + blocking: false, + autostart: true, + autorun: false, + assertions: [], + filters: [], + queue: [] + }); + + var tests = id("qunit-tests"), + banner = id("qunit-banner"), + result = id("qunit-testresult"); + + if ( tests ) { + tests.innerHTML = ""; + } + + if ( banner ) { + banner.className = ""; + } + + if ( result ) { + result.parentNode.removeChild( result ); + } + }, + + // call on start of module test to prepend name to all tests + module: function(name, testEnvironment) { + config.currentModule = name; + + synchronize(function() { + if ( config.currentModule ) { + QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all ); + } + + config.currentModule = name; + config.moduleTestEnvironment = testEnvironment; + config.moduleStats = { all: 0, bad: 0 }; + + QUnit.moduleStart( name, testEnvironment ); + }, true); + }, + + asyncTest: function(testName, expected, callback) { + if ( arguments.length === 2 ) { + callback = expected; + expected = 0; + } + + QUnit.test(testName, expected, callback, true); + }, + + test: function(testName, expected, callback, async) { + var name = testName, testEnvironment, testEnvironmentArg; + + if ( arguments.length === 2 ) { + callback = expected; + expected = null; + } + // is 2nd argument a testEnvironment? + if ( expected && typeof expected === 'object') { + testEnvironmentArg = expected; + expected = null; + } + + if ( config.currentModule ) { + name = config.currentModule + " module: " + name; + } + + if ( !validTest(name) ) { + return; + } + + synchronize(function() { + + testEnvironment = extend({ + setup: function() {}, + teardown: function() {} + }, config.moduleTestEnvironment); + if (testEnvironmentArg) { + extend(testEnvironment,testEnvironmentArg); + } + + QUnit.testStart( testName, testEnvironment ); + + // allow utility functions to access the current test environment + QUnit.current_testEnvironment = testEnvironment; + + config.assertions = []; + config.expected = expected; + + var tests = id("qunit-tests"); + if (tests) { + var b = document.createElement("strong"); + b.innerHTML = "Running " + name; + var li = document.createElement("li"); + li.appendChild( b ); + li.id = "current-test-output"; + tests.appendChild( li ) + } + + try { + if ( !config.pollution ) { + saveGlobal(); + } + + testEnvironment.setup.call(testEnvironment); + } catch(e) { + QUnit.ok( false, "Setup failed on " + name + ": " + e.message ); + } + }, true); + + synchronize(function() { + if ( async ) { + QUnit.stop(); + } + + try { + callback.call(testEnvironment); + } catch(e) { + fail("Test " + name + " died, exception and test follows", e, callback); + QUnit.ok( false, "Died on test #" + (config.assertions.length + 1) + ": " + e.message ); + // else next test will carry the responsibility + saveGlobal(); + + // Restart the tests if they're blocking + if ( config.blocking ) { + start(); + } + } + }, true); + + synchronize(function() { + try { + checkPollution(); + testEnvironment.teardown.call(testEnvironment); + } catch(e) { + QUnit.ok( false, "Teardown failed on " + name + ": " + e.message ); + } + }, true); + + synchronize(function() { + try { + QUnit.reset(); + } catch(e) { + fail("reset() failed, following Test " + name + ", exception and reset fn follows", e, reset); + } + + if ( config.expected && config.expected != config.assertions.length ) { + QUnit.ok( false, "Expected " + config.expected + " assertions, but " + config.assertions.length + " were run" ); + } + + var good = 0, bad = 0, + tests = id("qunit-tests"); + + config.stats.all += config.assertions.length; + config.moduleStats.all += config.assertions.length; + + if ( tests ) { + var ol = document.createElement("ol"); + ol.style.display = "none"; + + for ( var i = 0; i < config.assertions.length; i++ ) { + var assertion = config.assertions[i]; + + var li = document.createElement("li"); + li.className = assertion.result ? "pass" : "fail"; + li.appendChild(document.createTextNode(assertion.message || "(no message)")); + ol.appendChild( li ); + + if ( assertion.result ) { + good++; + } else { + bad++; + config.stats.bad++; + config.moduleStats.bad++; + } + } + + var b = document.createElement("strong"); + b.innerHTML = name + " <b style='color:black;'>(<b class='fail'>" + bad + "</b>, <b class='pass'>" + good + "</b>, " + config.assertions.length + ")</b>"; + + addEvent(b, "click", function() { + var next = b.nextSibling, display = next.style.display; + next.style.display = display === "none" ? "block" : "none"; + }); + + addEvent(b, "dblclick", function(e) { + var target = e && e.target ? e.target : window.event.srcElement; + if ( target.nodeName.toLowerCase() === "strong" ) { + var text = "", node = target.firstChild; + + while ( node.nodeType === 3 ) { + text += node.nodeValue; + node = node.nextSibling; + } + + text = text.replace(/(^\s*|\s*$)/g, ""); + + if ( window.location ) { + window.location.href = window.location.href.match(/^(.+?)(\?.*)?$/)[1] + "?" + encodeURIComponent(text); + } + } + }); + + var li = id("current-test-output"); + li.id = ""; + li.className = bad ? "fail" : "pass"; + li.removeChild( li.firstChild ); + li.appendChild( b ); + li.appendChild( ol ); + + if ( bad ) { + var toolbar = id("qunit-testrunner-toolbar"); + if ( toolbar ) { + toolbar.style.display = "block"; + id("qunit-filter-pass").disabled = null; + id("qunit-filter-missing").disabled = null; + } + } + + } else { + for ( var i = 0; i < config.assertions.length; i++ ) { + if ( !config.assertions[i].result ) { + bad++; + config.stats.bad++; + config.moduleStats.bad++; + } + } + } + + QUnit.testDone( testName, bad, config.assertions.length ); + + if ( !window.setTimeout && !config.queue.length ) { + done(); + } + }, true); + + if ( window.setTimeout && !config.doneTimer ) { + config.doneTimer = window.setTimeout(function(){ + if ( !config.queue.length ) { + done(); + } else { + synchronize( done ); + } + }, 13); + } + }, + + /** + * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through. + */ + expect: function(asserts) { + config.expected = asserts; + }, + + /** + * Asserts true. + * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); + */ + ok: function(a, msg) { + QUnit.log(a, msg); + + config.assertions.push({ + result: !!a, + message: msg + }); + }, + + /** + * Checks that the first two arguments are equal, with an optional message. + * Prints out both actual and expected values. + * + * Prefered to ok( actual == expected, message ) + * + * @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." ); + * + * @param Object actual + * @param Object expected + * @param String message (optional) + */ + equal: function(actual, expected, message) { + push(expected == actual, actual, expected, message); + }, + + notEqual: function(actual, expected, message) { + push(expected != actual, actual, expected, message); + }, + + deepEqual: function(actual, expected, message) { + push(QUnit.equiv(actual, expected), actual, expected, message); + }, + + notDeepEqual: function(actual, expected, message) { + push(!QUnit.equiv(actual, expected), actual, expected, message); + }, + + strictEqual: function(actual, expected, message) { + push(expected === actual, actual, expected, message); + }, + + notStrictEqual: function(actual, expected, message) { + push(expected !== actual, actual, expected, message); + }, + + start: function() { + // A slight delay, to avoid any current callbacks + if ( window.setTimeout ) { + window.setTimeout(function() { + if ( config.timeout ) { + clearTimeout(config.timeout); + } + + config.blocking = false; + process(); + }, 13); + } else { + config.blocking = false; + process(); + } + }, + + stop: function(timeout) { + config.blocking = true; + + if ( timeout && window.setTimeout ) { + config.timeout = window.setTimeout(function() { + QUnit.ok( false, "Test timed out" ); + QUnit.start(); + }, timeout); + } + }, + + /** + * Resets the test setup. Useful for tests that modify the DOM. + */ + reset: function() { + if ( window.jQuery ) { + jQuery("#main").html( config.fixture ); + jQuery.event.global = {}; + jQuery.ajaxSettings = extend({}, config.ajaxSettings); + } + }, + restart : function(){ + this.init(); + for(var i =0; i < config.cachelist.length; i++){ + synchronize(config.cachelist[i]); + } + if (window.setTimeout && !config.doneTimer) { + config.doneTimer = window.setTimeout(function(){ + if (!config.queue.length) { + done(); + } + else { + synchronize(done); + } + }, 13); + } + }, + /** + * Trigger an event on an element. + * + * @example triggerEvent( document.body, "click" ); + * + * @param DOMElement elem + * @param String type + */ + triggerEvent: function( elem, type, event ) { + if ( document.createEvent ) { + event = document.createEvent("MouseEvents"); + event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView, + 0, 0, 0, 0, 0, false, false, false, false, 0, null); + elem.dispatchEvent( event ); + + } else if ( elem.fireEvent ) { + elem.fireEvent("on"+type); + } + }, + + // Safe object type checking + is: function( type, obj ) { + return Object.prototype.toString.call( obj ) === "[object "+ type +"]"; + }, + + // Logging callbacks + done: function(failures, total) {}, + log: function(result, message) {}, + testStart: function(name, testEnvironment) {}, + testDone: function(name, failures, total) {}, + moduleStart: function(name, testEnvironment) {}, + moduleDone: function(name, failures, total) {} +}; + +// Backwards compatibility, deprecated +QUnit.equals = QUnit.equal; +QUnit.same = QUnit.deepEqual; + +// Maintain internal state +var config = { + // The queue of tests to run + queue: [], + + // block until document ready + blocking: true, + //a list of everything to run + cachelist : [] +}; + +// Load paramaters +(function() { + var location = window.location || { search: "", protocol: "file:" }, + GETParams = location.search.slice(1).split('&'); + + for ( var i = 0; i < GETParams.length; i++ ) { + GETParams[i] = decodeURIComponent( GETParams[i] ); + if ( GETParams[i] === "noglobals" ) { + GETParams.splice( i, 1 ); + i--; + config.noglobals = true; + } else if ( GETParams[i].search('=') > -1 ) { + GETParams.splice( i, 1 ); + i--; + } + } + + // restrict modules/tests by get parameters + config.filters = GETParams; + + // Figure out if we're running the tests from a server or not + QUnit.isLocal = !!(location.protocol === 'file:'); +})(); + +// Expose the API as global variables, unless an 'exports' +// object exists, in that case we assume we're in CommonJS +if ( typeof exports === "undefined" || typeof require === "undefined" ) { + extend(window, QUnit); + window.QUnit = QUnit; +} else { + extend(exports, QUnit); + exports.QUnit = QUnit; +} + +QUnit.config = config; + +if ( typeof document === "undefined" || document.readyState === "complete" ) { + config.autorun = true; +} + +addEvent(window, "load", function() { + // Initialize the config, saving the execution queue + var oldconfig = extend({}, config); + QUnit.init(); + extend(config, oldconfig); + + config.blocking = false; + + var userAgent = id("qunit-userAgent"); + if ( userAgent ) { + userAgent.innerHTML = navigator.userAgent; + } + + var toolbar = id("qunit-testrunner-toolbar"); + if ( toolbar ) { + toolbar.style.display = "none"; + + var filter = document.createElement("input"); + filter.type = "checkbox"; + filter.id = "qunit-filter-pass"; + filter.disabled = true; + addEvent( filter, "click", function() { + var li = document.getElementsByTagName("li"); + for ( var i = 0; i < li.length; i++ ) { + if ( li[i].className.indexOf("pass") > -1 ) { + li[i].style.display = filter.checked ? "none" : ""; + } + } + }); + toolbar.appendChild( filter ); + + var label = document.createElement("label"); + label.setAttribute("for", "qunit-filter-pass"); + label.innerHTML = "Hide passed tests"; + toolbar.appendChild( label ); + + var missing = document.createElement("input"); + missing.type = "checkbox"; + missing.id = "qunit-filter-missing"; + missing.disabled = true; + addEvent( missing, "click", function() { + var li = document.getElementsByTagName("li"); + for ( var i = 0; i < li.length; i++ ) { + if ( li[i].className.indexOf("fail") > -1 && li[i].innerHTML.indexOf('missing test - untested code is broken code') > - 1 ) { + li[i].parentNode.parentNode.style.display = missing.checked ? "none" : "block"; + } + } + }); + toolbar.appendChild( missing ); + + label = document.createElement("label"); + label.setAttribute("for", "qunit-filter-missing"); + label.innerHTML = "Hide missing tests (untested code is broken code)"; + toolbar.appendChild( label ); + } + + var main = id('main'); + if ( main ) { + config.fixture = main.innerHTML; + } + + if ( window.jQuery ) { + config.ajaxSettings = window.jQuery.ajaxSettings; + } + + if (config.autostart) { + QUnit.start(); + } +}); + +function done() { + if ( config.doneTimer && window.clearTimeout ) { + window.clearTimeout( config.doneTimer ); + config.doneTimer = null; + } + + if ( config.queue.length ) { + config.doneTimer = window.setTimeout(function(){ + if ( !config.queue.length ) { + done(); + } else { + synchronize( done ); + } + }, 13); + + return; + } + + config.autorun = true; + + // Log the last module results + if ( config.currentModule ) { + QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all ); + } + + var banner = id("qunit-banner"), + tests = id("qunit-tests"), + html = ['Tests completed in ', + +new Date - config.started, ' milliseconds.<br/>', + '<span class="passed">', config.stats.all - config.stats.bad, '</span> tests of <span class="total">', config.stats.all, '</span> passed, <span class="failed">', config.stats.bad,'</span> failed.'].join(''); + + if ( banner ) { + banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass"); + } + + if ( tests ) { + var result = id("qunit-testresult"); + + if ( !result ) { + result = document.createElement("p"); + result.id = "qunit-testresult"; + result.className = "result"; + tests.parentNode.insertBefore( result, tests.nextSibling ); + } + + result.innerHTML = html; + } + + QUnit.done( config.stats.bad, config.stats.all ); +} + +function validTest( name ) { + var i = config.filters.length, + run = false; + + if ( !i ) { + return true; + } + + while ( i-- ) { + var filter = config.filters[i], + not = filter.charAt(0) == '!'; + + if ( not ) { + filter = filter.slice(1); + } + + if ( name.indexOf(filter) !== -1 ) { + return !not; + } + + if ( not ) { + run = true; + } + } + + return run; +} + +function push(result, actual, expected, message) { + message = message || (result ? "okay" : "failed"); + QUnit.ok( result, message + ", expected: " + QUnit.jsDump.parse(expected) + " result: " + QUnit.jsDump.parse(actual) ); +} + +function synchronize( callback , save) { + config.queue.push( callback ); + if(save){ + config.cachelist.push( callback ) + } + if ( config.autorun && !config.blocking ) { + process(); + } +} +function process() { + var start = (new Date()).getTime(); + + while ( config.queue.length && !config.blocking ) { + if ( config.updateRate <= 0 || (((new Date()).getTime() - start) < config.updateRate) ) { + config.queue.shift()(); + + } else { + setTimeout( process, 13 ); + break; + } + } +} + +function saveGlobal() { + config.pollution = []; + + if ( config.noglobals ) { + for ( var key in window ) { + config.pollution.push( key ); + } + } +} + +function checkPollution( name ) { + var old = config.pollution; + saveGlobal(); + + var newGlobals = diff( old, config.pollution ); + if ( newGlobals.length > 0 ) { + ok( false, "Introduced global variable(s): " + newGlobals.join(", ") ); + config.expected++; + } + + var deletedGlobals = diff( config.pollution, old ); + if ( deletedGlobals.length > 0 ) { + ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") ); + config.expected++; + } +} + +// returns a new Array with the elements that are in a but not in b +function diff( a, b ) { + var result = a.slice(); + for ( var i = 0; i < result.length; i++ ) { + for ( var j = 0; j < b.length; j++ ) { + if ( result[i] === b[j] ) { + result.splice(i, 1); + i--; + break; + } + } + } + return result; +} + +function fail(message, exception, callback) { + if ( typeof console !== "undefined" && console.error && console.warn ) { + console.error(message); + console.error(exception); + console.warn(callback.toString()); + + } else if ( window.opera && opera.postError ) { + opera.postError(message, exception, callback.toString); + } +} + +function extend(a, b) { + for ( var prop in b ) { + a[prop] = b[prop]; + } + + return a; +} + +function addEvent(elem, type, fn) { + if ( elem.addEventListener ) { + elem.addEventListener( type, fn, false ); + } else if ( elem.attachEvent ) { + elem.attachEvent( "on" + type, fn ); + } else { + fn(); + } +} + +function id(name) { + return !!(typeof document !== "undefined" && document && document.getElementById) && + document.getElementById( name ); +} + +// Test for equality any JavaScript type. +// Discussions and reference: http://philrathe.com/articles/equiv +// Test suites: http://philrathe.com/tests/equiv +// Author: Philippe Rathé <prathe@gmail.com> +QUnit.equiv = function () { + + var innerEquiv; // the real equiv function + var callers = []; // stack to decide between skip/abort functions + var parents = []; // stack to avoiding loops from circular referencing + + + // Determine what is o. + function hoozit(o) { + if (QUnit.is("String", o)) { + return "string"; + + } else if (QUnit.is("Boolean", o)) { + return "boolean"; + + } else if (QUnit.is("Number", o)) { + + if (isNaN(o)) { + return "nan"; + } else { + return "number"; + } + + } else if (typeof o === "undefined") { + return "undefined"; + + // consider: typeof null === object + } else if (o === null) { + return "null"; + + // consider: typeof [] === object + } else if (QUnit.is( "Array", o)) { + return "array"; + + // consider: typeof new Date() === object + } else if (QUnit.is( "Date", o)) { + return "date"; + + // consider: /./ instanceof Object; + // /./ instanceof RegExp; + // typeof /./ === "function"; // => false in IE and Opera, + // true in FF and Safari + } else if (QUnit.is( "RegExp", o)) { + return "regexp"; + + } else if (typeof o === "object") { + return "object"; + + } else if (QUnit.is( "Function", o)) { + return "function"; + } else { + return undefined; + } + } + + // Call the o related callback with the given arguments. + function bindCallbacks(o, callbacks, args) { + var prop = hoozit(o); + if (prop) { + if (hoozit(callbacks[prop]) === "function") { + return callbacks[prop].apply(callbacks, args); + } else { + return callbacks[prop]; // or undefined + } + } + } + + var callbacks = function () { + + // for string, boolean, number and null + function useStrictEquality(b, a) { + if (b instanceof a.constructor || a instanceof b.constructor) { + // to catch short annotaion VS 'new' annotation of a declaration + // e.g. var i = 1; + // var j = new Number(1); + return a == b; + } else { + return a === b; + } + } + + return { + "string": useStrictEquality, + "boolean": useStrictEquality, + "number": useStrictEquality, + "null": useStrictEquality, + "undefined": useStrictEquality, + + "nan": function (b) { + return isNaN(b); + }, + + "date": function (b, a) { + return hoozit(b) === "date" && a.valueOf() === b.valueOf(); + }, + + "regexp": function (b, a) { + return hoozit(b) === "regexp" && + a.source === b.source && // the regex itself + a.global === b.global && // and its modifers (gmi) ... + a.ignoreCase === b.ignoreCase && + a.multiline === b.multiline; + }, + + // - skip when the property is a method of an instance (OOP) + // - abort otherwise, + // initial === would have catch identical references anyway + "function": function () { + var caller = callers[callers.length - 1]; + return caller !== Object && + typeof caller !== "undefined"; + }, + + "array": function (b, a) { + var i, j, loop; + var len; + + // b could be an object literal here + if ( ! (hoozit(b) === "array")) { + return false; + } + + len = a.length; + if (len !== b.length) { // safe and faster + return false; + } + + //track reference to avoid circular references + parents.push(a); + for (i = 0; i < len; i++) { + loop = false; + for(j=0;j<parents.length;j++){ + if(parents[j] === a[i]){ + loop = true;//dont rewalk array + } + } + if (!loop && ! innerEquiv(a[i], b[i])) { + parents.pop(); + return false; + } + } + parents.pop(); + return true; + }, + + "object": function (b, a) { + var i, j, loop; + var eq = true; // unless we can proove it + var aProperties = [], bProperties = []; // collection of strings + + // comparing constructors is more strict than using instanceof + if ( a.constructor !== b.constructor) { + return false; + } + + // stack constructor before traversing properties + callers.push(a.constructor); + //track reference to avoid circular references + parents.push(a); + + for (i in a) { // be strict: don't ensures hasOwnProperty and go deep + loop = false; + for(j=0;j<parents.length;j++){ + if(parents[j] === a[i]) + loop = true; //don't go down the same path twice + } + aProperties.push(i); // collect a's properties + + if (!loop && ! innerEquiv(a[i], b[i])) { + eq = false; + break; + } + } + + callers.pop(); // unstack, we are done + parents.pop(); + + for (i in b) { + bProperties.push(i); // collect b's properties + } + + // Ensures identical properties name + return eq && innerEquiv(aProperties.sort(), bProperties.sort()); + } + }; + }(); + + innerEquiv = function () { // can take multiple arguments + var args = Array.prototype.slice.apply(arguments); + if (args.length < 2) { + return true; // end transition + } + + return (function (a, b) { + if (a === b) { + return true; // catch the most you can + } else if (a === null || b === null || typeof a === "undefined" || typeof b === "undefined" || hoozit(a) !== hoozit(b)) { + return false; // don't lose time with error prone cases + } else { + return bindCallbacks(a, callbacks, [b, a]); + } + + // apply transition with (1..n) arguments + })(args[0], args[1]) && arguments.callee.apply(this, args.splice(1, args.length -1)); + }; + + return innerEquiv; + +}(); + +/** + * jsDump + * Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com + * Licensed under BSD (http://www.opensource.org/licenses/bsd-license.php) + * Date: 5/15/2008 + * @projectDescription Advanced and extensible data dumping for Javascript. + * @version 1.0.0 + * @author Ariel Flesler + * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html} + */ +QUnit.jsDump = (function() { + function quote( str ) { + return '"' + str.toString().replace(/"/g, '\\"') + '"'; + }; + function literal( o ) { + return o + ''; + }; + function join( pre, arr, post ) { + var s = jsDump.separator(), + base = jsDump.indent(), + inner = jsDump.indent(1); + if ( arr.join ) + arr = arr.join( ',' + s + inner ); + if ( !arr ) + return pre + post; + return [ pre, inner + arr, base + post ].join(s); + }; + function array( arr ) { + var i = arr.length, ret = Array(i); + this.up(); + while ( i-- ) + ret[i] = this.parse( arr[i] ); + this.down(); + return join( '[', ret, ']' ); + }; + + var reName = /^function (\w+)/; + + var jsDump = { + parse:function( obj, type ) { //type is used mostly internally, you can fix a (custom)type in advance + var parser = this.parsers[ type || this.typeOf(obj) ]; + type = typeof parser; + + return type == 'function' ? parser.call( this, obj ) : + type == 'string' ? parser : + this.parsers.error; + }, + typeOf:function( obj ) { + var type; + if ( obj === null ) { + type = "null"; + } else if (typeof obj === "undefined") { + type = "undefined"; + } else if (QUnit.is("RegExp", obj)) { + type = "regexp"; + } else if (QUnit.is("Date", obj)) { + type = "date"; + } else if (QUnit.is("Function", obj)) { + type = "function"; + } else if (obj.setInterval && obj.document && !obj.nodeType) { + type = "window"; + } else if (obj.nodeType === 9) { + type = "document"; + } else if (obj.nodeType) { + type = "node"; + } else if (typeof obj === "object" && typeof obj.length === "number" && obj.length >= 0) { + type = "array"; + } else { + type = typeof obj; + } + return type; + }, + separator:function() { + return this.multiline ? this.HTML ? '<br />' : '\n' : this.HTML ? ' ' : ' '; + }, + indent:function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing + if ( !this.multiline ) + return ''; + var chr = this.indentChar; + if ( this.HTML ) + chr = chr.replace(/\t/g,' ').replace(/ /g,' '); + return Array( this._depth_ + (extra||0) ).join(chr); + }, + up:function( a ) { + this._depth_ += a || 1; + }, + down:function( a ) { + this._depth_ -= a || 1; + }, + setParser:function( name, parser ) { + this.parsers[name] = parser; + }, + // The next 3 are exposed so you can use them + quote:quote, + literal:literal, + join:join, + // + _depth_: 1, + // This is the list of parsers, to modify them, use jsDump.setParser + parsers:{ + window: '[Window]', + document: '[Document]', + error:'[ERROR]', //when no parser is found, shouldn't happen + unknown: '[Unknown]', + 'null':'null', + undefined:'undefined', + 'function':function( fn ) { + var ret = 'function', + name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE + if ( name ) + ret += ' ' + name; + ret += '('; + + ret = [ ret, this.parse( fn, 'functionArgs' ), '){'].join(''); + return join( ret, this.parse(fn,'functionCode'), '}' ); + }, + array: array, + nodelist: array, + arguments: array, + object:function( map ) { + var ret = [ ]; + this.up(); + for ( var key in map ) + ret.push( this.parse(key,'key') + ': ' + this.parse(map[key]) ); + this.down(); + return join( '{', ret, '}' ); + }, + node:function( node ) { + var open = this.HTML ? '<' : '<', + close = this.HTML ? '>' : '>'; + + var tag = node.nodeName.toLowerCase(), + ret = open + tag; + + for ( var a in this.DOMAttrs ) { + var val = node[this.DOMAttrs[a]]; + if ( val ) + ret += ' ' + a + '=' + this.parse( val, 'attribute' ); + } + return ret + close + open + '/' + tag + close; + }, + functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function + var l = fn.length; + if ( !l ) return ''; + + var args = Array(l); + while ( l-- ) + args[l] = String.fromCharCode(97+l);//97 is 'a' + return ' ' + args.join(', ') + ' '; + }, + key:quote, //object calls it internally, the key part of an item in a map + functionCode:'[code]', //function calls it internally, it's the content of the function + attribute:quote, //node calls it internally, it's an html attribute value + string:quote, + date:quote, + regexp:literal, //regex + number:literal, + 'boolean':literal + }, + DOMAttrs:{//attributes to dump from nodes, name=>realName + id:'id', + name:'name', + 'class':'className' + }, + HTML:false,//if true, entities are escaped ( <, >, \t, space and \n ) + indentChar:' ',//indentation unit + multiline:false //if true, items in a collection, are separated by a \n, else just a space. + }; + + return jsDump; +})(); + +})(this); \ No newline at end of file diff --git a/browserid/static/funcunit/syn/syn.js b/browserid/static/funcunit/syn/syn.js new file mode 100644 index 0000000000000000000000000000000000000000..10c023721030340a6c84dddb8d3ac584982209de --- /dev/null +++ b/browserid/static/funcunit/syn/syn.js @@ -0,0 +1,7 @@ +steal( + 'synthetic', + 'mouse', + 'browsers', + 'key', + 'drag/drag' +); \ No newline at end of file diff --git a/browserid/static/funcunit/syn/synthetic.html b/browserid/static/funcunit/syn/synthetic.html new file mode 100644 index 0000000000000000000000000000000000000000..44e6b1c15f09ba83a206652c3d68b9a704836f4f --- /dev/null +++ b/browserid/static/funcunit/syn/synthetic.html @@ -0,0 +1,85 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" + "http://www.w3.org/TR/html4/strict.dtd"> +<html lang="en"> + <head> + <title>Synthetic Demo</title> + <style type='text/css'> + body {font-family: verdana; padding: 0px; margin: 0px;} + + #app { + + border-left: none; + border-right: none; + border-top: none; + border-bottom: solid 2px black; + overflow: hidden; + top: 0px; + + + width: 100%; + } + + + iframe { + height: 100%; + border: none; + width: 100%; + padding: 0px; + margin: 0px; + } + #code { + + padding: 0px; + margin: 0 0 0 210px; + border: none; + overflow: auto; + height: 100%; + } + + a { + outline: none + } + #bottom { + /*position: fixed;*/ + width: 100%; + bottom: 0px; + height: 196px; + border-top: solid 2px #E7ECEE; + border-bottom: solid 2px #E7ECEE; + } + #run { + float: left; + padding-top: 80px; + height: 118px; + width: 196px; + display: block; + text-align: center; + vertical-align: center; + font-size: 1.4em; + font-weight: bolder; + border-right: solid 2px #E7ECEE; + border-left: solid 2px #E7ECEE; + text-decoration: none; + } + #run:hover { + text-decoration: underline; + } + </style> + </head> + <body> + <div id='app'></div> + <div id='bottom'> + <a href="javascript://" id='run'>Replay ></a> + <pre id='code'>Click the tabs above to create replay code.</pre> + + </div> +<script type='text/javascript' src='../../steal/steal.js'></script> +<script type='text/javascript'> + steal.plugins('funcunit/syn','mxui/filler').start(); +</script> +<script type='text/javascript' src='demo/record.js'></script> +<script type='text/javascript' id="demo-source"> + +</script> + </body> +</html> \ No newline at end of file diff --git a/browserid/static/funcunit/syn/synthetic.js b/browserid/static/funcunit/syn/synthetic.js new file mode 100644 index 0000000000000000000000000000000000000000..1bb9ba83edf16ce243f8c23a6354e998874f8b5c --- /dev/null +++ b/browserid/static/funcunit/syn/synthetic.js @@ -0,0 +1,832 @@ +steal.then(function(){ +(function() { + var extend = function( d, s ) { + var p; + for (p in s) { + d[p] = s[p]; + } + return d; + }, + // only uses browser detection for key events + browser = { + msie: !! (window.attachEvent && !window.opera), + opera: !! window.opera, + webkit: navigator.userAgent.indexOf('AppleWebKit/') > -1, + safari: navigator.userAgent.indexOf('AppleWebKit/') > -1 && navigator.userAgent.indexOf('Chrome/') === -1, + gecko: navigator.userAgent.indexOf('Gecko') > -1, + mobilesafari: !! navigator.userAgent.match(/Apple.*Mobile.*Safari/), + rhino: navigator.userAgent.match(/Rhino/) && true + }, + createEventObject = function( type, options, element ) { + var event = element.ownerDocument.createEventObject(); + return extend(event, options); + }, + data = {}, + id = 1, + expando = "_synthetic" + new Date().getTime(), + bind, unbind, key = /keypress|keyup|keydown/, + page = /load|unload|abort|error|select|change|submit|reset|focus|blur|resize|scroll/, + //this is maintained so we can click on html and blur the active element + activeElement, + + /** + * @class Syn + * @download funcunit/dist/syn.js + * @test funcunit/synthetic/qunit.html + * Syn is used to simulate user actions. It creates synthetic events and + * performs their default behaviors. + * + * <h2>Basic Use</h2> + * The following clicks an input element with <code>id='description'</code> + * and then types <code>'Hello World'</code>. + * + @codestart + Syn.click({},'description') + .type("Hello World") + @codeend + * <h2>User Actions and Events</h2> + * <p>Syn is typically used to simulate user actions as opposed to triggering events. Typing characters + * is an example of a user action. The keypress that represents an <code>'a'</code> + * character being typed is an example of an event. + * </p> + * <p> + * While triggering events is supported, it's much more useful to simulate actual user behavior. The + * following actions are supported by Syn: + * </p> + * <ul> + * <li><code>[Syn.prototype.click click]</code> - a mousedown, focus, mouseup, and click.</li> + * <li><code>[Syn.prototype.dblclick dblclick]</code> - two <code>click!</code> events followed by a <code>dblclick</code>.</li> + * <li><code>[Syn.prototype.key key]</code> - types a single character (keydown, keypress, keyup).</li> + * <li><code>[Syn.prototype.type type]</code> - types multiple characters into an element.</li> + * <li><code>[Syn.prototype.move move]</code> - moves the mouse from one position to another (triggering mouseover / mouseouts).</li> + * <li><code>[Syn.prototype.drag drag]</code> - a mousedown, followed by mousemoves, and a mouseup.</li> + * </ul> + * All actions run asynchronously. + * Click on the links above for more + * information on how to use the specific action. + * <h2>Asynchronous Callbacks</h2> + * Actions don't complete immediately. This is almost + * entirely because <code>focus()</code> + * doesn't run immediately in IE. + * If you provide a callback function to Syn, it will + * be called after the action is completed. + * <br/>The following checks that "Hello World" was entered correctly: + @codestart + Syn.click({},'description') + .type("Hello World", function(){ + + ok("Hello World" == document.getElementById('description').value) + }) + @codeend + <h2>Asynchronous Chaining</h2> + <p>You might have noticed the [Syn.prototype.then then] method. It provides chaining + so you can do a sequence of events with a single (final) callback. + </p><p> + If an element isn't provided to then, it uses the previous Syn's element. + </p> + The following does a lot of stuff before checking the result: + @codestart + Syn.type('ice water','title') + .type('ice and water','description') + .click({},'create') + .drag({to: 'favorites'},'newRecipe', + function(){ + ok($('#newRecipe').parents('#favorites').length); + }) + @codeend + + <h2>jQuery Helper</h2> + If jQuery is present, Syn adds a triggerSyn helper you can use like: + @codestart + $("#description").triggerSyn("type","Hello World"); + @codeend + * <h2>Key Event Recording</h2> + * <p>Every browser has very different rules for dispatching key events. + * As there is no way to feature detect how a browser handles key events, + * synthetic uses a description of how the browser behaves generated + * by a recording application. </p> + * <p> + * If you want to support a browser not currently supported, you can + * record that browser's key event description and add it to + * <code>Syn.key.browsers</code> by it's navigator agent. + * </p> + @codestart + Syn.key.browsers["Envjs\ Resig/20070309 PilotFish/1.2.0.10\1.6"] = { + 'prevent': + {"keyup":[],"keydown":["char","keypress"],"keypress":["char"]}, + 'character': + { ... } + } + @codeend + * <h2>Limitations</h2> + * Syn fully supports IE 6+, FF 3+, Chrome, Safari, Opera 10+. + * With FF 1+, drag / move events are only partially supported. They will + * not trigger mouseover / mouseout events.<br/> + * Safari crashes when a mousedown is triggered on a select. Syn will not + * create this event. + * <h2>Contributing to Syn</h2> + * Have we missed something? We happily accept patches. The following are + * important objects and properties of Syn: + * <ul> + * <li><code>Syn.create</code> - contains methods to setup, convert options, and create an event of a specific type.</li> + * <li><code>Syn.defaults</code> - default behavior by event type (except for keys).</li> + * <li><code>Syn.key.defaults</code> - default behavior by key.</li> + * <li><code>Syn.keycodes</code> - supported keys you can type.</li> + * </ul> + * <h2>Roll Your Own Functional Test Framework</h2> + * <p>Syn is really the foundation of JavaScriptMVC's functional testing framework - [FuncUnit]. + * But, we've purposely made Syn work without any dependencies in the hopes that other frameworks or + * testing solutions can use it as well. + * </p> + * @constructor + * Creates a synthetic event on the element. + * @param {Object} type + * @param {Object} options + * @param {Object} element + * @param {Object} callback + * @return Syn + */ + Syn = function( type, options, element, callback ) { + return (new Syn.init(type, options, element, callback)); + }; + + bind = function( el, ev, f ) { + return el.addEventListener ? el.addEventListener(ev, f, false) : el.attachEvent("on" + ev, f); + }; + unbind = function( el, ev, f ) { + return el.addEventListener ? el.removeEventListener(ev, f, false) : el.detachEvent("on" + ev, f); + }; + + /** + * @Static + */ + extend(Syn, { + /** + * Creates a new synthetic event instance + * @hide + * @param {Object} type + * @param {Object} options + * @param {Object} element + * @param {Object} callback + */ + init: function( type, options, element, callback ) { + var args = Syn.args(options, element, callback), + self = this; + this.queue = []; + this.element = args.element; + + //run event + if ( typeof this[type] === "function" ) { + this[type](args.options, args.element, function( defaults, el ) { + args.callback && args.callback.apply(self, arguments); + self.done.apply(self, arguments); + }); + } else { + this.result = Syn.trigger(type, args.options, args.element); + args.callback && args.callback.call(this, args.element, this.result); + } + }, + jquery: function( el, fast ) { + if ( window.FuncUnit && window.FuncUnit.jquery ) { + return window.FuncUnit.jquery; + } + if ( el ) { + return Syn.helpers.getWindow(el).jQuery || window.jQuery; + } + else { + return window.jQuery; + } + }, + /** + * Returns an object with the args for a Syn. + * @hide + * @return {Object} + */ + args: function() { + var res = {}, + i = 0; + for ( ; i < arguments.length; i++ ) { + if ( typeof arguments[i] === 'function' ) { + res.callback = arguments[i]; + } else if ( arguments[i] && arguments[i].jquery ) { + res.element = arguments[i][0]; + } else if ( arguments[i] && arguments[i].nodeName ) { + res.element = arguments[i]; + } else if ( res.options && typeof arguments[i] === 'string' ) { //we can get by id + res.element = document.getElementById(arguments[i]); + } + else if ( arguments[i] ) { + res.options = arguments[i]; + } + } + return res; + }, + click: function( options, element, callback ) { + Syn('click!', options, element, callback); + }, + /** + * @attribute defaults + * Default actions for events. Each default function is called with this as its + * element. It should return true if a timeout + * should happen after it. If it returns an element, a timeout will happen + * and the next event will happen on that element. + */ + defaults: { + focus: function() { + if (!Syn.support.focusChanges ) { + var element = this, + nodeName = element.nodeName.toLowerCase(); + Syn.data(element, "syntheticvalue", element.value); + + //TODO, this should be textarea too + //and this might be for only text style inputs ... hmmmmm .... + if ( nodeName === "input" || nodeName === "textarea" ) { + bind(element, "blur", function() { + if ( Syn.data(element, "syntheticvalue") != element.value ) { + + Syn.trigger("change", {}, element); + } + unbind(element, "blur", arguments.callee); + }); + + } + } + }, + submit: function() { + Syn.onParents(this, function( el ) { + if ( el.nodeName.toLowerCase() === 'form' ) { + el.submit(); + return false; + } + }); + } + }, + changeOnBlur: function( element, prop, value ) { + + bind(element, "blur", function() { + if ( value !== element[prop] ) { + Syn.trigger("change", {}, element); + } + unbind(element, "blur", arguments.callee); + }); + + }, + /** + * Returns the closest element of a particular type. + * @hide + * @param {Object} el + * @param {Object} type + */ + closest: function( el, type ) { + while ( el && el.nodeName.toLowerCase() !== type.toLowerCase() ) { + el = el.parentNode; + } + return el; + }, + /** + * adds jQuery like data (adds an expando) and data exists FOREVER :) + * @hide + * @param {Object} el + * @param {Object} key + * @param {Object} value + */ + data: function( el, key, value ) { + var d; + if (!el[expando] ) { + el[expando] = id++; + } + if (!data[el[expando]] ) { + data[el[expando]] = {}; + } + d = data[el[expando]]; + if ( value ) { + data[el[expando]][key] = value; + } else { + return data[el[expando]][key]; + } + }, + /** + * Calls a function on the element and all parents of the element until the function returns + * false. + * @hide + * @param {Object} el + * @param {Object} func + */ + onParents: function( el, func ) { + var res; + while ( el && res !== false ) { + res = func(el); + el = el.parentNode; + } + return el; + }, + //regex to match focusable elements + focusable: /^(a|area|frame|iframe|label|input|select|textarea|button|html|object)$/i, + /** + * Returns if an element is focusable + * @hide + * @param {Object} elem + */ + isFocusable: function( elem ) { + var attributeNode; + return (this.focusable.test(elem.nodeName) || + ((attributeNode = elem.getAttributeNode("tabIndex")) + && attributeNode.specified)) && Syn.isVisible(elem); + }, + /** + * Returns if an element is visible or not + * @hide + * @param {Object} elem + */ + isVisible: function( elem ) { + return (elem.offsetWidth && elem.offsetHeight) || (elem.clientWidth && elem.clientHeight); + }, + /** + * Gets the tabIndex as a number or null + * @hide + * @param {Object} elem + */ + tabIndex: function( elem ) { + var attributeNode = elem.getAttributeNode("tabIndex"); + return attributeNode && attributeNode.specified && (parseInt(elem.getAttribute('tabIndex')) || 0); + }, + bind: bind, + unbind: unbind, + browser: browser, + //some generic helpers + helpers: { + createEventObject: createEventObject, + createBasicStandardEvent: function( type, defaults, doc ) { + var event; + try { + event = doc.createEvent("Events"); + } catch (e2) { + event = doc.createEvent("UIEvents"); + } finally { + event.initEvent(type, true, true); + extend(event, defaults); + } + return event; + }, + inArray: function( item, array ) { + var i =0; + for ( ; i < array.length; i++ ) { + if ( array[i] === item ) { + return i; + } + } + return -1; + }, + getWindow: function( element ) { + return element.ownerDocument.defaultView || element.ownerDocument.parentWindow; + }, + extend: extend, + scrollOffset: function( win , set) { + var doc = win.document.documentElement, + body = win.document.body; + if(set){ + window.scrollTo(set.left, set.top); + + } else { + return { + left: (doc && doc.scrollLeft || body && body.scrollLeft || 0) + (doc.clientLeft || 0), + top: (doc && doc.scrollTop || body && body.scrollTop || 0) + (doc.clientTop || 0) + }; + } + + }, + scrollDimensions: function(win){ + var doc = win.document.documentElement, + body = win.document.body, + docWidth = doc.clientWidth, + docHeight = doc.clientHeight, + compat = win.document.compatMode === "CSS1Compat"; + + return { + height: compat && docHeight || + body.clientHeight || docHeight, + width: compat && docWidth || + body.clientWidth || docWidth + }; + }, + addOffset: function( options, el ) { + var jq = Syn.jquery(el), + off; + if ( typeof options === 'object' && options.clientX === undefined && options.clientY === undefined && options.pageX === undefined && options.pageY === undefined && jq ) { + el = jq(el); + off = el.offset(); + options.pageX = off.left + el.width() / 2; + options.pageY = off.top + el.height() / 2; + } + } + }, + // place for key data + key: { + ctrlKey: null, + altKey: null, + shiftKey: null, + metaKey: null + }, + //triggers an event on an element, returns true if default events should be run + /** + * Dispatches an event and returns true if default events should be run. + * @hide + * @param {Object} event + * @param {Object} element + * @param {Object} type + * @param {Object} autoPrevent + */ + dispatch: function( event, element, type, autoPrevent ) { + + // dispatchEvent doesn't always work in IE (mostly in a popup) + if ( element.dispatchEvent && event ) { + var preventDefault = event.preventDefault, + prevents = autoPrevent ? -1 : 0; + + //automatically prevents the default behavior for this event + //this is to protect agianst nasty browser freezing bug in safari + if ( autoPrevent ) { + bind(element, type, function( ev ) { + ev.preventDefault(); + unbind(this, type, arguments.callee); + }); + } + + + event.preventDefault = function() { + prevents++; + if (++prevents > 0 ) { + preventDefault.apply(this, []); + } + }; + element.dispatchEvent(event); + return prevents <= 0; + } else { + try { + window.event = event; + } catch (e) {} + //source element makes sure element is still in the document + return element.sourceIndex <= 0 || (element.fireEvent && element.fireEvent('on' + type, event)); + } + }, + /** + * @attribute + * @hide + * An object of eventType -> function that create that event. + */ + create: { + //-------- PAGE EVENTS --------------------- + page: { + event: function( type, options, element ) { + var doc = Syn.helpers.getWindow(element).document || document, + event; + if ( doc.createEvent ) { + event = doc.createEvent("Events"); + + event.initEvent(type, true, true); + return event; + } + else { + try { + event = createEventObject(type, options, element); + } + catch (e) {} + return event; + } + } + }, + // unique events + focus: { + event: function( type, options, element ) { + Syn.onParents(element, function( el ) { + if ( Syn.isFocusable(el) ) { + if ( el.nodeName.toLowerCase() !== 'html' ) { + el.focus(); + activeElement = el; + } + else if ( activeElement ) { + // TODO: The HTML element isn't focasable in IE, but it is + // in FF. We should detect this and do a true focus instead + // of just a blur + var doc = Syn.helpers.getWindow(element).document; + if ( doc !== window.document ) { + return false; + } else if ( doc.activeElement ) { + doc.activeElement.blur(); + activeElement = null; + } else { + activeElement.blur(); + activeElement = null; + } + + + } + return false; + } + }); + return true; + } + } + }, + /** + * @attribute support + * Feature detected properties of a browser's event system. + * Support has the following properties: + * <ul> + * <li><code>clickChanges</code> - clicking on an option element creates a change event.</li> + * <li><code>clickSubmits</code> - clicking on a form button submits the form.</li> + * <li><code>mouseupSubmits</code> - a mouseup on a form button submits the form.</li> + * <li><code>radioClickChanges</code> - clicking a radio button changes the radio.</li> + * <li><code>focusChanges</code> - focus/blur creates a change event.</li> + * <li><code>linkHrefJS</code> - An achor's href JavaScript is run.</li> + * <li><code>mouseDownUpClicks</code> - A mousedown followed by mouseup creates a click event.</li> + * <li><code>tabKeyTabs</code> - A tab key changes tabs.</li> + * <li><code>keypressOnAnchorClicks</code> - Keying enter on an anchor triggers a click.</li> + * </ul> + */ + support: { + clickChanges: false, + clickSubmits: false, + keypressSubmits: false, + mouseupSubmits: false, + radioClickChanges: false, + focusChanges: false, + linkHrefJS: false, + keyCharacters: false, + backspaceWorks: false, + mouseDownUpClicks: false, + tabKeyTabs: false, + keypressOnAnchorClicks: false, + optionClickBubbles: false, + ready: 0 + }, + /** + * Creates a synthetic event and dispatches it on the element. + * This will run any default actions for the element. + * Typically you want to use Syn, but if you want the return value, use this. + * @param {String} type + * @param {Object} options + * @param {HTMLElement} element + * @return {Boolean} true if default events were run, false if otherwise. + */ + trigger: function( type, options, element ) { + options || (options = {}); + + var create = Syn.create, + setup = create[type] && create[type].setup, + kind = key.test(type) ? 'key' : (page.test(type) ? "page" : "mouse"), + createType = create[type] || {}, + createKind = create[kind], + event, ret, autoPrevent, dispatchEl = element; + + //any setup code? + Syn.support.ready === 2 && setup && setup(type, options, element); + + autoPrevent = options._autoPrevent; + //get kind + delete options._autoPrevent; + + if ( createType.event ) { + ret = createType.event(type, options, element); + } else { + //convert options + options = createKind.options ? createKind.options(type, options, element) : options; + + if (!Syn.support.changeBubbles && /option/i.test(element.nodeName) ) { + dispatchEl = element.parentNode; //jQuery expects clicks on select + } + + //create the event + event = createKind.event(type, options, dispatchEl); + + //send the event + ret = Syn.dispatch(event, dispatchEl, type, autoPrevent); + } + + //run default behavior + ret && Syn.support.ready === 2 && Syn.defaults[type] && Syn.defaults[type].call(element, options, autoPrevent); + return ret; + }, + eventSupported: function( eventName ) { + var el = document.createElement("div"); + eventName = "on" + eventName; + + var isSupported = (eventName in el); + if (!isSupported ) { + el.setAttribute(eventName, "return;"); + isSupported = typeof el[eventName] === "function"; + } + el = null; + + return isSupported; + } + + }); + /** + * @Prototype + */ + extend(Syn.init.prototype, { + /** + * @function then + * <p> + * Then is used to chain a sequence of actions to be run one after the other. + * This is useful when many asynchronous actions need to be performed before some + * final check needs to be made. + * </p> + * <p>The following clicks and types into the <code>id='age'</code> element and then checks that only numeric characters can be entered.</p> + * <h3>Example</h3> + * @codestart + * Syn('click',{},'age') + * .then('type','I am 12',function(){ + * equals($('#age').val(),"12") + * }) + * @codeend + * If the element argument is undefined, then the last element is used. + * + * @param {String} type The type of event or action to create: "_click", "_dblclick", "_drag", "_type". + * @param {Object} options Optiosn to pass to the event. + * @param {String|HTMLElement} [element] A element's id or an element. If undefined, defaults to the previous element. + * @param {Function} [callback] A function to callback after the action has run, but before any future chained actions are run. + */ + then: function( type, options, element, callback ) { + if ( Syn.autoDelay ) { + this.delay(); + } + var args = Syn.args(options, element, callback), + self = this; + + + //if stack is empty run right away + //otherwise ... unshift it + this.queue.unshift(function( el, prevented ) { + + if ( typeof this[type] === "function" ) { + this.element = args.element || el; + this[type](args.options, this.element, function( defaults, el ) { + args.callback && args.callback.apply(self, arguments); + self.done.apply(self, arguments); + }); + } else { + this.result = Syn.trigger(type, args.options, args.element); + args.callback && args.callback.call(this, args.element, this.result); + return this; + } + }) + return this; + }, + /** + * Delays the next command a set timeout. + * @param {Number} [timeout] + * @param {Function} [callback] + */ + delay: function( timeout, callback ) { + if ( typeof timeout === 'function' ) { + callback = timeout; + timeout = null; + } + timeout = timeout || 600; + var self = this; + this.queue.unshift(function() { + setTimeout(function() { + callback && callback.apply(self, []) + self.done.apply(self, arguments); + }, timeout); + }); + return this; + }, + done: function( defaults, el ) { + el && (this.element = el); + if ( this.queue.length ) { + this.queue.pop().call(this, this.element, defaults); + } + + }, + /** + * @function click + * Clicks an element by triggering a mousedown, + * mouseup, + * and a click event. + * <h3>Example</h3> + * @codestart + * Syn.click({},'create',function(){ + * //check something + * }) + * @codeend + * You can also provide the coordinates of the click. + * If jQuery is present, it will set clientX and clientY + * for you. Here's how to set it yourself: + * @codestart + * Syn.click( + * {clientX: 20, clientY: 100}, + * 'create', + * function(){ + * //check something + * }) + * @codeend + * You can also provide pageX and pageY and Syn will convert it for you. + * @param {Object} options + * @param {HTMLElement} element + * @param {Function} callback + */ + "_click": function( options, element, callback, force ) { + Syn.helpers.addOffset(options, element); + Syn.trigger("mousedown", options, element); + + //timeout is b/c IE is stupid and won't call focus handlers + setTimeout(function() { + Syn.trigger("mouseup", options, element); + if (!Syn.support.mouseDownUpClicks || force ) { + Syn.trigger("click", options, element); + callback(true); + } else { + //we still have to run the default (presumably) + Syn.create.click.setup('click', options, element); + Syn.defaults.click.call(element); + //must give time for callback + setTimeout(function() { + callback(true); + }, 1); + } + + }, 1); + }, + /** + * Right clicks in browsers that support it (everyone but opera). + * @param {Object} options + * @param {Object} element + * @param {Object} callback + */ + "_rightClick": function( options, element, callback ) { + Syn.helpers.addOffset(options, element); + var mouseopts = extend(extend({}, Syn.mouse.browser.right.mouseup), options); + + Syn.trigger("mousedown", mouseopts, element); + + //timeout is b/c IE is stupid and won't call focus handlers + setTimeout(function() { + Syn.trigger("mouseup", mouseopts, element); + if ( Syn.mouse.browser.contextmenu ) { + Syn.trigger("contextmenu", extend(extend({}, Syn.mouse.browser.right.contextmenu), options), element); + } + callback(true); + }, 1); + }, + /** + * @function dblclick + * Dblclicks an element. This runs two [Syn.prototype.click click] events followed by + * a dblclick on the element. + * <h3>Example</h3> + * @codestart + * Syn.dblclick({},'open') + * @codeend + * @param {Object} options + * @param {HTMLElement} element + * @param {Function} callback + */ + "_dblclick": function( options, element, callback ) { + Syn.helpers.addOffset(options, element); + var self = this; + this._click(options, element, function() { + setTimeout(function() { + self._click(options, element, function() { + Syn.trigger("dblclick", options, element); + callback(true); + }, true); + }, 2); + + }); + } + }); + + var actions = ["click", "dblclick", "move", "drag", "key", "type", 'rightClick'], + makeAction = function( name ) { + Syn[name] = function( options, element, callback ) { + return Syn("_" + name, options, element, callback); + }; + Syn.init.prototype[name] = function( options, element, callback ) { + return this.then("_" + name, options, element, callback); + }; + }, + i = 0; + for ( ; i < actions.length; i++ ) { + makeAction(actions[i]); + } + /** + * Used for creating and dispatching synthetic events. + * @codestart + * new MVC.Syn('click').send(MVC.$E('id')) + * @codeend + * @constructor Sets up a synthetic event. + * @param {String} type type of event, ex: 'click' + * @param {optional:Object} options + */ + if ( window.jQuery || (window.FuncUnit && window.FuncUnit.jquery) ) { + ((window.FuncUnit && window.FuncUnit.jquery) || window.jQuery).fn.triggerSyn = function( type, options, callback ) { + Syn(type, options, this[0], callback); + return this; + }; + } + + window.Syn = Syn; +}()); +}) \ No newline at end of file diff --git a/browserid/static/funcunit/syn/test/clickbasic.html b/browserid/static/funcunit/syn/test/clickbasic.html new file mode 100644 index 0000000000000000000000000000000000000000..7f839bac501377e36bc3088b5062568e87a23885 --- /dev/null +++ b/browserid/static/funcunit/syn/test/clickbasic.html @@ -0,0 +1,67 @@ +<!DOCTYPE html + PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xml:lang="en" xmlns="http://www.w3.org/1999/xhtml" lang="en"> +<head> + <meta http-equiv="content-type" content="text/html; charset=UTF-8"> + <meta http-equiv="edit-Type" edit="text/html; charset=utf-8"> + + + + + <link rel="stylesheet" type="text/css" href="http://fiddle.jshell.net/css/normalize.css"/> + <link rel="stylesheet" type="text/css" href="http://fiddle.jshell.net/css/result-light.css"/> + + + <script type='text/javascript' src='../../../steal/steal.js'></script> + <script type='text/javascript'> + steal.plugins('funcunit/synthetic').start(); + </script> + + + <style type='text/css'> + body { + background-color: transparent; + padding: 10px; + } + + </style> + <script type='text/javascript'> + //<![CDATA[ +window.onload = function() { + var el = document.getElementById('element'); + el.style.background = '#f99'; + + + var addListener = function(el, type, event){ + if (el.addEventListener) el.addEventListener(type, event, false); + else el.attachEvent('on' + type, event); + }; + + + addListener(el, 'click', function(){ + alert('weird') + el.innerHTML += ' fn'; + }); + + el.onclick = function(){ + el.innerHTML += ' attr'; + }; + + setTimeout(function(){ + Syn.click({}, el, function(){ + el.style.background = '#99f'; + }); + }, 1000); + + +}; + //]]> + </script> +</head> +<body> + <div id="element">hello</div> + + +</body> +</html> diff --git a/browserid/static/funcunit/syn/test/qunit/h3.html b/browserid/static/funcunit/syn/test/qunit/h3.html new file mode 100644 index 0000000000000000000000000000000000000000..40fd0d37b1926f137259248f364e0a707378e1e1 --- /dev/null +++ b/browserid/static/funcunit/syn/test/qunit/h3.html @@ -0,0 +1,6 @@ +<html> + <head></head> + <body> + <h3 id='strange'>Weird</h3> + </body> +</html> \ No newline at end of file diff --git a/browserid/static/funcunit/syn/test/qunit/key_test.js b/browserid/static/funcunit/syn/test/qunit/key_test.js new file mode 100644 index 0000000000000000000000000000000000000000..2a5415f71df64bc7e1bf756391746a4cc712e981 --- /dev/null +++ b/browserid/static/funcunit/syn/test/qunit/key_test.js @@ -0,0 +1,401 @@ +module("funcunit/synthetic/key",{ + setup: function() { + st.g("qunit-test-area").innerHTML = "<form id='outer'>"+ + "<div id='inner'>"+ + "<input type='input' id='key' value=''/>"+ + "<a href='#abc' id='focusLink'>click me</a>"+ + "<textarea id='synTextArea'></textarea>"+ + "</div></form>"; + } +}) +test("Key Characters", function(){ + st.g("key").value = ""; + Syn.key("a","key"); + equals(st.g("key").value, "a", "a written"); + + st.g("key").value = ""; + Syn.key("A","key"); + equals(st.g("key").value, "A", "A written"); + + st.g("key").value = ""; + Syn.key("1","key"); + equals(st.g("key").value, "1", "1 written"); +}) + +test("Key Event Order", 1, function(){ + var order = [], + recorder = function(ev){ + order.push(ev.type) + }; + + st.binder("key","keydown", recorder ); + st.binder("key","keypress", recorder ); + st.binder("key","keyup", recorder ); + stop(); + Syn.key("B","key", function(){ + same(order,["keydown","keypress","keyup"],"Key order is correct") + start(); + }); + +}) + +test("Key \\r Submits Forms", 1, function(){ + var submit = 0; + st.binder("outer","submit",function(ev){ + submit++; + if ( ev.preventDefault ) { + ev.preventDefault(); + } + ev.returnValue = false; + return false; + }); + stop() + Syn.key("\r","key", function(){ + equals(submit, 1, "submit on keypress"); + start(); + }) +}) + +test("Key \\r Clicks Links", 1, function(){ + var clicked = 0; + st.binder("focusLink","click",function(ev){ + clicked++; + if ( ev.preventDefault ) { + ev.preventDefault(); + } + ev.returnValue = false; + return false; + }); + stop() + Syn.key("\r","focusLink", function(){ + equals(clicked, 1, "clicked"); + start(); + }) +}); + +test("Key \\r Adds Newline in Textarea", function(){ + st.g('synTextArea').value = ""; + stop() + Syn.type("ab\rcd","synTextArea", function(){ + equals( st.g('synTextArea').value.replace("\r","") , "ab\ncd" ,"typed new line correctly") + start(); + }) +}); + +test("Key \\b", function(){ + st.g("key").value = ""; + stop(); + Syn.type("abc","key", function(){ + equals(st.g("key").value, "abc", "abc written"); + Syn.key("\b","key"); + equals(st.g("key").value, "ab", "ab written (key deleted)"); + start(); + }); +}) + + +//tests when the key is inserted +test("Key Character Order", function(){ + + var upVal, + pressVal, + downVal + st.binder("key","keyup",function(){ + upVal = st.g("key").value + } ); + st.binder("key","keypress",function(){ + pressVal = st.g("key").value + + } ); + st.binder("key","keydown",function(){ + downVal = st.g("key").value + } ); + stop(); + Syn.key("J","key", function(){ + equals(upVal, "J" , "Up Typing works") + equals(pressVal, "" , "Press Typing works") + equals(downVal, "" , "Down Typing works"); + start(); + }) + +}) + +asyncTest("page down, page up, home, end", function(){ + st.g("qunit-test-area").innerHTML = + "<div id='scrolldiv' style='width:100px;height:200px;overflow-y:scroll;' tabindex='0'>"+ + "<div id='innerdiv' style='height:1000px;'><a href='javascript://'>Scroll on me</a></div></div>"; + + //reset the scroll top + st.g("scrolldiv").scrollTop =0; + + //list of keys to press and what to test after the scroll event + var keyTest = { + "page-down": function() { + ok( st.g("scrolldiv").scrollTop > 10 , "Moved down") + }, + "page-up": function() { + ok( st.g("scrolldiv").scrollTop === 0 , "Moved back up (page-up)") + }, + "end" : function() { + var sd = st.g("scrolldiv") + ok( sd.scrollTop == sd.scrollHeight - sd.clientHeight , "Moved to the end") + }, + "home" : function() { + ok( st.g("scrolldiv").scrollTop === 0 , "Moved back up (home)") + } + }, + order = [], + i = 0, + runNext = function(){ + var name = order[i]; + if(!name){ + start(); + return; + } + Syn.key( name, "scrolldiv") + }; + for(var name in keyTest){ + if (keyTest.hasOwnProperty(name)) { + order.push(name) + } + } + + st.bind(st.g("scrolldiv"),"scroll",function(ev){ + keyTest[order[i]]() + i++; + setTimeout(runNext,1) + + } ); + stop(1000); + + st.g("scrolldiv").focus(); + runNext(); + +}) +test("range tests", function(){ + var selectText = function(el, start, end){ + if(el.setSelectionRange){ + if(!end){ + el.focus(); + el.setSelectionRange(start, start); + } else { + el.selectionStart = start; + el.selectionEnd = end; + } + }else if (el.createTextRange) { + //el.focus(); + var r = el.createTextRange(); + r.moveStart('character', start); + end = end || start; + r.moveEnd('character', end - el.value.length); + + r.select(); + } + } + st.g("qunit-test-area").innerHTML = "<form id='outer'><div id='inner'><input type='input' id='key' value=''/></div></form>"+ + "<textarea id='mytextarea' />"; + + var keyEl = st.g("key") + var textAreaEl = st.g("mytextarea") + + // test delete range + keyEl.value = "012345"; + selectText(keyEl, 1, 3); + + Syn.key("delete","key") + + equals(keyEl.value, "0345", "delete range works"); + + // test delete key + keyEl.value = "012345"; + selectText(keyEl, 2); + + Syn.key("delete","key"); + equals(keyEl.value, "01345", "delete works"); + + + // test character range + keyEl.value = "123456"; + selectText(keyEl, 1, 3); + + + Syn.key("a","key"); + equals(keyEl.value, "1a456", "character range works"); + + // test character key + keyEl.value = "123456"; + selectText(keyEl, 2); + + Syn.key("a","key"); + equals(keyEl.value, "12a3456", "character insertion works"); + + // test backspace range + keyEl.value = "123456"; + selectText(keyEl, 1, 3); + Syn.key("\b","key"); + equals(keyEl.value, "1456", "backspace range works"); + + // test backspace key + keyEl.value = "123456"; + selectText(keyEl, 2); + Syn.key("\b","key"); + equals(keyEl.value, "13456", "backspace works"); + + // test textarea ranges + textAreaEl.value = "123456"; + selectText(textAreaEl, 1, 3); + + Syn.key("delete",textAreaEl); + equals(textAreaEl.value, "1456", "delete range works in a textarea"); + + // test textarea ranges + textAreaEl.value = "123456"; + selectText(textAreaEl, 1, 3); + Syn.key("a",textAreaEl); + equals(textAreaEl.value, "1a456", "character range works in a textarea"); + + // test textarea ranges + textAreaEl.value = "123456"; + selectText(textAreaEl, 1, 3); + Syn.key("\b",textAreaEl); + equals(textAreaEl.value, "1456", "backspace range works in a textarea"); + + // test textarea ranges + textAreaEl.value = "123456"; + selectText(textAreaEl, 1, 3); + Syn.key("\r",textAreaEl); + + equals(textAreaEl.value.replace("\r",""), "1\n456", "return range works in a textarea"); + + //st.g("qunit-test-area").innerHTML = ""; + +}) + +test("Type with tabs", function(){ + st.g("qunit-test-area").innerHTML = + "<input id='third'/>" + + "<a tabindex='1' id='first' href='javascript://'>First</a>"+ + "<input tabindex='2' id='second'/>"+ + "<input id='fourth'/>" + st.g('first').focus(); + + var clicked = 0; + st.binder('first', 'click', function(){ + clicked++; + }) + stop(); + //give ie a second to focus + setTimeout(function(){ + Syn.type('\r\tSecond\tThird\tFourth', 'first', function(){ + equals(clicked,1,"clickd first"); + equals(st.g('second').value,"Second","moved to second"); + equals(st.g('third').value,"Third","moved to Third"); + equals(st.g('fourth').value,"Fourth","moved to Fourth"); + start(); + }) + },1) +}); + +test("Type with shift tabs", function(){ + st.g("qunit-test-area").innerHTML = + "<input id='third'/>" + + "<a tabindex='1' id='first' href='javascript://'>First</a>"+ + "<input tabindex='2' id='second'/>"+ + "<input id='fourth'/>" + st.g('first').focus(); + + var clicked = 0; + st.binder('first', 'click', function(){ + clicked++; + }) + stop(); + //give ie a second to focus + setTimeout(function(){ + Syn.type('[shift]4\t3\t2\t\r[shift-up]', 'fourth', function(){ + equals(clicked,1,"clickd first"); + equals(st.g('second').value,"2","moved to second"); + equals(st.g('third').value,"3","moved to Third"); + equals(st.g('fourth').value,"4","moved to Fourth"); + start(); + }) + },1) +}); + + +test("Type left and right", function(){ + stop() + Syn.type("012345678[left][left][left]\b",'key', function(){ + equals( st.g('key').value, "01234678", "left works" ); + + + Syn.type("[right][right]a",'key', function(){ + equals( st.g('key').value, "0123467a8", "right works" ); + start(); + }) + + }) + + +}); +test("Type left and delete", function(){ + stop() + Syn.type("123[left][delete]",'key', function(){ + equals( st.g('key').value, "12", "left delete works" ); + start(); + }) + +}); +test("Typing Shift", function(){ + stop() + + + var shift = false; + st.binder('key','keypress', function(ev){ + shift = ev.shiftKey + }) + Syn.type("[shift]A[shift-up]",'key',function(){ + ok(shift,"Shift key on") + start(); + }) +}) +test("Typing Shift then clicking", function(){ + stop() + + var shift = false; + st.binder('inner','click', function(ev){ + shift = ev.shiftKey + }) + Syn.type("[shift]A",'key') + .click({},'inner') + .type("[shift-up]",'key', function(){ + ok(shift,"Shift key on click") + start(); + }) +}) + + +test("Typing Shift Left and Right", function(){ + stop() + + Syn.type("012345678[shift][left][left][left][shift-up]\b[left]\b",'key', function(){ + equals( st.g('key').value, "01235", "shift left works" ); + + + + + Syn.type("[left][left][shift][right][right]\b[shift-up]",'key', function(){ + + equals( st.g('key').value, "015", "shift right works" ); + start(); + }) + + }) +}) + +test("shift characters", function(){ + stop() + Syn.type("@", 'key' , function(){ + equals( st.g('key').value, "@", "@ character works" ); + start(); + }) +}) diff --git a/browserid/static/funcunit/syn/test/qunit/mouse_test.js b/browserid/static/funcunit/syn/test/qunit/mouse_test.js new file mode 100644 index 0000000000000000000000000000000000000000..6adea3a48ac8667818bc287b8c89c0697bc9042d --- /dev/null +++ b/browserid/static/funcunit/syn/test/qunit/mouse_test.js @@ -0,0 +1,327 @@ +module("funcunit/synthetic/mouse",{ + setup: function() { + st.g("qunit-test-area").innerHTML = "<form id='outer'><div id='inner'>"+ + "<input type='checkbox' id='checkbox'/>"+ + "<input type='radio' name='radio' value='radio1' id='radio1'/>"+ + "<input type='radio' name='radio' value='radio2' id='radio2'/>"+ + "<a href='javascript:doSomething()' id='jsHref'>click me</a>"+ + "<a href='#aHash' id='jsHrefHash'>click me</a>"+ + "<input type='submit' id='submit'/></div></form>" + + } +}) + +test("Syn basics", function(){ + + ok(Syn,"Syn exists") + + st.g("qunit-test-area").innerHTML = "<div id='outer'><div id='inner'></div></div>" + var mouseover = 0, mouseoverf = function(){ + mouseover++; + }; + st.bind(st.g("outer"),"mouseover",mouseoverf ); + Syn("mouseover",st.g("inner")) + + st.unbinder("outer","mouseover",mouseoverf ); + equals(mouseover, 1, "Mouseover"); + Syn("mouseover",{},'inner') + + equals(mouseover, 1, "Mouseover on no event handlers"); + st.g("qunit-test-area").innerHTML = ""; + +}) + +test("Click Forms", function(){ + var submit = 0, submitf = function(ev){ + submit++; + if ( ev.preventDefault ) { + ev.preventDefault(); + } + ev.returnValue = false; + return false; + }; + st.bind(st.g("outer"),"submit",submitf ); + Syn.trigger("click",{},st.g("submit")); + Syn("submit",{},"outer") + + + equals(submit, 2, "Click on submit"); + + //make sure clicking the div does not submit the form + var click =0, clickf = function(ev){ + click++; + if ( ev.preventDefault ) { + ev.preventDefault(); + } + return false; + } + st.binder("inner","click",clickf ); + + Syn.trigger("click",{},st.g("submit")); + + equals(submit, 2, "Submit prevented"); + equals(click, 1, "Clicked"); + + st.unbinder("outer","submit",submitf ); + st.unbinder("inner","click",clickf ); +}) +test("Click Checkboxes", function(){ + var checkbox =0; + + st.binder("checkbox","change",function(ev){ + checkbox++; + }); + + st.g("checkbox").checked = false; + + Syn.trigger("click",{},st.g("checkbox")); + + ok(st.g("checkbox").checked, "click checks on"); + + Syn.trigger("click",{},st.g("checkbox")); + + ok(!st.g("checkbox").checked, "click checks off"); +}) + +test("Checkbox is checked on click", function(){ + var checkbox =0; + + st.g("checkbox").checked = false; + + st.binder("checkbox","click",function(ev){ + ok(st.g("checkbox").checked, "check is on during click"); + }) + + Syn.trigger("click",{},st.g("checkbox")); +}) + +test("Click Radio Buttons", function(){ + + var radio1=0, + radio2=0; + + st.g("radio1").checked = false; + //make sure changes are called + st.bind(st.g("radio1"),"change",function(ev){ + radio1++; + } ); + st.bind(st.g("radio2"),"change",function(ev){ + radio2++; + } ); + + Syn.trigger("click",{},st.g("radio1") ); + + equals(radio1, 1, "radio event"); + ok( st.g("radio1").checked, "radio checked" ); + + Syn.trigger("click",{},st.g("radio2") ); + + equals(radio2, 1, "radio event"); + ok( st.g("radio2").checked, "radio checked" ); + + + ok( !st.g("radio1").checked, "radio unchecked" ); + +}); + +test("Click! Event Order", 4, function(){ + var order = 0; + st.g("qunit-test-area").innerHTML = "<input id='focusme'/>"; + + + st.binder("focusme","mousedown",function(){ + equals(++order,1,"mousedown") + }); + + st.binder("focusme","focus",function(){ + equals(++order, 2,"focus") + }); + + st.binder("focusme","mouseup",function(){ + equals(++order,3,"mouseup") + }); + st.binder("focusme","click",function(ev){ + equals(++order,4,"click") + if(ev.preventDefault) + ev.preventDefault(); + ev.returnValue = false; + }); + + stop(); + Syn.click({},"focusme", function(){ + start(); + }) + +}) + +test("Click Anchor Runs HREF JavaScript", function(){ + var didSomething = false, + doSomething = window.doSomething; + window.doSomething = function(){ + didSomething = true; + } + + + Syn.trigger("click",{},st.g("jsHref")) + + ok( didSomething, "link href JS run" ); + + window.doSomething = doSomething; +}) + +test("Click! Anchor has href", function(){ + stop(); + st.binder("jsHrefHash","click",function(ev){ + ok(this.href.indexOf("#aHash") > -1 ,"got href"); + }); + + Syn.click({},"jsHrefHash", function(){ + equals(window.location.hash,"#aHash","hash set ...") + start(); + window.location.hash="" + }) +}) + +test("Click! Anchor Focuses", 2, function(){ + st.g("qunit-test-area").innerHTML = "<a href='#abc' id='focusme'>I am visible</a>"; + + st.binder("focusme","focus",function(ev){ + ok(true,"focused"); + }); + + st.binder("focusme","click",function(ev){ + ok(true,"clicked"); + st.g("qunit-test-area").innerHTML =""; + if(ev.preventDefault) + ev.preventDefault(); + ev.returnValue = false; + return false; + }); + stop(); + //need to give browsers a second to show element + + Syn.click({},"focusme", function(){ + start(); + }) + + + +}) +test("Click away causes Blur Change", function(){ + st.g("qunit-test-area").innerHTML = "<input id='one'/><input id='two'/>"; + + var change = 0, blur = 0; + + st.binder("one","blur",function(){ + blur++; + } ); + st.binder("one","change",function(){ + change++; + } ); + + stop(); + Syn.click({},"one") + .key("a") + .click({},"two", function(){ + start() + equals(change, 1 , "Change called once"); + equals(blur, 1 , "Blur called once"); + }) + +}); + +test("Click HTML causes blur change", function(){ + st.g("qunit-test-area").innerHTML = "<input id='one'/><input id='two'/>"; + + var change = 0; + st.binder("one","change",function(){ + change++; + } ); + + stop(); + Syn.click({},"one") + .key("a") + .click({},document.documentElement, function(){ + start() + equals(change, 1 , "Change called once"); + }) +}) +test("Right Click", function(){ + st.g("qunit-test-area").innerHTML = "<div id='one'>right click me</div>"; + stop() + var context = 0; + st.binder("one","contextmenu",function(){ + context++; + }); + + Syn.rightClick({},"one", function(){ + if(Syn.mouse.browser.contextmenu){ + equals(1, context, "context was called") + }else{ + ok(true,"context shouldn't be called in this browser") + } + start(); + }) +}) + +test("Double Click", function(){ + st.g("qunit-test-area").innerHTML = "<div id='dblclickme'>double click me</div>"; + stop() + var eventSequence = []; + st.binder("dblclickme","dblclick",function(){ + eventSequence.push('dblclick'); + }); + st.binder("dblclickme","click",function(){ + eventSequence.push('click'); + }); + + Syn.dblclick({},"dblclickme", function(){ + equals(eventSequence.join(', '), 'click, click, dblclick', 'expected event sequence for doubleclick'); + start(); + }) +}); + +// tests against IE9's weirdness where popup windows don't have dispatchEvent +test("h3 click in popup", 1,function(){ + st.g("qunit-test-area").innerHTML = ""; + + + stop(); + /*var page1 = st.rootJoin("funcunit/syn/test/qunit/h3.html"), + iframe = document.createElement('iframe'), + calls = 0; + + st.bind(iframe,"load",function(){ + var el = iframe.contentWindow.document.getElementById('strange') + st.bind(el,"click",function(){ + ok(true, "h3 was clicked"); + + }); + Syn.click( el ,{}, function(){ + start(); + }) + + + + }); + iframe.src = page1 + st.g("qunit-test-area").appendChild(iframe);*/ + + + var popup = window.open( st.rootJoin("funcunit/syn/test/qunit/h3.html"), "synthing") + + setTimeout(function(){ + var el = popup.document.getElementById('strange') + st.bind(el,"click",function(){ + ok(true, "h3 was clicked"); + + }); + Syn.click( el ,{}, function(){ + start(); + popup.close() + }) + + + + },500); +}); diff --git a/browserid/static/funcunit/syn/test/qunit/page1.html b/browserid/static/funcunit/syn/test/qunit/page1.html new file mode 100644 index 0000000000000000000000000000000000000000..2c3a50c67fc53a634b029b188c18df452949682d --- /dev/null +++ b/browserid/static/funcunit/syn/test/qunit/page1.html @@ -0,0 +1,6 @@ +<html> + <body> + page 1 + <input type='text' id='first'/> + </body> +</html> \ No newline at end of file diff --git a/browserid/static/funcunit/syn/test/qunit/page2.html b/browserid/static/funcunit/syn/test/qunit/page2.html new file mode 100644 index 0000000000000000000000000000000000000000..74d7c77f0de3e7e73cfd36125589ad514a965356 --- /dev/null +++ b/browserid/static/funcunit/syn/test/qunit/page2.html @@ -0,0 +1,6 @@ +<html> + <body> + page 2 + <input type='text' id='second'/> + </body> +</html> \ No newline at end of file diff --git a/browserid/static/jquery/class/test/qunit/qunit.js b/browserid/static/funcunit/syn/test/qunit/qunit.js similarity index 51% rename from browserid/static/jquery/class/test/qunit/qunit.js rename to browserid/static/funcunit/syn/test/qunit/qunit.js index 3a2d2068840250eb895bd842d2eb9cfe61fff14f..5255273fd615861bf03cf2bd6228ca60342ab2b5 100644 --- a/browserid/static/jquery/class/test/qunit/qunit.js +++ b/browserid/static/funcunit/syn/test/qunit/qunit.js @@ -1,5 +1,8 @@ //we probably have to have this only describing where the tests are steal - .plugins("jquery/class") //load your app + .plugins("jquery") + .plugins("funcunit/syn") //load your app .plugins('funcunit/qunit') //load qunit - .then("class_test") \ No newline at end of file + .then("syn_test", "mouse_test", "key_test" + + ) \ No newline at end of file diff --git a/browserid/static/funcunit/syn/test/qunit/syn_test.js b/browserid/static/funcunit/syn/test/qunit/syn_test.js new file mode 100644 index 0000000000000000000000000000000000000000..04c0fcc9563efb2928b2a2a9651ee0a7ca277ddd --- /dev/null +++ b/browserid/static/funcunit/syn/test/qunit/syn_test.js @@ -0,0 +1,139 @@ +module("funcunit/syn") + +st = { + g: function( id ) { + return document.getElementById(id) + }, + log: function( c ) { + if(st.g("mlog")) + st.g("mlog").innerHTML = st.g("mlog").innerHTML+c+"<br/>" + }, + binder: function( id, ev, f ) { + st.bind(st.g(id), ev, f) + }, + unbinder: function( id, ev, f ) { + st.unbind(st.g(id), ev, f) + }, + bind : function(el, ev, f){ + return el.addEventListener ? + el.addEventListener(ev, f, false) : + el.attachEvent("on"+ev, f) + }, + unbind : function(el, ev, f){ + return el.addEventListener ? + el.removeEventListener(ev, f, false) : + el.detachEvent("on"+ev, f) + }, + rootJoin : (typeof steal == "undefined" ? function(path){ + return "../../"+path; + } : + function(path){ return steal.root.join(path) } ) +}; + + +setTimeout(function(){ + if(Syn.support.ready ==2){ + for(var name in Syn.support){ + st.log(name+": "+Syn.support[name]) + } + }else{ + setTimeout(arguments.callee, 1); + } + +},1); + +test("Selecting a select element", function(){ + st.g("qunit-test-area").innerHTML = + "<form id='outer'><select name='select'><option value='1' id='one'>one</option><option value='2' id='two'>two</option></select></form>"; + + var change = 0, changef = function(){ + change++; + } + + st.g("outer").select.selectedIndex = 0; + + st.bind(st.g("outer").select,"change",changef ); + + stop() + Syn.click( st.g("two"), function(){ + equals(change, 1 , "change called once") + equals(st.g("outer").select.selectedIndex, 1, "Change Selected Index"); + //st.g("qunit-test-area").innerHTML = "" + start(); + }) + + + + +}) + +asyncTest("scrollTop triggers scroll events", function(){ + st.g("qunit-test-area").innerHTML = "<div id='scroller' style='height:100px;width: 100px;overflow:auto'>"+ + "<div style='height: 200px; width: 100%'>text"+ + "</div>"+ + "</div>"; + + st.binder("scroller","scroll",function(ev){ + ok(true,"scrolling created just by changing ScrollTop"); + st.g("qunit-test-area").innerHTML =""; + start(); + } ); + stop(); + setTimeout(function(){ + var sc = st.g("scroller"); + sc && (sc.scrollTop = 10); + + },13) + +}) + +test("focus triggers focus events", function(){ + st.g("qunit-test-area").innerHTML = "<input type='text' id='focusme'/>"; + + st.binder("focusme","focus",function(ev){ + ok(true,"focus creates event"); + st.g("qunit-test-area").innerHTML =""; + start(); + } ); + stop(); + setTimeout(function(){ + st.g("focusme").focus(); + + },10) + +}); + + +test("focus on an element then another in another page", function(){ + stop(10000); + var rootJoin = st.rootJoin; + + var page1 = rootJoin("funcunit/syn/test/qunit/page1.html"), + page2 = rootJoin("funcunit/syn/test/qunit/page2.html"), + iframe = document.createElement('iframe'), + calls = 0; + + st.bind(iframe,"load",function(){ + if(calls == 0){ + + Syn.click( iframe.contentWindow.document.getElementById("first") ,{}, function(){ + + iframe.contentWindow.location = page2; + }) + calls++; + }else{ + + Syn.click( iframe.contentWindow.document.documentElement ,{}, function(){ + start(); + + }) + } + + + }); + iframe.src = page1 + + + + st.g("qunit-test-area").appendChild(iframe); +}) diff --git a/browserid/static/funcunit/syn/test/submit.html b/browserid/static/funcunit/syn/test/submit.html new file mode 100644 index 0000000000000000000000000000000000000000..f8873ba32045d917ca1cacf9f2ab5b1929fd10f2 --- /dev/null +++ b/browserid/static/funcunit/syn/test/submit.html @@ -0,0 +1,29 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" + "http://www.w3.org/TR/html4/strict.dtd"> +<html lang="en"> + <head> + <title>Synthetic Submit Test</title> + <style type='text/css'> + + </style> + </head> + <body> + <form action='submitted.html' method='get'> + <input type='submit' id='submitter'/> + <input name='something' value='else'/> + </form> + <a href='' id='go'>Go</a> +<script type='text/javascript' src='../../../steal/steal.js'></script> +<script type='text/javascript'> + steal.plugins('funcunit/synthetic','jquery').start(); +</script> +<script type='text/javascript' id="demo-source"> + $("#go").click(function(ev){ + ev.preventDefault(); + setTimeout(function(){ + Syn.click({},'submitter') + },100) + }) +</script> + </body> +</html> \ No newline at end of file diff --git a/browserid/static/funcunit/syn/test/submitted.html b/browserid/static/funcunit/syn/test/submitted.html new file mode 100644 index 0000000000000000000000000000000000000000..2b8e3c2d24e33e69fa8e3b1bdf0e89afd58ba58c --- /dev/null +++ b/browserid/static/funcunit/syn/test/submitted.html @@ -0,0 +1,3 @@ +<html> + <h1>Sumibtted!</h1> +</html> \ No newline at end of file diff --git a/browserid/static/funcunit/template.html b/browserid/static/funcunit/template.html new file mode 100644 index 0000000000000000000000000000000000000000..7a109e3659918614cf47c5e99d35ba61daa18267 --- /dev/null +++ b/browserid/static/funcunit/template.html @@ -0,0 +1,18 @@ +<html> +<head> + <!-- Change HREF to point to your copy of qunit.css --> + <link rel="stylesheet" type="text/css" href="http://v3.javascriptmvc.com/funcunit/dist/qunit.css" /> + <!-- Change SRC to point to your copy of funcunit.js --> + <script type='text/javascript' src='http://v3.javascriptmvc.com/funcunit/dist/funcunit.js'></script> + <!-- Change SRC to point to your test.js --> + <script type='text/javascript' src='TEMPLATE_test.js'></script> + <title>TEMPLATE Test Suite</title> +</head> +<body> + <h1 id="qunit-header">TEMPLATE Test Suite</h1> + <h2 id="qunit-banner"></h2> + <div id="qunit-testrunner-toolbar"></div> + <h2 id="qunit-userAgent"></h2> + <ol id="qunit-tests"></ol> +</body> +</html> \ No newline at end of file diff --git a/browserid/static/funcunit/test/drag.html b/browserid/static/funcunit/test/drag.html new file mode 100644 index 0000000000000000000000000000000000000000..7e164e63b6dc0d01e97c72102bbfba9ec71b8178 --- /dev/null +++ b/browserid/static/funcunit/test/drag.html @@ -0,0 +1,63 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" + "http://www.w3.org/TR/html4/strict.dtd"> +<html lang="en"> + <head> + <title>Synthetic Typing Test</title> + <style type='text/css'> + #key th:nth-child(even) {background: #CCf} + #who th:nth-child(even) {background: #fCb} + .hover {background-color: blue} + #drag, #drop { + width: 20px; + height: 20px; + border:1px solid black; + } + #drag { + top: 0px; + background-color: green; + } + #drop { + margin-top:10px; + background-color: blue; + } + .clicked { + border:1px solid red; + } + </style> + </head> + <body> + <p id='start'>start</p> + <div class='over'>Move over me</div> + <p id='end'>end</p> + <div> + <div id='drag'></div> + <div id='drop'></div> + </div> + <div class='status'></div> +<script type='text/javascript' src='../../steal/steal.js'></script> +<script type='text/javascript'> + steal.plugins('jquery', 'jquery/event/drag', 'jquery/event/drop').start(); +</script> +<script type='text/javascript' id="demo-source"> + var hoveredOnce = false; + $(".over").bind('mouseover',function(){ + if (!hoveredOnce) { + $(this).addClass('hover') + $(document.body).append("<input type='text' id='typer' />") + hoveredOnce = true; + } + }) + + $('#drag') + .live("draginit", function(){}) + + $('#drop') + .live("dropover", function(){ + $(document.body).append("<a href='#' id='clicker'>click</a>") + $("#clicker").click(function(){ + $(".status").html("dragged") + }) + }) +</script> + </body> +</html> \ No newline at end of file diff --git a/browserid/static/funcunit/test/findclosest.html b/browserid/static/funcunit/test/findclosest.html new file mode 100644 index 0000000000000000000000000000000000000000..4053d6be04a916ce078396d6eff4ddcb6f0194d3 --- /dev/null +++ b/browserid/static/funcunit/test/findclosest.html @@ -0,0 +1,28 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" + "http://www.w3.org/TR/html4/strict.dtd"> +<html lang="en"> + <head> + <title>Synthetic Typing Test</title> + <style type='text/css'> + + </style> + </head> + <body> + <div class='baz'> + <div id='foo'> + <a href="javascript://">Holler!</a> + <div class='combo'>combo</div> + </div> + <div class='combo'>combo</div> + </div> +<script type='text/javascript' src='../../steal/steal.js'></script> +<script type='text/javascript'> + steal.plugins('jquery').then( function(){ + $(document.documentElement).click(function(ev){ + $(ev.target).addClass("iWasClicked") + }) + + }).start(); +</script> + </body> +</html> \ No newline at end of file diff --git a/browserid/static/funcunit/test/funcunit/find_closest_test.js b/browserid/static/funcunit/test/funcunit/find_closest_test.js new file mode 100644 index 0000000000000000000000000000000000000000..4286572ba1883af5aa8f041dac95bfc2b4a4fca6 --- /dev/null +++ b/browserid/static/funcunit/test/funcunit/find_closest_test.js @@ -0,0 +1,28 @@ +module("funcunit - find / closest",{ + setup: function() { + var self = this; + S.open("//funcunit/test/findclosest.html", function(){ + self.pageIsLoaded = true; + }, 10000) + } +}); + +test("closest", function(){ + S(":contains('Holler')").closest("#foo").click(function(){ + ok(this.hasClass("iWasClicked"),"we clicked #foo") + }) + S(":contains('Holler')").closest(".baz").click(function(){ + ok(S(".baz").hasClass("iWasClicked"),"we clicked .baz") + }) + +}) + +test("find", function(){ + S(":contains('Holler')") + .closest("#foo") + .find(".combo") + .click(function(){ + ok(S(".combo:eq(0)").hasClass("iWasClicked"),"we clicked the first combo") + ok(!S(".combo:eq(1)").hasClass("iWasClicked"),"we did not click the 2nd combo") + }) +}) diff --git a/browserid/static/funcunit/test/funcunit/funcunit.js b/browserid/static/funcunit/test/funcunit/funcunit.js new file mode 100644 index 0000000000000000000000000000000000000000..16b71e62708f6e58c3c2010029e392ff768f228a --- /dev/null +++ b/browserid/static/funcunit/test/funcunit/funcunit.js @@ -0,0 +1,7 @@ +steal + .plugins("funcunit") + .then("funcunit_test", + "find_closest_test", + "open_test", + "syn_test", + 'protodrag_test') \ No newline at end of file diff --git a/browserid/static/funcunit/test/funcunit/funcunit_test.js b/browserid/static/funcunit/test/funcunit/funcunit_test.js new file mode 100644 index 0000000000000000000000000000000000000000..0b2d53ec2632609d6f4eea18a89603664525d39d --- /dev/null +++ b/browserid/static/funcunit/test/funcunit/funcunit_test.js @@ -0,0 +1,101 @@ +module("funcunit - jQuery API",{ + setup: function() { + var self = this; + S.open("//funcunit/test/myapp.html", function(){ + self.pageIsLoaded = true; + }, 10000) + } +}) + +test("qUnit module setup works async", function(){ + ok(this.pageIsLoaded, "page is loaded set before") +}) + +test("Iframe access", function(){ + + equals(S("h2",0).text(), "Goodbye World", "text of iframe") + +}) + +test("typing alt and shift characters", function(){ + S('#typehere').type("@", function(){ + equals(S('#typehere').val(), "@", "types weird chars" ); + }) +}) + +test("html with function", 1, function(){ + S("#clickToChange").click() + .html(function(html){ + return html == "changed" + }, function(){ + equals(S("#clickToChange").html(),"changed","wait actually waits") + }) + +}) +test("Html with value", 1, function(){ + S("#clickToChange").click() + + .html("changed", function(){ + equals(S("#clickToChange").html(),"changed","wait actually waits") + }) + +}) + +test("Wait", function(){ + var before, + after + setTimeout(function(){ + before = true; + },2) + setTimeout(function(){ + after = true + },1000) + S.wait(20,function(){ + ok(before, 'after 2 ms') + ok(!after, 'before 1000ms') + + }) +}) + +test("hasClass", function(){ + var fast + + S("#hasClass").click(); + setTimeout(function(){ + fast = true + },50) + S("#hasClass").hasClass("someClass",true, function(){ + ok(fast,"waited until it has a class exists") + }); +}) + +test("Exists", function(){ + var fast; + + S("#exists").click(); + setTimeout(function(){ + fast = true + },50) + S("#exists p").exists(function(){ + ok(fast,"waited until it exists") + }); + +}) + +test("Trigger", function(){ + S("#trigger").trigger('myCustomEvent'); + S("#trigger p").text("I was triggered"); +}) +test("Confirm", function(){ + S("#confirm").click(); + S.confirm(true); + S("#confirm p").text("I was confirmed"); + S("#confirm").click(); + S.confirm(false); +}) +test("Accessing the window", function(){ + ok(S(S.window).width()> 20, "I can get the window's width") +}) +test("Accessing the document", function(){ + ok(S(S.window.document).width()> 20, "I can get the document's width") +}) diff --git a/browserid/static/funcunit/test/funcunit/open_test.js b/browserid/static/funcunit/test/funcunit/open_test.js new file mode 100644 index 0000000000000000000000000000000000000000..8ad973e88958cf6a5bc8f7317024802fc53ba513 --- /dev/null +++ b/browserid/static/funcunit/test/funcunit/open_test.js @@ -0,0 +1,30 @@ +module("funcunit-open") + +test("URL Test", function(){ + var path; + + path = FuncUnit.getAbsolutePath("http://foo.com") + equals(path, "http://foo.com", "paths match"); + + FuncUnit.jmvcRoot = "http://localhost/" + path = FuncUnit.getAbsolutePath("//myapp/mypage.html") + equals(path, "http://localhost/myapp/mypage.html", "paths match"); + + FuncUnit.jmvcRoot = null + + path = FuncUnit.getAbsolutePath("//myapp/mypage.html") + + equals(path, steal.root.join("myapp/mypage.html"), "paths match"); +}) + + + +test("Back to back opens", function(){ + S.open("//funcunit/test/myotherapp.html", null, 10000); + + S.open("//funcunit/test/myapp.html", null, 10000); + + S("#changelink").click(function(){ + equals(S("#changelink").text(), "Changed","href javascript run") + }) +}) \ No newline at end of file diff --git a/browserid/static/funcunit/test/funcunit/protodrag_test.js b/browserid/static/funcunit/test/funcunit/protodrag_test.js new file mode 100644 index 0000000000000000000000000000000000000000..c9f2ebaf5b167dab9fd79f57208a2992135c82fa --- /dev/null +++ b/browserid/static/funcunit/test/funcunit/protodrag_test.js @@ -0,0 +1,13 @@ +module("funcunit-prototype / scriptaculous drag",{ + setup: function() { + S.open("//funcunit/test/protodrag/myapp.html", null, 10000); + } +}) + + +test("Drag", function(){ + + S("#drag").drag("#drop", function(){ + equals(S("#drop").text(), "Drags 1", 'drag worked correctly') + }) +}) \ No newline at end of file diff --git a/browserid/static/funcunit/test/funcunit/syn_test.js b/browserid/static/funcunit/test/funcunit/syn_test.js new file mode 100644 index 0000000000000000000000000000000000000000..10cb573d1351f1aa327aedf971f669b67bc670e9 --- /dev/null +++ b/browserid/static/funcunit/test/funcunit/syn_test.js @@ -0,0 +1,41 @@ +module("funcunit-syn integration") + + +test("Type and slow Click", function(){ + S.open("//funcunit/test/myapp.html", null, 10000); + + S("#typehere").type("javascriptmvc", function(){ + equals(S("#seewhatyoutyped").text(), "typed javascriptmvc","typing"); + }) + + + + //click is going to run slow, to make sure we don't continue + //until after it is done. + S("#copy").click(function(){ + equals(S("#seewhatyoutyped").text(), "copied javascriptmvc","copy"); + }); + + + //S("#typehere").offset(function(offset){ + // ok(offset.top,"has values") + //}) +}) + +test("Move To", function(){ + S.open("//funcunit/test/drag.html", null, 10000); + S("#start").move("#end") + S("#typer").type("javascriptmvc",function(){ + equals(S("#typer").val(), "javascriptmvc","move test worked correctly"); + }) + +}) + +test("Drag To", function(){ + S.open("//funcunit/test/drag.html", null, 10000); + S("#drag").drag("#drop") + S("#clicker").click(function(){ + equals(S(".status").text(), "dragged", 'drag worked correctly') + }) + +}) \ No newline at end of file diff --git a/browserid/static/funcunit/test/jquery.event.drag.js b/browserid/static/funcunit/test/jquery.event.drag.js new file mode 100644 index 0000000000000000000000000000000000000000..206105b1b753788e8af71be7b50daaae896cd0fc --- /dev/null +++ b/browserid/static/funcunit/test/jquery.event.drag.js @@ -0,0 +1,801 @@ +// jquery/lang/vector/vector.js + +(function($){ + + var getSetZero = function(v){ return v !== undefined ? (this.array[0] = v) : this.array[0] }, + getSetOne = function(v){ return v !== undefined ? (this.array[1] = v) : this.array[1] } +/** + * @constructor + * A vector class + * @init creates a new vector instance from the arguments. Example: + * @codestart + * new jQuery.Vector(1,2) + * @codeend + * + */ +jQuery.Vector = function(){ + this.update( jQuery.makeArray(arguments) ); +}; +jQuery.Vector.prototype = +/* @Prototype*/ +{ + /** + * Applys the function to every item in the vector. Returns the new vector. + * @param {Function} f + * @return {jQuery.Vector} new vector class. + */ + app: function(f){ + var newArr = []; + + for(var i=0; i < this.array.length; i++) + newArr.push( f( this.array[i] ) ); + var vec = new jQuery.Vector(); + return vec.update(newArr); + }, + /** + * Adds two vectors together. Example: + * @codestart + * new Vector(1,2).plus(2,3) //-> <3,5> + * new Vector(3,5).plus(new Vector(4,5)) //-> <7,10> + * @codeend + * @return {jQuery.Vector} + */ + plus: function(){ + var args = arguments[0] instanceof jQuery.Vector ? + arguments[0].array : + jQuery.makeArray(arguments), + arr=this.array.slice(0), + vec = new jQuery.Vector(); + for(var i=0; i < args.length; i++) + arr[i] = (arr[i] ? arr[i] : 0) + args[i]; + return vec.update(arr); + }, + /** + * Like plus but subtracts 2 vectors + * @return {jQuery.Vector} + */ + minus: function(){ + var args = arguments[0] instanceof jQuery.Vector ? + arguments[0].array : + jQuery.makeArray(arguments), + arr=this.array.slice(0), vec = new jQuery.Vector(); + for(var i=0; i < args.length; i++) + arr[i] = (arr[i] ? arr[i] : 0) - args[i]; + return vec.update(arr); + }, + /** + * Returns the current vector if it is equal to the vector passed in. + * False if otherwise. + * @return {jQuery.Vector} + */ + equals : function(){ + var args = arguments[0] instanceof jQuery.Vector ? + arguments[0].array : + jQuery.makeArray(arguments), + arr=this.array.slice(0), vec = new jQuery.Vector(); + for(var i=0; i < args.length; i++) + if(arr[i] != args[i]) return null; + return vec.update(arr); + }, + /* + * Returns the 2nd value of the vector + * @return {Number} + */ + x : getSetZero, + width : getSetZero, + /** + * Returns the first value of the vector + * @return {Number} + */ + y : getSetOne, + height : getSetOne, + /** + * Same as x() + * @return {Number} + */ + top : getSetOne, + /** + * same as y() + * @return {Number} + */ + left : getSetZero, + /** + * returns (x,y) + * @return {String} + */ + toString: function(){ + return "("+this.array[0]+","+this.array[1]+")"; + }, + /** + * Replaces the vectors contents + * @param {Object} array + */ + update: function(array){ + if(this.array){ + for(var i =0; i < this.array.length; i++) delete this.array[i]; + } + this.array = array; + for(var i =0; i < array.length; i++) this[i]= this.array[i]; + return this; + } +}; + +jQuery.Event.prototype.vector = function(){ + if(this.originalEvent.synthetic){ + var doc = document.documentElement, body = document.body; + return new jQuery.Vector(this.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc.clientLeft || 0), + this.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc.clientTop || 0)); + }else{ + return new jQuery.Vector(this.pageX, this.pageY); + } +} + +jQuery.fn.offsetv = function() { + if(this[0] == window){ + return new jQuery.Vector(window.pageXOffset ? window.pageXOffset : document.documentElement.scrollLeft, + window.pageYOffset ? window.pageYOffset : document.documentElement.scrollTop) + }else{ + var offset = this.offset(); + return new jQuery.Vector(offset.left, offset.top); + } +}; + +jQuery.fn.dimensionsv = function(){ + if(this[0] == window) + return new jQuery.Vector(this.width(), this.height()); + else + return new jQuery.Vector(this.outerWidth(), this.outerHeight()); +} +jQuery.fn.centerv = function(){ + return this.offsetv().plus( this.dimensionsv().app(function(u){return u /2;}) ) +} + +jQuery.fn.makePositioned = function() { + return this.each(function(){ + var that = jQuery(this); + var pos = that.css('position'); + + if (!pos || pos == 'static') { + var style = { position: 'relative' }; + + if (window.opera) { + style.top = '0px'; + style.left = '0px'; + } + that.css(style); + } + }); +}; + + +})(jQuery); + +// jquery/event/livehack/livehack.js + +(function($){ + + + var event = jQuery.event, + + //helper that finds handlers by type and calls back a function, this is basically handle + findHelper = function(events, types, callback){ + for( var t =0; t< types.length; t++ ) { + var type = types[t], + typeHandlers, + all = type.indexOf(".") < 0, + namespaces, + namespace; + if ( !all ) { + namespaces = type.split("."); + type = namespaces.shift(); + namespace = new RegExp("(^|\\.)" + namespaces.slice(0).sort().join("\\.(?:.*\\.)?") + "(\\.|$)"); + } + typeHandlers = ( events[type] || [] ).slice(0); + + for( var h = 0; h <typeHandlers.length; h++ ) { + var handle = typeHandlers[h]; + if( !handle.selector && (all || namespace.test( handle.namespace )) ){ + callback(type, handle.origHandler || handle.handler); + } + } + } + } + + /** + * Finds event handlers of a given type on an element. + * @param {HTMLElement} el + * @param {Array} types an array of event names + * @param {String} [selector] optional selector + * @return {Array} an array of event handlers + */ + event.find = function(el, types, selector){ + var events = $.data(el, "events"), + handlers = []; + + if( !events ) { + return handlers; + } + + if( selector ) { + if (!events.live) { + return []; + } + var live = events.live; + + for ( var t = 0; t < live.length; t++ ) { + var liver = live[t]; + if( liver.selector === selector && $.inArray(liver.origType, types ) !== -1 ) { + handlers.push(liver.origHandler || liver.handler); + } + } + }else{ + // basically re-create handler's logic + findHelper(events, types, function(type, handler){ + handlers.push(handler); + }) + } + return handlers; + } + /** + * Finds + * @param {HTMLElement} el + * @param {Array} types + */ + event.findBySelector = function(el, types){ + var events = $.data(el, "events"), + selectors = {}, + //adds a handler for a given selector and event + add = function(selector, event, handler){ + var select = selectors[selector] || (selectors[selector] = {}), + events = select[event] || (select[event] = []); + events.push(handler); + }; + + if ( !events ) { + return selectors; + } + //first check live: + $.each( events.live||[] , function(i, live) { + if( $.inArray(live.origType, types ) !== -1 ) { + add( live.selector, live.origType, live.origHandler || live.handler ); + } + }) + //then check straight binds + + findHelper(events, types, function(type, handler){ + add("", type, handler); + }) + + return selectors; + } + $.fn.respondsTo = function(events){ + if(!this.length){ + return false; + }else{ + //add default ? + return event.find(this[0], $.isArray(events) ? events : [events]).length > 0; + } + } + $.fn.triggerHandled = function(event, data){ + event = ( typeof event == "string" ? $.Event(event) : event); + this.trigger(event, data); + return event.handled; + } + /** + * Only attaches one event handler for all types ... + * @param {Array} types llist of types that will delegate here + * @param {Object} startingEvent the first event to start listening to + * @param {Object} onFirst a function to call + */ + event.setupHelper = function(types, startingEvent, onFirst){ + if(!onFirst) { + onFirst = startingEvent; + startingEvent = null; + } + var add = function(handleObj){ + + var selector = handleObj.selector || ""; + if (selector) { + var bySelector = event.find(this, types, selector); + if (!bySelector.length) { + $(this).delegate(selector,startingEvent, onFirst ); + } + } + else { + //var bySelector = event.find(this, types, selector); + event.add(this, startingEvent, onFirst, { + selector: selector, + delegate: this + }); + } + + } + var remove = function(handleObj){ + var selector = handleObj.selector || ""; + if (selector) { + var bySelector = event.find(this, types, selector); + if (!bySelector.length) { + $(this).undelegate(selector,startingEvent, onFirst ); + } + } + else { + event.remove(this, startingEvent, onFirst, { + selector: selector, + delegate: this + }); + } + } + $.each(types, function(){ + event.special[this] = { + add: add, + remove: remove, + setup : function(){} + }; + }); + } + +})(jQuery); + +// jquery/event/drag/drag.js + +(function($){ + + //modify live + //steal the live handler .... + + + + var bind = function(object, method){ + var args = Array.prototype.slice.call(arguments, 2); + return function() { + var args2 = [this].concat(args, $.makeArray( arguments )); + return method.apply(object, args2); + }; + }, + event = $.event, handle = event.handle; + + /** + * @constructor jQuery.Drag + * @parent specialevents + * @plugin jquery/event/drag + * @download jquery/dist/jquery.event.drag.js + * @test jquery/event/drag/qunit.html + * Provides drag events as a special events to jQuery. + * A jQuery.Drag instance is created on a drag and passed + * as a parameter to the drag event callbacks. By calling + * methods on the drag event, you can alter the drag's + * behavior. + * <h2>Drag Events</h2> + * The drag plugin allows you to listen to the following events: + * <ul> + * <li><code>dragdown</code> - the mouse cursor is pressed down</li> + * <li><code>draginit</code> - the drag motion is started</li> + * <li><code>dragmove</code> - the drag is moved</li> + * <li><code>dragend</code> - the drag has ended</li> + * <li><code>dragover</code> - the drag is over a drop point</li> + * <li><code>dragout</code> - the drag moved out of a drop point</li> + * </ul> + * <p>Just by binding or delegating on one of these events, you make + * the element dragable. You can change the behavior of the drag + * by calling methods on the drag object passed to the callback. + * <h3>Example</h3> + * Here's a quick example: + * @codestart + * //makes the drag vertical + * $(".drags").live("draginit", function(event, drag){ + * drag.vertical(); + * }) + * //gets the position of the drag and uses that to set the width + * //of an element + * $(".resize").live("dragmove",function(event, drag){ + * $(this).width(drag.position.left() - $(this).offset().left ) + * }) + * @codeend + * <h2>Drag Object</h2> + * <p>The drag object is passed after the event to drag + * event callback functions. By calling methods + * and changing the properties of the drag object, + * you can alter how the drag behaves. + * </p> + * <p>The drag properties and methods:</p> + * <ul> + * <li><code>[jQuery.Drag.prototype.cancel cancel]</code> - stops the drag motion from happening</li> + * <li><code>[jQuery.Drag.prototype.ghost ghost]</code> - copys the draggable and drags the cloned element</li> + * <li><code>[jQuery.Drag.prototype.horizontal horizontal]</code> - limits the scroll to horizontal movement</li> + * <li><code>[jQuery.Drag.prototype.location location]</code> - where the drag should be on the screen</li> + * <li><code>[jQuery.Drag.prototype.mouseElementPosition mouseElementPosition]</code> - where the mouse should be on the drag</li> + * <li><code>[jQuery.Drag.prototype.only only]</code> - only have drags, no drops</li> + * <li><code>[jQuery.Drag.prototype.representative representative]</code> - move another element in place of this element</li> + * <li><code>[jQuery.Drag.prototype.revert revert]</code> - animate the drag back to its position</li> + * <li><code>[jQuery.Drag.prototype.vertical vertical]</code> - limit the drag to vertical movement</li> + * <li><code>[jQuery.Drag.prototype.limit limit]</code> - limit the drag within an element (*limit plugin)</li> + * <li><code>[jQuery.Drag.prototype.scrolls scrolls]</code> - scroll scrollable areas when dragging near their boundries (*scroll plugin)</li> + * </ul> + * <h2>Demo</h2> + * Now lets see some examples: + * @demo jquery/event/drag/drag.html 1000 + * @init + * The constructor is never called directly. + */ + $.Drag = function(){} + + /** + * @Static + */ + $.extend($.Drag, + { + lowerName: "drag", + current : null, + /** + * Called when someone mouses down on a draggable object. + * Gathers all callback functions and creates a new Draggable. + * @hide + */ + mousedown : function(ev, element){ + var isLeftButton = ev.button == 0 || ev.button == 1; + if( !isLeftButton || this.current) return; //only allows 1 drag at a time, but in future could allow more + + ev.preventDefault(); + //create Drag + var drag = new $.Drag(), + delegate = ev.liveFired || element, + selector = ev.handleObj.selector, + self = this; + this.current = drag; + drag.setup({ + element: element, + delegate: ev.liveFired || element, + selector: ev.handleObj.selector, + moved: false, + callbacks: { + dragdown: event.find(delegate, ["dragdown"], selector)[0], + draginit: event.find(delegate, ["draginit"], selector)[0], + dragover: event.find(delegate, ["dragover"], selector)[0], + dragmove: event.find(delegate, ["dragmove"], selector)[0], + dragout: event.find(delegate, ["dragout"], selector)[0], + dragend: event.find(delegate, ["dragend"], selector)[0] + }, + destroyed : function(){ + self.current = null; + } + }, ev) + + } + }) + + + + + + /** + * @Prototype + */ + $.extend($.Drag.prototype , { + setup : function(options, ev){ + this.noSelection(); + $.extend(this,options); + this.element = $(this.element); + this.event = ev; + this.moved = false; + this.allowOtherDrags = false; + var mousemove = bind(this, this.mousemove); + var mouseup = bind(this, this.mouseup); + this._mousemove = mousemove; + this._mouseup = mouseup; + $(document).bind('mousemove' ,mousemove); + $(document).bind('mouseup',mouseup); + this.callDown(this.element, ev) + }, + /** + * Unbinds listeners and allows other drags ... + * @hide + */ + destroy : function(){ + $(document).unbind('mousemove', this._mousemove); + $(document).unbind('mouseup', this._mouseup); + if(!this.moved){ + this.event = this.element = null; + } + this.selection(); + this.destroyed(); + }, + mousemove : function(docEl, ev){ + if(!this.moved){ + this.init(this.element, ev) + this.moved= true; + } + + var pointer = ev.vector(); + if (this._start_position && this._start_position.equals(pointer)) { + return; + } + //e.preventDefault(); + + this.draw(pointer, ev); + }, + mouseup : function(docEl,event){ + //if there is a current, we should call its dragstop + if(this.moved){ + this.end(event); + } + this.destroy(); + }, + noSelection : function(){ + document.documentElement.onselectstart = function() { return false; }; + document.documentElement.unselectable = "on"; + $(document.documentElement).css('-moz-user-select', 'none'); + }, + selection : function(){ + document.documentElement.onselectstart = function() { }; + document.documentElement.unselectable = "off"; + $(document.documentElement).css('-moz-user-select', ''); + }, + init : function( element, event){ + element = $(element); + var startElement = (this.movingElement = (this.element = $(element))); //the element that has been clicked on + //if a mousemove has come after the click + this._cancelled = false; //if the drag has been cancelled + this.event = event; + this.mouseStartPosition = event.vector(); //where the mouse is located + /** + * @attribute mouseElementPosition + * The position of start of the cursor on the element + */ + this.mouseElementPosition = this.mouseStartPosition.minus( this.element.offsetv() ); //where the mouse is on the Element + + this.callStart(element, event); + + //Check what they have set and respond accordingly + // if they canceled + if(this._cancelled == true) return; + //if they set something else as the element + + this.startPosition = startElement != this.movingElement ? this.movingElement.offsetv() : this.currentDelta(); + + this.movingElement.makePositioned(); + this.movingElement.css('zIndex',1000); + if(!this._only && this.constructor.responder) + this.constructor.responder.compile(event, this); + }, + callDown : function(element, event){ + if(this.callbacks[this.constructor.lowerName+"down"]) + this.callbacks[this.constructor.lowerName+"down"].call(element, event, this ); + }, + callStart : function(element, event){ + if(this.callbacks[this.constructor.lowerName+"init"]) + this.callbacks[this.constructor.lowerName+"init"].call(element, event, this ); + }, + /** + * Returns the position of the movingElement by taking its top and left. + * @hide + * @return {Vector} + */ + currentDelta: function() { + return new $.Vector( parseInt( this.movingElement.css('left') ) || 0 , + parseInt( this.movingElement.css('top') ) || 0 ) ; + }, + //draws the position of the dragmove object + draw: function(pointer, event){ + // only drag if we haven't been cancelled; + if(this._cancelled) return; + /** + * @attribute location + * The location of where the element should be in the page. This + * takes into account the start position of the cursor on the element. + */ + this.location = pointer.minus(this.mouseElementPosition); // the offset between the mouse pointer and the representative that the user asked for + // position = mouse - (dragOffset - dragTopLeft) - mousePosition + this.move( event ); + if(this._cancelled) return; + if(!event.isDefaultPrevented()) + this.position(this.location); + + //fill in + if(!this._only && this.constructor.responder) + this.constructor.responder.show(pointer, this, event); + }, + /** + * @hide + * Set the drag to only allow horizontal dragging + */ + position : function(offsetPositionv){ //should draw it on the page + var dragged_element_page_offset = this.movingElement.offsetv(); // the drag element's current page location + + var dragged_element_css_offset = this.currentDelta(); // the drag element's current left + top css attributes + + var dragged_element_position_vector = // the vector between the movingElement's page and css positions + dragged_element_page_offset.minus(dragged_element_css_offset); // this can be thought of as the original offset + + this.required_css_position = offsetPositionv.minus(dragged_element_position_vector) + + + + var style = this.movingElement[0].style; + if(!this._cancelled && !this._horizontal) { + style.top = this.required_css_position.top() + "px" + } + if(!this._cancelled && !this._vertical){ + style.left = this.required_css_position.left() + "px" + } + }, + move : function(event){ + if(this.callbacks[this.constructor.lowerName+"move"]) this.callbacks[this.constructor.lowerName+"move"].call(this.element, event, this ); + }, + /** + * Called on drag up + * @hide + * @param {Event} event a mouseup event signalling drag/drop has completed + */ + end : function(event){ + if(this._cancelled) return; + if(!this._only && this.constructor.responder) + this.constructor.responder.end(event, this); + + if(this.callbacks[this.constructor.lowerName+"end"]) + this.callbacks[this.constructor.lowerName+"end"].call(this.element, event, this ); + + if(this._revert){ + var self= this; + this.movingElement.animate( + { + top: this.startPosition.top()+"px", + left: this.startPosition.left()+"px"}, + function(){ + self.cleanup.apply(self, arguments) + } + ) + } + else + this.cleanup(); + this.event = null; + }, + /** + * Cleans up drag element after drag drop. + * @hide + */ + cleanup : function(){ + this.movingElement.css({zIndex: ""}) + if (this.movingElement[0] !== this.element[0]) + this.movingElement.css({ display: 'none' }); + if(this._removeMovingElement) + this.movingElement.remove(); + + this.movingElement = this.element = this.event = null; + }, + /** + * Stops drag drop from running. + */ + cancel: function() { + this._cancelled = true; + //this.end(this.event); + if(!this._only && this.constructor.responder) + this.constructor.responder.clear(this.event.vector(), this, this.event); + this.destroy(); + + }, + /** + * Clones the element and uses it as the moving element. + * @return {jQuery.fn} the ghost + */ + ghost: function(loc) { + // create a ghost by cloning the source element and attach the clone to the dom after the source element + var ghost = this.movingElement.clone().css('position','absolute'); + (loc ? $(loc) : this.movingElement ).after(ghost); + ghost.width(this.movingElement.width()) + .height(this.movingElement.height()) + + // store the original element and make the ghost the dragged element + this.movingElement = ghost; + this._removeMovingElement = true; + return ghost; + }, + /** + * Use a representative element, instead of the movingElement. + * @param {HTMLElement} element the element you want to actually drag + * @param {Number} offsetX the x position where you want your mouse on the object + * @param {Number} offsetY the y position where you want your mouse on the object + */ + representative : function( element, offsetX, offsetY ){ + this._offsetX = offsetX || 0; + this._offsetY = offsetY || 0; + + var p = this.mouseStartPosition; + + this.movingElement = $(element); + this.movingElement.css({ + top: (p.y() - this._offsetY) + "px", + left: (p.x() - this._offsetX) + "px", + display: 'block', + position: 'absolute' + }).show(); + + this.mouseElementPosition = new $.Vector(this._offsetX, this._offsetY) + }, + /** + * Makes the movingElement go back to its original position after drop. + * @codestart + * ".handle dragend" : function(el, ev, drag){ + * drag.revert() + * } + * @codeend + * @param {optional:Boolean} val optional, set to false if you don't want to revert. + */ + revert : function(val){ + this._revert = val == null ? true : val; + }, + /** + * Isolates the drag to vertical movement. + */ + vertical : function(){ + this._vertical = true; + }, + /** + * Isolates the drag to horizontal movement. + */ + horizontal : function(){ + this._horizontal = true; + }, + + + /** + * Respondables will not be alerted to this drag. + */ + only :function(only){ + return (this._only = (only === undefined ? true : only)); + } + }); + + /** + * @add jQuery.event.special static + */ + event.setupHelper( [ + /** + * @attribute dragdown + * Listens for when a drag movement has started on a mousedown. + * <p>Drag events are covered in more detail in [jQuery.Drag].</p> + * @codestart + * $(".handles").live("dragdown", function(ev, drag){}) + * @codeend + */ + 'dragdown', + /** + * @attribute draginit + * Called when the drag starts. + * <p>Drag events are covered in more detail in [jQuery.Drag].</p> + */ + 'draginit', + /** + * @attribute dragover + * Called when the drag is over a drop. + * <p>Drag events are covered in more detail in [jQuery.Drag].</p> + */ + 'dragover', + /** + * @attribute dragmove + * Called when the drag is moved. + * <p>Drag events are covered in more detail in [jQuery.Drag].</p> + */ + 'dragmove', + /** + * @attribute dragout + * When the drag leaves a drop point. + * <p>Drag events are covered in more detail in [jQuery.Drag].</p> + */ + 'dragout', + /** + * @attribute dragend + * Called when the drag is done. + * <p>Drag events are covered in more detail in [jQuery.Drag].</p> + */ + 'dragend' + ], "mousedown", function(e){ + $.Drag.mousedown.call($.Drag, e, this) + + } ) + + + + + +})(jQuery); + diff --git a/browserid/static/funcunit/test/jquery.event.drop.js b/browserid/static/funcunit/test/jquery.event.drop.js new file mode 100644 index 0000000000000000000000000000000000000000..6569856442151ee760d8df826976ad637a7dd9c5 --- /dev/null +++ b/browserid/static/funcunit/test/jquery.event.drop.js @@ -0,0 +1,439 @@ +// jquery/dom/within/within.js + +(function($){ + + var withinBox = function(x, y, left, top, width, height ){ + return (y >= top && + y < top + height && + x >= left && + x < left + width); + } +/** + * @function within + * @parent dom + * Returns if the elements are within the position + * @param {Object} x + * @param {Object} y + * @param {Object} cache + */ +$.fn.within= function(x, y, cache) { + var ret = [] + this.each(function(){ + var q = jQuery(this); + + if(this == document.documentElement) return ret.push(this); + + var offset = cache ? jQuery.data(this,"offset", q.offset()) : q.offset(); + + var res = withinBox(x, y, + offset.left, offset.top, + this.offsetWidth, this.offsetHeight ); + + if(res) ret.push(this); + }); + + return this.pushStack( jQuery.unique( ret ), "within", x+","+y ); +} + + +/** + * @function withinBox + * returns if elements are within the box + * @param {Object} left + * @param {Object} top + * @param {Object} width + * @param {Object} height + * @param {Object} cache + */ +$.fn.withinBox = function(left, top, width, height, cache){ + var ret = [] + this.each(function(){ + var q = jQuery(this); + + if(this == document.documentElement) return this.ret.push(this); + + var offset = cache ? jQuery.data(this,"offset", q.offset()) : q.offset(); + + var ew = q.width(), eh = q.height(); + + res = !( (offset.top > top+height) || (offset.top +eh < top) || (offset.left > left+width ) || (offset.left+ew < left)); + + if(res) + ret.push(this); + }); + return this.pushStack( jQuery.unique( ret ), "withinBox", jQuery.makeArray(arguments).join(",") ); +} + + +})(jQuery); + +// jquery/dom/compare/compare.js + +(function($){ + +/** + * @function compare + * @parent dom + * @download jquery/dist/jquery.compare.js + * Compares the position of two nodes and returns a bitmask detailing how they are positioned + * relative to each other. You can expect it to return the same results as + * [http://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-compareDocumentPosition | compareDocumentPosition]. + * Parts of this documentation and source come from [http://ejohn.org/blog/comparing-document-position | John Resig]. + * <h2>Demo</h2> + * @demo jquery/dom/compare/compare.html + * @test jquery/dom/compare/qunit.html + * @plugin dom/compare + * @param {HTMLElement} a the first node + * @param {HTMLElement} b the second node + * @return {Number} A bitmap with the following digit values: + * <table class='options'> + * <tr><th>Bits</th><th>Number</th><th>Meaning</th></tr> + * <tr><td>000000</td><td>0</td><td>Elements are identical.</td></tr> + * <tr><td>000001</td><td>1</td><td>The nodes are in different documents (or one is outside of a document).</td></tr> + * <tr><td>000010</td><td>2</td><td>Node B precedes Node A.</td></tr> + * <tr><td>000100</td><td>4</td><td>Node A precedes Node B.</td></tr> + * <tr><td>001000</td><td>8</td><td>Node B contains Node A.</td></tr> + * <tr><td>010000</td><td>16</td><td>Node A contains Node B.</td></tr> + * </table> + */ +jQuery.fn.compare = function(b){ //usually + //b is usually a relatedTarget, but b/c it is we have to avoid a few FF errors + + try{ //FF3 freaks out with XUL + b = b.jquery ? b[0] : b; + }catch(e){ + return null; + } + if (window.HTMLElement) { //make sure we aren't coming from XUL element + var s = HTMLElement.prototype.toString.call(b) + if (s == '[xpconnect wrapped native prototype]' || s == '[object XULElement]') return null; + } + if(this[0].compareDocumentPosition){ + return this[0].compareDocumentPosition(b); + } + if(this[0] == document && b != document) return 8; + var number = (this[0] !== b && this[0].contains(b) && 16) + (this[0] != b && b.contains(this[0]) && 8); + if(this[0].sourceIndex){ + number += (this[0].sourceIndex < b.sourceIndex && 4) + number += (this[0].sourceIndex > b.sourceIndex && 2) + }else{ + var range = document.createRange(), + sourceRange = document.createRange(), + compare; + range.selectNode(this[0]); + sourceRange.selectNode(b); + compare = range.compareBoundaryPoints(Range.START_TO_START, sourceRange); + number += (compare === -1 && 4) + number += (compare === 1 && 2) + } + + return number; +} + + +})(jQuery); + +// jquery/event/drop/drop.js + +(function($){ + + var event = $.event, + callHanders = function(){ + + }; + //somehow need to keep track of elements with selectors on them. When element is removed, somehow we need to know that + // + /** + * @add jQuery.event.special static + */ + var eventNames = [ + /** + * @attribute dropover + * Called when a drag is first moved over this drop element. + * <p>Drop events are covered in more detail in [jQuery.Drop].</p> + */ + "dropover", + /** + * @attribute dropon + * Called when a drag is dropped on a drop element. + * <p>Drop events are covered in more detail in [jQuery.Drop].</p> + */ + "dropon", + /** + * @attribute dropout + * Called when a drag is moved out of this drop. + * <p>Drop events are covered in more detail in [jQuery.Drop].</p> + */ + "dropout", + /** + * @attribute dropinit + * Called when a drag motion starts and the drop elements are initialized. + * <p>Drop events are covered in more detail in [jQuery.Drop].</p> + */ + "dropinit", + /** + * @attribute dropmove + * Called repeatedly when a drag is moved over a drop. + * <p>Drop events are covered in more detail in [jQuery.Drop].</p> + */ + "dropmove", + /** + * @attribute dropend + * Called when the drag is done for this drop. + * <p>Drop events are covered in more detail in [jQuery.Drop].</p> + */ + "dropend"]; + + + + /** + * @constructor jQuery.Drop + * @parent specialevents + * @plugin jquery/event/drop + * @download jquery/dist/jquery.event.drop.js + * @test jquery/event/drag/qunit.html + * + * Provides drop events as a special event to jQuery. + * By binding to a drop event, the callback functions will be + * called when during various phases of the drag event. + * <h2>Drop Events</h2> + * All drop events are called with the native event, an instance of drop, and the drag. Here are the available drop + * events: + * <ul> + * <li><code>dropinit</code> - the drag motion is started, drop positions are calculated.</li> + * <li><code>dropover</code> - a drag moves over a drop element, called once as the drop is dragged over the element.</li> + * <li><code>dropout</code> - a drag moves out of the drop element.</li> + * <li><code>dropmove</code> - a drag is moved over a drop element, called repeatedly as the element is moved.</li> + * <li><code>dropon</code> - a drag is released over a drop element.</li> + * <li><code>dropend</code> - the drag motion has completed.</li> + * </ul> + * <h2>Examples</h2> + * Here's how to listen for when a drag moves over a drop: + * @codestart + * $('.drop').live("dropover", function(ev, drop, drag){ + * $(this).addClass("drop-over") + * }) + * @codeend + * A bit more complex example: + * @demo jquery/event/drop/drop.html 1000 + * @init + * The constructor is never called directly. + */ + $.Drop = function(callbacks, element){ + jQuery.extend(this,callbacks); + this.element = element; + } + $.each(eventNames, function(){ + event.special[this] = { + add : function(handleObj){ + //add this element to the compiles list + var el = $(this), current = (el.data("dropEventCount") || 0); + el.data("dropEventCount", current+1 ) + if(current==0){ + $.Drop.addElement(this); + } + }, + remove : function(){ + var el = $(this), current = (el.data("dropEventCount") || 0); + el.data("dropEventCount", current-1 ) + if(current<=1){ + $.Drop.removeElement(this); + } + } + } + }) + $.extend($.Drop,{ + lowerName: "drop", + _elements: [], //elements that are listening for drops + _responders: [], //potential drop points + last_active: [], + endName: "dropon", + addElement : function(el){ + //check other elements + for(var i =0; i < this._elements.length ; i++ ){ + if(el ==this._elements[i]) return; + } + this._elements.push(el); + }, + removeElement : function(el){ + for(var i =0; i < this._elements.length ; i++ ){ + if(el == this._elements[i]){ + this._elements.splice(i,1) + return; + } + } + }, + /** + * @hide + * For a list of affected drops, sorts them by which is deepest in the DOM first. + */ + sortByDeepestChild: function(a, b) { + var compare = a.element.compare(b.element); + if(compare & 16 || compare & 4) return 1; + if(compare & 8 || compare & 2) return -1; + return 0; + }, + /** + * @hide + * Tests if a drop is within the point. + */ + isAffected: function(point, moveable, responder) { + return ((responder.element != moveable.element) && (responder.element.within(point[0], point[1], responder).length == 1)); + }, + /** + * @hide + * Calls dropout and sets last active to null + * @param {Object} drop + * @param {Object} drag + * @param {Object} event + */ + deactivate: function(responder, mover, event) { + responder.callHandlers(this.lowerName+'out',responder.element[0], event, mover) + }, + /** + * @hide + * Calls dropover + * @param {Object} drop + * @param {Object} drag + * @param {Object} event + */ + activate: function(responder, mover, event) { //this is where we should call over + //this.last_active = responder; + responder.callHandlers(this.lowerName+'over',responder.element[0], event, mover) + }, + move : function(responder, mover, event){ + responder.callHandlers(this.lowerName+'move',responder.element[0], event, mover) + }, + /** + * Gets all elements that are droppable, adds them + */ + compile : function(event, drag){ + var el, drops, selector, sels; + this.last_active = []; + for(var i=0; i < this._elements.length; i++){ //for each element + el = this._elements[i] + var drops = $.event.findBySelector(el, eventNames) + + for(selector in drops){ //find the selectors + sels = selector ? jQuery(selector, el) : [el]; + for(var e= 0; e < sels.length; e++){ //for each found element, create a drop point + jQuery.removeData(sels[e],"offset"); + this.add(sels[e], new this(drops[selector]), event, drag); + } + } + } + + }, + add: function(element, callbacks, event, drag) { + element = jQuery(element); + var responder = new $.Drop(callbacks, element); + responder.callHandlers(this.lowerName+'init', element[0], event, drag) + if(!responder._canceled){ + this._responders.push(responder); + } + }, + show : function(point, moveable, event){ + var element = moveable.element; + if(!this._responders.length) return; + + var respondable, + affected = [], + propagate = true, + i,j, la, toBeActivated, aff, + oldLastActive = this.last_active; + + for(var d =0 ; d < this._responders.length; d++ ){ + + if(this.isAffected(point, moveable, this._responders[d])){ + affected.push(this._responders[d]); + } + + } + + affected.sort(this.sortByDeepestChild); //we should only trigger on lowest children + event.stopRespondPropagate = function(){ + propagate = false; + } + //deactivate everything in last_active that isn't active + toBeActivated = affected.slice(); + this.last_active = affected; + for (j = 0; j < oldLastActive.length; j++) { + la = oldLastActive[j] + i = 0; + while((aff = toBeActivated[i])){ + if(la == aff){ + toBeActivated.splice(i,1);break; + }else{ + i++; + } + } + if(!aff){ + this.deactivate(la, moveable, event); + } + if(!propagate) return; + } + for(var i =0; i < toBeActivated.length; i++){ + this.activate(toBeActivated[i], moveable, event); + if(!propagate) return; + } + //activate everything in affected that isn't in last_active + + for (i = 0; i < affected.length; i++) { + this.move(affected[i], moveable, event); + + if(!propagate) return; + } + }, + end : function(event, moveable){ + var responder, la; + for(var r =0; r<this._responders.length; r++){ + this._responders[r].callHandlers(this.lowerName+'end', null, event, moveable); + } + //go through the actives ... if you are over one, call dropped on it + for(var i = 0; i < this.last_active.length; i++){ + la = this.last_active[i] + if( this.isAffected(event.vector(), moveable, la) && la[this.endName]){ + la.callHandlers(this.endName, null, event, moveable); + } + } + + + this.clear(); + }, + /** + * Called after dragging has stopped. + * @hide + */ + clear : function(){ + + this._responders = []; + } + }) + $.Drag.responder = $.Drop; + + $.extend($.Drop.prototype,{ + callHandlers : function(method, el, ev, drag){ + var length = this[method] ? this[method].length : 0 + for(var i =0; i < length; i++){ + this[method][i].call(el || this.element[0], ev, this, drag) + } + }, + /** + * Caches positions of draggable elements. This should be called in dropinit. For example: + * @codestart + * dropinit : function(el, ev, drop){ drop.cache_position() } + * @codeend + */ + cache: function(value){ + this._cache = value != null ? value : true; + }, + /** + * Prevents this drop from being dropped on. + */ + cancel : function(){ + this._canceled = true; + } + } ) + +})(jQuery); + diff --git a/browserid/static/funcunit/test/jquery.js b/browserid/static/funcunit/test/jquery.js new file mode 100644 index 0000000000000000000000000000000000000000..e7a9069326e311ac25851531837e3f6bbb028085 --- /dev/null +++ b/browserid/static/funcunit/test/jquery.js @@ -0,0 +1,6360 @@ +/*! + * jQuery JavaScript Library v1.4.3pre + * http://jquery.com/ + * + * Copyright 2010, John Resig + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * Copyright 2010, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * + * Date: Mon May 31 23:43:13 2010 -0500 + */ +(function( window, undefined ) { + +(function() { + +// Define a local copy of jQuery +var jQuery = function( selector, context ) { + // The jQuery object is actually just the init constructor 'enhanced' + return new jQuery.fn.init( selector, context ); + }, + + // Map over jQuery in case of overwrite + _jQuery = window.jQuery, + + // Map over the $ in case of overwrite + _$ = window.$, + + // Use the correct document accordingly with window argument (sandbox) + //document = window.document, + + // A central reference to the root jQuery(document) + rootjQuery, + + // A simple way to check for HTML strings or ID strings + // (both of which we optimize for) + quickExpr = /^[^<]*(<[\w\W]+>)[^>]*$|^#([\w\-]+)$/, + + // Is it a simple selector + isSimple = /^.[^:#\[\.,]*$/, + + // Check if a string has a non-whitespace character in it + rnotwhite = /\S/, + + // Used for trimming whitespace + trimLeft = /^\s+/, + trimRight = /\s+$/, + + // Match a standalone tag + rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/, + + // Keep a UserAgent string for use with jQuery.browser + userAgent = navigator.userAgent, + + // For matching the engine and version of the browser + browserMatch, + + // Has the ready events already been bound? + readyBound = false, + + // The functions to execute on DOM ready + readyList = [], + + // The ready event handler + DOMContentLoaded, + + // Save a reference to some core methods + toString = Object.prototype.toString, + hasOwn = Object.prototype.hasOwnProperty, + push = Array.prototype.push, + slice = Array.prototype.slice, + trim = String.prototype.trim, + indexOf = Array.prototype.indexOf; + +jQuery.fn = jQuery.prototype = { + init: function( selector, context ) { + var match, elem, ret, doc; + + // Handle $(""), $(null), or $(undefined) + if ( !selector ) { + return this; + } + + // Handle $(DOMElement) + if ( selector.nodeType ) { + this.context = this[0] = selector; + this.length = 1; + return this; + } + + // The body element only exists once, optimize finding it + if ( selector === "body" && !context ) { + this.context = document; + this[0] = document.body; + this.selector = "body"; + this.length = 1; + return this; + } + + // Handle HTML strings + if ( typeof selector === "string" ) { + // Are we dealing with HTML string or an ID? + match = quickExpr.exec( selector ); + + // Verify a match, and that no context was specified for #id + if ( match && (match[1] || !context) ) { + + // HANDLE: $(html) -> $(array) + if ( match[1] ) { + doc = (context ? context.ownerDocument || context : document); + + // If a single string is passed in and it's a single tag + // just do a createElement and skip the rest + ret = rsingleTag.exec( selector ); + + if ( ret ) { + if ( jQuery.isPlainObject( context ) ) { + selector = [ document.createElement( ret[1] ) ]; + jQuery.fn.attr.call( selector, context, true ); + + } else { + selector = [ doc.createElement( ret[1] ) ]; + } + + } else { + ret = buildFragment( [ match[1] ], [ doc ] ); + selector = (ret.cacheable ? ret.fragment.cloneNode(true) : ret.fragment).childNodes; + } + + return jQuery.merge( this, selector ); + + // HANDLE: $("#id") + } else { + elem = document.getElementById( match[2] ); + + if ( elem ) { + // Handle the case where IE and Opera return items + // by name instead of ID + if ( elem.id !== match[2] ) { + return rootjQuery.find( selector ); + } + + // Otherwise, we inject the element directly into the jQuery object + this.length = 1; + this[0] = elem; + } + + this.context = document; + this.selector = selector; + return this; + } + + // HANDLE: $("TAG") + } else if ( !context && /^\w+$/.test( selector ) ) { + this.selector = selector; + this.context = document; + selector = document.getElementsByTagName( selector ); + return jQuery.merge( this, selector ); + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return (context || rootjQuery).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return jQuery( context ).find( selector ); + } + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( jQuery.isFunction( selector ) ) { + return rootjQuery.ready( selector ); + } + + if (selector.selector !== undefined) { + this.selector = selector.selector; + this.context = selector.context; + } + + return jQuery.makeArray( selector, this ); + }, + + // Start with an empty selector + selector: "", + + // The current version of jQuery being used + jquery: "1.4.3pre", + + // The default length of a jQuery object is 0 + length: 0, + + // The number of elements contained in the matched element set + size: function() { + return this.length; + }, + + toArray: function() { + return slice.call( this, 0 ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + return num == null ? + + // Return a 'clean' array + this.toArray() : + + // Return just the object + ( num < 0 ? this.slice(num)[ 0 ] : this[ num ] ); + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems, name, selector ) { + // Build a new jQuery matched element set + var ret = jQuery(); + + if ( jQuery.isArray( elems ) ) { + push.apply( ret, elems ); + + } else { + jQuery.merge( ret, elems ); + } + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + ret.context = this.context; + + if ( name === "find" ) { + ret.selector = this.selector + (this.selector ? " " : "") + selector; + } else if ( name ) { + ret.selector = this.selector + "." + name + "(" + selector + ")"; + } + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + // (You can seed the arguments with an array of args, but this is + // only used internally.) + each: function( callback, args ) { + return jQuery.each( this, callback, args ); + }, + + ready: function( fn ) { + // Attach the listeners + jQuery.bindReady(); + + // If the DOM is already ready + if ( jQuery.isReady ) { + // Execute the function immediately + fn.call( document, jQuery ); + + // Otherwise, remember the function for later + } else if ( readyList ) { + // Add the function to the wait list + readyList.push( fn ); + } + + return this; + }, + + eq: function( i ) { + return i === -1 ? + this.slice( i ) : + this.slice( i, +i + 1 ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ), + "slice", slice.call(arguments).join(",") ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map(this, function( elem, i ) { + return callback.call( elem, i, elem ); + })); + }, + + end: function() { + return this.prevObject || jQuery(null); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: push, + sort: [].sort, + splice: [].splice +}; + +// Give the init function the jQuery prototype for later instantiation +jQuery.fn.init.prototype = jQuery.fn; + +jQuery.extend = jQuery.fn.extend = function() { + // copy reference to target object + var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options, name, src, copy; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + target = arguments[1] || {}; + // skip the boolean and the target + i = 2; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !jQuery.isFunction(target) ) { + target = {}; + } + + // extend jQuery itself if only one argument is passed + if ( length === i ) { + target = this; + --i; + } + + for ( ; i < length; i++ ) { + // Only deal with non-null/undefined values + if ( (options = arguments[ i ]) != null ) { + // Extend the base object + for ( name in options ) { + src = target[ name ]; + copy = options[ name ]; + + // Prevent never-ending loop + if ( target === copy ) { + continue; + } + + // Recurse if we're merging object literal values or arrays + if ( deep && copy && ( jQuery.isPlainObject(copy) || jQuery.isArray(copy) ) ) { + var clone = src && ( jQuery.isPlainObject(src) || jQuery.isArray(src) ) ? src + : jQuery.isArray(copy) ? [] : {}; + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend({ + noConflict: function( deep ) { + window.$ = _$; + + if ( deep ) { + window.jQuery = _jQuery; + } + + return jQuery; + }, + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // Handle when the DOM is ready + ready: function() { + // Make sure that the DOM is not already loaded + if ( !jQuery.isReady ) { + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if ( !document.body ) { + return setTimeout( jQuery.ready, 13 ); + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If there are functions bound, to execute + if ( readyList ) { + // Execute all of them + var fn, i = 0; + while ( (fn = readyList[ i++ ]) ) { + fn.call( document, jQuery ); + } + + // Reset the list of functions + readyList = null; + } + + // Trigger any bound ready events + if ( jQuery.fn.triggerHandler ) { + jQuery( document ).triggerHandler( "ready" ); + } + } + }, + + bindReady: function() { + if ( readyBound ) { + return; + } + + readyBound = true; + + // Catch cases where $(document).ready() is called after the + // browser event has already occurred. + if ( document.readyState === "complete" ) { + return jQuery.ready(); + } + + // Mozilla, Opera and webkit nightlies currently support this event + if ( document.addEventListener ) { + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", jQuery.ready, false ); + + // If IE event model is used + } else if ( document.attachEvent ) { + // ensure firing before onload, + // maybe late but safe also for iframes + document.attachEvent("onreadystatechange", DOMContentLoaded); + + // A fallback to window.onload, that will always work + window.attachEvent( "onload", jQuery.ready ); + + // If IE and not a frame + // continually check to see if the document is ready + var toplevel = false; + + try { + toplevel = window.frameElement == null; + } catch(e) {} + + if ( document.documentElement.doScroll && toplevel ) { + doScrollCheck(); + } + } + }, + + // See test/unit/core.js for details concerning isFunction. + // Since version 1.3, DOM methods and functions like alert + // aren't supported. They return false on IE (#2968). + isFunction: function( obj ) { + return toString.call(obj) === "[object Function]"; + }, + + isArray: function( obj ) { + return toString.call(obj) === "[object Array]"; + }, + + isPlainObject: function( obj ) { + // Must be an Object. + // Because of IE, we also have to check the presence of the constructor property. + // Make sure that DOM nodes and window objects don't pass through, as well + if ( !obj || toString.call(obj) !== "[object Object]" || obj.nodeType || obj.setInterval ) { + return false; + } + + // Not own constructor property must be Object + if ( obj.constructor && + !hasOwn.call(obj, "constructor") && + !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) { + return false; + } + + // Own properties are enumerated firstly, so to speed up, + // if last one is own, then all properties are own. + + var key; + for ( key in obj ) {} + + return key === undefined || hasOwn.call( obj, key ); + }, + + isEmptyObject: function( obj ) { + for ( var name in obj ) { + return false; + } + return true; + }, + + error: function( msg ) { + throw msg; + }, + + parseJSON: function( data ) { + if ( typeof data !== "string" || !data ) { + return null; + } + + // Make sure leading/trailing whitespace is removed (IE can't handle it) + data = jQuery.trim( data ); + + // Make sure the incoming data is actual JSON + // Logic borrowed from http://json.org/json2.js + if ( /^[\],:{}\s]*$/.test(data.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, "@") + .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, "]") + .replace(/(?:^|:|,)(?:\s*\[)+/g, "")) ) { + + // Try to use the native JSON parser first + return window.JSON && window.JSON.parse ? + window.JSON.parse( data ) : + (new Function("return " + data))(); + + } else { + jQuery.error( "Invalid JSON: " + data ); + } + }, + + noop: function() {}, + + // Evalulates a script in a global context + globalEval: function( data ) { + if ( data && rnotwhite.test(data) ) { + // Inspired by code by Andrea Giammarchi + // http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html + var head = document.getElementsByTagName("head")[0] || document.documentElement, + script = document.createElement("script"); + + script.type = "text/javascript"; + + if ( jQuery.support.scriptEval ) { + script.appendChild( document.createTextNode( data ) ); + } else { + script.text = data; + } + + // Use insertBefore instead of appendChild to circumvent an IE6 bug. + // This arises when a base node is used (#2709). + head.insertBefore( script, head.firstChild ); + head.removeChild( script ); + } + }, + + nodeName: function( elem, name ) { + return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase(); + }, + + // args is for internal usage only + each: function( object, callback, args ) { + var name, i = 0, + length = object.length, + isObj = length === undefined || jQuery.isFunction(object); + + if ( args ) { + if ( isObj ) { + for ( name in object ) { + if ( callback.apply( object[ name ], args ) === false ) { + break; + } + } + } else { + for ( ; i < length; ) { + if ( callback.apply( object[ i++ ], args ) === false ) { + break; + } + } + } + + // A special, fast, case for the most common use of each + } else { + if ( isObj ) { + for ( name in object ) { + if ( callback.call( object[ name ], name, object[ name ] ) === false ) { + break; + } + } + } else { + for ( var value = object[0]; + i < length && callback.call( value, i, value ) !== false; value = object[++i] ) {} + } + } + + return object; + }, + + // Use native String.trim function wherever possible + trim: trim ? + function( text ) { + return text == null ? + "" : + trim.call( text ); + } : + + // Otherwise use our own trimming functionality + function( text ) { + return text == null ? + "" : + text.toString().replace( trimLeft, "" ).replace( trimRight, "" ); + }, + + // results is for internal usage only + makeArray: function( array, results ) { + var ret = results || []; + + if ( array != null ) { + // The window, strings (and functions) also have 'length' + // The extra typeof function check is to prevent crashes + // in Safari 2 (See: #3039) + if ( array.length == null || typeof array === "string" || jQuery.isFunction(array) || (typeof array !== "function" && array.setInterval) ) { + push.call( ret, array ); + } else { + jQuery.merge( ret, array ); + } + } + + return ret; + }, + + inArray: function( elem, array ) { + if ( array.indexOf ) { + return array.indexOf( elem ); + } + + for ( var i = 0, length = array.length; i < length; i++ ) { + if ( array[ i ] === elem ) { + return i; + } + } + + return -1; + }, + + merge: function( first, second ) { + var i = first.length, j = 0; + + if ( typeof second.length === "number" ) { + for ( var l = second.length; j < l; j++ ) { + first[ i++ ] = second[ j ]; + } + + } else { + while ( second[j] !== undefined ) { + first[ i++ ] = second[ j++ ]; + } + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, inv ) { + var ret = [], retVal; + inv = !!inv; + + // Go through the array, only saving the items + // that pass the validator function + for ( var i = 0, length = elems.length; i < length; i++ ) { + retVal = !!callback( elems[ i ], i ); + if ( inv !== retVal ) { + ret.push( elems[ i ] ); + } + } + + return ret; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var ret = [], value; + + // Go through the array, translating each of the items to their + // new value (or values). + for ( var i = 0, length = elems.length; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret[ ret.length ] = value; + } + } + + return ret.concat.apply( [], ret ); + }, + + // A global GUID counter for objects + guid: 1, + + proxy: function( fn, proxy, thisObject ) { + if ( arguments.length === 2 ) { + if ( typeof proxy === "string" ) { + thisObject = fn; + fn = thisObject[ proxy ]; + proxy = undefined; + + } else if ( proxy && !jQuery.isFunction( proxy ) ) { + thisObject = proxy; + proxy = undefined; + } + } + + if ( !proxy && fn ) { + proxy = function() { + return fn.apply( thisObject || this, arguments ); + }; + } + + // Set the guid of unique handler to the same of original handler, so it can be removed + if ( fn ) { + proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++; + } + + // So proxy can be declared as an argument + return proxy; + }, + + // Mutifunctional method to get and set values to a collection + // The value/s can be optionally by executed if its a function + access: function( elems, key, value, exec, fn, pass ) { + var length = elems.length; + + // Setting many attributes + if ( typeof key === "object" ) { + for ( var k in key ) { + jQuery.access( elems, k, key[k], exec, fn, value ); + } + return elems; + } + + // Setting one attribute + if ( value !== undefined ) { + // Optionally, function values get executed if exec is true + exec = !pass && exec && jQuery.isFunction(value); + + for ( var i = 0; i < length; i++ ) { + fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass ); + } + + return elems; + } + + // Getting an attribute + return length ? fn( elems[0], key ) : undefined; + }, + + now: function() { + return (new Date()).getTime(); + }, + + // Use of jQuery.browser is frowned upon. + // More details: http://docs.jquery.com/Utilities/jQuery.browser + uaMatch: function( ua ) { + ua = ua.toLowerCase(); + + var match = /(webkit)[ \/]([\w.]+)/.exec( ua ) || + /(opera)(?:.*version)?[ \/]([\w.]+)/.exec( ua ) || + /(msie) ([\w.]+)/.exec( ua ) || + !/compatible/.test( ua ) && /(mozilla)(?:.*? rv:([\w.]+))?/.exec( ua ) || + []; + + return { browser: match[1] || "", version: match[2] || "0" }; + }, + + browser: {} +}); + +browserMatch = jQuery.uaMatch( userAgent ); +if ( browserMatch.browser ) { + jQuery.browser[ browserMatch.browser ] = true; + jQuery.browser.version = browserMatch.version; +} + +// Deprecated, use jQuery.browser.webkit instead +if ( jQuery.browser.webkit ) { + jQuery.browser.safari = true; +} + +if ( indexOf ) { + jQuery.inArray = function( elem, array ) { + return indexOf.call( array, elem ); + }; +} + +// Verify that \s matches non-breaking spaces +// (IE fails on this test) +if ( !/\s/.test( "\xA0" ) ) { + trimLeft = /^[\s\xA0]+/; + trimRight = /[\s\xA0]+$/; +} + +// All jQuery objects should point back to these +rootjQuery = jQuery(document); + +// Cleanup functions for the document ready method +if ( document.addEventListener ) { + DOMContentLoaded = function() { + document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false ); + jQuery.ready(); + }; + +} else if ( document.attachEvent ) { + DOMContentLoaded = function() { + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if ( document.readyState === "complete" ) { + document.detachEvent( "onreadystatechange", DOMContentLoaded ); + jQuery.ready(); + } + }; +} + +// The DOM ready check for Internet Explorer +function doScrollCheck() { + if ( jQuery.isReady ) { + return; + } + + try { + // If IE is used, use the trick by Diego Perini + // http://javascript.nwbox.com/IEContentLoaded/ + document.documentElement.doScroll("left"); + } catch(e) { + setTimeout( doScrollCheck, 1 ); + return; + } + + // and execute any waiting functions + jQuery.ready(); +} + +// Expose jQuery to the global object +window.jQuery = window.$ = jQuery; + +})(); +(function() { + + jQuery.support = {}; + + var root = document.documentElement, + script = document.createElement("script"), + div = document.createElement("div"), + id = "script" + jQuery.now(); + + div.style.display = "none"; + div.innerHTML = " <link/><table></table><a href='/a' style='color:red;float:left;opacity:.55;'>a</a><input type='checkbox'/>"; + + var all = div.getElementsByTagName("*"), + a = div.getElementsByTagName("a")[0]; + + // Can't get basic test support + if ( !all || !all.length || !a ) { + return; + } + + jQuery.support = { + // IE strips leading whitespace when .innerHTML is used + leadingWhitespace: div.firstChild.nodeType === 3, + + // Make sure that tbody elements aren't automatically inserted + // IE will insert them into empty tables + tbody: !div.getElementsByTagName("tbody").length, + + // Make sure that link elements get serialized correctly by innerHTML + // This requires a wrapper element in IE + htmlSerialize: !!div.getElementsByTagName("link").length, + + // Get the style information from getAttribute + // (IE uses .cssText insted) + style: /red/.test( a.getAttribute("style") ), + + // Make sure that URLs aren't manipulated + // (IE normalizes it by default) + hrefNormalized: a.getAttribute("href") === "/a", + + // Make sure that element opacity exists + // (IE uses filter instead) + // Use a regex to work around a WebKit issue. See #5145 + opacity: /^0.55$/.test( a.style.opacity ), + + // Verify style float existence + // (IE uses styleFloat instead of cssFloat) + cssFloat: !!a.style.cssFloat, + + // Make sure that if no value is specified for a checkbox + // that it defaults to "on". + // (WebKit defaults to "" instead) + checkOn: div.getElementsByTagName("input")[0].value === "on", + + // Make sure that a selected-by-default option has a working selected property. + // (WebKit defaults to false instead of true, IE too, if it's in an optgroup) + optSelected: document.createElement("select").appendChild( document.createElement("option") ).selected, + + // Will be defined later + checkClone: false, + scriptEval: false, + noCloneEvent: true, + boxModel: null + }; + + script.type = "text/javascript"; + try { + script.appendChild( document.createTextNode( "window." + id + "=1;" ) ); + } catch(e) {} + + root.insertBefore( script, root.firstChild ); + + // Make sure that the execution of code works by injecting a script + // tag with appendChild/createTextNode + // (IE doesn't support this, fails, and uses .text instead) + if ( window[ id ] ) { + jQuery.support.scriptEval = true; + delete window[ id ]; + } + + root.removeChild( script ); + + if ( div.attachEvent && div.fireEvent ) { + div.attachEvent("onclick", function click() { + // Cloning a node shouldn't copy over any + // bound event handlers (IE does this) + jQuery.support.noCloneEvent = false; + div.detachEvent("onclick", click); + }); + div.cloneNode(true).fireEvent("onclick"); + } + + div = document.createElement("div"); + div.innerHTML = "<input type='radio' name='radiotest' checked='checked'/>"; + + var fragment = document.createDocumentFragment(); + fragment.appendChild( div.firstChild ); + + // WebKit doesn't clone checked state correctly in fragments + jQuery.support.checkClone = fragment.cloneNode(true).cloneNode(true).lastChild.checked; + + // Figure out if the W3C box model works as expected + // document.body must exist before we can do this + jQuery(function() { + var div = document.createElement("div"); + div.style.width = div.style.paddingLeft = "1px"; + + document.body.appendChild( div ); + jQuery.boxModel = jQuery.support.boxModel = div.offsetWidth === 2; + document.body.removeChild( div ).style.display = 'none'; + div = null; + }); + + // Technique from Juriy Zaytsev + // http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/ + var eventSupported = function( eventName ) { + var el = document.createElement("div"); + eventName = "on" + eventName; + + var isSupported = (eventName in el); + if ( !isSupported ) { + el.setAttribute(eventName, "return;"); + isSupported = typeof el[eventName] === "function"; + } + el = null; + + return isSupported; + }; + + jQuery.support.submitBubbles = eventSupported("submit"); + jQuery.support.changeBubbles = eventSupported("change"); + + // release memory in IE + root = script = div = all = a = null; +})(); + +jQuery.props = { + "for": "htmlFor", + "class": "className", + readonly: "readOnly", + maxlength: "maxLength", + cellspacing: "cellSpacing", + rowspan: "rowSpan", + colspan: "colSpan", + tabindex: "tabIndex", + usemap: "useMap", + frameborder: "frameBorder" +}; +var windowData = {}; + +jQuery.extend({ + cache: {}, + + // Please use with caution + uuid: 0, + + // Unique for each copy of jQuery on the page + expando: "jQuery" + jQuery.now(), + + // The following elements throw uncatchable exceptions if you + // attempt to add expando properties to them. + noData: { + "embed": true, + "object": true, + "applet": true + }, + + data: function( elem, name, data ) { + if ( elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()] ) { + return; + } + + elem = elem == window ? + windowData : + elem; + + var id = elem[ jQuery.expando ], cache = jQuery.cache, thisCache, + isNode = elem.nodeType; + + if ( !id && typeof name === "string" && data === undefined ) { + return; + } + + // Get the data from the object directly + if ( !isNode ) { + cache = elem; + id = jQuery.expando; + + // Compute a unique ID for the element + } else if ( !id ) { + elem[ jQuery.expando ] = id = ++jQuery.uuid; + } + + // Avoid generating a new cache unless none exists and we + // want to manipulate it. + if ( typeof name === "object" ) { + cache[ id ] = jQuery.extend(true, {}, name); + + } else if ( !cache[ id ] ) { + cache[ id ] = {}; + } + + thisCache = cache[ id ]; + + // Prevent overriding the named cache with undefined values + if ( data !== undefined ) { + thisCache[ name ] = data; + } + + return typeof name === "string" ? thisCache[ name ] : thisCache; + }, + + removeData: function( elem, name ) { + if ( elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()] ) { + return; + } + + elem = elem == window ? + windowData : + elem; + + var id = elem[ jQuery.expando ], cache = jQuery.cache, + isNode = elem.nodeType, thisCache = isNode ? cache[ id ] : id; + + // If we want to remove a specific section of the element's data + if ( name ) { + if ( thisCache ) { + // Remove the section of cache data + delete thisCache[ name ]; + + // If we've removed all the data, remove the element's cache + if ( jQuery.isEmptyObject(thisCache) ) { + jQuery.removeData( elem ); + } + } + + // Otherwise, we want to remove all of the element's data + } else { + if ( jQuery.support.deleteExpando || !isNode ) { + delete elem[ jQuery.expando ]; + + } else if ( elem.removeAttribute ) { + elem.removeAttribute( jQuery.expando ); + } + + // Completely remove the data cache + if ( isNode ) { + delete cache[ id ]; + } + } + } +}); + +jQuery.fn.extend({ + data: function( key, value ) { + if ( typeof key === "undefined" && this.length ) { + return jQuery.data( this[0] ); + + } else if ( typeof key === "object" ) { + return this.each(function() { + jQuery.data( this, key ); + }); + } + + var parts = key.split("."); + parts[1] = parts[1] ? "." + parts[1] : ""; + + if ( value === undefined ) { + var data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]); + + if ( data === undefined && this.length ) { + data = jQuery.data( this[0], key ); + } + return data === undefined && parts[1] ? + this.data( parts[0] ) : + data; + } else { + return this.trigger("setData" + parts[1] + "!", [parts[0], value]).each(function() { + jQuery.data( this, key, value ); + }); + } + }, + + removeData: function( key ) { + return this.each(function() { + jQuery.removeData( this, key ); + }); + } +}); +jQuery.extend({ + queue: function( elem, type, data ) { + if ( !elem ) { + return; + } + + type = (type || "fx") + "queue"; + var q = jQuery.data( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( !data ) { + return q || []; + } + + if ( !q || jQuery.isArray(data) ) { + q = jQuery.data( elem, type, jQuery.makeArray(data) ); + + } else { + q.push( data ); + } + + return q; + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), fn = queue.shift(); + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + } + + if ( fn ) { + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift("inprogress"); + } + + fn.call(elem, function() { + jQuery.dequeue(elem, type); + }); + } + } +}); + +jQuery.fn.extend({ + queue: function( type, data ) { + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + } + + if ( data === undefined ) { + return jQuery.queue( this[0], type ); + } + return this.each(function( i, elem ) { + var queue = jQuery.queue( this, type, data ); + + if ( type === "fx" && queue[0] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + }); + }, + dequeue: function( type ) { + return this.each(function() { + jQuery.dequeue( this, type ); + }); + }, + + // Based off of the plugin by Clint Helfers, with permission. + // http://blindsignals.com/index.php/2009/07/jquery-delay/ + delay: function( time, type ) { + time = jQuery.fx ? jQuery.fx.speeds[time] || time : time; + type = type || "fx"; + + return this.queue( type, function() { + var elem = this; + setTimeout(function() { + jQuery.dequeue( elem, type ); + }, time ); + }); + }, + + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + } +}); +var rclass = /[\n\t]/g, + rspace = /\s+/, + rreturn = /\r/g, + rspecialurl = /href|src|style/, + rtype = /(button|input)/i, + rfocusable = /(button|input|object|select|textarea)/i, + rclickable = /^(a|area)$/i, + rradiocheck = /radio|checkbox/; + +jQuery.fn.extend({ + attr: function( name, value ) { + return jQuery.access( this, name, value, true, jQuery.attr ); + }, + + removeAttr: function( name, fn ) { + return this.each(function(){ + jQuery.attr( this, name, "" ); + if ( this.nodeType === 1 ) { + this.removeAttribute( name ); + } + }); + }, + + addClass: function( value ) { + if ( jQuery.isFunction(value) ) { + return this.each(function(i) { + var self = jQuery(this); + self.addClass( value.call(this, i, self.attr("class")) ); + }); + } + + if ( value && typeof value === "string" ) { + var classNames = (value || "").split( rspace ); + + for ( var i = 0, l = this.length; i < l; i++ ) { + var elem = this[i]; + + if ( elem.nodeType === 1 ) { + if ( !elem.className ) { + elem.className = value; + + } else { + var className = " " + elem.className + " ", setClass = elem.className; + for ( var c = 0, cl = classNames.length; c < cl; c++ ) { + if ( className.indexOf( " " + classNames[c] + " " ) < 0 ) { + setClass += " " + classNames[c]; + } + } + elem.className = jQuery.trim( setClass ); + } + } + } + } + + return this; + }, + + removeClass: function( value ) { + if ( jQuery.isFunction(value) ) { + return this.each(function(i) { + var self = jQuery(this); + self.removeClass( value.call(this, i, self.attr("class")) ); + }); + } + + if ( (value && typeof value === "string") || value === undefined ) { + var classNames = (value || "").split(rspace); + + for ( var i = 0, l = this.length; i < l; i++ ) { + var elem = this[i]; + + if ( elem.nodeType === 1 && elem.className ) { + if ( value ) { + var className = (" " + elem.className + " ").replace(rclass, " "); + for ( var c = 0, cl = classNames.length; c < cl; c++ ) { + className = className.replace(" " + classNames[c] + " ", " "); + } + elem.className = jQuery.trim( className ); + + } else { + elem.className = ""; + } + } + } + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + var type = typeof value, isBool = typeof stateVal === "boolean"; + + if ( jQuery.isFunction( value ) ) { + return this.each(function(i) { + var self = jQuery(this); + self.toggleClass( value.call(this, i, self.attr("class"), stateVal), stateVal ); + }); + } + + return this.each(function() { + if ( type === "string" ) { + // toggle individual class names + var className, i = 0, self = jQuery(this), + state = stateVal, + classNames = value.split( rspace ); + + while ( (className = classNames[ i++ ]) ) { + // check each className given, space seperated list + state = isBool ? state : !self.hasClass( className ); + self[ state ? "addClass" : "removeClass" ]( className ); + } + + } else if ( type === "undefined" || type === "boolean" ) { + if ( this.className ) { + // store className if set + jQuery.data( this, "__className__", this.className ); + } + + // toggle whole className + this.className = this.className || value === false ? "" : jQuery.data( this, "__className__" ) || ""; + } + }); + }, + + hasClass: function( selector ) { + var className = " " + selector + " "; + for ( var i = 0, l = this.length; i < l; i++ ) { + if ( (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) { + return true; + } + } + + return false; + }, + + val: function( value ) { + if ( value === undefined ) { + var elem = this[0]; + + if ( elem ) { + if ( jQuery.nodeName( elem, "option" ) ) { + return (elem.attributes.value || {}).specified ? elem.value : elem.text; + } + + // We need to handle select boxes special + if ( jQuery.nodeName( elem, "select" ) ) { + var index = elem.selectedIndex, + values = [], + options = elem.options, + one = elem.type === "select-one"; + + // Nothing was selected + if ( index < 0 ) { + return null; + } + + // Loop through all the selected options + for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) { + var option = options[ i ]; + + if ( option.selected ) { + // Get the specifc value for the option + value = jQuery(option).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } + } + + return values; + } + + // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified + if ( rradiocheck.test( elem.type ) && !jQuery.support.checkOn ) { + return elem.getAttribute("value") === null ? "on" : elem.value; + } + + + // Everything else, we just grab the value + return (elem.value || "").replace(rreturn, ""); + + } + + return undefined; + } + + var isFunction = jQuery.isFunction(value); + + return this.each(function(i) { + var self = jQuery(this), val = value; + + if ( this.nodeType !== 1 ) { + return; + } + + if ( isFunction ) { + val = value.call(this, i, self.val()); + } + + // Typecast each time if the value is a Function and the appended + // value is therefore different each time. + if ( typeof val === "number" ) { + val += ""; + } + + if ( jQuery.isArray(val) && rradiocheck.test( this.type ) ) { + this.checked = jQuery.inArray( self.val(), val ) >= 0; + + } else if ( jQuery.nodeName( this, "select" ) ) { + var values = jQuery.makeArray(val); + + jQuery( "option", this ).each(function() { + this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0; + }); + + if ( !values.length ) { + this.selectedIndex = -1; + } + + } else { + this.value = val; + } + }); + } +}); + +jQuery.extend({ + attrFn: { + val: true, + css: true, + html: true, + text: true, + data: true, + width: true, + height: true, + offset: true + }, + + attr: function( elem, name, value, pass ) { + // don't set attributes on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 ) { + return undefined; + } + + if ( pass && name in jQuery.attrFn ) { + return jQuery(elem)[name](value); + } + + var notxml = elem.nodeType !== 1 || !jQuery.isXMLDoc( elem ), + // Whether we are setting (or getting) + set = value !== undefined; + + // Try to normalize/fix the name + name = notxml && jQuery.props[ name ] || name; + + // Only do all the following if this is a node (faster for style) + if ( elem.nodeType === 1 ) { + // These attributes require special treatment + var special = rspecialurl.test( name ); + + // Safari mis-reports the default selected property of an option + // Accessing the parent's selectedIndex property fixes it + if ( name === "selected" && !jQuery.support.optSelected ) { + var parent = elem.parentNode; + if ( parent ) { + parent.selectedIndex; + + // Make sure that it also works with optgroups, see #5701 + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + } + + // If applicable, access the attribute via the DOM 0 way + if ( name in elem && notxml && !special ) { + if ( set ) { + // We can't allow the type property to be changed (since it causes problems in IE) + if ( name === "type" && rtype.test( elem.nodeName ) && elem.parentNode ) { + jQuery.error( "type property can't be changed" ); + } + + elem[ name ] = value; + } + + // browsers index elements by id/name on forms, give priority to attributes. + if ( jQuery.nodeName( elem, "form" ) && elem.getAttributeNode(name) ) { + return elem.getAttributeNode( name ).nodeValue; + } + + // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set + // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + if ( name === "tabIndex" ) { + var attributeNode = elem.getAttributeNode( "tabIndex" ); + + return attributeNode && attributeNode.specified ? + attributeNode.value : + rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? + 0 : + undefined; + } + + return elem[ name ]; + } + + if ( !jQuery.support.style && notxml && name === "style" ) { + if ( set ) { + elem.style.cssText = "" + value; + } + + return elem.style.cssText; + } + + if ( set ) { + // convert the value to a string (all browsers do this but IE) see #1070 + elem.setAttribute( name, "" + value ); + } + + var attr = !jQuery.support.hrefNormalized && notxml && special ? + // Some attributes require a special call on IE + elem.getAttribute( name, 2 ) : + elem.getAttribute( name ); + + // Non-existent attributes return null, we normalize to undefined + return attr === null ? undefined : attr; + } + + // elem is actually elem.style ... set the style + // Using attr for specific style information is now deprecated. Use style instead. + return jQuery.style( elem, name, value ); + } +}); +var rnamespaces = /\.(.*)$/, + fcleanup = function( nm ) { + return nm.replace(/[^\w\s\.\|`]/g, function( ch ) { + return "\\" + ch; + }); + }, + focusCounts = {focusin: 0, focusout: 0}; + +/* + * A number of helper functions used for managing events. + * Many of the ideas behind this code originated from + * Dean Edwards' addEvent library. + */ +jQuery.event = { + + // Bind an event to an element + // Original by Dean Edwards + add: function( elem, types, handler, data ) { + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // For whatever reason, IE has trouble passing the window object + // around, causing it to be cloned in the process + if ( elem.setInterval && ( elem !== window && !elem.frameElement ) ) { + elem = window; + } + + if ( handler === false ) { + handler = returnFalse; + } + + var handleObjIn, handleObj; + + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + } + + // Make sure that the function being executed has a unique ID + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure + var elemData = jQuery.data( elem ); + + // If no elemData is found then we must be trying to bind to one of the + // banned noData elements + if ( !elemData ) { + return; + } + + var events = elemData.events = elemData.events || {}, + eventHandle = elemData.handle; + + if ( !eventHandle ) { + elemData.handle = eventHandle = function() { + // Handle the second event of a trigger and when + // an event is called after a page has unloaded + return typeof jQuery !== "undefined" && !jQuery.event.triggered ? + jQuery.event.handle.apply( eventHandle.elem, arguments ) : + undefined; + }; + } + + // Add elem as a property of the handle function + // This is to prevent a memory leak with non-native events in IE. + eventHandle.elem = elem; + + // Handle multiple events separated by a space + // jQuery(...).bind("mouseover mouseout", fn); + types = types.split(" "); + + var type, i = 0, namespaces; + + while ( (type = types[ i++ ]) ) { + handleObj = handleObjIn ? + jQuery.extend({}, handleObjIn) : + { handler: handler, data: data }; + + // Namespaced event handlers + if ( type.indexOf(".") > -1 ) { + namespaces = type.split("."); + type = namespaces.shift(); + handleObj.namespace = namespaces.slice(0).sort().join("."); + + } else { + namespaces = []; + handleObj.namespace = ""; + } + + handleObj.type = type; + if ( !handleObj.guid ) { + handleObj.guid = handler.guid; + } + + // Get the current list of functions bound to this event + var handlers = events[ type ], + special = jQuery.event.special[ type ] || {}; + + // Init the event handler queue + if ( !handlers ) { + handlers = events[ type ] = []; + + // Check for a special event handler + // Only use addEventListener/attachEvent if the special + // events handler returns false + if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + // Bind the global event handler to the element + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle, false ); + + } else if ( elem.attachEvent ) { + elem.attachEvent( "on" + type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add the function to the element's handler list + handlers.push( handleObj ); + + // Keep track of which events have been used, for global triggering + jQuery.event.global[ type ] = true; + } + + // Nullify elem to prevent memory leaks in IE + elem = null; + }, + + global: {}, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, pos ) { + // don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + if ( handler === false ) { + handler = returnFalse; + } + + var ret, type, fn, j, i = 0, all, namespaces, namespace, special, eventType, handleObj, origType, + elemData = jQuery.data( elem ), + events = elemData && elemData.events; + + if ( !elemData || !events ) { + return; + } + + // types is actually an event object here + if ( types && types.type ) { + handler = types.handler; + types = types.type; + } + + // Unbind all events for the element + if ( !types || typeof types === "string" && types.charAt(0) === "." ) { + types = types || ""; + + for ( type in events ) { + jQuery.event.remove( elem, type + types ); + } + + return; + } + + // Handle multiple events separated by a space + // jQuery(...).unbind("mouseover mouseout", fn); + types = types.split(" "); + + while ( (type = types[ i++ ]) ) { + origType = type; + handleObj = null; + all = type.indexOf(".") < 0; + namespaces = []; + + if ( !all ) { + // Namespaced event handlers + namespaces = type.split("."); + type = namespaces.shift(); + + namespace = new RegExp("(^|\\.)" + + jQuery.map( namespaces.slice(0).sort(), fcleanup ).join("\\.(?:.*\\.)?") + "(\\.|$)"); + } + + eventType = events[ type ]; + + if ( !eventType ) { + continue; + } + + if ( !handler ) { + for ( j = 0; j < eventType.length; j++ ) { + handleObj = eventType[ j ]; + + if ( all || namespace.test( handleObj.namespace ) ) { + jQuery.event.remove( elem, origType, handleObj.handler, j ); + eventType.splice( j--, 1 ); + } + } + + continue; + } + + special = jQuery.event.special[ type ] || {}; + + for ( j = pos || 0; j < eventType.length; j++ ) { + handleObj = eventType[ j ]; + + if ( handler.guid === handleObj.guid ) { + // remove the given handler for the given type + if ( all || namespace.test( handleObj.namespace ) ) { + if ( pos == null ) { + eventType.splice( j--, 1 ); + } + + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + + if ( pos != null ) { + break; + } + } + } + + // remove generic event handler if no more handlers exist + if ( eventType.length === 0 || pos != null && eventType.length === 1 ) { + if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) { + removeEvent( elem, type, elemData.handle ); + } + + ret = null; + delete events[ type ]; + } + } + + // Remove the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + var handle = elemData.handle; + if ( handle ) { + handle.elem = null; + } + + delete elemData.events; + delete elemData.handle; + + if ( jQuery.isEmptyObject( elemData ) ) { + jQuery.removeData( elem ); + } + } + }, + + // bubbling is internal + trigger: function( event, data, elem /*, bubbling */ ) { + // Event object or event type + var type = event.type || event, + bubbling = arguments[3]; + + if ( !bubbling ) { + event = typeof event === "object" ? + // jQuery.Event object + event[ jQuery.expando ] ? event : + // Object literal + jQuery.extend( jQuery.Event(type), event ) : + // Just the event type (string) + jQuery.Event(type); + + if ( type.indexOf("!") >= 0 ) { + event.type = type = type.slice(0, -1); + event.exclusive = true; + } + + // Handle a global trigger + if ( !elem ) { + // Don't bubble custom events when global (to avoid too much overhead) + event.stopPropagation(); + + // Only trigger if we've ever bound an event for it + if ( jQuery.event.global[ type ] ) { + jQuery.each( jQuery.cache, function() { + if ( this.events && this.events[type] ) { + jQuery.event.trigger( event, data, this.handle.elem ); + } + }); + } + } + + // Handle triggering a single element + + // don't do events on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 ) { + return undefined; + } + + // Clean up in case it is reused + event.result = undefined; + event.target = elem; + + // Clone the incoming data, if any + data = jQuery.makeArray( data ); + data.unshift( event ); + } + + event.currentTarget = elem; + + // Trigger the event, it is assumed that "handle" is a function + var handle = jQuery.data( elem, "handle" ); + if ( handle ) { + handle.apply( elem, data ); + } + + var parent = elem.parentNode || elem.ownerDocument; + + // Trigger an inline bound script + try { + if ( !(elem && elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()]) ) { + if ( elem[ "on" + type ] && elem[ "on" + type ].apply( elem, data ) === false ) { + event.result = false; + } + } + + // prevent IE from throwing an error for some elements with some event types, see #3533 + } catch (inlineError) {} + + if ( !event.isPropagationStopped() && parent ) { + jQuery.event.trigger( event, data, parent, true ); + + } else if ( !event.isDefaultPrevented() ) { + var target = event.target, old, targetType = type.replace(/\..*$/, ""), + isClick = jQuery.nodeName(target, "a") && targetType === "click", + special = jQuery.event.special[ targetType ] || {}; + + if ( (!special._default || special._default.call( elem, event ) === false) && + !isClick && !(target && target.nodeName && jQuery.noData[target.nodeName.toLowerCase()]) ) { + + try { + if ( target[ targetType ] ) { + // Make sure that we don't accidentally re-trigger the onFOO events + old = target[ "on" + targetType ]; + + if ( old ) { + target[ "on" + targetType ] = null; + } + + jQuery.event.triggered = true; + target[ targetType ](); + } + + // prevent IE from throwing an error for some elements with some event types, see #3533 + } catch (triggerError) {} + + if ( old ) { + target[ "on" + targetType ] = old; + } + + jQuery.event.triggered = false; + } + } + }, + + handle: function( event ) { + var all, handlers, namespaces, namespace_sort = [], namespace_re, events, args = jQuery.makeArray( arguments ); + + event = args[0] = jQuery.event.fix( event || window.event ); + event.currentTarget = this; + + // Namespaced event handlers + all = event.type.indexOf(".") < 0 && !event.exclusive; + + if ( !all ) { + namespaces = event.type.split("."); + event.type = namespaces.shift(); + namespace_sort = namespaces.slice(0).sort(); + namespace_re = new RegExp("(^|\\.)" + namespace_sort.join("\\.(?:.*\\.)?") + "(\\.|$)"); + } + + event.namespace = event.namespace || namespace_sort.join("."); + + events = jQuery.data(this, "events"); + handlers = (events || {})[ event.type ]; + + if ( events && handlers ) { + // Clone the handlers to prevent manipulation + handlers = handlers.slice(0); + + for ( var j = 0, l = handlers.length; j < l; j++ ) { + var handleObj = handlers[ j ]; + + // Filter the functions by class + if ( all || namespace_re.test( handleObj.namespace ) ) { + // Pass in a reference to the handler function itself + // So that we can later remove it + event.handler = handleObj.handler; + event.data = handleObj.data; + event.handleObj = handleObj; + + var oldHandle = event.handled + ret = handleObj.handler.apply( this, arguments ); + event.handled = event.handled ===null || handleObj.handler === liveHandler ? oldHandle : true + + if ( ret !== undefined ) { + event.result = ret; + if ( ret === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + + if ( event.isImmediatePropagationStopped() ) { + break; + } + } + } + } + + return event.result; + }, + + props: "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "), + + fix: function( event ) { + if ( event[ jQuery.expando ] ) { + return event; + } + + // store a copy of the original event object + // and "clone" to set read-only properties + var originalEvent = event; + event = jQuery.Event( originalEvent ); + + for ( var i = this.props.length, prop; i; ) { + prop = this.props[ --i ]; + event[ prop ] = originalEvent[ prop ]; + } + + // Fix target property, if necessary + if ( !event.target ) { + event.target = event.srcElement || document; // Fixes #1925 where srcElement might not be defined either + } + + // check if target is a textnode (safari) + if ( event.target.nodeType === 3 ) { + event.target = event.target.parentNode; + } + + // Add relatedTarget, if necessary + if ( !event.relatedTarget && event.fromElement ) { + event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement; + } + + // Calculate pageX/Y if missing and clientX/Y available + if ( event.pageX == null && event.clientX != null ) { + var doc = document.documentElement, body = document.body; + event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0); + event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0); + } + + // Add which for key events + if ( !event.which && ((event.charCode || event.charCode === 0) ? event.charCode : event.keyCode) ) { + event.which = event.charCode || event.keyCode; + } + + // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs) + if ( !event.metaKey && event.ctrlKey ) { + event.metaKey = event.ctrlKey; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + // Note: button is not normalized, so don't use it + if ( !event.which && event.button !== undefined ) { + event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) )); + } + + return event; + }, + + // Deprecated, use jQuery.guid instead + guid: 1E8, + + // Deprecated, use jQuery.proxy instead + proxy: jQuery.proxy, + + special: { + ready: { + // Make sure the ready event is setup + setup: jQuery.bindReady, + teardown: jQuery.noop + }, + + live: { + add: function( handleObj ) { + jQuery.event.add( this, + liveConvert( handleObj.origType, handleObj.selector ), + jQuery.extend({}, handleObj, {handler: liveHandler, guid: handleObj.handler.guid}) ); + }, + + remove: function( handleObj ) { + jQuery.event.remove( this, liveConvert( handleObj.origType, handleObj.selector ), handleObj ); + } + }, + + beforeunload: { + setup: function( data, namespaces, eventHandle ) { + // We only want to do this special case on windows + if ( this.setInterval ) { + this.onbeforeunload = eventHandle; + } + }, + + teardown: function( namespaces, eventHandle ) { + if ( this.onbeforeunload === eventHandle ) { + this.onbeforeunload = null; + } + } + } + } +}; + +var removeEvent = document.removeEventListener ? + function( elem, type, handle ) { + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle, false ); + } + } : + function( elem, type, handle ) { + if ( elem.detachEvent ) { + elem.detachEvent( "on" + type, handle ); + } + }; + +jQuery.Event = function( src ) { + // Allow instantiation without the 'new' keyword + if ( !this.preventDefault ) { + return new jQuery.Event( src ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + // Event type + } else { + this.type = src; + } + + // timeStamp is buggy for some events on Firefox(#3843) + // So we won't rely on the native value + this.timeStamp = jQuery.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +function returnFalse() { + return false; +} +function returnTrue() { + return true; +} + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + preventDefault: function() { + this.isDefaultPrevented = returnTrue; + + var e = this.originalEvent; + if ( !e ) { + return; + } + + // if preventDefault exists run it on the original event + if ( e.preventDefault ) { + e.preventDefault(); + } + // otherwise set the returnValue property of the original event to false (IE) + e.returnValue = false; + }, + stopPropagation: function() { + this.isPropagationStopped = returnTrue; + + var e = this.originalEvent; + if ( !e ) { + return; + } + // if stopPropagation exists run it on the original event + if ( e.stopPropagation ) { + e.stopPropagation(); + } + // otherwise set the cancelBubble property of the original event to true (IE) + e.cancelBubble = true; + }, + stopImmediatePropagation: function() { + this.isImmediatePropagationStopped = returnTrue; + this.stopPropagation(); + }, + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse +}; + +// Checks if an event happened on an element within another element +// Used in jQuery.event.special.mouseenter and mouseleave handlers +var withinElement = function( event ) { + // Check if mouse(over|out) are still within the same parent element + var parent = event.relatedTarget; + + // Firefox sometimes assigns relatedTarget a XUL element + // which we cannot access the parentNode property of + try { + // Traverse up the tree + while ( parent && parent !== this ) { + parent = parent.parentNode; + } + + if ( parent !== this ) { + // set the correct event type + event.type = event.data; + + // handle event if we actually just moused on to a non sub-element + jQuery.event.handle.apply( this, arguments ); + } + + // assuming we've left the element since we most likely mousedover a xul element + } catch(e) { } +}, + +// In case of event delegation, we only need to rename the event.type, +// liveHandler will take care of the rest. +delegate = function( event ) { + event.type = event.data; + jQuery.event.handle.apply( this, arguments ); +}; + +// Create mouseenter and mouseleave events +jQuery.each({ + mouseenter: "mouseover", + mouseleave: "mouseout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + setup: function( data ) { + jQuery.event.add( this, fix, data && data.selector ? delegate : withinElement, orig ); + }, + teardown: function( data ) { + jQuery.event.remove( this, fix, data && data.selector ? delegate : withinElement ); + } + }; +}); + +// submit delegation +if ( !jQuery.support.submitBubbles ) { + + jQuery.event.special.submit = { + setup: function( data, namespaces ) { + if ( this.nodeName.toLowerCase() !== "form" ) { + jQuery.event.add(this, "click.specialSubmit", function( e ) { + var elem = e.target, type = elem.type; + + if ( (type === "submit" || type === "image") && jQuery( elem ).closest("form").length ) { + return trigger( "submit", this, arguments ); + } + }); + + jQuery.event.add(this, "keypress.specialSubmit", function( e ) { + var elem = e.target, type = elem.type; + + if ( (type === "text" || type === "password") && jQuery( elem ).closest("form").length && e.keyCode === 13 ) { + return trigger( "submit", this, arguments ); + } + }); + + } else { + return false; + } + }, + + teardown: function( namespaces ) { + jQuery.event.remove( this, ".specialSubmit" ); + } + }; + +} + +// change delegation, happens here so we have bind. +if ( !jQuery.support.changeBubbles ) { + + var formElems = /textarea|input|select/i, + + changeFilters, + + getVal = function( elem ) { + var type = elem.type, val = elem.value; + + if ( type === "radio" || type === "checkbox" ) { + val = elem.checked; + + } else if ( type === "select-multiple" ) { + val = elem.selectedIndex > -1 ? + jQuery.map( elem.options, function( elem ) { + return elem.selected; + }).join("-") : + ""; + + } else if ( elem.nodeName.toLowerCase() === "select" ) { + val = elem.selectedIndex; + } + + return val; + }, + + testChange = function testChange( e ) { + var elem = e.target, data, val; + + if ( !formElems.test( elem.nodeName ) || elem.readOnly ) { + return; + } + + data = jQuery.data( elem, "_change_data" ); + val = getVal(elem); + + // the current data will be also retrieved by beforeactivate + if ( e.type !== "focusout" || elem.type !== "radio" ) { + jQuery.data( elem, "_change_data", val ); + } + + if ( data === undefined || val === data ) { + return; + } + + if ( data != null || val ) { + e.type = "change"; + return jQuery.event.trigger( e, arguments[1], elem ); + } + }; + + jQuery.event.special.change = { + filters: { + focusout: testChange, + + beforedeactivate: testChange, + + click: function( e ) { + var elem = e.target, type = elem.type; + + if ( type === "radio" || type === "checkbox" || elem.nodeName.toLowerCase() === "select" ) { + return testChange.call( this, e ); + } + }, + + // Change has to be called before submit + // Keydown will be called before keypress, which is used in submit-event delegation + keydown: function( e ) { + var elem = e.target, type = elem.type; + + if ( (e.keyCode === 13 && elem.nodeName.toLowerCase() !== "textarea") || + (e.keyCode === 32 && (type === "checkbox" || type === "radio")) || + type === "select-multiple" ) { + return testChange.call( this, e ); + } + }, + + // Beforeactivate happens also before the previous element is blurred + // with this event you can't trigger a change event, but you can store + // information/focus[in] is not needed anymore + beforeactivate: function( e ) { + var elem = e.target; + jQuery.data( elem, "_change_data", getVal(elem) ); + } + }, + + setup: function( data, namespaces ) { + if ( this.type === "file" ) { + return false; + } + + for ( var type in changeFilters ) { + jQuery.event.add( this, type + ".specialChange", changeFilters[type] ); + } + + return formElems.test( this.nodeName ); + }, + + teardown: function( namespaces ) { + jQuery.event.remove( this, ".specialChange" ); + + return formElems.test( this.nodeName ); + } + }; + + changeFilters = jQuery.event.special.change.filters; +} + +function trigger( type, elem, args ) { + args[0].type = type; + return jQuery.event.handle.apply( elem, args ); +} + +// Create "bubbling" focus and blur events +if ( document.addEventListener ) { + jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { + jQuery.event.special[ fix ] = { + setup: function() { + if( focusCounts[fix] === 0 ){ + document.addEventListener( orig, handler, true); + } + focusCounts[fix]++; + }, + teardown: function() { + focusCounts[fix]--; + if( focusCounts[fix] === 0 ){ + document.removeEventListener( orig, handler, true); + } + } + }; + + function handler( e ) { + e = jQuery.event.fix( e ); + e.type = fix; + return jQuery.event.trigger( e, null, e.target ); + } + }); +} + +jQuery.each(["bind", "one"], function( i, name ) { + jQuery.fn[ name ] = function( type, data, fn ) { + // Handle object literals + if ( typeof type === "object" ) { + for ( var key in type ) { + this[ name ](key, data, type[key], fn); + } + return this; + } + + if ( jQuery.isFunction( data ) || data === false ) { + fn = data; + data = undefined; + } + + var handler = name === "one" ? jQuery.proxy( fn, function( event ) { + jQuery( this ).unbind( event, handler ); + return fn.apply( this, arguments ); + }) : fn; + + if ( type === "unload" && name !== "one" ) { + this.one( type, data, fn ); + + } else { + for ( var i = 0, l = this.length; i < l; i++ ) { + jQuery.event.add( this[i], type, handler, data ); + } + } + + return this; + }; +}); + +jQuery.fn.extend({ + unbind: function( type, fn ) { + // Handle object literals + if ( typeof type === "object" && !type.preventDefault ) { + for ( var key in type ) { + this.unbind(key, type[key]); + } + + } else { + for ( var i = 0, l = this.length; i < l; i++ ) { + jQuery.event.remove( this[i], type, fn ); + } + } + + return this; + }, + + delegate: function( selector, types, data, fn ) { + return this.live( types, data, fn, selector ); + }, + + undelegate: function( selector, types, fn ) { + if ( arguments.length === 0 ) { + return this.unbind( "live" ); + + } else { + return this.die( types, null, fn, selector ); + } + }, + + trigger: function( type, data ) { + return this.each(function() { + jQuery.event.trigger( type, data, this ); + }); + }, + + triggerHandler: function( type, data ) { + if ( this[0] ) { + var event = jQuery.Event( type ); + event.preventDefault(); + event.stopPropagation(); + jQuery.event.trigger( event, data, this[0] ); + return event.result; + } + }, + + toggle: function( fn ) { + // Save reference to arguments for access in closure + var args = arguments, i = 1; + + // link all the functions, so any of them can unbind this click handler + while ( i < args.length ) { + jQuery.proxy( fn, args[ i++ ] ); + } + + return this.click( jQuery.proxy( fn, function( event ) { + // Figure out which function to execute + var lastToggle = ( jQuery.data( this, "lastToggle" + fn.guid ) || 0 ) % i; + jQuery.data( this, "lastToggle" + fn.guid, lastToggle + 1 ); + + // Make sure that clicks stop + event.preventDefault(); + + // and execute the function + return args[ lastToggle ].apply( this, arguments ) || false; + })); + }, + + hover: function( fnOver, fnOut ) { + return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); + } +}); + +var liveMap = { + focus: "focusin", + blur: "focusout", + mouseenter: "mouseover", + mouseleave: "mouseout" +}; + +jQuery.each(["live", "die"], function( i, name ) { + jQuery.fn[ name ] = function( types, data, fn, origSelector /* Internal Use Only */ ) { + var type, i = 0, match, namespaces, preType, + selector = origSelector || this.selector, + context = origSelector ? this : jQuery( this.context ); + + if ( jQuery.isFunction( data ) ) { + fn = data; + data = undefined; + } + + types = (types || "").split(" "); + + while ( (type = types[ i++ ]) != null ) { + match = rnamespaces.exec( type ); + namespaces = ""; + + if ( match ) { + namespaces = match[0]; + type = type.replace( rnamespaces, "" ); + } + + if ( type === "hover" ) { + types.push( "mouseenter" + namespaces, "mouseleave" + namespaces ); + continue; + } + + preType = type; + + if ( type === "focus" || type === "blur" ) { + types.push( liveMap[ type ] + namespaces ); + type = type + namespaces; + + } else { + type = (liveMap[ type ] || type) + namespaces; + } + + if ( name === "live" ) { + // bind live handler + for ( var j = 0, l = context.length; j < l; j++ ) { + jQuery.event.add( context[j], "live." + liveConvert( type, selector ), + { data: data, selector: selector, handler: fn, origType: type, origHandler: fn, preType: preType } ); + } + + } else { + // unbind live handler + context.unbind( "live." + liveConvert( type, selector ), fn ); + } + } + + return this; + }; +}); + +function liveHandler( event ) { + var stop, maxLevel, elems = [], selectors = [], + related, match, handleObj, elem, j, i, l, data, close, namespace, + events = jQuery.data( this, "events" ); + + // Make sure we avoid non-left-click bubbling in Firefox (#3861) + if ( event.liveFired === this || !events || !events.live || event.button && event.type === "click" ) { + return; + } + + if ( event.namespace ) { + namespace = new RegExp("(^|\\.)" + event.namespace.split(".").join("\\.(?:.*\\.)?") + "(\\.|$)"); + } + + event.liveFired = this; + + var live = events.live.slice(0); + + for ( j = 0; j < live.length; j++ ) { + handleObj = live[j]; + + if ( handleObj.origType.replace( rnamespaces, "" ) === event.type ) { + selectors.push( handleObj.selector ); + + } else { + live.splice( j--, 1 ); + } + } + + match = jQuery( event.target ).closest( selectors, event.currentTarget ); + + for ( i = 0, l = match.length; i < l; i++ ) { + close = match[i]; + + for ( j = 0; j < live.length; j++ ) { + handleObj = live[j]; + + if ( close.selector === handleObj.selector && (!namespace || namespace.test( handleObj.namespace )) ) { + elem = close.elem; + related = null; + + // Those two events require additional checking + if ( handleObj.preType === "mouseenter" || handleObj.preType === "mouseleave" ) { + event.type = handleObj.preType; + related = jQuery( event.relatedTarget ).closest( handleObj.selector )[0]; + } + + if ( !related || related !== elem ) { + elems.push({ elem: elem, handleObj: handleObj, level: close.level }); + } + } + } + } + + for ( i = 0, l = elems.length; i < l; i++ ) { + match = elems[i]; + + if ( maxLevel && match.level > maxLevel ) { + break; + } + + event.currentTarget = match.elem; + event.data = match.handleObj.data; + event.handleObj = match.handleObj; + + var oldHandle = event.handled; + ret = match.handleObj.origHandler.apply( match.elem, arguments ); + event.handled = event.handled === null ? oldHandle : true; + + if ( ret === false || event.isPropagationStopped() ) { + maxLevel = match.level; + + if ( ret === false ) { + stop = false; + } + } + } + + return stop; +} + +function liveConvert( type, selector ) { + return (type && type !== "*" ? type + "." : "") + selector.replace(/\./g, "`").replace(/ /g, "&"); +} + +jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " + + "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + + "change select submit keydown keypress keyup error").split(" "), function( i, name ) { + + // Handle event binding + jQuery.fn[ name ] = function( data, fn ) { + if ( fn == null ) { + fn = data; + data = null; + } + + return arguments.length > 0 ? + this.bind( name, data, fn ) : + this.trigger( name ); + }; + + if ( jQuery.attrFn ) { + jQuery.attrFn[ name ] = true; + } +}); + +// Prevent memory leaks in IE +// Window isn't included so as not to unbind existing unload events +// More info: +// - http://isaacschlueter.com/2006/10/msie-memory-leaks/ +if ( window.attachEvent && !window.addEventListener ) { + window.attachEvent("onunload", function() { + for ( var id in jQuery.cache ) { + if ( jQuery.cache[ id ].handle ) { + // Try/Catch is to handle iframes being unloaded, see #4280 + try { + jQuery.event.remove( jQuery.cache[ id ].handle.elem ); + } catch(e) {} + } + } + }); +} +/*! + * Sizzle CSS Selector Engine - v1.0 + * Copyright 2009, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * More information: http://sizzlejs.com/ + */ +(function(){ + +var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, + done = 0, + toString = Object.prototype.toString, + hasDuplicate = false, + baseHasDuplicate = true; + +// Here we check if the JavaScript engine is using some sort of +// optimization where it does not always call our comparision +// function. If that is the case, discard the hasDuplicate value. +// Thus far that includes Google Chrome. +[0, 0].sort(function(){ + baseHasDuplicate = false; + return 0; +}); + +var Sizzle = function(selector, context, results, seed) { + results = results || []; + context = context || document; + + var origContext = context; + + if ( context.nodeType !== 1 && context.nodeType !== 9 ) { + return []; + } + + if ( !selector || typeof selector !== "string" ) { + return results; + } + + var parts = [], m, set, checkSet, extra, prune = true, contextXML = Sizzle.isXML(context), + soFar = selector, ret, cur, pop, i; + + // Reset the position of the chunker regexp (start from head) + do { + chunker.exec(""); + m = chunker.exec(soFar); + + if ( m ) { + soFar = m[3]; + + parts.push( m[1] ); + + if ( m[2] ) { + extra = m[3]; + break; + } + } + } while ( m ); + + if ( parts.length > 1 && origPOS.exec( selector ) ) { + if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { + set = posProcess( parts[0] + parts[1], context ); + } else { + set = Expr.relative[ parts[0] ] ? + [ context ] : + Sizzle( parts.shift(), context ); + + while ( parts.length ) { + selector = parts.shift(); + + if ( Expr.relative[ selector ] ) { + selector += parts.shift(); + } + + set = posProcess( selector, set ); + } + } + } else { + // Take a shortcut and set the context if the root selector is an ID + // (but not if it'll be faster if the inner selector is an ID) + if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && + Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { + ret = Sizzle.find( parts.shift(), context, contextXML ); + context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0]; + } + + if ( context ) { + ret = seed ? + { expr: parts.pop(), set: makeArray(seed) } : + Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); + set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set; + + if ( parts.length > 0 ) { + checkSet = makeArray(set); + } else { + prune = false; + } + + while ( parts.length ) { + cur = parts.pop(); + pop = cur; + + if ( !Expr.relative[ cur ] ) { + cur = ""; + } else { + pop = parts.pop(); + } + + if ( pop == null ) { + pop = context; + } + + Expr.relative[ cur ]( checkSet, pop, contextXML ); + } + } else { + checkSet = parts = []; + } + } + + if ( !checkSet ) { + checkSet = set; + } + + if ( !checkSet ) { + Sizzle.error( cur || selector ); + } + + if ( toString.call(checkSet) === "[object Array]" ) { + if ( !prune ) { + results.push.apply( results, checkSet ); + } else if ( context && context.nodeType === 1 ) { + for ( i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) { + results.push( set[i] ); + } + } + } else { + for ( i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && checkSet[i].nodeType === 1 ) { + results.push( set[i] ); + } + } + } + } else { + makeArray( checkSet, results ); + } + + if ( extra ) { + Sizzle( extra, origContext, results, seed ); + Sizzle.uniqueSort( results ); + } + + return results; +}; + +Sizzle.uniqueSort = function(results){ + if ( sortOrder ) { + hasDuplicate = baseHasDuplicate; + results.sort(sortOrder); + + if ( hasDuplicate ) { + for ( var i = 1; i < results.length; i++ ) { + if ( results[i] === results[i-1] ) { + results.splice(i--, 1); + } + } + } + } + + return results; +}; + +Sizzle.matches = function(expr, set){ + return Sizzle(expr, null, null, set); +}; + +Sizzle.find = function(expr, context, isXML){ + var set; + + if ( !expr ) { + return []; + } + + for ( var i = 0, l = Expr.order.length; i < l; i++ ) { + var type = Expr.order[i], match; + + if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { + var left = match[1]; + match.splice(1,1); + + if ( left.substr( left.length - 1 ) !== "\\" ) { + match[1] = (match[1] || "").replace(/\\/g, ""); + set = Expr.find[ type ]( match, context, isXML ); + if ( set != null ) { + expr = expr.replace( Expr.match[ type ], "" ); + break; + } + } + } + } + + if ( !set ) { + set = context.getElementsByTagName("*"); + } + + return {set: set, expr: expr}; +}; + +Sizzle.filter = function(expr, set, inplace, not){ + var old = expr, result = [], curLoop = set, match, anyFound, + isXMLFilter = set && set[0] && Sizzle.isXML(set[0]); + + while ( expr && set.length ) { + for ( var type in Expr.filter ) { + if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) { + var filter = Expr.filter[ type ], found, item, left = match[1]; + anyFound = false; + + match.splice(1,1); + + if ( left.substr( left.length - 1 ) === "\\" ) { + continue; + } + + if ( curLoop === result ) { + result = []; + } + + if ( Expr.preFilter[ type ] ) { + match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); + + if ( !match ) { + anyFound = found = true; + } else if ( match === true ) { + continue; + } + } + + if ( match ) { + for ( var i = 0; (item = curLoop[i]) != null; i++ ) { + if ( item ) { + found = filter( item, match, i, curLoop ); + var pass = not ^ !!found; + + if ( inplace && found != null ) { + if ( pass ) { + anyFound = true; + } else { + curLoop[i] = false; + } + } else if ( pass ) { + result.push( item ); + anyFound = true; + } + } + } + } + + if ( found !== undefined ) { + if ( !inplace ) { + curLoop = result; + } + + expr = expr.replace( Expr.match[ type ], "" ); + + if ( !anyFound ) { + return []; + } + + break; + } + } + } + + // Improper expression + if ( expr === old ) { + if ( anyFound == null ) { + Sizzle.error( expr ); + } else { + break; + } + } + + old = expr; + } + + return curLoop; +}; + +Sizzle.error = function( msg ) { + throw "Syntax error, unrecognized expression: " + msg; +}; + +var Expr = Sizzle.selectors = { + order: [ "ID", "NAME", "TAG" ], + match: { + ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, + CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, + NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/, + ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/, + TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/, + CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+\-]*)\))?/, + POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/, + PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/ + }, + leftMatch: {}, + attrMap: { + "class": "className", + "for": "htmlFor" + }, + attrHandle: { + href: function(elem){ + return elem.getAttribute("href"); + } + }, + relative: { + "+": function(checkSet, part){ + var isPartStr = typeof part === "string", + isTag = isPartStr && !/\W/.test(part), + isPartStrNotTag = isPartStr && !isTag; + + if ( isTag ) { + part = part.toLowerCase(); + } + + for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { + if ( (elem = checkSet[i]) ) { + while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} + + checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ? + elem || false : + elem === part; + } + } + + if ( isPartStrNotTag ) { + Sizzle.filter( part, checkSet, true ); + } + }, + ">": function(checkSet, part){ + var isPartStr = typeof part === "string", + elem, i = 0, l = checkSet.length; + + if ( isPartStr && !/\W/.test(part) ) { + part = part.toLowerCase(); + + for ( ; i < l; i++ ) { + elem = checkSet[i]; + if ( elem ) { + var parent = elem.parentNode; + checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false; + } + } + } else { + for ( ; i < l; i++ ) { + elem = checkSet[i]; + if ( elem ) { + checkSet[i] = isPartStr ? + elem.parentNode : + elem.parentNode === part; + } + } + + if ( isPartStr ) { + Sizzle.filter( part, checkSet, true ); + } + } + }, + "": function(checkSet, part, isXML){ + var doneName = done++, checkFn = dirCheck, nodeCheck; + + if ( typeof part === "string" && !/\W/.test(part) ) { + part = part.toLowerCase(); + nodeCheck = part; + checkFn = dirNodeCheck; + } + + checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML); + }, + "~": function(checkSet, part, isXML){ + var doneName = done++, checkFn = dirCheck, nodeCheck; + + if ( typeof part === "string" && !/\W/.test(part) ) { + part = part.toLowerCase(); + nodeCheck = part; + checkFn = dirNodeCheck; + } + + checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML); + } + }, + find: { + ID: function(match, context, isXML){ + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + return m ? [m] : []; + } + }, + NAME: function(match, context){ + if ( typeof context.getElementsByName !== "undefined" ) { + var ret = [], results = context.getElementsByName(match[1]); + + for ( var i = 0, l = results.length; i < l; i++ ) { + if ( results[i].getAttribute("name") === match[1] ) { + ret.push( results[i] ); + } + } + + return ret.length === 0 ? null : ret; + } + }, + TAG: function(match, context){ + return context.getElementsByTagName(match[1]); + } + }, + preFilter: { + CLASS: function(match, curLoop, inplace, result, not, isXML){ + match = " " + match[1].replace(/\\/g, "") + " "; + + if ( isXML ) { + return match; + } + + for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { + if ( elem ) { + if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n]/g, " ").indexOf(match) >= 0) ) { + if ( !inplace ) { + result.push( elem ); + } + } else if ( inplace ) { + curLoop[i] = false; + } + } + } + + return false; + }, + ID: function(match){ + return match[1].replace(/\\/g, ""); + }, + TAG: function(match, curLoop){ + return match[1].toLowerCase(); + }, + CHILD: function(match){ + if ( match[1] === "nth" ) { + // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' + var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec( + match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" || + !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); + + // calculate the numbers (first)n+(last) including if they are negative + match[2] = (test[1] + (test[2] || 1)) - 0; + match[3] = test[3] - 0; + } + + // TODO: Move to normal caching system + match[0] = done++; + + return match; + }, + ATTR: function(match, curLoop, inplace, result, not, isXML){ + var name = match[1].replace(/\\/g, ""); + + if ( !isXML && Expr.attrMap[name] ) { + match[1] = Expr.attrMap[name]; + } + + if ( match[2] === "~=" ) { + match[4] = " " + match[4] + " "; + } + + return match; + }, + PSEUDO: function(match, curLoop, inplace, result, not){ + if ( match[1] === "not" ) { + // If we're dealing with a complex expression, or a simple one + if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { + match[3] = Sizzle(match[3], null, null, curLoop); + } else { + var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); + if ( !inplace ) { + result.push.apply( result, ret ); + } + return false; + } + } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { + return true; + } + + return match; + }, + POS: function(match){ + match.unshift( true ); + return match; + } + }, + filters: { + enabled: function(elem){ + return elem.disabled === false && elem.type !== "hidden"; + }, + disabled: function(elem){ + return elem.disabled === true; + }, + checked: function(elem){ + return elem.checked === true; + }, + selected: function(elem){ + // Accessing this property makes selected-by-default + // options in Safari work properly + elem.parentNode.selectedIndex; + return elem.selected === true; + }, + parent: function(elem){ + return !!elem.firstChild; + }, + empty: function(elem){ + return !elem.firstChild; + }, + has: function(elem, i, match){ + return !!Sizzle( match[3], elem ).length; + }, + header: function(elem){ + return (/h\d/i).test( elem.nodeName ); + }, + text: function(elem){ + return "text" === elem.type; + }, + radio: function(elem){ + return "radio" === elem.type; + }, + checkbox: function(elem){ + return "checkbox" === elem.type; + }, + file: function(elem){ + return "file" === elem.type; + }, + password: function(elem){ + return "password" === elem.type; + }, + submit: function(elem){ + return "submit" === elem.type; + }, + image: function(elem){ + return "image" === elem.type; + }, + reset: function(elem){ + return "reset" === elem.type; + }, + button: function(elem){ + return "button" === elem.type || elem.nodeName.toLowerCase() === "button"; + }, + input: function(elem){ + return (/input|select|textarea|button/i).test(elem.nodeName); + } + }, + setFilters: { + first: function(elem, i){ + return i === 0; + }, + last: function(elem, i, match, array){ + return i === array.length - 1; + }, + even: function(elem, i){ + return i % 2 === 0; + }, + odd: function(elem, i){ + return i % 2 === 1; + }, + lt: function(elem, i, match){ + return i < match[3] - 0; + }, + gt: function(elem, i, match){ + return i > match[3] - 0; + }, + nth: function(elem, i, match){ + return match[3] - 0 === i; + }, + eq: function(elem, i, match){ + return match[3] - 0 === i; + } + }, + filter: { + PSEUDO: function(elem, match, i, array){ + var name = match[1], filter = Expr.filters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + } else if ( name === "contains" ) { + return (elem.textContent || elem.innerText || Sizzle.getText([ elem ]) || "").indexOf(match[3]) >= 0; + } else if ( name === "not" ) { + var not = match[3]; + + for ( var j = 0, l = not.length; j < l; j++ ) { + if ( not[j] === elem ) { + return false; + } + } + + return true; + } else { + Sizzle.error( "Syntax error, unrecognized expression: " + name ); + } + }, + CHILD: function(elem, match){ + var type = match[1], node = elem; + switch (type) { + case 'only': + case 'first': + while ( (node = node.previousSibling) ) { + if ( node.nodeType === 1 ) { + return false; + } + } + if ( type === "first" ) { + return true; + } + node = elem; + case 'last': + while ( (node = node.nextSibling) ) { + if ( node.nodeType === 1 ) { + return false; + } + } + return true; + case 'nth': + var first = match[2], last = match[3]; + + if ( first === 1 && last === 0 ) { + return true; + } + + var doneName = match[0], + parent = elem.parentNode; + + if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) { + var count = 0; + for ( node = parent.firstChild; node; node = node.nextSibling ) { + if ( node.nodeType === 1 ) { + node.nodeIndex = ++count; + } + } + parent.sizcache = doneName; + } + + var diff = elem.nodeIndex - last; + if ( first === 0 ) { + return diff === 0; + } else { + return ( diff % first === 0 && diff / first >= 0 ); + } + } + }, + ID: function(elem, match){ + return elem.nodeType === 1 && elem.getAttribute("id") === match; + }, + TAG: function(elem, match){ + return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match; + }, + CLASS: function(elem, match){ + return (" " + (elem.className || elem.getAttribute("class")) + " ") + .indexOf( match ) > -1; + }, + ATTR: function(elem, match){ + var name = match[1], + result = Expr.attrHandle[ name ] ? + Expr.attrHandle[ name ]( elem ) : + elem[ name ] != null ? + elem[ name ] : + elem.getAttribute( name ), + value = result + "", + type = match[2], + check = match[4]; + + return result == null ? + type === "!=" : + type === "=" ? + value === check : + type === "*=" ? + value.indexOf(check) >= 0 : + type === "~=" ? + (" " + value + " ").indexOf(check) >= 0 : + !check ? + value && result !== false : + type === "!=" ? + value !== check : + type === "^=" ? + value.indexOf(check) === 0 : + type === "$=" ? + value.substr(value.length - check.length) === check : + type === "|=" ? + value === check || value.substr(0, check.length + 1) === check + "-" : + false; + }, + POS: function(elem, match, i, array){ + var name = match[2], filter = Expr.setFilters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + } + } + } +}; + +var origPOS = Expr.match.POS, + fescape = function(all, num){ + return "\\" + (num - 0 + 1); + }; + +for ( var type in Expr.match ) { + Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) ); + Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) ); +} + +var makeArray = function(array, results) { + array = Array.prototype.slice.call( array, 0 ); + + if ( results ) { + results.push.apply( results, array ); + return results; + } + + return array; +}; + +// Perform a simple check to determine if the browser is capable of +// converting a NodeList to an array using builtin methods. +// Also verifies that the returned array holds DOM nodes +// (which is not the case in the Blackberry browser) +try { + Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType; + +// Provide a fallback method if it does not work +} catch(e){ + makeArray = function(array, results) { + var ret = results || [], i = 0; + + if ( toString.call(array) === "[object Array]" ) { + Array.prototype.push.apply( ret, array ); + } else { + if ( typeof array.length === "number" ) { + for ( var l = array.length; i < l; i++ ) { + ret.push( array[i] ); + } + } else { + for ( ; array[i]; i++ ) { + ret.push( array[i] ); + } + } + } + + return ret; + }; +} + +var sortOrder; + +if ( document.documentElement.compareDocumentPosition ) { + sortOrder = function( a, b ) { + if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { + if ( a == b ) { + hasDuplicate = true; + } + return a.compareDocumentPosition ? -1 : 1; + } + + var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1; + if ( ret === 0 ) { + hasDuplicate = true; + } + return ret; + }; +} else if ( "sourceIndex" in document.documentElement ) { + sortOrder = function( a, b ) { + if ( !a.sourceIndex || !b.sourceIndex ) { + if ( a == b ) { + hasDuplicate = true; + } + return a.sourceIndex ? -1 : 1; + } + + var ret = a.sourceIndex - b.sourceIndex; + if ( ret === 0 ) { + hasDuplicate = true; + } + return ret; + }; +} else if ( document.createRange ) { + sortOrder = function( a, b ) { + if ( !a.ownerDocument || !b.ownerDocument ) { + if ( a == b ) { + hasDuplicate = true; + } + return a.ownerDocument ? -1 : 1; + } + + var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange(); + aRange.setStart(a, 0); + aRange.setEnd(a, 0); + bRange.setStart(b, 0); + bRange.setEnd(b, 0); + var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange); + if ( ret === 0 ) { + hasDuplicate = true; + } + return ret; + }; +} + +// Utility function for retreiving the text value of an array of DOM nodes +Sizzle.getText = function( elems ) { + var ret = "", elem; + + for ( var i = 0; elems[i]; i++ ) { + elem = elems[i]; + + // Get the text from text nodes and CDATA nodes + if ( elem.nodeType === 3 || elem.nodeType === 4 ) { + ret += elem.nodeValue; + + // Traverse everything else, except comment nodes + } else if ( elem.nodeType !== 8 ) { + ret += Sizzle.getText( elem.childNodes ); + } + } + + return ret; +}; + +// Check to see if the browser returns elements by name when +// querying by getElementById (and provide a workaround) +(function(){ + // We're going to inject a fake input element with a specified name + var form = document.createElement("div"), + id = "script" + (new Date()).getTime(); + form.innerHTML = "<a name='" + id + "'/>"; + + // Inject it into the root element, check its status, and remove it quickly + var root = document.documentElement; + root.insertBefore( form, root.firstChild ); + + // The workaround has to do additional checks after a getElementById + // Which slows things down for other browsers (hence the branching) + if ( document.getElementById( id ) ) { + Expr.find.ID = function(match, context, isXML){ + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : []; + } + }; + + Expr.filter.ID = function(elem, match){ + var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); + return elem.nodeType === 1 && node && node.nodeValue === match; + }; + } + + root.removeChild( form ); + root = form = null; // release memory in IE +})(); + +(function(){ + // Check to see if the browser returns only elements + // when doing getElementsByTagName("*") + + // Create a fake element + var div = document.createElement("div"); + div.appendChild( document.createComment("") ); + + // Make sure no comments are found + if ( div.getElementsByTagName("*").length > 0 ) { + Expr.find.TAG = function(match, context){ + var results = context.getElementsByTagName(match[1]); + + // Filter out possible comments + if ( match[1] === "*" ) { + var tmp = []; + + for ( var i = 0; results[i]; i++ ) { + if ( results[i].nodeType === 1 ) { + tmp.push( results[i] ); + } + } + + results = tmp; + } + + return results; + }; + } + + // Check to see if an attribute returns normalized href attributes + div.innerHTML = "<a href='#'></a>"; + if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && + div.firstChild.getAttribute("href") !== "#" ) { + Expr.attrHandle.href = function(elem){ + return elem.getAttribute("href", 2); + }; + } + + div = null; // release memory in IE +})(); + +if ( document.querySelectorAll ) { + (function(){ + var oldSizzle = Sizzle, div = document.createElement("div"); + div.innerHTML = "<p class='TEST'></p>"; + + // Safari can't handle uppercase or unicode characters when + // in quirks mode. + if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { + return; + } + + Sizzle = function(query, context, extra, seed){ + context = context || document; + + // Only use querySelectorAll on non-XML documents + // (ID selectors don't work in non-HTML documents) + if ( !seed && context.nodeType === 9 && !Sizzle.isXML(context) ) { + try { + return makeArray( context.querySelectorAll(query), extra ); + } catch(e){} + } + + return oldSizzle(query, context, extra, seed); + }; + + for ( var prop in oldSizzle ) { + Sizzle[ prop ] = oldSizzle[ prop ]; + } + + div = null; // release memory in IE + })(); +} + +(function(){ + var div = document.createElement("div"); + + div.innerHTML = "<div class='test e'></div><div class='test'></div>"; + + // Opera can't find a second classname (in 9.6) + // Also, make sure that getElementsByClassName actually exists + if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) { + return; + } + + // Safari caches class attributes, doesn't catch changes (in 3.2) + div.lastChild.className = "e"; + + if ( div.getElementsByClassName("e").length === 1 ) { + return; + } + + Expr.order.splice(1, 0, "CLASS"); + Expr.find.CLASS = function(match, context, isXML) { + if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { + return context.getElementsByClassName(match[1]); + } + }; + + div = null; // release memory in IE +})(); + +function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + elem = elem[dir]; + var match = false; + + while ( elem ) { + if ( elem.sizcache === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 && !isXML ){ + elem.sizcache = doneName; + elem.sizset = i; + } + + if ( elem.nodeName.toLowerCase() === cur ) { + match = elem; + break; + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + elem = elem[dir]; + var match = false; + + while ( elem ) { + if ( elem.sizcache === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 ) { + if ( !isXML ) { + elem.sizcache = doneName; + elem.sizset = i; + } + if ( typeof cur !== "string" ) { + if ( elem === cur ) { + match = true; + break; + } + + } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { + match = elem; + break; + } + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +Sizzle.contains = document.compareDocumentPosition ? function(a, b){ + return !!(a.compareDocumentPosition(b) & 16); +} : function(a, b){ + return a !== b && (a.contains ? a.contains(b) : true); +}; + +Sizzle.isXML = function(elem){ + // documentElement is verified for cases where it doesn't yet exist + // (such as loading iframes in IE - #4833) + var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement; + return documentElement ? documentElement.nodeName !== "HTML" : false; +}; + +var posProcess = function(selector, context){ + var tmpSet = [], later = "", match, + root = context.nodeType ? [context] : context; + + // Position selectors must be done after the filter + // And so must :not(positional) so we move all PSEUDOs to the end + while ( (match = Expr.match.PSEUDO.exec( selector )) ) { + later += match[0]; + selector = selector.replace( Expr.match.PSEUDO, "" ); + } + + selector = Expr.relative[selector] ? selector + "*" : selector; + + for ( var i = 0, l = root.length; i < l; i++ ) { + Sizzle( selector, root[i], tmpSet ); + } + + return Sizzle.filter( later, tmpSet ); +}; + +// EXPOSE +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; +jQuery.expr[":"] = jQuery.expr.filters; +jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; + + +})(); +var runtil = /Until$/, + rparentsprev = /^(?:parents|prevUntil|prevAll)/, + // Note: This RegExp should be improved, or likely pulled from Sizzle + rmultiselector = /,/, + isSimple = /^.[^:#\[\.,]*$/, + slice = Array.prototype.slice; + +jQuery.fn.extend({ + find: function( selector ) { + var ret = this.pushStack( "", "find", selector ), length = 0; + + for ( var i = 0, l = this.length; i < l; i++ ) { + length = ret.length; + jQuery.find( selector, this[i], ret ); + + if ( i > 0 ) { + // Make sure that the results are unique + for ( var n = length; n < ret.length; n++ ) { + for ( var r = 0; r < length; r++ ) { + if ( ret[r] === ret[n] ) { + ret.splice(n--, 1); + break; + } + } + } + } + } + + return ret; + }, + + has: function( target ) { + var targets = jQuery( target ); + return this.filter(function() { + for ( var i = 0, l = targets.length; i < l; i++ ) { + if ( jQuery.contains( this, targets[i] ) ) { + return true; + } + } + }); + }, + + not: function( selector ) { + return this.pushStack( winnow(this, selector, false), "not", selector); + }, + + filter: function( selector ) { + return this.pushStack( winnow(this, selector, true), "filter", selector ); + }, + + is: function( selector ) { + return !!selector && jQuery.filter( selector, this ).length > 0; + }, + + closest: function( selectors, context ) { + if ( jQuery.isArray( selectors ) ) { + var ret = [], cur = this[0], match, matches = {}, selector, level = 1; + + if ( cur && selectors.length ) { + for ( var i = 0, l = selectors.length; i < l; i++ ) { + selector = selectors[i]; + + if ( !matches[selector] ) { + matches[selector] = jQuery.expr.match.POS.test( selector ) ? + jQuery( selector, context || this.context ) : + selector; + } + } + + while ( cur && cur.ownerDocument && cur !== context ) { + for ( selector in matches ) { + match = matches[selector]; + + if ( match.jquery ? match.index(cur) > -1 : jQuery(cur).is(match) ) { + ret.push({ selector: selector, elem: cur, level: level }); + } + } + cur = cur.parentNode; + level++; + } + } + + return ret; + } + + var pos = jQuery.expr.match.POS.test( selectors ) ? + jQuery( selectors, context || this.context ) : null; + + return this.map(function( i, cur ) { + while ( cur && cur.ownerDocument && cur !== context ) { + if ( pos ? pos.index(cur) > -1 : jQuery(cur).is(selectors) ) { + return cur; + } + cur = cur.parentNode; + } + return null; + }); + }, + + // Determine the position of an element within + // the matched set of elements + index: function( elem ) { + if ( !elem || typeof elem === "string" ) { + return jQuery.inArray( this[0], + // If it receives a string, the selector is used + // If it receives nothing, the siblings are used + elem ? jQuery( elem ) : this.parent().children() ); + } + // Locate the position of the desired element + return jQuery.inArray( + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[0] : elem, this ); + }, + + add: function( selector, context ) { + var set = typeof selector === "string" ? + jQuery( selector, context || this.context ) : + jQuery.makeArray( selector ), + all = jQuery.merge( this.get(), set ); + + return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ? + all : + jQuery.unique( all ) ); + }, + + andSelf: function() { + return this.add( this.prevObject ); + } +}); + +// A painfully simple check to see if an element is disconnected +// from a document (should be improved, where feasible). +function isDisconnected( node ) { + return !node || !node.parentNode || node.parentNode.nodeType === 11; +} + +jQuery.each({ + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return jQuery.dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, i, until ) { + return jQuery.dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return jQuery.nth( elem, 2, "nextSibling" ); + }, + prev: function( elem ) { + return jQuery.nth( elem, 2, "previousSibling" ); + }, + nextAll: function( elem ) { + return jQuery.dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return jQuery.dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, i, until ) { + return jQuery.dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, i, until ) { + return jQuery.dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return jQuery.sibling( elem.parentNode.firstChild, elem ); + }, + children: function( elem ) { + return jQuery.sibling( elem.firstChild ); + }, + contents: function( elem ) { + return jQuery.nodeName( elem, "iframe" ) ? + elem.contentDocument || elem.contentWindow.document : + jQuery.makeArray( elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var ret = jQuery.map( this, fn, until ); + + if ( !runtil.test( name ) ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + ret = jQuery.filter( selector, ret ); + } + + ret = this.length > 1 ? jQuery.unique( ret ) : ret; + + if ( (this.length > 1 || rmultiselector.test( selector )) && rparentsprev.test( name ) ) { + ret = ret.reverse(); + } + + return this.pushStack( ret, name, slice.call(arguments).join(",") ); + }; +}); + +jQuery.extend({ + filter: function( expr, elems, not ) { + if ( not ) { + expr = ":not(" + expr + ")"; + } + + return jQuery.find.matches(expr, elems); + }, + + dir: function( elem, dir, until ) { + var matched = [], cur = elem[dir]; + while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) { + if ( cur.nodeType === 1 ) { + matched.push( cur ); + } + cur = cur[dir]; + } + return matched; + }, + + nth: function( cur, result, dir, elem ) { + result = result || 1; + var num = 0; + + for ( ; cur; cur = cur[dir] ) { + if ( cur.nodeType === 1 && ++num === result ) { + break; + } + } + + return cur; + }, + + sibling: function( n, elem ) { + var r = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + r.push( n ); + } + } + + return r; + } +}); + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, keep ) { + if ( jQuery.isFunction( qualifier ) ) { + return jQuery.grep(elements, function( elem, i ) { + var retVal = !!qualifier.call( elem, i, elem ); + return retVal === keep; + }); + + } else if ( qualifier.nodeType ) { + return jQuery.grep(elements, function( elem, i ) { + return (elem === qualifier) === keep; + }); + + } else if ( typeof qualifier === "string" ) { + var filtered = jQuery.grep(elements, function( elem ) { + return elem.nodeType === 1; + }); + + if ( isSimple.test( qualifier ) ) { + return jQuery.filter(qualifier, filtered, !keep); + } else { + qualifier = jQuery.filter( qualifier, filtered ); + } + } + + return jQuery.grep(elements, function( elem, i ) { + return (jQuery.inArray( elem, qualifier ) >= 0) === keep; + }); +} +var rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g, + rleadingWhitespace = /^\s+/, + rxhtmlTag = /(<([\w:]+)[^>]*?)\/>/g, + rselfClosing = /^(?:area|br|col|embed|hr|img|input|link|meta|param)$/i, + rtagName = /<([\w:]+)/, + rtbody = /<tbody/i, + rhtml = /<|&#?\w+;/, + rnocache = /<script|<object|<embed|<option|<style/i, + rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i, // checked="checked" or checked (html5) + fcloseTag = function( all, front, tag ) { + return rselfClosing.test( tag ) ? + all : + front + "></" + tag + ">"; + }, + wrapMap = { + option: [ 1, "<select multiple='multiple'>", "</select>" ], + legend: [ 1, "<fieldset>", "</fieldset>" ], + thead: [ 1, "<table>", "</table>" ], + tr: [ 2, "<table><tbody>", "</tbody></table>" ], + td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ], + col: [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ], + area: [ 1, "<map>", "</map>" ], + _default: [ 0, "", "" ] + }; + +wrapMap.optgroup = wrapMap.option; +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +// IE can't serialize <link> and <script> tags normally +if ( !jQuery.support.htmlSerialize ) { + wrapMap._default = [ 1, "div<div>", "</div>" ]; +} + +jQuery.fn.extend({ + text: function( text ) { + if ( jQuery.isFunction(text) ) { + return this.each(function(i) { + var self = jQuery(this); + self.text( text.call(this, i, self.text()) ); + }); + } + + if ( typeof text !== "object" && text !== undefined ) { + return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) ); + } + + return jQuery.text( this ); + }, + + wrapAll: function( html ) { + if ( jQuery.isFunction( html ) ) { + return this.each(function(i) { + jQuery(this).wrapAll( html.call(this, i) ); + }); + } + + if ( this[0] ) { + // The elements to wrap the target around + var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true); + + if ( this[0].parentNode ) { + wrap.insertBefore( this[0] ); + } + + wrap.map(function() { + var elem = this; + + while ( elem.firstChild && elem.firstChild.nodeType === 1 ) { + elem = elem.firstChild; + } + + return elem; + }).append(this); + } + + return this; + }, + + wrapInner: function( html ) { + if ( jQuery.isFunction( html ) ) { + return this.each(function(i) { + jQuery(this).wrapInner( html.call(this, i) ); + }); + } + + return this.each(function() { + var self = jQuery( this ), contents = self.contents(); + + if ( contents.length ) { + contents.wrapAll( html ); + + } else { + self.append( html ); + } + }); + }, + + wrap: function( html ) { + return this.each(function() { + jQuery( this ).wrapAll( html ); + }); + }, + + unwrap: function() { + return this.parent().each(function() { + if ( !jQuery.nodeName( this, "body" ) ) { + jQuery( this ).replaceWith( this.childNodes ); + } + }).end(); + }, + + append: function() { + return this.domManip(arguments, true, function( elem ) { + if ( this.nodeType === 1 ) { + this.appendChild( elem ); + } + }); + }, + + prepend: function() { + return this.domManip(arguments, true, function( elem ) { + if ( this.nodeType === 1 ) { + this.insertBefore( elem, this.firstChild ); + } + }); + }, + + before: function() { + if ( this[0] && this[0].parentNode ) { + return this.domManip(arguments, false, function( elem ) { + this.parentNode.insertBefore( elem, this ); + }); + } else if ( arguments.length ) { + var set = jQuery(arguments[0]); + set.push.apply( set, this.toArray() ); + return this.pushStack( set, "before", arguments ); + } + }, + + after: function() { + if ( this[0] && this[0].parentNode ) { + return this.domManip(arguments, false, function( elem ) { + this.parentNode.insertBefore( elem, this.nextSibling ); + }); + } else if ( arguments.length ) { + var set = this.pushStack( this, "after", arguments ); + set.push.apply( set, jQuery(arguments[0]).toArray() ); + return set; + } + }, + + // keepData is for internal use only--do not document + remove: function( selector, keepData ) { + for ( var i = 0, elem; (elem = this[i]) != null; i++ ) { + if ( !selector || jQuery.filter( selector, [ elem ] ).length ) { + if ( !keepData && elem.nodeType === 1 ) { + jQuery.cleanData( elem.getElementsByTagName("*") ); + jQuery.cleanData( [ elem ] ); + } + + if ( elem.parentNode ) { + elem.parentNode.removeChild( elem ); + } + } + } + + return this; + }, + + empty: function() { + for ( var i = 0, elem; (elem = this[i]) != null; i++ ) { + // Remove element nodes and prevent memory leaks + if ( elem.nodeType === 1 ) { + jQuery.cleanData( elem.getElementsByTagName("*") ); + } + + // Remove any remaining nodes + while ( elem.firstChild ) { + elem.removeChild( elem.firstChild ); + } + } + + return this; + }, + + clone: function( events ) { + // Do the clone + var ret = this.map(function() { + if ( !jQuery.support.noCloneEvent && !jQuery.isXMLDoc(this) ) { + // IE copies events bound via attachEvent when + // using cloneNode. Calling detachEvent on the + // clone will also remove the events from the orignal + // In order to get around this, we use innerHTML. + // Unfortunately, this means some modifications to + // attributes in IE that are actually only stored + // as properties will not be copied (such as the + // the name attribute on an input). + var html = this.outerHTML, ownerDocument = this.ownerDocument; + if ( !html ) { + var div = ownerDocument.createElement("div"); + div.appendChild( this.cloneNode(true) ); + html = div.innerHTML; + } + + return jQuery.clean([html.replace(rinlinejQuery, "") + // Handle the case in IE 8 where action=/test/> self-closes a tag + .replace(/\=([^="'>\s]+\/)>/g, '="$1">') + .replace(rleadingWhitespace, "")], ownerDocument)[0]; + } else { + return this.cloneNode(true); + } + }); + + // Copy the events from the original to the clone + if ( events === true ) { + cloneCopyEvent( this, ret ); + cloneCopyEvent( this.find("*"), ret.find("*") ); + } + + // Return the cloned set + return ret; + }, + + html: function( value ) { + if ( value === undefined ) { + return this[0] && this[0].nodeType === 1 ? + this[0].innerHTML.replace(rinlinejQuery, "") : + null; + + // See if we can take a shortcut and just use innerHTML + } else if ( typeof value === "string" && !rnocache.test( value ) && + (jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value )) && + !wrapMap[ (rtagName.exec( value ) || ["", ""])[1].toLowerCase() ] ) { + + value = value.replace(rxhtmlTag, fcloseTag); + + try { + for ( var i = 0, l = this.length; i < l; i++ ) { + // Remove element nodes and prevent memory leaks + if ( this[i].nodeType === 1 ) { + jQuery.cleanData( this[i].getElementsByTagName("*") ); + this[i].innerHTML = value; + } + } + + // If using innerHTML throws an exception, use the fallback method + } catch(e) { + this.empty().append( value ); + } + + } else if ( jQuery.isFunction( value ) ) { + this.each(function(i){ + var self = jQuery(this), old = self.html(); + self.empty().append(function(){ + return value.call( this, i, old ); + }); + }); + + } else { + this.empty().append( value ); + } + + return this; + }, + + replaceWith: function( value ) { + if ( this[0] && this[0].parentNode ) { + // Make sure that the elements are removed from the DOM before they are inserted + // this can help fix replacing a parent with child elements + if ( jQuery.isFunction( value ) ) { + return this.each(function(i) { + var self = jQuery(this), old = self.html(); + self.replaceWith( value.call( this, i, old ) ); + }); + } + + if ( typeof value !== "string" ) { + value = jQuery(value).detach(); + } + + return this.each(function() { + var next = this.nextSibling, parent = this.parentNode; + + jQuery(this).remove(); + + if ( next ) { + jQuery(next).before( value ); + } else { + jQuery(parent).append( value ); + } + }); + } else { + return this.pushStack( jQuery(jQuery.isFunction(value) ? value() : value), "replaceWith", value ); + } + }, + + detach: function( selector ) { + return this.remove( selector, true ); + }, + + domManip: function( args, table, callback ) { + var results, first, value = args[0], scripts = [], fragment, parent; + + // We can't cloneNode fragments that contain checked, in WebKit + if ( !jQuery.support.checkClone && arguments.length === 3 && typeof value === "string" && rchecked.test( value ) ) { + return this.each(function() { + jQuery(this).domManip( args, table, callback, true ); + }); + } + + if ( jQuery.isFunction(value) ) { + return this.each(function(i) { + var self = jQuery(this); + args[0] = value.call(this, i, table ? self.html() : undefined); + self.domManip( args, table, callback ); + }); + } + + if ( this[0] ) { + parent = value && value.parentNode; + + // If we're in a fragment, just use that instead of building a new one + if ( jQuery.support.parentNode && parent && parent.nodeType === 11 && parent.childNodes.length === this.length ) { + results = { fragment: parent }; + + } else { + results = buildFragment( args, this, scripts ); + } + + fragment = results.fragment; + + if ( fragment.childNodes.length === 1 ) { + first = fragment = fragment.firstChild; + } else { + first = fragment.firstChild; + } + + if ( first ) { + table = table && jQuery.nodeName( first, "tr" ); + + for ( var i = 0, l = this.length; i < l; i++ ) { + callback.call( + table ? + root(this[i], first) : + this[i], + i > 0 || results.cacheable || this.length > 1 ? + fragment.cloneNode(true) : + fragment + ); + } + } + + if ( scripts.length ) { + jQuery.each( scripts, evalScript ); + } + } + + return this; + } +}); + +function root( elem, cur ) { + return jQuery.nodeName(elem, "table") ? + (elem.getElementsByTagName("tbody")[0] || + elem.appendChild(elem.ownerDocument.createElement("tbody"))) : + elem; +} + +function cloneCopyEvent(orig, ret) { + var i = 0; + + ret.each(function() { + if ( this.nodeName !== (orig[i] && orig[i].nodeName) ) { + return; + } + + var oldData = jQuery.data( orig[i++] ), curData = jQuery.data( this, oldData ), events = oldData && oldData.events; + + if ( events ) { + delete curData.handle; + curData.events = {}; + + for ( var type in events ) { + for ( var handler in events[ type ] ) { + jQuery.event.add( this, type, events[ type ][ handler ], events[ type ][ handler ].data ); + } + } + } + }); +} + +function buildFragment( args, nodes, scripts ) { + var fragment, cacheable, cacheresults, + doc = (nodes && nodes[0] ? nodes[0].ownerDocument || nodes[0] : document); + + // Only cache "small" (1/2 KB) strings that are associated with the main document + // Cloning options loses the selected state, so don't cache them + // IE 6 doesn't like it when you put <object> or <embed> elements in a fragment + // Also, WebKit does not clone 'checked' attributes on cloneNode, so don't cache + if ( args.length === 1 && typeof args[0] === "string" && args[0].length < 512 && doc === document && + !rnocache.test( args[0] ) && (jQuery.support.checkClone || !rchecked.test( args[0] )) ) { + + cacheable = true; + cacheresults = jQuery.fragments[ args[0] ]; + if ( cacheresults ) { + if ( cacheresults !== 1 ) { + fragment = cacheresults; + } + } + } + + if ( !fragment ) { + fragment = doc.createDocumentFragment(); + jQuery.clean( args, doc, fragment, scripts ); + } + + if ( cacheable ) { + jQuery.fragments[ args[0] ] = cacheresults ? fragment : 1; + } + + return { fragment: fragment, cacheable: cacheable }; +} + +jQuery.fragments = {}; + +jQuery.each({ + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var ret = [], insert = jQuery( selector ), + parent = this.length === 1 && this[0].parentNode; + + if ( parent && parent.nodeType === 11 && parent.childNodes.length === 1 && insert.length === 1 ) { + insert[ original ]( this[0] ); + return this; + + } else { + for ( var i = 0, l = insert.length; i < l; i++ ) { + var elems = (i > 0 ? this.clone(true) : this).get(); + jQuery( insert[i] )[ original ]( elems ); + ret = ret.concat( elems ); + } + + return this.pushStack( ret, name, insert.selector ); + } + }; +}); + +jQuery.extend({ + clean: function( elems, context, fragment, scripts ) { + context = context || document; + + // !context.createElement fails in IE with an error but returns typeof 'object' + if ( typeof context.createElement === "undefined" ) { + context = context.ownerDocument || context[0] && context[0].ownerDocument || document; + } + + var ret = []; + + for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { + if ( typeof elem === "number" ) { + elem += ""; + } + + if ( !elem ) { + continue; + } + + // Convert html string into DOM nodes + if ( typeof elem === "string" && !rhtml.test( elem ) ) { + elem = context.createTextNode( elem ); + + } else if ( typeof elem === "string" ) { + // Fix "XHTML"-style tags in all browsers + elem = elem.replace(rxhtmlTag, fcloseTag); + + // Trim whitespace, otherwise indexOf won't work as expected + var tag = (rtagName.exec( elem ) || ["", ""])[1].toLowerCase(), + wrap = wrapMap[ tag ] || wrapMap._default, + depth = wrap[0], + div = context.createElement("div"); + + // Go to html and back, then peel off extra wrappers + div.innerHTML = wrap[1] + elem + wrap[2]; + + // Move to the right depth + while ( depth-- ) { + div = div.lastChild; + } + + // Remove IE's autoinserted <tbody> from table fragments + if ( !jQuery.support.tbody ) { + + // String was a <table>, *may* have spurious <tbody> + var hasBody = rtbody.test(elem), + tbody = tag === "table" && !hasBody ? + div.firstChild && div.firstChild.childNodes : + + // String was a bare <thead> or <tfoot> + wrap[1] === "<table>" && !hasBody ? + div.childNodes : + []; + + for ( var j = tbody.length - 1; j >= 0 ; --j ) { + if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length ) { + tbody[ j ].parentNode.removeChild( tbody[ j ] ); + } + } + + } + + // IE completely kills leading whitespace when innerHTML is used + if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) { + div.insertBefore( context.createTextNode( rleadingWhitespace.exec(elem)[0] ), div.firstChild ); + } + + elem = div.childNodes; + } + + if ( elem.nodeType ) { + ret.push( elem ); + } else { + ret = jQuery.merge( ret, elem ); + } + } + + if ( fragment ) { + for ( i = 0; ret[i]; i++ ) { + if ( scripts && jQuery.nodeName( ret[i], "script" ) && (!ret[i].type || ret[i].type.toLowerCase() === "text/javascript") ) { + scripts.push( ret[i].parentNode ? ret[i].parentNode.removeChild( ret[i] ) : ret[i] ); + + } else { + if ( ret[i].nodeType === 1 ) { + ret.splice.apply( ret, [i + 1, 0].concat(jQuery.makeArray(ret[i].getElementsByTagName("script"))) ); + } + fragment.appendChild( ret[i] ); + } + } + } + + return ret; + }, + + cleanData: function( elems ) { + var data, id, cache = jQuery.cache, + special = jQuery.event.special, + deleteExpando = jQuery.support.deleteExpando; + + for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { + if ( elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()] ) { + continue; + } + + id = elem[ jQuery.expando ]; + + if ( id ) { + data = cache[ id ]; + + if ( data && data.events ) { + for ( var type in data.events ) { + if ( special[ type ] ) { + jQuery.event.remove( elem, type ); + + } else { + removeEvent( elem, type, data.handle ); + } + } + } + + if ( deleteExpando ) { + delete elem[ jQuery.expando ]; + + } else if ( elem.removeAttribute ) { + elem.removeAttribute( jQuery.expando ); + } + + delete cache[ id ]; + } + } + } +}); + +function evalScript( i, elem ) { + if ( elem.src ) { + jQuery.ajax({ + url: elem.src, + async: false, + dataType: "script" + }); + } else { + jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" ); + } + + if ( elem.parentNode ) { + elem.parentNode.removeChild( elem ); + } +}// exclude the following css properties to add px +var rexclude = /z-?index|font-?weight|opacity|zoom|line-?height/i, + ralpha = /alpha\([^)]*\)/, + ropacity = /opacity=([^)]*)/, + rfloat = /float/i, + rdashAlpha = /-([a-z])/ig, + rupper = /([A-Z])/g, + rnumpx = /^-?\d+(?:px)?$/i, + rnum = /^-?\d/, + + cssShow = { position: "absolute", visibility: "hidden", display:"block" }, + cssWidth = [ "Left", "Right" ], + cssHeight = [ "Top", "Bottom" ], + + // cache check for defaultView.getComputedStyle + getComputedStyle = document.defaultView && document.defaultView.getComputedStyle, + // normalize float css property + styleFloat = jQuery.support.cssFloat ? "cssFloat" : "styleFloat", + fcamelCase = function( all, letter ) { + return letter.toUpperCase(); + }; + +jQuery.fn.css = function( name, value ) { + return jQuery.access( this, name, value, true, function( elem, name, value ) { + if ( value === undefined ) { + return jQuery.curCSS( elem, name ); + } + + if ( typeof value === "number" && !rexclude.test(name) ) { + value += "px"; + } + + jQuery.style( elem, name, value ); + }); +}; + +jQuery.extend({ + style: function( elem, name, value ) { + // don't set styles on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 ) { + return undefined; + } + + // ignore negative width and height values #1599 + if ( (name === "width" || name === "height") && parseFloat(value) < 0 ) { + value = undefined; + } + + var style = elem.style || elem, set = value !== undefined; + + // IE uses filters for opacity + if ( !jQuery.support.opacity && name === "opacity" ) { + if ( set ) { + // IE has trouble with opacity if it does not have layout + // Force it by setting the zoom level + style.zoom = 1; + + // Set the alpha filter to set the opacity + var opacity = parseInt( value, 10 ) + "" === "NaN" ? "" : "alpha(opacity=" + value * 100 + ")"; + var filter = style.filter || jQuery.curCSS( elem, "filter" ) || ""; + style.filter = ralpha.test(filter) ? filter.replace(ralpha, opacity) : opacity; + } + + return style.filter && style.filter.indexOf("opacity=") >= 0 ? + (parseFloat( ropacity.exec(style.filter)[1] ) / 100) + "": + ""; + } + + // Make sure we're using the right name for getting the float value + if ( rfloat.test( name ) ) { + name = styleFloat; + } + + name = name.replace(rdashAlpha, fcamelCase); + + if ( set ) { + style[ name ] = value; + } + + return style[ name ]; + }, + + css: function( elem, name, force, extra ) { + if ( name === "width" || name === "height" ) { + if ( elem.offsetWidth !== 0 ) { + val = getWH( elem, name, extra ); + + } else { + jQuery.swap( elem, cssShow, function() { + val = getWH( elem, name, extra ); + }); + } + + return Math.max(0, Math.round(val)); + } + + return jQuery.curCSS( elem, name, force ); + }, + + curCSS: function( elem, name, force ) { + var ret, style = elem.style, filter; + + // IE uses filters for opacity + if ( !jQuery.support.opacity && name === "opacity" && elem.currentStyle ) { + ret = ropacity.test(elem.currentStyle.filter || "") ? + (parseFloat(RegExp.$1) / 100) + "" : + ""; + + return ret === "" ? + "1" : + ret; + } + + // Make sure we're using the right name for getting the float value + if ( rfloat.test( name ) ) { + name = styleFloat; + } + + if ( !force && style && style[ name ] ) { + ret = style[ name ]; + + } else if ( getComputedStyle ) { + + // Only "float" is needed here + if ( rfloat.test( name ) ) { + name = "float"; + } + + name = name.replace( rupper, "-$1" ).toLowerCase(); + + var defaultView = elem.ownerDocument.defaultView; + + if ( !defaultView ) { + return null; + } + + var computedStyle = defaultView.getComputedStyle( elem, null ); + + if ( computedStyle ) { + ret = computedStyle.getPropertyValue( name ); + } + + // We should always get a number back from opacity + if ( name === "opacity" && ret === "" ) { + ret = "1"; + } + + } else if ( elem.currentStyle ) { + var camelCase = name.replace(rdashAlpha, fcamelCase); + + ret = elem.currentStyle[ name ] || elem.currentStyle[ camelCase ]; + + // From the awesome hack by Dean Edwards + // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 + + // If we're not dealing with a regular pixel number + // but a number that has a weird ending, we need to convert it to pixels + if ( !rnumpx.test( ret ) && rnum.test( ret ) ) { + // Remember the original values + var left = style.left, rsLeft = elem.runtimeStyle.left; + + // Put in the new values to get a computed value out + elem.runtimeStyle.left = elem.currentStyle.left; + style.left = camelCase === "fontSize" ? "1em" : (ret || 0); + ret = style.pixelLeft + "px"; + + // Revert the changed values + style.left = left; + elem.runtimeStyle.left = rsLeft; + } + } + + return ret; + }, + + // A method for quickly swapping in/out CSS properties to get correct calculations + swap: function( elem, options, callback ) { + var old = {}; + + // Remember the old values, and insert the new ones + for ( var name in options ) { + old[ name ] = elem.style[ name ]; + elem.style[ name ] = options[ name ]; + } + + callback.call( elem ); + + // Revert the old values + for ( name in options ) { + elem.style[ name ] = old[ name ]; + } + } +}); + +function getWH( elem, name, extra ) { + var which = name === "width" ? cssWidth : cssHeight, + val = name === "width" ? elem.offsetWidth : elem.offsetHeight; + + if ( extra === "border" ) { + return val; + } + + jQuery.each( which, function() { + if ( !extra ) { + val -= parseFloat(jQuery.curCSS( elem, "padding" + this, true)) || 0; + } + + if ( extra === "margin" ) { + val += parseFloat(jQuery.curCSS( elem, "margin" + this, true)) || 0; + + } else { + val -= parseFloat(jQuery.curCSS( elem, "border" + this + "Width", true)) || 0; + } + }); + + return val; +} + +if ( jQuery.expr && jQuery.expr.filters ) { + jQuery.expr.filters.hidden = function( elem ) { + var width = elem.offsetWidth, height = elem.offsetHeight, + skip = elem.nodeName.toLowerCase() === "tr"; + + return width === 0 && height === 0 && !skip ? + true : + width > 0 && height > 0 && !skip ? + false : + jQuery.curCSS(elem, "display") === "none"; + }; + + jQuery.expr.filters.visible = function( elem ) { + return !jQuery.expr.filters.hidden( elem ); + }; +} +var jsc = jQuery.now(), + rscript = /<script(.|\s)*?\/script>/gi, + rselectTextarea = /select|textarea/i, + rinput = /color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week/i, + jsre = /\=\?(&|$)/, + rquery = /\?/, + rts = /(\?|&)_=.*?(&|$)/, + rurl = /^(\w+:)?\/\/([^\/?#]+)/, + r20 = /%20/g, + + // Keep a copy of the old load method + _load = jQuery.fn.load; + +jQuery.fn.extend({ + load: function( url, params, callback ) { + if ( typeof url !== "string" && _load ) { + return _load.apply( this, arguments ); + + // Don't do a request if no elements are being requested + } else if ( !this.length ) { + return this; + } + + var off = url.indexOf(" "); + if ( off >= 0 ) { + var selector = url.slice(off, url.length); + url = url.slice(0, off); + } + + // Default to a GET request + var type = "GET"; + + // If the second parameter was provided + if ( params ) { + // If it's a function + if ( jQuery.isFunction( params ) ) { + // We assume that it's the callback + callback = params; + params = null; + + // Otherwise, build a param string + } else if ( typeof params === "object" ) { + params = jQuery.param( params, jQuery.ajaxSettings.traditional ); + type = "POST"; + } + } + + var self = this; + + // Request the remote document + jQuery.ajax({ + url: url, + type: type, + dataType: "html", + data: params, + complete: function( res, status ) { + // If successful, inject the HTML into all the matched elements + if ( status === "success" || status === "notmodified" ) { + // See if a selector was specified + self.html( selector ? + // Create a dummy div to hold the results + jQuery("<div />") + // inject the contents of the document in, removing the scripts + // to avoid any 'Permission Denied' errors in IE + .append(res.responseText.replace(rscript, "")) + + // Locate the specified elements + .find(selector) : + + // If not, just inject the full result + res.responseText ); + } + + if ( callback ) { + self.each( callback, [res.responseText, status, res] ); + } + } + }); + + return this; + }, + + serialize: function() { + return jQuery.param(this.serializeArray()); + }, + + serializeArray: function() { + return this.map(function() { + return this.elements ? jQuery.makeArray(this.elements) : this; + }) + .filter(function() { + return this.name && !this.disabled && + (this.checked || rselectTextarea.test(this.nodeName) || + rinput.test(this.type)); + }) + .map(function( i, elem ) { + var val = jQuery(this).val(); + + return val == null ? + null : + jQuery.isArray(val) ? + jQuery.map( val, function( val, i ) { + return { name: elem.name, value: val }; + }) : + { name: elem.name, value: val }; + }).get(); + } +}); + +// Attach a bunch of functions for handling common AJAX events +jQuery.each( "ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "), function( i, o ) { + jQuery.fn[o] = function( f ) { + return this.bind(o, f); + }; +}); + +jQuery.extend({ + get: function( url, data, callback, type ) { + // shift arguments if data argument was omited + if ( jQuery.isFunction( data ) ) { + type = type || callback; + callback = data; + data = null; + } + + return jQuery.ajax({ + type: "GET", + url: url, + data: data, + success: callback, + dataType: type + }); + }, + + getScript: function( url, callback ) { + return jQuery.get(url, null, callback, "script"); + }, + + getJSON: function( url, data, callback ) { + return jQuery.get(url, data, callback, "json"); + }, + + post: function( url, data, callback, type ) { + // shift arguments if data argument was omited + if ( jQuery.isFunction( data ) ) { + type = type || callback; + callback = data; + data = {}; + } + + return jQuery.ajax({ + type: "POST", + url: url, + data: data, + success: callback, + dataType: type + }); + }, + + ajaxSetup: function( settings ) { + jQuery.extend( jQuery.ajaxSettings, settings ); + }, + + ajaxSettings: { + url: location.href, + global: true, + type: "GET", + contentType: "application/x-www-form-urlencoded", + processData: true, + async: true, + /* + timeout: 0, + data: null, + username: null, + password: null, + traditional: false, + */ + // Create the request object; Microsoft failed to properly + // implement the XMLHttpRequest in IE7 (can't request local files), + // so we use the ActiveXObject when it is available + // This function can be overriden by calling jQuery.ajaxSetup + xhr: window.XMLHttpRequest && (window.location.protocol !== "file:" || !window.ActiveXObject) ? + function() { + return new window.XMLHttpRequest(); + } : + function() { + try { + return new window.ActiveXObject("Microsoft.XMLHTTP"); + } catch(e) {} + }, + accepts: { + xml: "application/xml, text/xml", + html: "text/html", + script: "text/javascript, application/javascript", + json: "application/json, text/javascript", + text: "text/plain", + _default: "*/*" + } + }, + + ajax: function( origSettings ) { + var s = jQuery.extend(true, {}, jQuery.ajaxSettings, origSettings), + jsonp, status, data, type = s.type.toUpperCase(); + + s.context = origSettings && origSettings.context || s; + + // convert data if not already a string + if ( s.data && s.processData && typeof s.data !== "string" ) { + s.data = jQuery.param( s.data, s.traditional ); + } + + // Handle JSONP Parameter Callbacks + if ( s.dataType === "jsonp" ) { + if ( type === "GET" ) { + if ( !jsre.test( s.url ) ) { + s.url += (rquery.test( s.url ) ? "&" : "?") + (s.jsonp || "callback") + "=?"; + } + } else if ( !s.data || !jsre.test(s.data) ) { + s.data = (s.data ? s.data + "&" : "") + (s.jsonp || "callback") + "=?"; + } + s.dataType = "json"; + } + + // Build temporary JSONP function + if ( s.dataType === "json" && (s.data && jsre.test(s.data) || jsre.test(s.url)) ) { + jsonp = s.jsonpCallback || ("jsonp" + jsc++); + + // Replace the =? sequence both in the query string and the data + if ( s.data ) { + s.data = (s.data + "").replace(jsre, "=" + jsonp + "$1"); + } + + s.url = s.url.replace(jsre, "=" + jsonp + "$1"); + + // We need to make sure + // that a JSONP style response is executed properly + s.dataType = "script"; + + // Handle JSONP-style loading + window[ jsonp ] = window[ jsonp ] || function( tmp ) { + data = tmp; + jQuery.ajax.handleSuccess( s, xhr, status, data ); + jQuery.ajax.handleComplete( s, xhr, status, data ); + // Garbage collect + window[ jsonp ] = undefined; + + try { + delete window[ jsonp ]; + } catch( jsonpError ) {} + + if ( head ) { + head.removeChild( script ); + } + }; + } + + if ( s.dataType === "script" && s.cache === null ) { + s.cache = false; + } + + if ( s.cache === false && type === "GET" ) { + var ts = jQuery.now(); + + // try replacing _= if it is there + var ret = s.url.replace(rts, "$1_=" + ts + "$2"); + + // if nothing was replaced, add timestamp to the end + s.url = ret + ((ret === s.url) ? (rquery.test(s.url) ? "&" : "?") + "_=" + ts : ""); + } + + // If data is available, append data to url for get requests + if ( s.data && type === "GET" ) { + s.url += (rquery.test(s.url) ? "&" : "?") + s.data; + } + + // Watch for a new set of requests + if ( s.global && jQuery.ajax.active++ === 0 ) { + jQuery.event.trigger( "ajaxStart" ); + } + + // Matches an absolute URL, and saves the domain + var parts = rurl.exec( s.url ), + remote = parts && (parts[1] && parts[1] !== location.protocol || parts[2] !== location.host); + + // If we're requesting a remote document + // and trying to load JSON or Script with a GET + if ( s.dataType === "script" && type === "GET" && remote ) { + var head = document.getElementsByTagName("head")[0] || document.documentElement; + var script = document.createElement("script"); + script.src = s.url; + if ( s.scriptCharset ) { + script.charset = s.scriptCharset; + } + + // Handle Script loading + if ( !jsonp ) { + var done = false; + + // Attach handlers for all browsers + script.onload = script.onreadystatechange = function() { + if ( !done && (!this.readyState || + this.readyState === "loaded" || this.readyState === "complete") ) { + done = true; + jQuery.ajax.handleSuccess( s, xhr, status, data ); + jQuery.ajax.handleComplete( s, xhr, status, data ); + + // Handle memory leak in IE + script.onload = script.onreadystatechange = null; + if ( head && script.parentNode ) { + head.removeChild( script ); + } + } + }; + } + + // Use insertBefore instead of appendChild to circumvent an IE6 bug. + // This arises when a base node is used (#2709 and #4378). + head.insertBefore( script, head.firstChild ); + + // We handle everything using the script element injection + return undefined; + } + + var requestDone = false; + + // Create the request object + var xhr = s.xhr(); + + if ( !xhr ) { + return; + } + + // Open the socket + // Passing null username, generates a login popup on Opera (#2865) + if ( s.username ) { + xhr.open(type, s.url, s.async, s.username, s.password); + } else { + xhr.open(type, s.url, s.async); + } + + // Need an extra try/catch for cross domain requests in Firefox 3 + try { + // Set the correct header, if data is being sent + if ( s.data || origSettings && origSettings.contentType ) { + xhr.setRequestHeader("Content-Type", s.contentType); + } + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + if ( jQuery.lastModified[s.url] ) { + xhr.setRequestHeader("If-Modified-Since", jQuery.lastModified[s.url]); + } + + if ( jQuery.ajax.etag[s.url] ) { + xhr.setRequestHeader("If-None-Match", jQuery.ajax.etag[s.url]); + } + } + + // Set header so the called script knows that it's an XMLHttpRequest + // Only send the header if it's not a remote XHR + if ( !remote ) { + xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest"); + } + + // Set the Accepts header for the server, depending on the dataType + xhr.setRequestHeader("Accept", s.dataType && s.accepts[ s.dataType ] ? + s.accepts[ s.dataType ] + ", */*" : + s.accepts._default ); + } catch( headerError ) {} + + // Allow custom headers/mimetypes and early abort + if ( s.beforeSend && s.beforeSend.call(s.context, xhr, s) === false ) { + // Handle the global AJAX counter + if ( s.global && jQuery.ajax.active-- === 1 ) { + jQuery.event.trigger( "ajaxStop" ); + } + + // close opended socket + xhr.abort(); + return false; + } + + if ( s.global ) { + jQuery.ajax.triggerGlobal( s, "ajaxSend", [xhr, s] ); + } + + // Wait for a response to come back + var onreadystatechange = xhr.onreadystatechange = function( isTimeout ) { + // The request was aborted + if ( !xhr || xhr.readyState === 0 || isTimeout === "abort" ) { + // Opera doesn't call onreadystatechange before this point + // so we simulate the call + if ( !requestDone ) { + jQuery.ajax.handleComplete( s, xhr, status, data ); + } + + requestDone = true; + if ( xhr ) { + xhr.onreadystatechange = jQuery.noop; + } + + // The transfer is complete and the data is available, or the request timed out + } else if ( !requestDone && xhr && (xhr.readyState === 4 || isTimeout === "timeout") ) { + requestDone = true; + xhr.onreadystatechange = jQuery.noop; + + status = isTimeout === "timeout" ? + "timeout" : + !jQuery.ajax.httpSuccess( xhr ) ? + "error" : + s.ifModified && jQuery.ajax.httpNotModified( xhr, s.url ) ? + "notmodified" : + "success"; + + var errMsg; + + if ( status === "success" ) { + // Watch for, and catch, XML document parse errors + try { + // process the data (runs the xml through httpData regardless of callback) + data = jQuery.ajax.httpData( xhr, s.dataType, s ); + } catch( parserError ) { + status = "parsererror"; + errMsg = parserError; + } + } + + // Make sure that the request was successful or notmodified + if ( status === "success" || status === "notmodified" ) { + // JSONP handles its own success callback + if ( !jsonp ) { + jQuery.ajax.handleSuccess( s, xhr, status, data ); + } + } else { + jQuery.ajax.handleError( s, xhr, status, errMsg ); + } + + // Fire the complete handlers + jQuery.ajax.handleComplete( s, xhr, status, data ); + + if ( isTimeout === "timeout" ) { + xhr.abort(); + } + + // Stop memory leaks + if ( s.async ) { + xhr = null; + } + } + }; + + // Override the abort handler, if we can (IE doesn't allow it, but that's OK) + // Opera doesn't fire onreadystatechange at all on abort + try { + var oldAbort = xhr.abort; + xhr.abort = function() { + if ( xhr ) { + oldAbort.call( xhr ); + } + + onreadystatechange( "abort" ); + }; + } catch( abortError ) {} + + // Timeout checker + if ( s.async && s.timeout > 0 ) { + setTimeout(function() { + // Check to see if the request is still happening + if ( xhr && !requestDone ) { + onreadystatechange( "timeout" ); + } + }, s.timeout); + } + + // Send the data + try { + xhr.send( type === "POST" || type === "PUT" || type === "DELETE" ? s.data : null ); + + } catch( sendError ) { + jQuery.ajax.handleError( s, xhr, null, e ); + + // Fire the complete handlers + jQuery.ajax.handleComplete( s, xhr, status, data ); + } + + // firefox 1.5 doesn't fire statechange for sync requests + if ( !s.async ) { + onreadystatechange(); + } + + // return XMLHttpRequest to allow aborting the request etc. + return xhr; + }, + + // Serialize an array of form elements or a set of + // key/values into a query string + param: function( a, traditional ) { + var s = [], add = function( key, value ) { + // If value is a function, invoke it and return its value + value = jQuery.isFunction(value) ? value() : value; + s[ s.length ] = encodeURIComponent(key) + "=" + encodeURIComponent(value); + }; + + // Set traditional to true for jQuery <= 1.3.2 behavior. + if ( traditional === undefined ) { + traditional = jQuery.ajaxSettings.traditional; + } + + // If an array was passed in, assume that it is an array of form elements. + if ( jQuery.isArray(a) || a.jquery ) { + // Serialize the form elements + jQuery.each( a, function() { + add( this.name, this.value ); + }); + + } else { + // If traditional, encode the "old" way (the way 1.3.2 or older + // did it), otherwise encode params recursively. + for ( var prefix in a ) { + buildParams( prefix, a[prefix], traditional, add ); + } + } + + // Return the resulting serialization + return s.join("&").replace(r20, "+"); + } +}); + +function buildParams( prefix, obj, traditional, add ) { + if ( jQuery.isArray(obj) ) { + // Serialize array item. + jQuery.each( obj, function( i, v ) { + if ( traditional || /\[\]$/.test( prefix ) ) { + // Treat each array item as a scalar. + add( prefix, v ); + + } else { + // If array item is non-scalar (array or object), encode its + // numeric index to resolve deserialization ambiguity issues. + // Note that rack (as of 1.0.0) can't currently deserialize + // nested arrays properly, and attempting to do so may cause + // a server error. Possible fixes are to modify rack's + // deserialization algorithm or to provide an option or flag + // to force array serialization to be shallow. + buildParams( prefix + "[" + ( typeof v === "object" || jQuery.isArray(v) ? i : "" ) + "]", v, traditional, add ); + } + }); + + } else if ( !traditional && obj != null && typeof obj === "object" ) { + // Serialize object item. + jQuery.each( obj, function( k, v ) { + buildParams( prefix + "[" + k + "]", v, traditional, add ); + }); + + } else { + // Serialize scalar item. + add( prefix, obj ); + } +} + +jQuery.extend( jQuery.ajax, { + + // Counter for holding the number of active queries + active: 0, + + // Last-Modified header cache for next request + lastModified: {}, + etag: {}, + + handleError: function( s, xhr, status, e ) { + // If a local callback was specified, fire it + if ( s.error ) { + s.error.call( s.context, xhr, status, e ); + } + + // Fire the global callback + if ( s.global ) { + jQuery.ajax.triggerGlobal( s, "ajaxError", [xhr, s, e] ); + } + }, + + handleSuccess: function( s, xhr, status, data ) { + // If a local callback was specified, fire it and pass it the data + if ( s.success ) { + s.success.call( s.context, data, status, xhr ); + } + + // Fire the global callback + if ( s.global ) { + jQuery.ajax.triggerGlobal( s, "ajaxSuccess", [xhr, s] ); + } + }, + + handleComplete: function( s, xhr, status ) { + // Process result + if ( s.complete ) { + s.complete.call( s.context, xhr, status ); + } + + // The request was completed + if ( s.global ) { + jQuery.ajax.triggerGlobal( s, "ajaxComplete", [xhr, s] ); + } + + // Handle the global AJAX counter + if ( s.global && jQuery.ajax.active-- === 1 ) { + jQuery.event.trigger( "ajaxStop" ); + } + }, + + triggerGlobal: function( s, type, args ) { + (s.context && s.context.url == null ? jQuery(s.context) : jQuery.event).trigger(type, args); + }, + + // Determines if an XMLHttpRequest was successful or not + httpSuccess: function( xhr ) { + try { + // IE error sometimes returns 1223 when it should be 204 so treat it as success, see #1450 + return !xhr.status && location.protocol === "file:" || + // Opera returns 0 when status is 304 + ( xhr.status >= 200 && xhr.status < 300 ) || + xhr.status === 304 || xhr.status === 1223 || xhr.status === 0; + } catch(e) {} + + return false; + }, + + // Determines if an XMLHttpRequest returns NotModified + httpNotModified: function( xhr, url ) { + var lastModified = xhr.getResponseHeader("Last-Modified"), + etag = xhr.getResponseHeader("Etag"); + + if ( lastModified ) { + jQuery.ajax.lastModified[url] = lastModified; + } + + if ( etag ) { + jQuery.ajax.etag[url] = etag; + } + + // Opera returns 0 when status is 304 + return xhr.status === 304 || xhr.status === 0; + }, + + httpData: function( xhr, type, s ) { + var ct = xhr.getResponseHeader("content-type") || "", + xml = type === "xml" || !type && ct.indexOf("xml") >= 0, + data = xml ? xhr.responseXML : xhr.responseText; + + if ( xml && data.documentElement.nodeName === "parsererror" ) { + jQuery.error( "parsererror" ); + } + + // Allow a pre-filtering function to sanitize the response + // s is checked to keep backwards compatibility + if ( s && s.dataFilter ) { + data = s.dataFilter( data, type ); + } + + // The filter can actually parse the response + if ( typeof data === "string" ) { + // Get the JavaScript object, if JSON is used. + if ( type === "json" || !type && ct.indexOf("json") >= 0 ) { + data = jQuery.parseJSON( data ); + + // If the type is "script", eval it in global context + } else if ( type === "script" || !type && ct.indexOf("javascript") >= 0 ) { + jQuery.globalEval( data ); + } + } + + return data; + } + +}); + +// For backwards compatibility +jQuery.extend( jQuery.ajax ); +var elemdisplay = {}, + rfxtypes = /toggle|show|hide/, + rfxnum = /^([+\-]=)?([\d+.\-]+)(.*)$/, + timerId, + fxAttrs = [ + // height animations + [ "height", "marginTop", "marginBottom", "paddingTop", "paddingBottom" ], + // width animations + [ "width", "marginLeft", "marginRight", "paddingLeft", "paddingRight" ], + // opacity animations + [ "opacity" ] + ]; + +jQuery.fn.extend({ + show: function( speed, callback ) { + if ( speed || speed === 0) { + return this.animate( genFx("show", 3), speed, callback); + + } else { + for ( var i = 0, l = this.length; i < l; i++ ) { + var old = jQuery.data(this[i], "olddisplay"); + + this[i].style.display = old || ""; + + if ( jQuery.css(this[i], "display") === "none" ) { + var nodeName = this[i].nodeName, display; + + if ( elemdisplay[ nodeName ] ) { + display = elemdisplay[ nodeName ]; + + } else { + var elem = jQuery("<" + nodeName + " />").appendTo("body"); + + display = elem.css("display"); + + if ( display === "none" ) { + display = "block"; + } + + elem.remove(); + + elemdisplay[ nodeName ] = display; + } + + jQuery.data(this[i], "olddisplay", display); + } + } + + // Set the display of the elements in a second loop + // to avoid the constant reflow + for ( var j = 0, k = this.length; j < k; j++ ) { + this[j].style.display = jQuery.data(this[j], "olddisplay") || ""; + } + + return this; + } + }, + + hide: function( speed, callback ) { + if ( speed || speed === 0 ) { + return this.animate( genFx("hide", 3), speed, callback); + + } else { + for ( var i = 0, l = this.length; i < l; i++ ) { + var old = jQuery.data(this[i], "olddisplay"); + if ( !old && old !== "none" ) { + jQuery.data(this[i], "olddisplay", jQuery.css(this[i], "display")); + } + } + + // Set the display of the elements in a second loop + // to avoid the constant reflow + for ( var j = 0, k = this.length; j < k; j++ ) { + this[j].style.display = "none"; + } + + return this; + } + }, + + // Save the old toggle function + _toggle: jQuery.fn.toggle, + + toggle: function( fn, fn2 ) { + var bool = typeof fn === "boolean"; + + if ( jQuery.isFunction(fn) && jQuery.isFunction(fn2) ) { + this._toggle.apply( this, arguments ); + + } else if ( fn == null || bool ) { + this.each(function() { + var state = bool ? fn : jQuery(this).is(":hidden"); + jQuery(this)[ state ? "show" : "hide" ](); + }); + + } else { + this.animate(genFx("toggle", 3), fn, fn2); + } + + return this; + }, + + fadeTo: function( speed, to, callback ) { + return this.filter(":hidden").css("opacity", 0).show().end() + .animate({opacity: to}, speed, callback); + }, + + animate: function( prop, speed, easing, callback ) { + var optall = jQuery.speed(speed, easing, callback); + + if ( jQuery.isEmptyObject( prop ) ) { + return this.each( optall.complete ); + } + + return this[ optall.queue === false ? "each" : "queue" ](function() { + var opt = jQuery.extend({}, optall), p, + hidden = this.nodeType === 1 && jQuery(this).is(":hidden"), + self = this; + + for ( p in prop ) { + var name = p.replace(rdashAlpha, fcamelCase); + + if ( p !== name ) { + prop[ name ] = prop[ p ]; + delete prop[ p ]; + p = name; + } + + if ( prop[p] === "hide" && hidden || prop[p] === "show" && !hidden ) { + return opt.complete.call(this); + } + + if ( ( p === "height" || p === "width" ) && this.style ) { + // Store display property + opt.display = jQuery.css(this, "display"); + + // Make sure that nothing sneaks out + opt.overflow = this.style.overflow; + } + + if ( jQuery.isArray( prop[p] ) ) { + // Create (if needed) and add to specialEasing + (opt.specialEasing = opt.specialEasing || {})[p] = prop[p][1]; + prop[p] = prop[p][0]; + } + } + + if ( opt.overflow != null ) { + this.style.overflow = "hidden"; + } + + opt.curAnim = jQuery.extend({}, prop); + + jQuery.each( prop, function( name, val ) { + var e = new jQuery.fx( self, opt, name ); + + if ( rfxtypes.test(val) ) { + e[ val === "toggle" ? hidden ? "show" : "hide" : val ]( prop ); + + } else { + var parts = rfxnum.exec(val), + start = e.cur(true) || 0; + + if ( parts ) { + var end = parseFloat( parts[2] ), + unit = parts[3] || "px"; + + // We need to compute starting value + if ( unit !== "px" ) { + self.style[ name ] = (end || 1) + unit; + start = ((end || 1) / e.cur(true)) * start; + self.style[ name ] = start + unit; + } + + // If a +=/-= token was provided, we're doing a relative animation + if ( parts[1] ) { + end = ((parts[1] === "-=" ? -1 : 1) * end) + start; + } + + e.custom( start, end, unit ); + + } else { + e.custom( start, val, "" ); + } + } + }); + + // For JS strict compliance + return true; + }); + }, + + stop: function( clearQueue, gotoEnd ) { + var timers = jQuery.timers; + + if ( clearQueue ) { + this.queue([]); + } + + this.each(function() { + // go in reverse order so anything added to the queue during the loop is ignored + for ( var i = timers.length - 1; i >= 0; i-- ) { + if ( timers[i].elem === this ) { + if (gotoEnd) { + // force the next step to be the last + timers[i](true); + } + + timers.splice(i, 1); + } + } + }); + + // start the next in the queue if the last step wasn't forced + if ( !gotoEnd ) { + this.dequeue(); + } + + return this; + } + +}); + +function genFx( type, num ) { + var obj = {}; + + jQuery.each( fxAttrs.concat.apply([], fxAttrs.slice(0,num)), function() { + obj[ this ] = type; + }); + + return obj; +} + +// Generate shortcuts for custom animations +jQuery.each({ + slideDown: genFx("show", 1), + slideUp: genFx("hide", 1), + slideToggle: genFx("toggle", 1), + fadeIn: { opacity: "show" }, + fadeOut: { opacity: "hide" } +}, function( name, props ) { + jQuery.fn[ name ] = function( speed, callback ) { + return this.animate( props, speed, callback ); + }; +}); + +jQuery.extend({ + speed: function( speed, easing, fn ) { + var opt = speed && typeof speed === "object" ? speed : { + complete: fn || !fn && easing || + jQuery.isFunction( speed ) && speed, + duration: speed, + easing: fn && easing || easing && !jQuery.isFunction(easing) && easing + }; + + opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration : + jQuery.fx.speeds[opt.duration] || jQuery.fx.speeds._default; + + // Queueing + opt.old = opt.complete; + opt.complete = function() { + if ( opt.queue !== false ) { + jQuery(this).dequeue(); + } + if ( jQuery.isFunction( opt.old ) ) { + opt.old.call( this ); + } + }; + + return opt; + }, + + easing: { + linear: function( p, n, firstNum, diff ) { + return firstNum + diff * p; + }, + swing: function( p, n, firstNum, diff ) { + return ((-Math.cos(p*Math.PI)/2) + 0.5) * diff + firstNum; + } + }, + + timers: [], + + fx: function( elem, options, prop ) { + this.options = options; + this.elem = elem; + this.prop = prop; + + if ( !options.orig ) { + options.orig = {}; + } + } + +}); + +jQuery.fx.prototype = { + // Simple function for setting a style value + update: function() { + if ( this.options.step ) { + this.options.step.call( this.elem, this.now, this ); + } + + (jQuery.fx.step[this.prop] || jQuery.fx.step._default)( this ); + + // Set display property to block for height/width animations + if ( ( this.prop === "height" || this.prop === "width" ) && this.elem.style ) { + this.elem.style.display = "block"; + } + }, + + // Get the current size + cur: function( force ) { + if ( this.elem[this.prop] != null && (!this.elem.style || this.elem.style[this.prop] == null) ) { + return this.elem[ this.prop ]; + } + + var r = parseFloat(jQuery.css(this.elem, this.prop, force)); + return r && r > -10000 ? r : parseFloat(jQuery.curCSS(this.elem, this.prop)) || 0; + }, + + // Start an animation from one number to another + custom: function( from, to, unit ) { + this.startTime = jQuery.now(); + this.start = from; + this.end = to; + this.unit = unit || this.unit || "px"; + this.now = this.start; + this.pos = this.state = 0; + + var self = this; + function t( gotoEnd ) { + return self.step(gotoEnd); + } + + t.elem = this.elem; + + if ( t() && jQuery.timers.push(t) && !timerId ) { + timerId = setInterval(jQuery.fx.tick, 13); + } + }, + + // Simple 'show' function + show: function() { + // Remember where we started, so that we can go back to it later + this.options.orig[this.prop] = jQuery.style( this.elem, this.prop ); + this.options.show = true; + + // Begin the animation + // Make sure that we start at a small width/height to avoid any + // flash of content + this.custom(this.prop === "width" || this.prop === "height" ? 1 : 0, this.cur()); + + // Start by showing the element + jQuery( this.elem ).show(); + }, + + // Simple 'hide' function + hide: function() { + // Remember where we started, so that we can go back to it later + this.options.orig[this.prop] = jQuery.style( this.elem, this.prop ); + this.options.hide = true; + + // Begin the animation + this.custom(this.cur(), 0); + }, + + // Each step of an animation + step: function( gotoEnd ) { + var t = jQuery.now(), done = true; + + if ( gotoEnd || t >= this.options.duration + this.startTime ) { + this.now = this.end; + this.pos = this.state = 1; + this.update(); + + this.options.curAnim[ this.prop ] = true; + + for ( var i in this.options.curAnim ) { + if ( this.options.curAnim[i] !== true ) { + done = false; + } + } + + if ( done ) { + if ( this.options.display != null ) { + // Reset the overflow + this.elem.style.overflow = this.options.overflow; + + // Reset the display + var old = jQuery.data(this.elem, "olddisplay"); + this.elem.style.display = old ? old : this.options.display; + + if ( jQuery.css(this.elem, "display") === "none" ) { + this.elem.style.display = "block"; + } + } + + // Hide the element if the "hide" operation was done + if ( this.options.hide ) { + jQuery(this.elem).hide(); + } + + // Reset the properties, if the item has been hidden or shown + if ( this.options.hide || this.options.show ) { + for ( var p in this.options.curAnim ) { + jQuery.style(this.elem, p, this.options.orig[p]); + } + } + + // Execute the complete function + this.options.complete.call( this.elem ); + } + + return false; + + } else { + var n = t - this.startTime; + this.state = n / this.options.duration; + + // Perform the easing function, defaults to swing + var specialEasing = this.options.specialEasing && this.options.specialEasing[this.prop]; + var defaultEasing = this.options.easing || (jQuery.easing.swing ? "swing" : "linear"); + this.pos = jQuery.easing[specialEasing || defaultEasing](this.state, n, 0, 1, this.options.duration); + this.now = this.start + ((this.end - this.start) * this.pos); + + // Perform the next step of the animation + this.update(); + } + + return true; + } +}; + +jQuery.extend( jQuery.fx, { + tick: function() { + var timers = jQuery.timers; + + for ( var i = 0; i < timers.length; i++ ) { + if ( !timers[i]() ) { + timers.splice(i--, 1); + } + } + + if ( !timers.length ) { + jQuery.fx.stop(); + } + }, + + stop: function() { + clearInterval( timerId ); + timerId = null; + }, + + speeds: { + slow: 600, + fast: 200, + // Default speed + _default: 400 + }, + + step: { + opacity: function( fx ) { + jQuery.style(fx.elem, "opacity", fx.now); + }, + + _default: function( fx ) { + if ( fx.elem.style && fx.elem.style[ fx.prop ] != null ) { + fx.elem.style[ fx.prop ] = (fx.prop === "width" || fx.prop === "height" ? Math.max(0, fx.now) : fx.now) + fx.unit; + } else { + fx.elem[ fx.prop ] = fx.now; + } + } + } +}); + +if ( jQuery.expr && jQuery.expr.filters ) { + jQuery.expr.filters.animated = function( elem ) { + return jQuery.grep(jQuery.timers, function( fn ) { + return elem === fn.elem; + }).length; + }; +} +if ( "getBoundingClientRect" in document.documentElement ) { + jQuery.fn.offset = function( options ) { + var elem = this[0]; + + if ( options ) { + return this.each(function( i ) { + jQuery.offset.setOffset( this, options, i ); + }); + } + + if ( !elem || !elem.ownerDocument ) { + return null; + } + + if ( elem === elem.ownerDocument.body ) { + return jQuery.offset.bodyOffset( elem ); + } + + var box = elem.getBoundingClientRect(), + doc = elem.ownerDocument, + body = doc.body, + docElem = doc.documentElement, + win = getWindow(doc), + clientTop = docElem.clientTop || body.clientTop || 0, + clientLeft = docElem.clientLeft || body.clientLeft || 0, + scrollTop = (win.pageYOffset || jQuery.support.boxModel && docElem.scrollTop || body.scrollTop ), + scrollLeft = (win.pageXOffset || jQuery.support.boxModel && docElem.scrollLeft || body.scrollLeft), + top = box.top + scrollTop - clientTop, + left = box.left + scrollLeft - clientLeft; + + return { top: top, left: left }; + }; + +} else { + jQuery.fn.offset = function( options ) { + var elem = this[0]; + + if ( options ) { + return this.each(function( i ) { + jQuery.offset.setOffset( this, options, i ); + }); + } + + if ( !elem || !elem.ownerDocument ) { + return null; + } + + if ( elem === elem.ownerDocument.body ) { + return jQuery.offset.bodyOffset( elem ); + } + + jQuery.offset.initialize(); + + var offsetParent = elem.offsetParent, prevOffsetParent = elem, + doc = elem.ownerDocument, computedStyle, docElem = doc.documentElement, + body = doc.body, defaultView = doc.defaultView, + prevComputedStyle = defaultView ? defaultView.getComputedStyle( elem, null ) : elem.currentStyle, + top = elem.offsetTop, left = elem.offsetLeft; + + while ( (elem = elem.parentNode) && elem !== body && elem !== docElem ) { + if ( jQuery.offset.supportsFixedPosition && prevComputedStyle.position === "fixed" ) { + break; + } + + computedStyle = defaultView ? defaultView.getComputedStyle(elem, null) : elem.currentStyle; + top -= elem.scrollTop; + left -= elem.scrollLeft; + + if ( elem === offsetParent ) { + top += elem.offsetTop; + left += elem.offsetLeft; + + if ( jQuery.offset.doesNotAddBorder && !(jQuery.offset.doesAddBorderForTableAndCells && /^t(able|d|h)$/i.test(elem.nodeName)) ) { + top += parseFloat( computedStyle.borderTopWidth ) || 0; + left += parseFloat( computedStyle.borderLeftWidth ) || 0; + } + + prevOffsetParent = offsetParent; + offsetParent = elem.offsetParent; + } + + if ( jQuery.offset.subtractsBorderForOverflowNotVisible && computedStyle.overflow !== "visible" ) { + top += parseFloat( computedStyle.borderTopWidth ) || 0; + left += parseFloat( computedStyle.borderLeftWidth ) || 0; + } + + prevComputedStyle = computedStyle; + } + + if ( prevComputedStyle.position === "relative" || prevComputedStyle.position === "static" ) { + top += body.offsetTop; + left += body.offsetLeft; + } + + if ( jQuery.offset.supportsFixedPosition && prevComputedStyle.position === "fixed" ) { + top += Math.max( docElem.scrollTop, body.scrollTop ); + left += Math.max( docElem.scrollLeft, body.scrollLeft ); + } + + return { top: top, left: left }; + }; +} + +jQuery.offset = { + initialize: function() { + var body = document.body, container = document.createElement("div"), innerDiv, checkDiv, table, td, bodyMarginTop = parseFloat( jQuery.curCSS(body, "marginTop", true) ) || 0, + html = "<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>"; + + jQuery.extend( container.style, { position: "absolute", top: 0, left: 0, margin: 0, border: 0, width: "1px", height: "1px", visibility: "hidden" } ); + + container.innerHTML = html; + body.insertBefore( container, body.firstChild ); + innerDiv = container.firstChild; + checkDiv = innerDiv.firstChild; + td = innerDiv.nextSibling.firstChild.firstChild; + + this.doesNotAddBorder = (checkDiv.offsetTop !== 5); + this.doesAddBorderForTableAndCells = (td.offsetTop === 5); + + checkDiv.style.position = "fixed"; + checkDiv.style.top = "20px"; + + // safari subtracts parent border width here which is 5px + this.supportsFixedPosition = (checkDiv.offsetTop === 20 || checkDiv.offsetTop === 15); + checkDiv.style.position = checkDiv.style.top = ""; + + innerDiv.style.overflow = "hidden"; + innerDiv.style.position = "relative"; + + this.subtractsBorderForOverflowNotVisible = (checkDiv.offsetTop === -5); + + this.doesNotIncludeMarginInBodyOffset = (body.offsetTop !== bodyMarginTop); + + body.removeChild( container ); + body = container = innerDiv = checkDiv = table = td = null; + jQuery.offset.initialize = jQuery.noop; + }, + + bodyOffset: function( body ) { + var top = body.offsetTop, left = body.offsetLeft; + + jQuery.offset.initialize(); + + if ( jQuery.offset.doesNotIncludeMarginInBodyOffset ) { + top += parseFloat( jQuery.curCSS(body, "marginTop", true) ) || 0; + left += parseFloat( jQuery.curCSS(body, "marginLeft", true) ) || 0; + } + + return { top: top, left: left }; + }, + + setOffset: function( elem, options, i ) { + var position = jQuery.curCSS( elem, "position" ); + + // set position first, in-case top/left are set even on static elem + if ( position === "static" ) { + elem.style.position = "relative"; + } + + var curElem = jQuery( elem ), + curOffset = curElem.offset(), + curCSSTop = jQuery.curCSS( elem, "top", true ), + curCSSLeft = jQuery.curCSS( elem, "left", true ), + calculatePosition = (position === "absolute" && jQuery.inArray('auto', [curCSSTop, curCSSLeft]) > -1), + props = {}, curPosition = {}, curTop, curLeft; + + // need to be able to calculate position if either top or left is auto and position is absolute + if ( calculatePosition ) { + curPosition = curElem.position(); + } + + curTop = calculatePosition ? curPosition.top : parseInt( curCSSTop, 10 ) || 0; + curLeft = calculatePosition ? curPosition.left : parseInt( curCSSLeft, 10 ) || 0; + + if ( jQuery.isFunction( options ) ) { + options = options.call( elem, i, curOffset ); + } + + props = (!curElem.is(":visible") && !curOffset.top && !curOffset.left) || isNaN(curTop) || isNaN(curLeft) ? + { top : options.top, left: options.left } + : { + top: (options.top - curOffset.top) + curTop, + left: (options.left - curOffset.left) + curLeft + }; + + if ( "using" in options ) { + options.using.call( elem, props ); + } else { + curElem.css( props ); + } + } +}; + + +jQuery.fn.extend({ + position: function() { + if ( !this[0] ) { + return null; + } + + var elem = this[0], + + // Get *real* offsetParent + offsetParent = this.offsetParent(), + + // Get correct offsets + offset = this.offset(), + parentOffset = /^body|html$/i.test(offsetParent[0].nodeName) ? { top: 0, left: 0 } : offsetParent.offset(); + + // Subtract element margins + // note: when an element has margin: auto the offsetLeft and marginLeft + // are the same in Safari causing offset.left to incorrectly be 0 + offset.top -= parseFloat( jQuery.curCSS(elem, "marginTop", true) ) || 0; + offset.left -= parseFloat( jQuery.curCSS(elem, "marginLeft", true) ) || 0; + + // Add offsetParent borders + parentOffset.top += parseFloat( jQuery.curCSS(offsetParent[0], "borderTopWidth", true) ) || 0; + parentOffset.left += parseFloat( jQuery.curCSS(offsetParent[0], "borderLeftWidth", true) ) || 0; + + // Subtract the two offsets + return { + top: offset.top - parentOffset.top, + left: offset.left - parentOffset.left + }; + }, + + offsetParent: function() { + return this.map(function() { + var offsetParent = this.offsetParent || document.body; + while ( offsetParent && (!/^body|html$/i.test(offsetParent.nodeName) && jQuery.css(offsetParent, "position") === "static") ) { + offsetParent = offsetParent.offsetParent; + } + return offsetParent; + }); + } +}); + + +// Create scrollLeft and scrollTop methods +jQuery.each( ["Left", "Top"], function( i, name ) { + var method = "scroll" + name; + + jQuery.fn[ method ] = function(val) { + var elem = this[0], win; + + if ( !elem ) { + return null; + } + + if ( val !== undefined ) { + // Set the scroll offset + return this.each(function() { + win = getWindow( this ); + + if ( win ) { + win.scrollTo( + !i ? val : jQuery(win).scrollLeft(), + i ? val : jQuery(win).scrollTop() + ); + + } else { + this[ method ] = val; + } + }); + } else { + win = getWindow( elem ); + + // Return the scroll offset + return win ? ("pageXOffset" in win) ? win[ i ? "pageYOffset" : "pageXOffset" ] : + jQuery.support.boxModel && win.document.documentElement[ method ] || + win.document.body[ method ] : + elem[ method ]; + } + }; +}); + +function getWindow( elem ) { + return ("scrollTo" in elem && elem.document) ? + elem : + elem.nodeType === 9 ? + elem.defaultView || elem.parentWindow : + false; +} +// Create innerHeight, innerWidth, outerHeight and outerWidth methods +jQuery.each([ "Height", "Width" ], function( i, name ) { + + var type = name.toLowerCase(); + + // innerHeight and innerWidth + jQuery.fn["inner" + name] = function() { + return this[0] ? + jQuery.css( this[0], type, false, "padding" ) : + null; + }; + + // outerHeight and outerWidth + jQuery.fn["outer" + name] = function( margin ) { + return this[0] ? + jQuery.css( this[0], type, false, margin ? "margin" : "border" ) : + null; + }; + + jQuery.fn[ type ] = function( size ) { + // Get window width or height + var elem = this[0]; + if ( !elem ) { + return size == null ? null : this; + } + + if ( jQuery.isFunction( size ) ) { + return this.each(function( i ) { + var self = jQuery( this ); + self[ type ]( size.call( this, i, self[ type ]() ) ); + }); + } + + return ("scrollTo" in elem && elem.document) ? // does it walk and quack like a window? + // Everyone else use document.documentElement or document.body depending on Quirks vs Standards mode + elem.document.compatMode === "CSS1Compat" && elem.document.documentElement[ "client" + name ] || + elem.document.body[ "client" + name ] : + + // Get document width or height + (elem.nodeType === 9) ? // is it a document + // Either scroll[Width/Height] or offset[Width/Height], whichever is greater + Math.max( + elem.documentElement["client" + name], + elem.body["scroll" + name], elem.documentElement["scroll" + name], + elem.body["offset" + name], elem.documentElement["offset" + name] + ) : + + // Get or set width or height on the element + size === undefined ? + // Get width or height on the element + jQuery.css( elem, type ) : + + // Set the width or height on the element (default to pixels if value is unitless) + this.css( type, typeof size === "string" ? size : size + "px" ); + }; + +}); +})(window); diff --git a/browserid/static/funcunit/test/myapp.html b/browserid/static/funcunit/test/myapp.html new file mode 100644 index 0000000000000000000000000000000000000000..cdfbcd6c78356543d7cdc004cc6669da26ca0410 --- /dev/null +++ b/browserid/static/funcunit/test/myapp.html @@ -0,0 +1,119 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> +<html> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> + <title>Untitled Document</title> + <style> + #drag, #drop { + width:20px; + height:20px; + position:relative; + } + #drag { + border:1px solid green; + background-color:green; + left:0px; + top:0px; + } + #drop { + border:1px solid yellow; + background-color:yellow; + left:40px; + top:40px; + } + </style> + <script src="jquery.js" type='text/javascript'></script> + <script src="jquery.event.drag.js" type='text/javascript'></script> + <script src="jquery.event.drop.js" type='text/javascript'></script> + <script type='text/javascript'> + $(function(){ + // on key up, write the value + $("#typehere").keyup(function(){ + $("#seewhatyoutyped").html("typed "+this.value) + }) + + //when copy is clicked, change the text + $("#copy").click(function(){ + //run slowwwww + var sum = 0, date = new Date(); + for(var i =0; i< 10; i++){ + $("*").each(function(){ + $(this).width() + $(this).height() + }) + } + $("#seewhatyoutyped").html("copied "+$("#typehere").val()) + }) + + $('#clickToChange').click(function(){ + setTimeout(function(){ + $('#clickToChange').html("changed") + },300) + }) + //has class + $("#hasClass").click(function(){ + setTimeout(function(){ + $("#hasClass").addClass('someClass') + },200) + }) + + //exists + $("#exists").click(function(){ + setTimeout(function(){ + $("#exists").append("<p>I exist</p>") + },200) + }) + $('#trigger').bind('myCustomEvent', function(){ + $("#trigger").append("<p>I was triggered</p>") + }) + + //confirm + $('#confirm').bind('click', function(){ + if(confirm("are you sure?")){ + $("#confirm .result").html("<p>I was confirmed</p>") + } else { + $("#confirm .result").html("<p>I was not confirmed</p>") + } + }) + }) + changeText = function(){ + $('#changelink').text('Changed') + } + + $('#drag').live("draginit", function(){}) + .live("dragover", function(){ + $(this).css('background-color', 'black').addClass("dragover") + }) + $('#drop').live("dropover", function(){ + $(this).css('background-color', 'blue').addClass("dropover") + }) + $('#drop').live("dropout", function(){ + $(this).css('background-color', 'green').addClass("dropout") + }) + + + + </script> + </head> + <body> + <h1>Hello World</h1> + <input id="typehere"/><input type='submit' value="Copy" id='copy'/> + <p id='seewhatyoutyped'></p> + <iframe src='myotherapp.html'></iframe> + <div id='clickToChange'>Change Thist Text</div> + <a href="javascript:changeText()" id='changelink'>Change</a> + <div id='drag'></div> + <div id='drop'></div> + <h3>hasClass</h3> + <div id='hasClass'>Click Me</div> + <h3>Exists</h3> + <div id='exists'>Click Me</div> + <h3>Trigger Custom Event</h3> + <div id='trigger'>Trigger Me</div> + <div id='confirm'>Confirm Me + <div class='result'></div> + </div> + + + </body> +</html> \ No newline at end of file diff --git a/browserid/static/funcunit/test/myotherapp.html b/browserid/static/funcunit/test/myotherapp.html new file mode 100644 index 0000000000000000000000000000000000000000..2f2465cb8e7c44e222121693e795cbb765a58075 --- /dev/null +++ b/browserid/static/funcunit/test/myotherapp.html @@ -0,0 +1,15 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> +<html> + <head> + <title>Untitled Document</title> + <script src="../../../jquery/jquery.js" type='text/javascript'></script> + <script type='text/javascript'> + C = function(){} + + + </script> + </head> + <body onload=""> + <h2>Goodbye World</h2> + </body> +</html> \ No newline at end of file diff --git a/browserid/static/funcunit/test/protodrag/dragdrop.js b/browserid/static/funcunit/test/protodrag/dragdrop.js new file mode 100644 index 0000000000000000000000000000000000000000..15c6dbca68ba56e5862fc38d2fd74e29aa699744 --- /dev/null +++ b/browserid/static/funcunit/test/protodrag/dragdrop.js @@ -0,0 +1,974 @@ +// script.aculo.us dragdrop.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009 + +// Copyright (c) 2005-2009 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// +// script.aculo.us is freely distributable under the terms of an MIT-style license. +// For details, see the script.aculo.us web site: http://script.aculo.us/ + +if(Object.isUndefined(Effect)) + throw("dragdrop.js requires including script.aculo.us' effects.js library"); + +var Droppables = { + drops: [], + + remove: function(element) { + this.drops = this.drops.reject(function(d) { return d.element==$(element) }); + }, + + add: function(element) { + element = $(element); + var options = Object.extend({ + greedy: true, + hoverclass: null, + tree: false + }, arguments[1] || { }); + + // cache containers + if(options.containment) { + options._containers = []; + var containment = options.containment; + if(Object.isArray(containment)) { + containment.each( function(c) { options._containers.push($(c)) }); + } else { + options._containers.push($(containment)); + } + } + + if(options.accept) options.accept = [options.accept].flatten(); + + Element.makePositioned(element); // fix IE + options.element = element; + + this.drops.push(options); + }, + + findDeepestChild: function(drops) { + deepest = drops[0]; + + for (i = 1; i < drops.length; ++i) + if (Element.isParent(drops[i].element, deepest.element)) + deepest = drops[i]; + + return deepest; + }, + + isContained: function(element, drop) { + var containmentNode; + if(drop.tree) { + containmentNode = element.treeNode; + } else { + containmentNode = element.parentNode; + } + return drop._containers.detect(function(c) { return containmentNode == c }); + }, + + isAffected: function(point, element, drop) { + return ( + (drop.element!=element) && + ((!drop._containers) || + this.isContained(element, drop)) && + ((!drop.accept) || + (Element.classNames(element).detect( + function(v) { return drop.accept.include(v) } ) )) && + Position.within(drop.element, point[0], point[1]) ); + }, + + deactivate: function(drop) { + if(drop.hoverclass) + Element.removeClassName(drop.element, drop.hoverclass); + this.last_active = null; + }, + + activate: function(drop) { + if(drop.hoverclass) + Element.addClassName(drop.element, drop.hoverclass); + this.last_active = drop; + }, + + show: function(point, element) { + if(!this.drops.length) return; + var drop, affected = []; + + this.drops.each( function(drop) { + if(Droppables.isAffected(point, element, drop)) + affected.push(drop); + }); + + if(affected.length>0) + drop = Droppables.findDeepestChild(affected); + + if(this.last_active && this.last_active != drop) this.deactivate(this.last_active); + if (drop) { + Position.within(drop.element, point[0], point[1]); + if(drop.onHover) + drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element)); + + if (drop != this.last_active) Droppables.activate(drop); + } + }, + + fire: function(event, element) { + if(!this.last_active) return; + Position.prepare(); + + if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active)) + if (this.last_active.onDrop) { + this.last_active.onDrop(element, this.last_active.element, event); + return true; + } + }, + + reset: function() { + if(this.last_active) + this.deactivate(this.last_active); + } +}; + +var Draggables = { + drags: [], + observers: [], + + register: function(draggable) { + if(this.drags.length == 0) { + this.eventMouseUp = this.endDrag.bindAsEventListener(this); + this.eventMouseMove = this.updateDrag.bindAsEventListener(this); + this.eventKeypress = this.keyPress.bindAsEventListener(this); + + Event.observe(document, "mouseup", this.eventMouseUp); + Event.observe(document, "mousemove", this.eventMouseMove); + Event.observe(document, "keypress", this.eventKeypress); + } + this.drags.push(draggable); + }, + + unregister: function(draggable) { + this.drags = this.drags.reject(function(d) { return d==draggable }); + if(this.drags.length == 0) { + Event.stopObserving(document, "mouseup", this.eventMouseUp); + Event.stopObserving(document, "mousemove", this.eventMouseMove); + Event.stopObserving(document, "keypress", this.eventKeypress); + } + }, + + activate: function(draggable) { + if(draggable.options.delay) { + this._timeout = setTimeout(function() { + Draggables._timeout = null; + window.focus(); + Draggables.activeDraggable = draggable; + }.bind(this), draggable.options.delay); + } else { + window.focus(); // allows keypress events if window isn't currently focused, fails for Safari + this.activeDraggable = draggable; + } + }, + + deactivate: function() { + this.activeDraggable = null; + }, + + updateDrag: function(event) { + if(!this.activeDraggable) return; + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + // Mozilla-based browsers fire successive mousemove events with + // the same coordinates, prevent needless redrawing (moz bug?) + if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return; + this._lastPointer = pointer; + + this.activeDraggable.updateDrag(event, pointer); + }, + + endDrag: function(event) { + if(this._timeout) { + clearTimeout(this._timeout); + this._timeout = null; + } + if(!this.activeDraggable) return; + this._lastPointer = null; + this.activeDraggable.endDrag(event); + this.activeDraggable = null; + }, + + keyPress: function(event) { + if(this.activeDraggable) + this.activeDraggable.keyPress(event); + }, + + addObserver: function(observer) { + this.observers.push(observer); + this._cacheObserverCallbacks(); + }, + + removeObserver: function(element) { // element instead of observer fixes mem leaks + this.observers = this.observers.reject( function(o) { return o.element==element }); + this._cacheObserverCallbacks(); + }, + + notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag' + if(this[eventName+'Count'] > 0) + this.observers.each( function(o) { + if(o[eventName]) o[eventName](eventName, draggable, event); + }); + if(draggable.options[eventName]) draggable.options[eventName](draggable, event); + }, + + _cacheObserverCallbacks: function() { + ['onStart','onEnd','onDrag'].each( function(eventName) { + Draggables[eventName+'Count'] = Draggables.observers.select( + function(o) { return o[eventName]; } + ).length; + }); + } +}; + +/*--------------------------------------------------------------------------*/ + +var Draggable = Class.create({ + initialize: function(element) { + var defaults = { + handle: false, + reverteffect: function(element, top_offset, left_offset) { + var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02; + new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur, + queue: {scope:'_draggable', position:'end'} + }); + }, + endeffect: function(element) { + var toOpacity = Object.isNumber(element._opacity) ? element._opacity : 1.0; + new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity, + queue: {scope:'_draggable', position:'end'}, + afterFinish: function(){ + Draggable._dragging[element] = false + } + }); + }, + zindex: 1000, + revert: false, + quiet: false, + scroll: false, + scrollSensitivity: 20, + scrollSpeed: 15, + snap: false, // false, or xy or [x,y] or function(x,y){ return [x,y] } + delay: 0 + }; + + if(!arguments[1] || Object.isUndefined(arguments[1].endeffect)) + Object.extend(defaults, { + starteffect: function(element) { + element._opacity = Element.getOpacity(element); + Draggable._dragging[element] = true; + new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7}); + } + }); + + var options = Object.extend(defaults, arguments[1] || { }); + + this.element = $(element); + + if(options.handle && Object.isString(options.handle)) + this.handle = this.element.down('.'+options.handle, 0); + + if(!this.handle) this.handle = $(options.handle); + if(!this.handle) this.handle = this.element; + + if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) { + options.scroll = $(options.scroll); + this._isScrollChild = Element.childOf(this.element, options.scroll); + } + + Element.makePositioned(this.element); // fix IE + + this.options = options; + this.dragging = false; + + this.eventMouseDown = this.initDrag.bindAsEventListener(this); + Event.observe(this.handle, "mousedown", this.eventMouseDown); + + Draggables.register(this); + }, + + destroy: function() { + Event.stopObserving(this.handle, "mousedown", this.eventMouseDown); + Draggables.unregister(this); + }, + + currentDelta: function() { + return([ + parseInt(Element.getStyle(this.element,'left') || '0'), + parseInt(Element.getStyle(this.element,'top') || '0')]); + }, + + initDrag: function(event) { + if(!Object.isUndefined(Draggable._dragging[this.element]) && + Draggable._dragging[this.element]) return; + if(Event.isLeftClick(event)) { + // abort on form elements, fixes a Firefox issue + var src = Event.element(event); + if((tag_name = src.tagName.toUpperCase()) && ( + tag_name=='INPUT' || + tag_name=='SELECT' || + tag_name=='OPTION' || + tag_name=='BUTTON' || + tag_name=='TEXTAREA')) return; + + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + var pos = this.element.cumulativeOffset(); + this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) }); + + Draggables.activate(this); + Event.stop(event); + } + }, + + startDrag: function(event) { + this.dragging = true; + if(!this.delta) + this.delta = this.currentDelta(); + + if(this.options.zindex) { + this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0); + this.element.style.zIndex = this.options.zindex; + } + + if(this.options.ghosting) { + this._clone = this.element.cloneNode(true); + this._originallyAbsolute = (this.element.getStyle('position') == 'absolute'); + if (!this._originallyAbsolute) + Position.absolutize(this.element); + this.element.parentNode.insertBefore(this._clone, this.element); + } + + if(this.options.scroll) { + if (this.options.scroll == window) { + var where = this._getWindowScroll(this.options.scroll); + this.originalScrollLeft = where.left; + this.originalScrollTop = where.top; + } else { + this.originalScrollLeft = this.options.scroll.scrollLeft; + this.originalScrollTop = this.options.scroll.scrollTop; + } + } + + Draggables.notify('onStart', this, event); + + if(this.options.starteffect) this.options.starteffect(this.element); + }, + + updateDrag: function(event, pointer) { + if(!this.dragging) this.startDrag(event); + + if(!this.options.quiet){ + Position.prepare(); + Droppables.show(pointer, this.element); + } + + Draggables.notify('onDrag', this, event); + + this.draw(pointer); + if(this.options.change) this.options.change(this); + + if(this.options.scroll) { + this.stopScrolling(); + + var p; + if (this.options.scroll == window) { + with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; } + } else { + p = Position.page(this.options.scroll); + p[0] += this.options.scroll.scrollLeft + Position.deltaX; + p[1] += this.options.scroll.scrollTop + Position.deltaY; + p.push(p[0]+this.options.scroll.offsetWidth); + p.push(p[1]+this.options.scroll.offsetHeight); + } + var speed = [0,0]; + if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity); + if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity); + if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity); + if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity); + this.startScrolling(speed); + } + + // fix AppleWebKit rendering + if(Prototype.Browser.WebKit) window.scrollBy(0,0); + + Event.stop(event); + }, + + finishDrag: function(event, success) { + this.dragging = false; + + if(this.options.quiet){ + Position.prepare(); + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + Droppables.show(pointer, this.element); + } + + if(this.options.ghosting) { + if (!this._originallyAbsolute) + Position.relativize(this.element); + delete this._originallyAbsolute; + Element.remove(this._clone); + this._clone = null; + } + + var dropped = false; + if(success) { + dropped = Droppables.fire(event, this.element); + if (!dropped) dropped = false; + } + if(dropped && this.options.onDropped) this.options.onDropped(this.element); + Draggables.notify('onEnd', this, event); + + var revert = this.options.revert; + if(revert && Object.isFunction(revert)) revert = revert(this.element); + + var d = this.currentDelta(); + if(revert && this.options.reverteffect) { + if (dropped == 0 || revert != 'failure') + this.options.reverteffect(this.element, + d[1]-this.delta[1], d[0]-this.delta[0]); + } else { + this.delta = d; + } + + if(this.options.zindex) + this.element.style.zIndex = this.originalZ; + + if(this.options.endeffect) + this.options.endeffect(this.element); + + Draggables.deactivate(this); + Droppables.reset(); + }, + + keyPress: function(event) { + if(event.keyCode!=Event.KEY_ESC) return; + this.finishDrag(event, false); + Event.stop(event); + }, + + endDrag: function(event) { + if(!this.dragging) return; + this.stopScrolling(); + this.finishDrag(event, true); + Event.stop(event); + }, + + draw: function(point) { + var pos = this.element.cumulativeOffset(); + if(this.options.ghosting) { + var r = Position.realOffset(this.element); + pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY; + } + + var d = this.currentDelta(); + pos[0] -= d[0]; pos[1] -= d[1]; + + if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) { + pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft; + pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop; + } + + var p = [0,1].map(function(i){ + return (point[i]-pos[i]-this.offset[i]) + }.bind(this)); + + if(this.options.snap) { + if(Object.isFunction(this.options.snap)) { + p = this.options.snap(p[0],p[1],this); + } else { + if(Object.isArray(this.options.snap)) { + p = p.map( function(v, i) { + return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this)); + } else { + p = p.map( function(v) { + return (v/this.options.snap).round()*this.options.snap }.bind(this)); + } + }} + + var style = this.element.style; + if((!this.options.constraint) || (this.options.constraint=='horizontal')) + style.left = p[0] + "px"; + if((!this.options.constraint) || (this.options.constraint=='vertical')) + style.top = p[1] + "px"; + + if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering + }, + + stopScrolling: function() { + if(this.scrollInterval) { + clearInterval(this.scrollInterval); + this.scrollInterval = null; + Draggables._lastScrollPointer = null; + } + }, + + startScrolling: function(speed) { + if(!(speed[0] || speed[1])) return; + this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed]; + this.lastScrolled = new Date(); + this.scrollInterval = setInterval(this.scroll.bind(this), 10); + }, + + scroll: function() { + var current = new Date(); + var delta = current - this.lastScrolled; + this.lastScrolled = current; + if(this.options.scroll == window) { + with (this._getWindowScroll(this.options.scroll)) { + if (this.scrollSpeed[0] || this.scrollSpeed[1]) { + var d = delta / 1000; + this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] ); + } + } + } else { + this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000; + this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000; + } + + Position.prepare(); + Droppables.show(Draggables._lastPointer, this.element); + Draggables.notify('onDrag', this); + if (this._isScrollChild) { + Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer); + Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000; + Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000; + if (Draggables._lastScrollPointer[0] < 0) + Draggables._lastScrollPointer[0] = 0; + if (Draggables._lastScrollPointer[1] < 0) + Draggables._lastScrollPointer[1] = 0; + this.draw(Draggables._lastScrollPointer); + } + + if(this.options.change) this.options.change(this); + }, + + _getWindowScroll: function(w) { + var T, L, W, H; + with (w.document) { + if (w.document.documentElement && documentElement.scrollTop) { + T = documentElement.scrollTop; + L = documentElement.scrollLeft; + } else if (w.document.body) { + T = body.scrollTop; + L = body.scrollLeft; + } + if (w.innerWidth) { + W = w.innerWidth; + H = w.innerHeight; + } else if (w.document.documentElement && documentElement.clientWidth) { + W = documentElement.clientWidth; + H = documentElement.clientHeight; + } else { + W = body.offsetWidth; + H = body.offsetHeight; + } + } + return { top: T, left: L, width: W, height: H }; + } +}); + +Draggable._dragging = { }; + +/*--------------------------------------------------------------------------*/ + +var SortableObserver = Class.create({ + initialize: function(element, observer) { + this.element = $(element); + this.observer = observer; + this.lastValue = Sortable.serialize(this.element); + }, + + onStart: function() { + this.lastValue = Sortable.serialize(this.element); + }, + + onEnd: function() { + Sortable.unmark(); + if(this.lastValue != Sortable.serialize(this.element)) + this.observer(this.element) + } +}); + +var Sortable = { + SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/, + + sortables: { }, + + _findRootElement: function(element) { + while (element.tagName.toUpperCase() != "BODY") { + if(element.id && Sortable.sortables[element.id]) return element; + element = element.parentNode; + } + }, + + options: function(element) { + element = Sortable._findRootElement($(element)); + if(!element) return; + return Sortable.sortables[element.id]; + }, + + destroy: function(element){ + element = $(element); + var s = Sortable.sortables[element.id]; + + if(s) { + Draggables.removeObserver(s.element); + s.droppables.each(function(d){ Droppables.remove(d) }); + s.draggables.invoke('destroy'); + + delete Sortable.sortables[s.element.id]; + } + }, + + create: function(element) { + element = $(element); + var options = Object.extend({ + element: element, + tag: 'li', // assumes li children, override with tag: 'tagname' + dropOnEmpty: false, + tree: false, + treeTag: 'ul', + overlap: 'vertical', // one of 'vertical', 'horizontal' + constraint: 'vertical', // one of 'vertical', 'horizontal', false + containment: element, // also takes array of elements (or id's); or false + handle: false, // or a CSS class + only: false, + delay: 0, + hoverclass: null, + ghosting: false, + quiet: false, + scroll: false, + scrollSensitivity: 20, + scrollSpeed: 15, + format: this.SERIALIZE_RULE, + + // these take arrays of elements or ids and can be + // used for better initialization performance + elements: false, + handles: false, + + onChange: Prototype.emptyFunction, + onUpdate: Prototype.emptyFunction + }, arguments[1] || { }); + + // clear any old sortable with same element + this.destroy(element); + + // build options for the draggables + var options_for_draggable = { + revert: true, + quiet: options.quiet, + scroll: options.scroll, + scrollSpeed: options.scrollSpeed, + scrollSensitivity: options.scrollSensitivity, + delay: options.delay, + ghosting: options.ghosting, + constraint: options.constraint, + handle: options.handle }; + + if(options.starteffect) + options_for_draggable.starteffect = options.starteffect; + + if(options.reverteffect) + options_for_draggable.reverteffect = options.reverteffect; + else + if(options.ghosting) options_for_draggable.reverteffect = function(element) { + element.style.top = 0; + element.style.left = 0; + }; + + if(options.endeffect) + options_for_draggable.endeffect = options.endeffect; + + if(options.zindex) + options_for_draggable.zindex = options.zindex; + + // build options for the droppables + var options_for_droppable = { + overlap: options.overlap, + containment: options.containment, + tree: options.tree, + hoverclass: options.hoverclass, + onHover: Sortable.onHover + }; + + var options_for_tree = { + onHover: Sortable.onEmptyHover, + overlap: options.overlap, + containment: options.containment, + hoverclass: options.hoverclass + }; + + // fix for gecko engine + Element.cleanWhitespace(element); + + options.draggables = []; + options.droppables = []; + + // drop on empty handling + if(options.dropOnEmpty || options.tree) { + Droppables.add(element, options_for_tree); + options.droppables.push(element); + } + + (options.elements || this.findElements(element, options) || []).each( function(e,i) { + var handle = options.handles ? $(options.handles[i]) : + (options.handle ? $(e).select('.' + options.handle)[0] : e); + options.draggables.push( + new Draggable(e, Object.extend(options_for_draggable, { handle: handle }))); + Droppables.add(e, options_for_droppable); + if(options.tree) e.treeNode = element; + options.droppables.push(e); + }); + + if(options.tree) { + (Sortable.findTreeElements(element, options) || []).each( function(e) { + Droppables.add(e, options_for_tree); + e.treeNode = element; + options.droppables.push(e); + }); + } + + // keep reference + this.sortables[element.identify()] = options; + + // for onupdate + Draggables.addObserver(new SortableObserver(element, options.onUpdate)); + + }, + + // return all suitable-for-sortable elements in a guaranteed order + findElements: function(element, options) { + return Element.findChildren( + element, options.only, options.tree ? true : false, options.tag); + }, + + findTreeElements: function(element, options) { + return Element.findChildren( + element, options.only, options.tree ? true : false, options.treeTag); + }, + + onHover: function(element, dropon, overlap) { + if(Element.isParent(dropon, element)) return; + + if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) { + return; + } else if(overlap>0.5) { + Sortable.mark(dropon, 'before'); + if(dropon.previousSibling != element) { + var oldParentNode = element.parentNode; + element.style.visibility = "hidden"; // fix gecko rendering + dropon.parentNode.insertBefore(element, dropon); + if(dropon.parentNode!=oldParentNode) + Sortable.options(oldParentNode).onChange(element); + Sortable.options(dropon.parentNode).onChange(element); + } + } else { + Sortable.mark(dropon, 'after'); + var nextElement = dropon.nextSibling || null; + if(nextElement != element) { + var oldParentNode = element.parentNode; + element.style.visibility = "hidden"; // fix gecko rendering + dropon.parentNode.insertBefore(element, nextElement); + if(dropon.parentNode!=oldParentNode) + Sortable.options(oldParentNode).onChange(element); + Sortable.options(dropon.parentNode).onChange(element); + } + } + }, + + onEmptyHover: function(element, dropon, overlap) { + var oldParentNode = element.parentNode; + var droponOptions = Sortable.options(dropon); + + if(!Element.isParent(dropon, element)) { + var index; + + var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only}); + var child = null; + + if(children) { + var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap); + + for (index = 0; index < children.length; index += 1) { + if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) { + offset -= Element.offsetSize (children[index], droponOptions.overlap); + } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) { + child = index + 1 < children.length ? children[index + 1] : null; + break; + } else { + child = children[index]; + break; + } + } + } + + dropon.insertBefore(element, child); + + Sortable.options(oldParentNode).onChange(element); + droponOptions.onChange(element); + } + }, + + unmark: function() { + if(Sortable._marker) Sortable._marker.hide(); + }, + + mark: function(dropon, position) { + // mark on ghosting only + var sortable = Sortable.options(dropon.parentNode); + if(sortable && !sortable.ghosting) return; + + if(!Sortable._marker) { + Sortable._marker = + ($('dropmarker') || Element.extend(document.createElement('DIV'))). + hide().addClassName('dropmarker').setStyle({position:'absolute'}); + document.getElementsByTagName("body").item(0).appendChild(Sortable._marker); + } + var offsets = dropon.cumulativeOffset(); + Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'}); + + if(position=='after') + if(sortable.overlap == 'horizontal') + Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'}); + else + Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'}); + + Sortable._marker.show(); + }, + + _tree: function(element, options, parent) { + var children = Sortable.findElements(element, options) || []; + + for (var i = 0; i < children.length; ++i) { + var match = children[i].id.match(options.format); + + if (!match) continue; + + var child = { + id: encodeURIComponent(match ? match[1] : null), + element: element, + parent: parent, + children: [], + position: parent.children.length, + container: $(children[i]).down(options.treeTag) + }; + + /* Get the element containing the children and recurse over it */ + if (child.container) + this._tree(child.container, options, child); + + parent.children.push (child); + } + + return parent; + }, + + tree: function(element) { + element = $(element); + var sortableOptions = this.options(element); + var options = Object.extend({ + tag: sortableOptions.tag, + treeTag: sortableOptions.treeTag, + only: sortableOptions.only, + name: element.id, + format: sortableOptions.format + }, arguments[1] || { }); + + var root = { + id: null, + parent: null, + children: [], + container: element, + position: 0 + }; + + return Sortable._tree(element, options, root); + }, + + /* Construct a [i] index for a particular node */ + _constructIndex: function(node) { + var index = ''; + do { + if (node.id) index = '[' + node.position + ']' + index; + } while ((node = node.parent) != null); + return index; + }, + + sequence: function(element) { + element = $(element); + var options = Object.extend(this.options(element), arguments[1] || { }); + + return $(this.findElements(element, options) || []).map( function(item) { + return item.id.match(options.format) ? item.id.match(options.format)[1] : ''; + }); + }, + + setSequence: function(element, new_sequence) { + element = $(element); + var options = Object.extend(this.options(element), arguments[2] || { }); + + var nodeMap = { }; + this.findElements(element, options).each( function(n) { + if (n.id.match(options.format)) + nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode]; + n.parentNode.removeChild(n); + }); + + new_sequence.each(function(ident) { + var n = nodeMap[ident]; + if (n) { + n[1].appendChild(n[0]); + delete nodeMap[ident]; + } + }); + }, + + serialize: function(element) { + element = $(element); + var options = Object.extend(Sortable.options(element), arguments[1] || { }); + var name = encodeURIComponent( + (arguments[1] && arguments[1].name) ? arguments[1].name : element.id); + + if (options.tree) { + return Sortable.tree(element, arguments[1]).children.map( function (item) { + return [name + Sortable._constructIndex(item) + "[id]=" + + encodeURIComponent(item.id)].concat(item.children.map(arguments.callee)); + }).flatten().join('&'); + } else { + return Sortable.sequence(element, arguments[1]).map( function(item) { + return name + "[]=" + encodeURIComponent(item); + }).join('&'); + } + } +}; + +// Returns true if child is contained within element +Element.isParent = function(child, element) { + if (!child.parentNode || child == element) return false; + if (child.parentNode == element) return true; + return Element.isParent(child.parentNode, element); +}; + +Element.findChildren = function(element, only, recursive, tagName) { + if(!element.hasChildNodes()) return null; + tagName = tagName.toUpperCase(); + if(only) only = [only].flatten(); + var elements = []; + $A(element.childNodes).each( function(e) { + if(e.tagName && e.tagName.toUpperCase()==tagName && + (!only || (Element.classNames(e).detect(function(v) { return only.include(v) })))) + elements.push(e); + if(recursive) { + var grandchildren = Element.findChildren(e, only, recursive, tagName); + if(grandchildren) elements.push(grandchildren); + } + }); + + return (elements.length>0 ? elements.flatten() : []); +}; + +Element.offsetSize = function (element, type) { + return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')]; +}; \ No newline at end of file diff --git a/browserid/static/funcunit/test/protodrag/effects.js b/browserid/static/funcunit/test/protodrag/effects.js new file mode 100644 index 0000000000000000000000000000000000000000..fcaf0757e9b0cb0079f42d327d35692e62b975fc --- /dev/null +++ b/browserid/static/funcunit/test/protodrag/effects.js @@ -0,0 +1,1123 @@ +// script.aculo.us effects.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009 + +// Copyright (c) 2005-2009 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// Contributors: +// Justin Palmer (http://encytemedia.com/) +// Mark Pilgrim (http://diveintomark.org/) +// Martin Bialasinki +// +// script.aculo.us is freely distributable under the terms of an MIT-style license. +// For details, see the script.aculo.us web site: http://script.aculo.us/ + +// converts rgb() and #xxx to #xxxxxx format, +// returns self (or first argument) if not convertable +String.prototype.parseColor = function() { + var color = '#'; + if (this.slice(0,4) == 'rgb(') { + var cols = this.slice(4,this.length-1).split(','); + var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3); + } else { + if (this.slice(0,1) == '#') { + if (this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase(); + if (this.length==7) color = this.toLowerCase(); + } + } + return (color.length==7 ? color : (arguments[0] || this)); +}; + +/*--------------------------------------------------------------------------*/ + +Element.collectTextNodes = function(element) { + return $A($(element).childNodes).collect( function(node) { + return (node.nodeType==3 ? node.nodeValue : + (node.hasChildNodes() ? Element.collectTextNodes(node) : '')); + }).flatten().join(''); +}; + +Element.collectTextNodesIgnoreClass = function(element, className) { + return $A($(element).childNodes).collect( function(node) { + return (node.nodeType==3 ? node.nodeValue : + ((node.hasChildNodes() && !Element.hasClassName(node,className)) ? + Element.collectTextNodesIgnoreClass(node, className) : '')); + }).flatten().join(''); +}; + +Element.setContentZoom = function(element, percent) { + element = $(element); + element.setStyle({fontSize: (percent/100) + 'em'}); + if (Prototype.Browser.WebKit) window.scrollBy(0,0); + return element; +}; + +Element.getInlineOpacity = function(element){ + return $(element).style.opacity || ''; +}; + +Element.forceRerendering = function(element) { + try { + element = $(element); + var n = document.createTextNode(' '); + element.appendChild(n); + element.removeChild(n); + } catch(e) { } +}; + +/*--------------------------------------------------------------------------*/ + +var Effect = { + _elementDoesNotExistError: { + name: 'ElementDoesNotExistError', + message: 'The specified DOM element does not exist, but is required for this effect to operate' + }, + Transitions: { + linear: Prototype.K, + sinoidal: function(pos) { + return (-Math.cos(pos*Math.PI)/2) + 0.5; + }, + reverse: function(pos) { + return 1-pos; + }, + flicker: function(pos) { + var pos = ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4; + return pos > 1 ? 1 : pos; + }, + wobble: function(pos) { + return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5; + }, + pulse: function(pos, pulses) { + return (-Math.cos((pos*((pulses||5)-0.5)*2)*Math.PI)/2) + 0.5; + }, + spring: function(pos) { + return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6)); + }, + none: function(pos) { + return 0; + }, + full: function(pos) { + return 1; + } + }, + DefaultOptions: { + duration: 1.0, // seconds + fps: 100, // 100= assume 66fps max. + sync: false, // true for combining + from: 0.0, + to: 1.0, + delay: 0.0, + queue: 'parallel' + }, + tagifyText: function(element) { + var tagifyStyle = 'position:relative'; + if (Prototype.Browser.IE) tagifyStyle += ';zoom:1'; + + element = $(element); + $A(element.childNodes).each( function(child) { + if (child.nodeType==3) { + child.nodeValue.toArray().each( function(character) { + element.insertBefore( + new Element('span', {style: tagifyStyle}).update( + character == ' ' ? String.fromCharCode(160) : character), + child); + }); + Element.remove(child); + } + }); + }, + multiple: function(element, effect) { + var elements; + if (((typeof element == 'object') || + Object.isFunction(element)) && + (element.length)) + elements = element; + else + elements = $(element).childNodes; + + var options = Object.extend({ + speed: 0.1, + delay: 0.0 + }, arguments[2] || { }); + var masterDelay = options.delay; + + $A(elements).each( function(element, index) { + new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay })); + }); + }, + PAIRS: { + 'slide': ['SlideDown','SlideUp'], + 'blind': ['BlindDown','BlindUp'], + 'appear': ['Appear','Fade'] + }, + toggle: function(element, effect, options) { + element = $(element); + effect = (effect || 'appear').toLowerCase(); + + return Effect[ Effect.PAIRS[ effect ][ element.visible() ? 1 : 0 ] ](element, Object.extend({ + queue: { position:'end', scope:(element.id || 'global'), limit: 1 } + }, options || {})); + } +}; + +Effect.DefaultOptions.transition = Effect.Transitions.sinoidal; + +/* ------------- core effects ------------- */ + +Effect.ScopedQueue = Class.create(Enumerable, { + initialize: function() { + this.effects = []; + this.interval = null; + }, + _each: function(iterator) { + this.effects._each(iterator); + }, + add: function(effect) { + var timestamp = new Date().getTime(); + + var position = Object.isString(effect.options.queue) ? + effect.options.queue : effect.options.queue.position; + + switch(position) { + case 'front': + // move unstarted effects after this effect + this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) { + e.startOn += effect.finishOn; + e.finishOn += effect.finishOn; + }); + break; + case 'with-last': + timestamp = this.effects.pluck('startOn').max() || timestamp; + break; + case 'end': + // start effect after last queued effect has finished + timestamp = this.effects.pluck('finishOn').max() || timestamp; + break; + } + + effect.startOn += timestamp; + effect.finishOn += timestamp; + + if (!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit)) + this.effects.push(effect); + + if (!this.interval) + this.interval = setInterval(this.loop.bind(this), 15); + }, + remove: function(effect) { + this.effects = this.effects.reject(function(e) { return e==effect }); + if (this.effects.length == 0) { + clearInterval(this.interval); + this.interval = null; + } + }, + loop: function() { + var timePos = new Date().getTime(); + for(var i=0, len=this.effects.length;i<len;i++) + this.effects[i] && this.effects[i].loop(timePos); + } +}); + +Effect.Queues = { + instances: $H(), + get: function(queueName) { + if (!Object.isString(queueName)) return queueName; + + return this.instances.get(queueName) || + this.instances.set(queueName, new Effect.ScopedQueue()); + } +}; +Effect.Queue = Effect.Queues.get('global'); + +Effect.Base = Class.create({ + position: null, + start: function(options) { + if (options && options.transition === false) options.transition = Effect.Transitions.linear; + this.options = Object.extend(Object.extend({ },Effect.DefaultOptions), options || { }); + this.currentFrame = 0; + this.state = 'idle'; + this.startOn = this.options.delay*1000; + this.finishOn = this.startOn+(this.options.duration*1000); + this.fromToDelta = this.options.to-this.options.from; + this.totalTime = this.finishOn-this.startOn; + this.totalFrames = this.options.fps*this.options.duration; + + this.render = (function() { + function dispatch(effect, eventName) { + if (effect.options[eventName + 'Internal']) + effect.options[eventName + 'Internal'](effect); + if (effect.options[eventName]) + effect.options[eventName](effect); + } + + return function(pos) { + if (this.state === "idle") { + this.state = "running"; + dispatch(this, 'beforeSetup'); + if (this.setup) this.setup(); + dispatch(this, 'afterSetup'); + } + if (this.state === "running") { + pos = (this.options.transition(pos) * this.fromToDelta) + this.options.from; + this.position = pos; + dispatch(this, 'beforeUpdate'); + if (this.update) this.update(pos); + dispatch(this, 'afterUpdate'); + } + }; + })(); + + this.event('beforeStart'); + if (!this.options.sync) + Effect.Queues.get(Object.isString(this.options.queue) ? + 'global' : this.options.queue.scope).add(this); + }, + loop: function(timePos) { + if (timePos >= this.startOn) { + if (timePos >= this.finishOn) { + this.render(1.0); + this.cancel(); + this.event('beforeFinish'); + if (this.finish) this.finish(); + this.event('afterFinish'); + return; + } + var pos = (timePos - this.startOn) / this.totalTime, + frame = (pos * this.totalFrames).round(); + if (frame > this.currentFrame) { + this.render(pos); + this.currentFrame = frame; + } + } + }, + cancel: function() { + if (!this.options.sync) + Effect.Queues.get(Object.isString(this.options.queue) ? + 'global' : this.options.queue.scope).remove(this); + this.state = 'finished'; + }, + event: function(eventName) { + if (this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this); + if (this.options[eventName]) this.options[eventName](this); + }, + inspect: function() { + var data = $H(); + for(property in this) + if (!Object.isFunction(this[property])) data.set(property, this[property]); + return '#<Effect:' + data.inspect() + ',options:' + $H(this.options).inspect() + '>'; + } +}); + +Effect.Parallel = Class.create(Effect.Base, { + initialize: function(effects) { + this.effects = effects || []; + this.start(arguments[1]); + }, + update: function(position) { + this.effects.invoke('render', position); + }, + finish: function(position) { + this.effects.each( function(effect) { + effect.render(1.0); + effect.cancel(); + effect.event('beforeFinish'); + if (effect.finish) effect.finish(position); + effect.event('afterFinish'); + }); + } +}); + +Effect.Tween = Class.create(Effect.Base, { + initialize: function(object, from, to) { + object = Object.isString(object) ? $(object) : object; + var args = $A(arguments), method = args.last(), + options = args.length == 5 ? args[3] : null; + this.method = Object.isFunction(method) ? method.bind(object) : + Object.isFunction(object[method]) ? object[method].bind(object) : + function(value) { object[method] = value }; + this.start(Object.extend({ from: from, to: to }, options || { })); + }, + update: function(position) { + this.method(position); + } +}); + +Effect.Event = Class.create(Effect.Base, { + initialize: function() { + this.start(Object.extend({ duration: 0 }, arguments[0] || { })); + }, + update: Prototype.emptyFunction +}); + +Effect.Opacity = Class.create(Effect.Base, { + initialize: function(element) { + this.element = $(element); + if (!this.element) throw(Effect._elementDoesNotExistError); + // make this work on IE on elements without 'layout' + if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout)) + this.element.setStyle({zoom: 1}); + var options = Object.extend({ + from: this.element.getOpacity() || 0.0, + to: 1.0 + }, arguments[1] || { }); + this.start(options); + }, + update: function(position) { + this.element.setOpacity(position); + } +}); + +Effect.Move = Class.create(Effect.Base, { + initialize: function(element) { + this.element = $(element); + if (!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ + x: 0, + y: 0, + mode: 'relative' + }, arguments[1] || { }); + this.start(options); + }, + setup: function() { + this.element.makePositioned(); + this.originalLeft = parseFloat(this.element.getStyle('left') || '0'); + this.originalTop = parseFloat(this.element.getStyle('top') || '0'); + if (this.options.mode == 'absolute') { + this.options.x = this.options.x - this.originalLeft; + this.options.y = this.options.y - this.originalTop; + } + }, + update: function(position) { + this.element.setStyle({ + left: (this.options.x * position + this.originalLeft).round() + 'px', + top: (this.options.y * position + this.originalTop).round() + 'px' + }); + } +}); + +// for backwards compatibility +Effect.MoveBy = function(element, toTop, toLeft) { + return new Effect.Move(element, + Object.extend({ x: toLeft, y: toTop }, arguments[3] || { })); +}; + +Effect.Scale = Class.create(Effect.Base, { + initialize: function(element, percent) { + this.element = $(element); + if (!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ + scaleX: true, + scaleY: true, + scaleContent: true, + scaleFromCenter: false, + scaleMode: 'box', // 'box' or 'contents' or { } with provided values + scaleFrom: 100.0, + scaleTo: percent + }, arguments[2] || { }); + this.start(options); + }, + setup: function() { + this.restoreAfterFinish = this.options.restoreAfterFinish || false; + this.elementPositioning = this.element.getStyle('position'); + + this.originalStyle = { }; + ['top','left','width','height','fontSize'].each( function(k) { + this.originalStyle[k] = this.element.style[k]; + }.bind(this)); + + this.originalTop = this.element.offsetTop; + this.originalLeft = this.element.offsetLeft; + + var fontSize = this.element.getStyle('font-size') || '100%'; + ['em','px','%','pt'].each( function(fontSizeType) { + if (fontSize.indexOf(fontSizeType)>0) { + this.fontSize = parseFloat(fontSize); + this.fontSizeType = fontSizeType; + } + }.bind(this)); + + this.factor = (this.options.scaleTo - this.options.scaleFrom)/100; + + this.dims = null; + if (this.options.scaleMode=='box') + this.dims = [this.element.offsetHeight, this.element.offsetWidth]; + if (/^content/.test(this.options.scaleMode)) + this.dims = [this.element.scrollHeight, this.element.scrollWidth]; + if (!this.dims) + this.dims = [this.options.scaleMode.originalHeight, + this.options.scaleMode.originalWidth]; + }, + update: function(position) { + var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position); + if (this.options.scaleContent && this.fontSize) + this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType }); + this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale); + }, + finish: function(position) { + if (this.restoreAfterFinish) this.element.setStyle(this.originalStyle); + }, + setDimensions: function(height, width) { + var d = { }; + if (this.options.scaleX) d.width = width.round() + 'px'; + if (this.options.scaleY) d.height = height.round() + 'px'; + if (this.options.scaleFromCenter) { + var topd = (height - this.dims[0])/2; + var leftd = (width - this.dims[1])/2; + if (this.elementPositioning == 'absolute') { + if (this.options.scaleY) d.top = this.originalTop-topd + 'px'; + if (this.options.scaleX) d.left = this.originalLeft-leftd + 'px'; + } else { + if (this.options.scaleY) d.top = -topd + 'px'; + if (this.options.scaleX) d.left = -leftd + 'px'; + } + } + this.element.setStyle(d); + } +}); + +Effect.Highlight = Class.create(Effect.Base, { + initialize: function(element) { + this.element = $(element); + if (!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || { }); + this.start(options); + }, + setup: function() { + // Prevent executing on elements not in the layout flow + if (this.element.getStyle('display')=='none') { this.cancel(); return; } + // Disable background image during the effect + this.oldStyle = { }; + if (!this.options.keepBackgroundImage) { + this.oldStyle.backgroundImage = this.element.getStyle('background-image'); + this.element.setStyle({backgroundImage: 'none'}); + } + if (!this.options.endcolor) + this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff'); + if (!this.options.restorecolor) + this.options.restorecolor = this.element.getStyle('background-color'); + // init color calculations + this._base = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this)); + this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this)); + }, + update: function(position) { + this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){ + return m+((this._base[i]+(this._delta[i]*position)).round().toColorPart()); }.bind(this)) }); + }, + finish: function() { + this.element.setStyle(Object.extend(this.oldStyle, { + backgroundColor: this.options.restorecolor + })); + } +}); + +Effect.ScrollTo = function(element) { + var options = arguments[1] || { }, + scrollOffsets = document.viewport.getScrollOffsets(), + elementOffsets = $(element).cumulativeOffset(); + + if (options.offset) elementOffsets[1] += options.offset; + + return new Effect.Tween(null, + scrollOffsets.top, + elementOffsets[1], + options, + function(p){ scrollTo(scrollOffsets.left, p.round()); } + ); +}; + +/* ------------- combination effects ------------- */ + +Effect.Fade = function(element) { + element = $(element); + var oldOpacity = element.getInlineOpacity(); + var options = Object.extend({ + from: element.getOpacity() || 1.0, + to: 0.0, + afterFinishInternal: function(effect) { + if (effect.options.to!=0) return; + effect.element.hide().setStyle({opacity: oldOpacity}); + } + }, arguments[1] || { }); + return new Effect.Opacity(element,options); +}; + +Effect.Appear = function(element) { + element = $(element); + var options = Object.extend({ + from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0), + to: 1.0, + // force Safari to render floated elements properly + afterFinishInternal: function(effect) { + effect.element.forceRerendering(); + }, + beforeSetup: function(effect) { + effect.element.setOpacity(effect.options.from).show(); + }}, arguments[1] || { }); + return new Effect.Opacity(element,options); +}; + +Effect.Puff = function(element) { + element = $(element); + var oldStyle = { + opacity: element.getInlineOpacity(), + position: element.getStyle('position'), + top: element.style.top, + left: element.style.left, + width: element.style.width, + height: element.style.height + }; + return new Effect.Parallel( + [ new Effect.Scale(element, 200, + { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }), + new Effect.Opacity(element, { sync: true, to: 0.0 } ) ], + Object.extend({ duration: 1.0, + beforeSetupInternal: function(effect) { + Position.absolutize(effect.effects[0].element); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide().setStyle(oldStyle); } + }, arguments[1] || { }) + ); +}; + +Effect.BlindUp = function(element) { + element = $(element); + element.makeClipping(); + return new Effect.Scale(element, 0, + Object.extend({ scaleContent: false, + scaleX: false, + restoreAfterFinish: true, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping(); + } + }, arguments[1] || { }) + ); +}; + +Effect.BlindDown = function(element) { + element = $(element); + var elementDimensions = element.getDimensions(); + return new Effect.Scale(element, 100, Object.extend({ + scaleContent: false, + scaleX: false, + scaleFrom: 0, + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, + restoreAfterFinish: true, + afterSetup: function(effect) { + effect.element.makeClipping().setStyle({height: '0px'}).show(); + }, + afterFinishInternal: function(effect) { + effect.element.undoClipping(); + } + }, arguments[1] || { })); +}; + +Effect.SwitchOff = function(element) { + element = $(element); + var oldOpacity = element.getInlineOpacity(); + return new Effect.Appear(element, Object.extend({ + duration: 0.4, + from: 0, + transition: Effect.Transitions.flicker, + afterFinishInternal: function(effect) { + new Effect.Scale(effect.element, 1, { + duration: 0.3, scaleFromCenter: true, + scaleX: false, scaleContent: false, restoreAfterFinish: true, + beforeSetup: function(effect) { + effect.element.makePositioned().makeClipping(); + }, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity}); + } + }); + } + }, arguments[1] || { })); +}; + +Effect.DropOut = function(element) { + element = $(element); + var oldStyle = { + top: element.getStyle('top'), + left: element.getStyle('left'), + opacity: element.getInlineOpacity() }; + return new Effect.Parallel( + [ new Effect.Move(element, {x: 0, y: 100, sync: true }), + new Effect.Opacity(element, { sync: true, to: 0.0 }) ], + Object.extend( + { duration: 0.5, + beforeSetup: function(effect) { + effect.effects[0].element.makePositioned(); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle); + } + }, arguments[1] || { })); +}; + +Effect.Shake = function(element) { + element = $(element); + var options = Object.extend({ + distance: 20, + duration: 0.5 + }, arguments[1] || {}); + var distance = parseFloat(options.distance); + var split = parseFloat(options.duration) / 10.0; + var oldStyle = { + top: element.getStyle('top'), + left: element.getStyle('left') }; + return new Effect.Move(element, + { x: distance, y: 0, duration: split, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -distance, y: 0, duration: split, afterFinishInternal: function(effect) { + effect.element.undoPositioned().setStyle(oldStyle); + }}); }}); }}); }}); }}); }}); +}; + +Effect.SlideDown = function(element) { + element = $(element).cleanWhitespace(); + // SlideDown need to have the content of the element wrapped in a container element with fixed height! + var oldInnerBottom = element.down().getStyle('bottom'); + var elementDimensions = element.getDimensions(); + return new Effect.Scale(element, 100, Object.extend({ + scaleContent: false, + scaleX: false, + scaleFrom: window.opera ? 0 : 1, + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, + restoreAfterFinish: true, + afterSetup: function(effect) { + effect.element.makePositioned(); + effect.element.down().makePositioned(); + if (window.opera) effect.element.setStyle({top: ''}); + effect.element.makeClipping().setStyle({height: '0px'}).show(); + }, + afterUpdateInternal: function(effect) { + effect.element.down().setStyle({bottom: + (effect.dims[0] - effect.element.clientHeight) + 'px' }); + }, + afterFinishInternal: function(effect) { + effect.element.undoClipping().undoPositioned(); + effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); } + }, arguments[1] || { }) + ); +}; + +Effect.SlideUp = function(element) { + element = $(element).cleanWhitespace(); + var oldInnerBottom = element.down().getStyle('bottom'); + var elementDimensions = element.getDimensions(); + return new Effect.Scale(element, window.opera ? 0 : 1, + Object.extend({ scaleContent: false, + scaleX: false, + scaleMode: 'box', + scaleFrom: 100, + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, + restoreAfterFinish: true, + afterSetup: function(effect) { + effect.element.makePositioned(); + effect.element.down().makePositioned(); + if (window.opera) effect.element.setStyle({top: ''}); + effect.element.makeClipping().show(); + }, + afterUpdateInternal: function(effect) { + effect.element.down().setStyle({bottom: + (effect.dims[0] - effect.element.clientHeight) + 'px' }); + }, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping().undoPositioned(); + effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); + } + }, arguments[1] || { }) + ); +}; + +// Bug in opera makes the TD containing this element expand for a instance after finish +Effect.Squish = function(element) { + return new Effect.Scale(element, window.opera ? 1 : 0, { + restoreAfterFinish: true, + beforeSetup: function(effect) { + effect.element.makeClipping(); + }, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping(); + } + }); +}; + +Effect.Grow = function(element) { + element = $(element); + var options = Object.extend({ + direction: 'center', + moveTransition: Effect.Transitions.sinoidal, + scaleTransition: Effect.Transitions.sinoidal, + opacityTransition: Effect.Transitions.full + }, arguments[1] || { }); + var oldStyle = { + top: element.style.top, + left: element.style.left, + height: element.style.height, + width: element.style.width, + opacity: element.getInlineOpacity() }; + + var dims = element.getDimensions(); + var initialMoveX, initialMoveY; + var moveX, moveY; + + switch (options.direction) { + case 'top-left': + initialMoveX = initialMoveY = moveX = moveY = 0; + break; + case 'top-right': + initialMoveX = dims.width; + initialMoveY = moveY = 0; + moveX = -dims.width; + break; + case 'bottom-left': + initialMoveX = moveX = 0; + initialMoveY = dims.height; + moveY = -dims.height; + break; + case 'bottom-right': + initialMoveX = dims.width; + initialMoveY = dims.height; + moveX = -dims.width; + moveY = -dims.height; + break; + case 'center': + initialMoveX = dims.width / 2; + initialMoveY = dims.height / 2; + moveX = -dims.width / 2; + moveY = -dims.height / 2; + break; + } + + return new Effect.Move(element, { + x: initialMoveX, + y: initialMoveY, + duration: 0.01, + beforeSetup: function(effect) { + effect.element.hide().makeClipping().makePositioned(); + }, + afterFinishInternal: function(effect) { + new Effect.Parallel( + [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }), + new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }), + new Effect.Scale(effect.element, 100, { + scaleMode: { originalHeight: dims.height, originalWidth: dims.width }, + sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true}) + ], Object.extend({ + beforeSetup: function(effect) { + effect.effects[0].element.setStyle({height: '0px'}).show(); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle); + } + }, options) + ); + } + }); +}; + +Effect.Shrink = function(element) { + element = $(element); + var options = Object.extend({ + direction: 'center', + moveTransition: Effect.Transitions.sinoidal, + scaleTransition: Effect.Transitions.sinoidal, + opacityTransition: Effect.Transitions.none + }, arguments[1] || { }); + var oldStyle = { + top: element.style.top, + left: element.style.left, + height: element.style.height, + width: element.style.width, + opacity: element.getInlineOpacity() }; + + var dims = element.getDimensions(); + var moveX, moveY; + + switch (options.direction) { + case 'top-left': + moveX = moveY = 0; + break; + case 'top-right': + moveX = dims.width; + moveY = 0; + break; + case 'bottom-left': + moveX = 0; + moveY = dims.height; + break; + case 'bottom-right': + moveX = dims.width; + moveY = dims.height; + break; + case 'center': + moveX = dims.width / 2; + moveY = dims.height / 2; + break; + } + + return new Effect.Parallel( + [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }), + new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}), + new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }) + ], Object.extend({ + beforeStartInternal: function(effect) { + effect.effects[0].element.makePositioned().makeClipping(); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); } + }, options) + ); +}; + +Effect.Pulsate = function(element) { + element = $(element); + var options = arguments[1] || { }, + oldOpacity = element.getInlineOpacity(), + transition = options.transition || Effect.Transitions.linear, + reverser = function(pos){ + return 1 - transition((-Math.cos((pos*(options.pulses||5)*2)*Math.PI)/2) + 0.5); + }; + + return new Effect.Opacity(element, + Object.extend(Object.extend({ duration: 2.0, from: 0, + afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); } + }, options), {transition: reverser})); +}; + +Effect.Fold = function(element) { + element = $(element); + var oldStyle = { + top: element.style.top, + left: element.style.left, + width: element.style.width, + height: element.style.height }; + element.makeClipping(); + return new Effect.Scale(element, 5, Object.extend({ + scaleContent: false, + scaleX: false, + afterFinishInternal: function(effect) { + new Effect.Scale(element, 1, { + scaleContent: false, + scaleY: false, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping().setStyle(oldStyle); + } }); + }}, arguments[1] || { })); +}; + +Effect.Morph = Class.create(Effect.Base, { + initialize: function(element) { + this.element = $(element); + if (!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ + style: { } + }, arguments[1] || { }); + + if (!Object.isString(options.style)) this.style = $H(options.style); + else { + if (options.style.include(':')) + this.style = options.style.parseStyle(); + else { + this.element.addClassName(options.style); + this.style = $H(this.element.getStyles()); + this.element.removeClassName(options.style); + var css = this.element.getStyles(); + this.style = this.style.reject(function(style) { + return style.value == css[style.key]; + }); + options.afterFinishInternal = function(effect) { + effect.element.addClassName(effect.options.style); + effect.transforms.each(function(transform) { + effect.element.style[transform.style] = ''; + }); + }; + } + } + this.start(options); + }, + + setup: function(){ + function parseColor(color){ + if (!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff'; + color = color.parseColor(); + return $R(0,2).map(function(i){ + return parseInt( color.slice(i*2+1,i*2+3), 16 ); + }); + } + this.transforms = this.style.map(function(pair){ + var property = pair[0], value = pair[1], unit = null; + + if (value.parseColor('#zzzzzz') != '#zzzzzz') { + value = value.parseColor(); + unit = 'color'; + } else if (property == 'opacity') { + value = parseFloat(value); + if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout)) + this.element.setStyle({zoom: 1}); + } else if (Element.CSS_LENGTH.test(value)) { + var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/); + value = parseFloat(components[1]); + unit = (components.length == 3) ? components[2] : null; + } + + var originalValue = this.element.getStyle(property); + return { + style: property.camelize(), + originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(originalValue || 0), + targetValue: unit=='color' ? parseColor(value) : value, + unit: unit + }; + }.bind(this)).reject(function(transform){ + return ( + (transform.originalValue == transform.targetValue) || + ( + transform.unit != 'color' && + (isNaN(transform.originalValue) || isNaN(transform.targetValue)) + ) + ); + }); + }, + update: function(position) { + var style = { }, transform, i = this.transforms.length; + while(i--) + style[(transform = this.transforms[i]).style] = + transform.unit=='color' ? '#'+ + (Math.round(transform.originalValue[0]+ + (transform.targetValue[0]-transform.originalValue[0])*position)).toColorPart() + + (Math.round(transform.originalValue[1]+ + (transform.targetValue[1]-transform.originalValue[1])*position)).toColorPart() + + (Math.round(transform.originalValue[2]+ + (transform.targetValue[2]-transform.originalValue[2])*position)).toColorPart() : + (transform.originalValue + + (transform.targetValue - transform.originalValue) * position).toFixed(3) + + (transform.unit === null ? '' : transform.unit); + this.element.setStyle(style, true); + } +}); + +Effect.Transform = Class.create({ + initialize: function(tracks){ + this.tracks = []; + this.options = arguments[1] || { }; + this.addTracks(tracks); + }, + addTracks: function(tracks){ + tracks.each(function(track){ + track = $H(track); + var data = track.values().first(); + this.tracks.push($H({ + ids: track.keys().first(), + effect: Effect.Morph, + options: { style: data } + })); + }.bind(this)); + return this; + }, + play: function(){ + return new Effect.Parallel( + this.tracks.map(function(track){ + var ids = track.get('ids'), effect = track.get('effect'), options = track.get('options'); + var elements = [$(ids) || $$(ids)].flatten(); + return elements.map(function(e){ return new effect(e, Object.extend({ sync:true }, options)) }); + }).flatten(), + this.options + ); + } +}); + +Element.CSS_PROPERTIES = $w( + 'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' + + 'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' + + 'borderRightColor borderRightStyle borderRightWidth borderSpacing ' + + 'borderTopColor borderTopStyle borderTopWidth bottom clip color ' + + 'fontSize fontWeight height left letterSpacing lineHeight ' + + 'marginBottom marginLeft marginRight marginTop markerOffset maxHeight '+ + 'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' + + 'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' + + 'right textIndent top width wordSpacing zIndex'); + +Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/; + +String.__parseStyleElement = document.createElement('div'); +String.prototype.parseStyle = function(){ + var style, styleRules = $H(); + if (Prototype.Browser.WebKit) + style = new Element('div',{style:this}).style; + else { + String.__parseStyleElement.innerHTML = '<div style="' + this + '"></div>'; + style = String.__parseStyleElement.childNodes[0].style; + } + + Element.CSS_PROPERTIES.each(function(property){ + if (style[property]) styleRules.set(property, style[property]); + }); + + if (Prototype.Browser.IE && this.include('opacity')) + styleRules.set('opacity', this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1]); + + return styleRules; +}; + +if (document.defaultView && document.defaultView.getComputedStyle) { + Element.getStyles = function(element) { + var css = document.defaultView.getComputedStyle($(element), null); + return Element.CSS_PROPERTIES.inject({ }, function(styles, property) { + styles[property] = css[property]; + return styles; + }); + }; +} else { + Element.getStyles = function(element) { + element = $(element); + var css = element.currentStyle, styles; + styles = Element.CSS_PROPERTIES.inject({ }, function(results, property) { + results[property] = css[property]; + return results; + }); + if (!styles.opacity) styles.opacity = element.getOpacity(); + return styles; + }; +} + +Effect.Methods = { + morph: function(element, style) { + element = $(element); + new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || { })); + return element; + }, + visualEffect: function(element, effect, options) { + element = $(element); + var s = effect.dasherize().camelize(), klass = s.charAt(0).toUpperCase() + s.substring(1); + new Effect[klass](element, options); + return element; + }, + highlight: function(element, options) { + element = $(element); + new Effect.Highlight(element, options); + return element; + } +}; + +$w('fade appear grow shrink fold blindUp blindDown slideUp slideDown '+ + 'pulsate shake puff squish switchOff dropOut').each( + function(effect) { + Effect.Methods[effect] = function(element, options){ + element = $(element); + Effect[effect.charAt(0).toUpperCase() + effect.substring(1)](element, options); + return element; + }; + } +); + +$w('getInlineOpacity forceRerendering setContentZoom collectTextNodes collectTextNodesIgnoreClass getStyles').each( + function(f) { Effect.Methods[f] = Element[f]; } +); + +Element.addMethods(Effect.Methods); \ No newline at end of file diff --git a/browserid/static/funcunit/test/protodrag/funcunit_test.js b/browserid/static/funcunit/test/protodrag/funcunit_test.js new file mode 100644 index 0000000000000000000000000000000000000000..3e31bd6e865ac9320297186e78efe6e6b7b469a2 --- /dev/null +++ b/browserid/static/funcunit/test/protodrag/funcunit_test.js @@ -0,0 +1,20 @@ +module("myapp",{ + setup: function(){ + S.open("demo/myapp.html") + } +}) + +test("Copy Test", function(){ + S("#typehere").type("javascript1mvc[left][left][left]\b", function(){ + equals(S("#seewhatyoutyped").text(), "typed javascriptmvc","typing"); + }) + S("#copy").click(function(){ + equals(S("#seewhatyoutyped").text(), "copied javascriptmvc","copy"); + }) +}) + +test("Drag Test", function(){ + S("#drag").drag("#drop", function(){ + equals(S("#drop").text(), "Drags 1", 'drag worked correctly') + }) +}) \ No newline at end of file diff --git a/browserid/static/funcunit/test/protodrag/myapp.html b/browserid/static/funcunit/test/protodrag/myapp.html new file mode 100644 index 0000000000000000000000000000000000000000..78578c02f97f9aa5ea52b4aedbb327b636c392cb --- /dev/null +++ b/browserid/static/funcunit/test/protodrag/myapp.html @@ -0,0 +1,63 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> +<html> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> + <title>My Demo App</title> + <script src="prototype.js" type='text/javascript'></script> + <script src="effects.js" type='text/javascript'></script> + <script src="dragdrop.js" type='text/javascript'></script> + <style> + #drag { + width: 100px; + height: 30px; + border: solid 1px black; + cursor: pointer; + } + #drop { + width: 100px; + height: 30px; + border: solid 1px blue; + float: right; + } + .hover { + background-color: green; + } + </style> + <script type='text/javascript'> + document.observe("dom:loaded", function() { + $("typehere").observe("keyup",function(){ + $("seewhatyoutyped").innerHTML = ("typed "+this.value) + }) + $("typehere").value = ""; + $("copy").observe("click",function(){ + $("seewhatyoutyped").innerHTML = ("copied "+$("typehere").value) + }); + var counts = 0; + new Draggable('drag', {revert: true}); + Droppables.add('drop',{ + hoverclass: "hover", + onDrop : function(drag, drop){ + drop.innerHTML = "Drags "+(++counts) + } + }) + }); + + out = function(text){ + $("outer").innerHTML = $("outer").innerHTML + text + "<br/>" + } + </script> + + </head> + <body> + <h2>FuncUnit Demo</h2> + <h2>Type Something and Click Copy to see it below:</h2> + <input id="typehere"/><input type='submit' value="Copy" id='copy'/> + <p id='seewhatyoutyped'></p> + <h2>Drag Drop</h2> + <div id='drag'>Drag Me</div> + <div style='width: 500px'> + <div id='drop'>Drop Here</div> + </div> + <pre id='outer'></pre> + </body> +</html> diff --git a/browserid/static/funcunit/test/protodrag/prototype.js b/browserid/static/funcunit/test/protodrag/prototype.js new file mode 100644 index 0000000000000000000000000000000000000000..845ab7fb4eb488bcb134b6cad2f21e2430ab91ac --- /dev/null +++ b/browserid/static/funcunit/test/protodrag/prototype.js @@ -0,0 +1,4874 @@ +/* Prototype JavaScript framework, version 1.6.1 + * (c) 2005-2009 Sam Stephenson + * + * Prototype is freely distributable under the terms of an MIT-style license. + * For details, see the Prototype web site: http://www.prototypejs.org/ + * + *--------------------------------------------------------------------------*/ + +var Prototype = { + Version: '1.6.1', + + Browser: (function(){ + var ua = navigator.userAgent; + var isOpera = Object.prototype.toString.call(window.opera) == '[object Opera]'; + return { + IE: !!window.attachEvent && !isOpera, + Opera: isOpera, + WebKit: ua.indexOf('AppleWebKit/') > -1, + Gecko: ua.indexOf('Gecko') > -1 && ua.indexOf('KHTML') === -1, + MobileSafari: /Apple.*Mobile.*Safari/.test(ua) + } + })(), + + BrowserFeatures: { + XPath: !!document.evaluate, + SelectorsAPI: !!document.querySelector, + ElementExtensions: (function() { + var constructor = window.Element || window.HTMLElement; + return !!(constructor && constructor.prototype); + })(), + SpecificElementExtensions: (function() { + if (typeof window.HTMLDivElement !== 'undefined') + return true; + + var div = document.createElement('div'); + var form = document.createElement('form'); + var isSupported = false; + + if (div['__proto__'] && (div['__proto__'] !== form['__proto__'])) { + isSupported = true; + } + + div = form = null; + + return isSupported; + })() + }, + + ScriptFragment: '<script[^>]*>([\\S\\s]*?)<\/script>', + JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/, + + emptyFunction: function() { }, + K: function(x) { return x } +}; + +if (Prototype.Browser.MobileSafari) + Prototype.BrowserFeatures.SpecificElementExtensions = false; + + +var Abstract = { }; + + +var Try = { + these: function() { + var returnValue; + + for (var i = 0, length = arguments.length; i < length; i++) { + var lambda = arguments[i]; + try { + returnValue = lambda(); + break; + } catch (e) { } + } + + return returnValue; + } +}; + +/* Based on Alex Arnell's inheritance implementation. */ + +var Class = (function() { + function subclass() {}; + function create() { + var parent = null, properties = $A(arguments); + if (Object.isFunction(properties[0])) + parent = properties.shift(); + + function klass() { + this.initialize.apply(this, arguments); + } + + Object.extend(klass, Class.Methods); + klass.superclass = parent; + klass.subclasses = []; + + if (parent) { + subclass.prototype = parent.prototype; + klass.prototype = new subclass; + parent.subclasses.push(klass); + } + + for (var i = 0; i < properties.length; i++) + klass.addMethods(properties[i]); + + if (!klass.prototype.initialize) + klass.prototype.initialize = Prototype.emptyFunction; + + klass.prototype.constructor = klass; + return klass; + } + + function addMethods(source) { + var ancestor = this.superclass && this.superclass.prototype; + var properties = Object.keys(source); + + if (!Object.keys({ toString: true }).length) { + if (source.toString != Object.prototype.toString) + properties.push("toString"); + if (source.valueOf != Object.prototype.valueOf) + properties.push("valueOf"); + } + + for (var i = 0, length = properties.length; i < length; i++) { + var property = properties[i], value = source[property]; + if (ancestor && Object.isFunction(value) && + value.argumentNames().first() == "$super") { + var method = value; + value = (function(m) { + return function() { return ancestor[m].apply(this, arguments); }; + })(property).wrap(method); + + value.valueOf = method.valueOf.bind(method); + value.toString = method.toString.bind(method); + } + this.prototype[property] = value; + } + + return this; + } + + return { + create: create, + Methods: { + addMethods: addMethods + } + }; +})(); +(function() { + + var _toString = Object.prototype.toString; + + function extend(destination, source) { + for (var property in source) + destination[property] = source[property]; + return destination; + } + + function inspect(object) { + try { + if (isUndefined(object)) return 'undefined'; + if (object === null) return 'null'; + return object.inspect ? object.inspect() : String(object); + } catch (e) { + if (e instanceof RangeError) return '...'; + throw e; + } + } + + function toJSON(object) { + var type = typeof object; + switch (type) { + case 'undefined': + case 'function': + case 'unknown': return; + case 'boolean': return object.toString(); + } + + if (object === null) return 'null'; + if (object.toJSON) return object.toJSON(); + if (isElement(object)) return; + + var results = []; + for (var property in object) { + var value = toJSON(object[property]); + if (!isUndefined(value)) + results.push(property.toJSON() + ': ' + value); + } + + return '{' + results.join(', ') + '}'; + } + + function toQueryString(object) { + return $H(object).toQueryString(); + } + + function toHTML(object) { + return object && object.toHTML ? object.toHTML() : String.interpret(object); + } + + function keys(object) { + var results = []; + for (var property in object) + results.push(property); + return results; + } + + function values(object) { + var results = []; + for (var property in object) + results.push(object[property]); + return results; + } + + function clone(object) { + return extend({ }, object); + } + + function isElement(object) { + return !!(object && object.nodeType == 1); + } + + function isArray(object) { + return _toString.call(object) == "[object Array]"; + } + + + function isHash(object) { + return object instanceof Hash; + } + + function isFunction(object) { + return typeof object === "function"; + } + + function isString(object) { + return _toString.call(object) == "[object String]"; + } + + function isNumber(object) { + return _toString.call(object) == "[object Number]"; + } + + function isUndefined(object) { + return typeof object === "undefined"; + } + + extend(Object, { + extend: extend, + inspect: inspect, + toJSON: toJSON, + toQueryString: toQueryString, + toHTML: toHTML, + keys: keys, + values: values, + clone: clone, + isElement: isElement, + isArray: isArray, + isHash: isHash, + isFunction: isFunction, + isString: isString, + isNumber: isNumber, + isUndefined: isUndefined + }); +})(); +Object.extend(Function.prototype, (function() { + var slice = Array.prototype.slice; + + function update(array, args) { + var arrayLength = array.length, length = args.length; + while (length--) array[arrayLength + length] = args[length]; + return array; + } + + function merge(array, args) { + array = slice.call(array, 0); + return update(array, args); + } + + function argumentNames() { + var names = this.toString().match(/^[\s\(]*function[^(]*\(([^)]*)\)/)[1] + .replace(/\/\/.*?[\r\n]|\/\*(?:.|[\r\n])*?\*\//g, '') + .replace(/\s+/g, '').split(','); + return names.length == 1 && !names[0] ? [] : names; + } + + function bind(context) { + if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this; + var __method = this, args = slice.call(arguments, 1); + return function() { + var a = merge(args, arguments); + return __method.apply(context, a); + } + } + + function bindAsEventListener(context) { + var __method = this, args = slice.call(arguments, 1); + return function(event) { + var a = update([event || window.event], args); + return __method.apply(context, a); + } + } + + function curry() { + if (!arguments.length) return this; + var __method = this, args = slice.call(arguments, 0); + return function() { + var a = merge(args, arguments); + return __method.apply(this, a); + } + } + + function delay(timeout) { + var __method = this, args = slice.call(arguments, 1); + timeout = timeout * 1000 + return window.setTimeout(function() { + return __method.apply(__method, args); + }, timeout); + } + + function defer() { + var args = update([0.01], arguments); + return this.delay.apply(this, args); + } + + function wrap(wrapper) { + var __method = this; + return function() { + var a = update([__method.bind(this)], arguments); + return wrapper.apply(this, a); + } + } + + function methodize() { + if (this._methodized) return this._methodized; + var __method = this; + return this._methodized = function() { + var a = update([this], arguments); + return __method.apply(null, a); + }; + } + + return { + argumentNames: argumentNames, + bind: bind, + bindAsEventListener: bindAsEventListener, + curry: curry, + delay: delay, + defer: defer, + wrap: wrap, + methodize: methodize + } +})()); + + +Date.prototype.toJSON = function() { + return '"' + this.getUTCFullYear() + '-' + + (this.getUTCMonth() + 1).toPaddedString(2) + '-' + + this.getUTCDate().toPaddedString(2) + 'T' + + this.getUTCHours().toPaddedString(2) + ':' + + this.getUTCMinutes().toPaddedString(2) + ':' + + this.getUTCSeconds().toPaddedString(2) + 'Z"'; +}; + + +RegExp.prototype.match = RegExp.prototype.test; + +RegExp.escape = function(str) { + return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1'); +}; +var PeriodicalExecuter = Class.create({ + initialize: function(callback, frequency) { + this.callback = callback; + this.frequency = frequency; + this.currentlyExecuting = false; + + this.registerCallback(); + }, + + registerCallback: function() { + this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); + }, + + execute: function() { + this.callback(this); + }, + + stop: function() { + if (!this.timer) return; + clearInterval(this.timer); + this.timer = null; + }, + + onTimerEvent: function() { + if (!this.currentlyExecuting) { + try { + this.currentlyExecuting = true; + this.execute(); + this.currentlyExecuting = false; + } catch(e) { + this.currentlyExecuting = false; + throw e; + } + } + } +}); +Object.extend(String, { + interpret: function(value) { + return value == null ? '' : String(value); + }, + specialChar: { + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '\\': '\\\\' + } +}); + +Object.extend(String.prototype, (function() { + + function prepareReplacement(replacement) { + if (Object.isFunction(replacement)) return replacement; + var template = new Template(replacement); + return function(match) { return template.evaluate(match) }; + } + + function gsub(pattern, replacement) { + var result = '', source = this, match; + replacement = prepareReplacement(replacement); + + if (Object.isString(pattern)) + pattern = RegExp.escape(pattern); + + if (!(pattern.length || pattern.source)) { + replacement = replacement(''); + return replacement + source.split('').join(replacement) + replacement; + } + + while (source.length > 0) { + if (match = source.match(pattern)) { + result += source.slice(0, match.index); + result += String.interpret(replacement(match)); + source = source.slice(match.index + match[0].length); + } else { + result += source, source = ''; + } + } + return result; + } + + function sub(pattern, replacement, count) { + replacement = prepareReplacement(replacement); + count = Object.isUndefined(count) ? 1 : count; + + return this.gsub(pattern, function(match) { + if (--count < 0) return match[0]; + return replacement(match); + }); + } + + function scan(pattern, iterator) { + this.gsub(pattern, iterator); + return String(this); + } + + function truncate(length, truncation) { + length = length || 30; + truncation = Object.isUndefined(truncation) ? '...' : truncation; + return this.length > length ? + this.slice(0, length - truncation.length) + truncation : String(this); + } + + function strip() { + return this.replace(/^\s+/, '').replace(/\s+$/, ''); + } + + function stripTags() { + return this.replace(/<\w+(\s+("[^"]*"|'[^']*'|[^>])+)?>|<\/\w+>/gi, ''); + } + + function stripScripts() { + return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), ''); + } + + function extractScripts() { + var matchAll = new RegExp(Prototype.ScriptFragment, 'img'); + var matchOne = new RegExp(Prototype.ScriptFragment, 'im'); + return (this.match(matchAll) || []).map(function(scriptTag) { + return (scriptTag.match(matchOne) || ['', ''])[1]; + }); + } + + function evalScripts() { + return this.extractScripts().map(function(script) { return eval(script) }); + } + + function escapeHTML() { + return this.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); + } + + function unescapeHTML() { + return this.stripTags().replace(/</g,'<').replace(/>/g,'>').replace(/&/g,'&'); + } + + + function toQueryParams(separator) { + var match = this.strip().match(/([^?#]*)(#.*)?$/); + if (!match) return { }; + + return match[1].split(separator || '&').inject({ }, function(hash, pair) { + if ((pair = pair.split('='))[0]) { + var key = decodeURIComponent(pair.shift()); + var value = pair.length > 1 ? pair.join('=') : pair[0]; + if (value != undefined) value = decodeURIComponent(value); + + if (key in hash) { + if (!Object.isArray(hash[key])) hash[key] = [hash[key]]; + hash[key].push(value); + } + else hash[key] = value; + } + return hash; + }); + } + + function toArray() { + return this.split(''); + } + + function succ() { + return this.slice(0, this.length - 1) + + String.fromCharCode(this.charCodeAt(this.length - 1) + 1); + } + + function times(count) { + return count < 1 ? '' : new Array(count + 1).join(this); + } + + function camelize() { + var parts = this.split('-'), len = parts.length; + if (len == 1) return parts[0]; + + var camelized = this.charAt(0) == '-' + ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1) + : parts[0]; + + for (var i = 1; i < len; i++) + camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1); + + return camelized; + } + + function capitalize() { + return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase(); + } + + function underscore() { + return this.replace(/::/g, '/') + .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2') + .replace(/([a-z\d])([A-Z])/g, '$1_$2') + .replace(/-/g, '_') + .toLowerCase(); + } + + function dasherize() { + return this.replace(/_/g, '-'); + } + + function inspect(useDoubleQuotes) { + var escapedString = this.replace(/[\x00-\x1f\\]/g, function(character) { + if (character in String.specialChar) { + return String.specialChar[character]; + } + return '\\u00' + character.charCodeAt().toPaddedString(2, 16); + }); + if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"'; + return "'" + escapedString.replace(/'/g, '\\\'') + "'"; + } + + function toJSON() { + return this.inspect(true); + } + + function unfilterJSON(filter) { + return this.replace(filter || Prototype.JSONFilter, '$1'); + } + + function isJSON() { + var str = this; + if (str.blank()) return false; + str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, ''); + return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str); + } + + function evalJSON(sanitize) { + var json = this.unfilterJSON(); + try { + if (!sanitize || json.isJSON()) return eval('(' + json + ')'); + } catch (e) { } + throw new SyntaxError('Badly formed JSON string: ' + this.inspect()); + } + + function include(pattern) { + return this.indexOf(pattern) > -1; + } + + function startsWith(pattern) { + return this.indexOf(pattern) === 0; + } + + function endsWith(pattern) { + var d = this.length - pattern.length; + return d >= 0 && this.lastIndexOf(pattern) === d; + } + + function empty() { + return this == ''; + } + + function blank() { + return /^\s*$/.test(this); + } + + function interpolate(object, pattern) { + return new Template(this, pattern).evaluate(object); + } + + return { + gsub: gsub, + sub: sub, + scan: scan, + truncate: truncate, + strip: String.prototype.trim ? String.prototype.trim : strip, + stripTags: stripTags, + stripScripts: stripScripts, + extractScripts: extractScripts, + evalScripts: evalScripts, + escapeHTML: escapeHTML, + unescapeHTML: unescapeHTML, + toQueryParams: toQueryParams, + parseQuery: toQueryParams, + toArray: toArray, + succ: succ, + times: times, + camelize: camelize, + capitalize: capitalize, + underscore: underscore, + dasherize: dasherize, + inspect: inspect, + toJSON: toJSON, + unfilterJSON: unfilterJSON, + isJSON: isJSON, + evalJSON: evalJSON, + include: include, + startsWith: startsWith, + endsWith: endsWith, + empty: empty, + blank: blank, + interpolate: interpolate + }; +})()); + +var Template = Class.create({ + initialize: function(template, pattern) { + this.template = template.toString(); + this.pattern = pattern || Template.Pattern; + }, + + evaluate: function(object) { + if (object && Object.isFunction(object.toTemplateReplacements)) + object = object.toTemplateReplacements(); + + return this.template.gsub(this.pattern, function(match) { + if (object == null) return (match[1] + ''); + + var before = match[1] || ''; + if (before == '\\') return match[2]; + + var ctx = object, expr = match[3]; + var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/; + match = pattern.exec(expr); + if (match == null) return before; + + while (match != null) { + var comp = match[1].startsWith('[') ? match[2].replace(/\\\\]/g, ']') : match[1]; + ctx = ctx[comp]; + if (null == ctx || '' == match[3]) break; + expr = expr.substring('[' == match[3] ? match[1].length : match[0].length); + match = pattern.exec(expr); + } + + return before + String.interpret(ctx); + }); + } +}); +Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; + +var $break = { }; + +var Enumerable = (function() { + function each(iterator, context) { + var index = 0; + try { + this._each(function(value) { + iterator.call(context, value, index++); + }); + } catch (e) { + if (e != $break) throw e; + } + return this; + } + + function eachSlice(number, iterator, context) { + var index = -number, slices = [], array = this.toArray(); + if (number < 1) return array; + while ((index += number) < array.length) + slices.push(array.slice(index, index+number)); + return slices.collect(iterator, context); + } + + function all(iterator, context) { + iterator = iterator || Prototype.K; + var result = true; + this.each(function(value, index) { + result = result && !!iterator.call(context, value, index); + if (!result) throw $break; + }); + return result; + } + + function any(iterator, context) { + iterator = iterator || Prototype.K; + var result = false; + this.each(function(value, index) { + if (result = !!iterator.call(context, value, index)) + throw $break; + }); + return result; + } + + function collect(iterator, context) { + iterator = iterator || Prototype.K; + var results = []; + this.each(function(value, index) { + results.push(iterator.call(context, value, index)); + }); + return results; + } + + function detect(iterator, context) { + var result; + this.each(function(value, index) { + if (iterator.call(context, value, index)) { + result = value; + throw $break; + } + }); + return result; + } + + function findAll(iterator, context) { + var results = []; + this.each(function(value, index) { + if (iterator.call(context, value, index)) + results.push(value); + }); + return results; + } + + function grep(filter, iterator, context) { + iterator = iterator || Prototype.K; + var results = []; + + if (Object.isString(filter)) + filter = new RegExp(RegExp.escape(filter)); + + this.each(function(value, index) { + if (filter.match(value)) + results.push(iterator.call(context, value, index)); + }); + return results; + } + + function include(object) { + if (Object.isFunction(this.indexOf)) + if (this.indexOf(object) != -1) return true; + + var found = false; + this.each(function(value) { + if (value == object) { + found = true; + throw $break; + } + }); + return found; + } + + function inGroupsOf(number, fillWith) { + fillWith = Object.isUndefined(fillWith) ? null : fillWith; + return this.eachSlice(number, function(slice) { + while(slice.length < number) slice.push(fillWith); + return slice; + }); + } + + function inject(memo, iterator, context) { + this.each(function(value, index) { + memo = iterator.call(context, memo, value, index); + }); + return memo; + } + + function invoke(method) { + var args = $A(arguments).slice(1); + return this.map(function(value) { + return value[method].apply(value, args); + }); + } + + function max(iterator, context) { + iterator = iterator || Prototype.K; + var result; + this.each(function(value, index) { + value = iterator.call(context, value, index); + if (result == null || value >= result) + result = value; + }); + return result; + } + + function min(iterator, context) { + iterator = iterator || Prototype.K; + var result; + this.each(function(value, index) { + value = iterator.call(context, value, index); + if (result == null || value < result) + result = value; + }); + return result; + } + + function partition(iterator, context) { + iterator = iterator || Prototype.K; + var trues = [], falses = []; + this.each(function(value, index) { + (iterator.call(context, value, index) ? + trues : falses).push(value); + }); + return [trues, falses]; + } + + function pluck(property) { + var results = []; + this.each(function(value) { + results.push(value[property]); + }); + return results; + } + + function reject(iterator, context) { + var results = []; + this.each(function(value, index) { + if (!iterator.call(context, value, index)) + results.push(value); + }); + return results; + } + + function sortBy(iterator, context) { + return this.map(function(value, index) { + return { + value: value, + criteria: iterator.call(context, value, index) + }; + }).sort(function(left, right) { + var a = left.criteria, b = right.criteria; + return a < b ? -1 : a > b ? 1 : 0; + }).pluck('value'); + } + + function toArray() { + return this.map(); + } + + function zip() { + var iterator = Prototype.K, args = $A(arguments); + if (Object.isFunction(args.last())) + iterator = args.pop(); + + var collections = [this].concat(args).map($A); + return this.map(function(value, index) { + return iterator(collections.pluck(index)); + }); + } + + function size() { + return this.toArray().length; + } + + function inspect() { + return '#<Enumerable:' + this.toArray().inspect() + '>'; + } + + + + + + + + + + return { + each: each, + eachSlice: eachSlice, + all: all, + every: all, + any: any, + some: any, + collect: collect, + map: collect, + detect: detect, + findAll: findAll, + select: findAll, + filter: findAll, + grep: grep, + include: include, + member: include, + inGroupsOf: inGroupsOf, + inject: inject, + invoke: invoke, + max: max, + min: min, + partition: partition, + pluck: pluck, + reject: reject, + sortBy: sortBy, + toArray: toArray, + entries: toArray, + zip: zip, + size: size, + inspect: inspect, + find: detect + }; +})(); +function $A(iterable) { + if (!iterable) return []; + if ('toArray' in Object(iterable)) return iterable.toArray(); + var length = iterable.length || 0, results = new Array(length); + while (length--) results[length] = iterable[length]; + return results; +} + +function $w(string) { + if (!Object.isString(string)) return []; + string = string.strip(); + return string ? string.split(/\s+/) : []; +} + +Array.from = $A; + + +(function() { + var arrayProto = Array.prototype, + slice = arrayProto.slice, + _each = arrayProto.forEach; // use native browser JS 1.6 implementation if available + + function each(iterator) { + for (var i = 0, length = this.length; i < length; i++) + iterator(this[i]); + } + if (!_each) _each = each; + + function clear() { + this.length = 0; + return this; + } + + function first() { + return this[0]; + } + + function last() { + return this[this.length - 1]; + } + + function compact() { + return this.select(function(value) { + return value != null; + }); + } + + function flatten() { + return this.inject([], function(array, value) { + if (Object.isArray(value)) + return array.concat(value.flatten()); + array.push(value); + return array; + }); + } + + function without() { + var values = slice.call(arguments, 0); + return this.select(function(value) { + return !values.include(value); + }); + } + + function reverse(inline) { + return (inline !== false ? this : this.toArray())._reverse(); + } + + function uniq(sorted) { + return this.inject([], function(array, value, index) { + if (0 == index || (sorted ? array.last() != value : !array.include(value))) + array.push(value); + return array; + }); + } + + function intersect(array) { + return this.uniq().findAll(function(item) { + return array.detect(function(value) { return item === value }); + }); + } + + + function clone() { + return slice.call(this, 0); + } + + function size() { + return this.length; + } + + function inspect() { + return '[' + this.map(Object.inspect).join(', ') + ']'; + } + + function toJSON() { + var results = []; + this.each(function(object) { + var value = Object.toJSON(object); + if (!Object.isUndefined(value)) results.push(value); + }); + return '[' + results.join(', ') + ']'; + } + + function indexOf(item, i) { + i || (i = 0); + var length = this.length; + if (i < 0) i = length + i; + for (; i < length; i++) + if (this[i] === item) return i; + return -1; + } + + function lastIndexOf(item, i) { + i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1; + var n = this.slice(0, i).reverse().indexOf(item); + return (n < 0) ? n : i - n - 1; + } + + function concat() { + var array = slice.call(this, 0), item; + for (var i = 0, length = arguments.length; i < length; i++) { + item = arguments[i]; + if (Object.isArray(item) && !('callee' in item)) { + for (var j = 0, arrayLength = item.length; j < arrayLength; j++) + array.push(item[j]); + } else { + array.push(item); + } + } + return array; + } + + Object.extend(arrayProto, Enumerable); + + if (!arrayProto._reverse) + arrayProto._reverse = arrayProto.reverse; + + Object.extend(arrayProto, { + _each: _each, + clear: clear, + first: first, + last: last, + compact: compact, + flatten: flatten, + without: without, + reverse: reverse, + uniq: uniq, + intersect: intersect, + clone: clone, + toArray: clone, + size: size, + inspect: inspect, + toJSON: toJSON + }); + + var CONCAT_ARGUMENTS_BUGGY = (function() { + return [].concat(arguments)[0][0] !== 1; + })(1,2) + + if (CONCAT_ARGUMENTS_BUGGY) arrayProto.concat = concat; + + if (!arrayProto.indexOf) arrayProto.indexOf = indexOf; + if (!arrayProto.lastIndexOf) arrayProto.lastIndexOf = lastIndexOf; +})(); +function $H(object) { + return new Hash(object); +}; + +var Hash = Class.create(Enumerable, (function() { + function initialize(object) { + this._object = Object.isHash(object) ? object.toObject() : Object.clone(object); + } + + function _each(iterator) { + for (var key in this._object) { + var value = this._object[key], pair = [key, value]; + pair.key = key; + pair.value = value; + iterator(pair); + } + } + + function set(key, value) { + return this._object[key] = value; + } + + function get(key) { + if (this._object[key] !== Object.prototype[key]) + return this._object[key]; + } + + function unset(key) { + var value = this._object[key]; + delete this._object[key]; + return value; + } + + function toObject() { + return Object.clone(this._object); + } + + function keys() { + return this.pluck('key'); + } + + function values() { + return this.pluck('value'); + } + + function index(value) { + var match = this.detect(function(pair) { + return pair.value === value; + }); + return match && match.key; + } + + function merge(object) { + return this.clone().update(object); + } + + function update(object) { + return new Hash(object).inject(this, function(result, pair) { + result.set(pair.key, pair.value); + return result; + }); + } + + function toQueryPair(key, value) { + if (Object.isUndefined(value)) return key; + return key + '=' + encodeURIComponent(String.interpret(value)); + } + + function toQueryString() { + return this.inject([], function(results, pair) { + var key = encodeURIComponent(pair.key), values = pair.value; + + if (values && typeof values == 'object') { + if (Object.isArray(values)) + return results.concat(values.map(toQueryPair.curry(key))); + } else results.push(toQueryPair(key, values)); + return results; + }).join('&'); + } + + function inspect() { + return '#<Hash:{' + this.map(function(pair) { + return pair.map(Object.inspect).join(': '); + }).join(', ') + '}>'; + } + + function toJSON() { + return Object.toJSON(this.toObject()); + } + + function clone() { + return new Hash(this); + } + + return { + initialize: initialize, + _each: _each, + set: set, + get: get, + unset: unset, + toObject: toObject, + toTemplateReplacements: toObject, + keys: keys, + values: values, + index: index, + merge: merge, + update: update, + toQueryString: toQueryString, + inspect: inspect, + toJSON: toJSON, + clone: clone + }; +})()); + +Hash.from = $H; +Object.extend(Number.prototype, (function() { + function toColorPart() { + return this.toPaddedString(2, 16); + } + + function succ() { + return this + 1; + } + + function times(iterator, context) { + $R(0, this, true).each(iterator, context); + return this; + } + + function toPaddedString(length, radix) { + var string = this.toString(radix || 10); + return '0'.times(length - string.length) + string; + } + + function toJSON() { + return isFinite(this) ? this.toString() : 'null'; + } + + function abs() { + return Math.abs(this); + } + + function round() { + return Math.round(this); + } + + function ceil() { + return Math.ceil(this); + } + + function floor() { + return Math.floor(this); + } + + return { + toColorPart: toColorPart, + succ: succ, + times: times, + toPaddedString: toPaddedString, + toJSON: toJSON, + abs: abs, + round: round, + ceil: ceil, + floor: floor + }; +})()); + +function $R(start, end, exclusive) { + return new ObjectRange(start, end, exclusive); +} + +var ObjectRange = Class.create(Enumerable, (function() { + function initialize(start, end, exclusive) { + this.start = start; + this.end = end; + this.exclusive = exclusive; + } + + function _each(iterator) { + var value = this.start; + while (this.include(value)) { + iterator(value); + value = value.succ(); + } + } + + function include(value) { + if (value < this.start) + return false; + if (this.exclusive) + return value < this.end; + return value <= this.end; + } + + return { + initialize: initialize, + _each: _each, + include: include + }; +})()); + + + +var Ajax = { + getTransport: function() { + return Try.these( + function() {return new XMLHttpRequest()}, + function() {return new ActiveXObject('Msxml2.XMLHTTP')}, + function() {return new ActiveXObject('Microsoft.XMLHTTP')} + ) || false; + }, + + activeRequestCount: 0 +}; + +Ajax.Responders = { + responders: [], + + _each: function(iterator) { + this.responders._each(iterator); + }, + + register: function(responder) { + if (!this.include(responder)) + this.responders.push(responder); + }, + + unregister: function(responder) { + this.responders = this.responders.without(responder); + }, + + dispatch: function(callback, request, transport, json) { + this.each(function(responder) { + if (Object.isFunction(responder[callback])) { + try { + responder[callback].apply(responder, [request, transport, json]); + } catch (e) { } + } + }); + } +}; + +Object.extend(Ajax.Responders, Enumerable); + +Ajax.Responders.register({ + onCreate: function() { Ajax.activeRequestCount++ }, + onComplete: function() { Ajax.activeRequestCount-- } +}); +Ajax.Base = Class.create({ + initialize: function(options) { + this.options = { + method: 'post', + asynchronous: true, + contentType: 'application/x-www-form-urlencoded', + encoding: 'UTF-8', + parameters: '', + evalJSON: true, + evalJS: true + }; + Object.extend(this.options, options || { }); + + this.options.method = this.options.method.toLowerCase(); + + if (Object.isString(this.options.parameters)) + this.options.parameters = this.options.parameters.toQueryParams(); + else if (Object.isHash(this.options.parameters)) + this.options.parameters = this.options.parameters.toObject(); + } +}); +Ajax.Request = Class.create(Ajax.Base, { + _complete: false, + + initialize: function($super, url, options) { + $super(options); + this.transport = Ajax.getTransport(); + this.request(url); + }, + + request: function(url) { + this.url = url; + this.method = this.options.method; + var params = Object.clone(this.options.parameters); + + if (!['get', 'post'].include(this.method)) { + params['_method'] = this.method; + this.method = 'post'; + } + + this.parameters = params; + + if (params = Object.toQueryString(params)) { + if (this.method == 'get') + this.url += (this.url.include('?') ? '&' : '?') + params; + else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) + params += '&_='; + } + + try { + var response = new Ajax.Response(this); + if (this.options.onCreate) this.options.onCreate(response); + Ajax.Responders.dispatch('onCreate', this, response); + + this.transport.open(this.method.toUpperCase(), this.url, + this.options.asynchronous); + + if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1); + + this.transport.onreadystatechange = this.onStateChange.bind(this); + this.setRequestHeaders(); + + this.body = this.method == 'post' ? (this.options.postBody || params) : null; + this.transport.send(this.body); + + /* Force Firefox to handle ready state 4 for synchronous requests */ + if (!this.options.asynchronous && this.transport.overrideMimeType) + this.onStateChange(); + + } + catch (e) { + this.dispatchException(e); + } + }, + + onStateChange: function() { + var readyState = this.transport.readyState; + if (readyState > 1 && !((readyState == 4) && this._complete)) + this.respondToReadyState(this.transport.readyState); + }, + + setRequestHeaders: function() { + var headers = { + 'X-Requested-With': 'XMLHttpRequest', + 'X-Prototype-Version': Prototype.Version, + 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*' + }; + + if (this.method == 'post') { + headers['Content-type'] = this.options.contentType + + (this.options.encoding ? '; charset=' + this.options.encoding : ''); + + /* Force "Connection: close" for older Mozilla browsers to work + * around a bug where XMLHttpRequest sends an incorrect + * Content-length header. See Mozilla Bugzilla #246651. + */ + if (this.transport.overrideMimeType && + (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005) + headers['Connection'] = 'close'; + } + + if (typeof this.options.requestHeaders == 'object') { + var extras = this.options.requestHeaders; + + if (Object.isFunction(extras.push)) + for (var i = 0, length = extras.length; i < length; i += 2) + headers[extras[i]] = extras[i+1]; + else + $H(extras).each(function(pair) { headers[pair.key] = pair.value }); + } + + for (var name in headers) + this.transport.setRequestHeader(name, headers[name]); + }, + + success: function() { + var status = this.getStatus(); + return !status || (status >= 200 && status < 300); + }, + + getStatus: function() { + try { + return this.transport.status || 0; + } catch (e) { return 0 } + }, + + respondToReadyState: function(readyState) { + var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this); + + if (state == 'Complete') { + try { + this._complete = true; + (this.options['on' + response.status] + || this.options['on' + (this.success() ? 'Success' : 'Failure')] + || Prototype.emptyFunction)(response, response.headerJSON); + } catch (e) { + this.dispatchException(e); + } + + var contentType = response.getHeader('Content-type'); + if (this.options.evalJS == 'force' + || (this.options.evalJS && this.isSameOrigin() && contentType + && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i))) + this.evalResponse(); + } + + try { + (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON); + Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON); + } catch (e) { + this.dispatchException(e); + } + + if (state == 'Complete') { + this.transport.onreadystatechange = Prototype.emptyFunction; + } + }, + + isSameOrigin: function() { + var m = this.url.match(/^\s*https?:\/\/[^\/]*/); + return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({ + protocol: location.protocol, + domain: document.domain, + port: location.port ? ':' + location.port : '' + })); + }, + + getHeader: function(name) { + try { + return this.transport.getResponseHeader(name) || null; + } catch (e) { return null; } + }, + + evalResponse: function() { + try { + return eval((this.transport.responseText || '').unfilterJSON()); + } catch (e) { + this.dispatchException(e); + } + }, + + dispatchException: function(exception) { + (this.options.onException || Prototype.emptyFunction)(this, exception); + Ajax.Responders.dispatch('onException', this, exception); + } +}); + +Ajax.Request.Events = + ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; + + + + + + + + +Ajax.Response = Class.create({ + initialize: function(request){ + this.request = request; + var transport = this.transport = request.transport, + readyState = this.readyState = transport.readyState; + + if((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) { + this.status = this.getStatus(); + this.statusText = this.getStatusText(); + this.responseText = String.interpret(transport.responseText); + this.headerJSON = this._getHeaderJSON(); + } + + if(readyState == 4) { + var xml = transport.responseXML; + this.responseXML = Object.isUndefined(xml) ? null : xml; + this.responseJSON = this._getResponseJSON(); + } + }, + + status: 0, + + statusText: '', + + getStatus: Ajax.Request.prototype.getStatus, + + getStatusText: function() { + try { + return this.transport.statusText || ''; + } catch (e) { return '' } + }, + + getHeader: Ajax.Request.prototype.getHeader, + + getAllHeaders: function() { + try { + return this.getAllResponseHeaders(); + } catch (e) { return null } + }, + + getResponseHeader: function(name) { + return this.transport.getResponseHeader(name); + }, + + getAllResponseHeaders: function() { + return this.transport.getAllResponseHeaders(); + }, + + _getHeaderJSON: function() { + var json = this.getHeader('X-JSON'); + if (!json) return null; + json = decodeURIComponent(escape(json)); + try { + return json.evalJSON(this.request.options.sanitizeJSON || + !this.request.isSameOrigin()); + } catch (e) { + this.request.dispatchException(e); + } + }, + + _getResponseJSON: function() { + var options = this.request.options; + if (!options.evalJSON || (options.evalJSON != 'force' && + !(this.getHeader('Content-type') || '').include('application/json')) || + this.responseText.blank()) + return null; + try { + return this.responseText.evalJSON(options.sanitizeJSON || + !this.request.isSameOrigin()); + } catch (e) { + this.request.dispatchException(e); + } + } +}); + +Ajax.Updater = Class.create(Ajax.Request, { + initialize: function($super, container, url, options) { + this.container = { + success: (container.success || container), + failure: (container.failure || (container.success ? null : container)) + }; + + options = Object.clone(options); + var onComplete = options.onComplete; + options.onComplete = (function(response, json) { + this.updateContent(response.responseText); + if (Object.isFunction(onComplete)) onComplete(response, json); + }).bind(this); + + $super(url, options); + }, + + updateContent: function(responseText) { + var receiver = this.container[this.success() ? 'success' : 'failure'], + options = this.options; + + if (!options.evalScripts) responseText = responseText.stripScripts(); + + if (receiver = $(receiver)) { + if (options.insertion) { + if (Object.isString(options.insertion)) { + var insertion = { }; insertion[options.insertion] = responseText; + receiver.insert(insertion); + } + else options.insertion(receiver, responseText); + } + else receiver.update(responseText); + } + } +}); + +Ajax.PeriodicalUpdater = Class.create(Ajax.Base, { + initialize: function($super, container, url, options) { + $super(options); + this.onComplete = this.options.onComplete; + + this.frequency = (this.options.frequency || 2); + this.decay = (this.options.decay || 1); + + this.updater = { }; + this.container = container; + this.url = url; + + this.start(); + }, + + start: function() { + this.options.onComplete = this.updateComplete.bind(this); + this.onTimerEvent(); + }, + + stop: function() { + this.updater.options.onComplete = undefined; + clearTimeout(this.timer); + (this.onComplete || Prototype.emptyFunction).apply(this, arguments); + }, + + updateComplete: function(response) { + if (this.options.decay) { + this.decay = (response.responseText == this.lastText ? + this.decay * this.options.decay : 1); + + this.lastText = response.responseText; + } + this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency); + }, + + onTimerEvent: function() { + this.updater = new Ajax.Updater(this.container, this.url, this.options); + } +}); + + + +function $(element) { + if (arguments.length > 1) { + for (var i = 0, elements = [], length = arguments.length; i < length; i++) + elements.push($(arguments[i])); + return elements; + } + if (Object.isString(element)) + element = document.getElementById(element); + return Element.extend(element); +} + +if (Prototype.BrowserFeatures.XPath) { + document._getElementsByXPath = function(expression, parentElement) { + var results = []; + var query = document.evaluate(expression, $(parentElement) || document, + null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); + for (var i = 0, length = query.snapshotLength; i < length; i++) + results.push(Element.extend(query.snapshotItem(i))); + return results; + }; +} + +/*--------------------------------------------------------------------------*/ + +if (!window.Node) var Node = { }; + +if (!Node.ELEMENT_NODE) { + Object.extend(Node, { + ELEMENT_NODE: 1, + ATTRIBUTE_NODE: 2, + TEXT_NODE: 3, + CDATA_SECTION_NODE: 4, + ENTITY_REFERENCE_NODE: 5, + ENTITY_NODE: 6, + PROCESSING_INSTRUCTION_NODE: 7, + COMMENT_NODE: 8, + DOCUMENT_NODE: 9, + DOCUMENT_TYPE_NODE: 10, + DOCUMENT_FRAGMENT_NODE: 11, + NOTATION_NODE: 12 + }); +} + + +(function(global) { + + var SETATTRIBUTE_IGNORES_NAME = (function(){ + var elForm = document.createElement("form"); + var elInput = document.createElement("input"); + var root = document.documentElement; + elInput.setAttribute("name", "test"); + elForm.appendChild(elInput); + root.appendChild(elForm); + var isBuggy = elForm.elements + ? (typeof elForm.elements.test == "undefined") + : null; + root.removeChild(elForm); + elForm = elInput = null; + return isBuggy; + })(); + + var element = global.Element; + global.Element = function(tagName, attributes) { + attributes = attributes || { }; + tagName = tagName.toLowerCase(); + var cache = Element.cache; + if (SETATTRIBUTE_IGNORES_NAME && attributes.name) { + tagName = '<' + tagName + ' name="' + attributes.name + '">'; + delete attributes.name; + return Element.writeAttribute(document.createElement(tagName), attributes); + } + if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName)); + return Element.writeAttribute(cache[tagName].cloneNode(false), attributes); + }; + Object.extend(global.Element, element || { }); + if (element) global.Element.prototype = element.prototype; +})(this); + +Element.cache = { }; +Element.idCounter = 1; + +Element.Methods = { + visible: function(element) { + return $(element).style.display != 'none'; + }, + + toggle: function(element) { + element = $(element); + Element[Element.visible(element) ? 'hide' : 'show'](element); + return element; + }, + + + hide: function(element) { + element = $(element); + element.style.display = 'none'; + return element; + }, + + show: function(element) { + element = $(element); + element.style.display = ''; + return element; + }, + + remove: function(element) { + element = $(element); + element.parentNode.removeChild(element); + return element; + }, + + update: (function(){ + + var SELECT_ELEMENT_INNERHTML_BUGGY = (function(){ + var el = document.createElement("select"), + isBuggy = true; + el.innerHTML = "<option value=\"test\">test</option>"; + if (el.options && el.options[0]) { + isBuggy = el.options[0].nodeName.toUpperCase() !== "OPTION"; + } + el = null; + return isBuggy; + })(); + + var TABLE_ELEMENT_INNERHTML_BUGGY = (function(){ + try { + var el = document.createElement("table"); + if (el && el.tBodies) { + el.innerHTML = "<tbody><tr><td>test</td></tr></tbody>"; + var isBuggy = typeof el.tBodies[0] == "undefined"; + el = null; + return isBuggy; + } + } catch (e) { + return true; + } + })(); + + var SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDING = (function () { + var s = document.createElement("script"), + isBuggy = false; + try { + s.appendChild(document.createTextNode("")); + isBuggy = !s.firstChild || + s.firstChild && s.firstChild.nodeType !== 3; + } catch (e) { + isBuggy = true; + } + s = null; + return isBuggy; + })(); + + function update(element, content) { + element = $(element); + + if (content && content.toElement) + content = content.toElement(); + + if (Object.isElement(content)) + return element.update().insert(content); + + content = Object.toHTML(content); + + var tagName = element.tagName.toUpperCase(); + + if (tagName === 'SCRIPT' && SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDING) { + element.text = content; + return element; + } + + if (SELECT_ELEMENT_INNERHTML_BUGGY || TABLE_ELEMENT_INNERHTML_BUGGY) { + if (tagName in Element._insertionTranslations.tags) { + while (element.firstChild) { + element.removeChild(element.firstChild); + } + Element._getContentFromAnonymousElement(tagName, content.stripScripts()) + .each(function(node) { + element.appendChild(node) + }); + } + else { + element.innerHTML = content.stripScripts(); + } + } + else { + element.innerHTML = content.stripScripts(); + } + + content.evalScripts.bind(content).defer(); + return element; + } + + return update; + })(), + + replace: function(element, content) { + element = $(element); + if (content && content.toElement) content = content.toElement(); + else if (!Object.isElement(content)) { + content = Object.toHTML(content); + var range = element.ownerDocument.createRange(); + range.selectNode(element); + content.evalScripts.bind(content).defer(); + content = range.createContextualFragment(content.stripScripts()); + } + element.parentNode.replaceChild(content, element); + return element; + }, + + insert: function(element, insertions) { + element = $(element); + + if (Object.isString(insertions) || Object.isNumber(insertions) || + Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML))) + insertions = {bottom:insertions}; + + var content, insert, tagName, childNodes; + + for (var position in insertions) { + content = insertions[position]; + position = position.toLowerCase(); + insert = Element._insertionTranslations[position]; + + if (content && content.toElement) content = content.toElement(); + if (Object.isElement(content)) { + insert(element, content); + continue; + } + + content = Object.toHTML(content); + + tagName = ((position == 'before' || position == 'after') + ? element.parentNode : element).tagName.toUpperCase(); + + childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); + + if (position == 'top' || position == 'after') childNodes.reverse(); + childNodes.each(insert.curry(element)); + + content.evalScripts.bind(content).defer(); + } + + return element; + }, + + wrap: function(element, wrapper, attributes) { + element = $(element); + if (Object.isElement(wrapper)) + $(wrapper).writeAttribute(attributes || { }); + else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes); + else wrapper = new Element('div', wrapper); + if (element.parentNode) + element.parentNode.replaceChild(wrapper, element); + wrapper.appendChild(element); + return wrapper; + }, + + inspect: function(element) { + element = $(element); + var result = '<' + element.tagName.toLowerCase(); + $H({'id': 'id', 'className': 'class'}).each(function(pair) { + var property = pair.first(), attribute = pair.last(); + var value = (element[property] || '').toString(); + if (value) result += ' ' + attribute + '=' + value.inspect(true); + }); + return result + '>'; + }, + + recursivelyCollect: function(element, property) { + element = $(element); + var elements = []; + while (element = element[property]) + if (element.nodeType == 1) + elements.push(Element.extend(element)); + return elements; + }, + + ancestors: function(element) { + return Element.recursivelyCollect(element, 'parentNode'); + }, + + descendants: function(element) { + return Element.select(element, "*"); + }, + + firstDescendant: function(element) { + element = $(element).firstChild; + while (element && element.nodeType != 1) element = element.nextSibling; + return $(element); + }, + + immediateDescendants: function(element) { + if (!(element = $(element).firstChild)) return []; + while (element && element.nodeType != 1) element = element.nextSibling; + if (element) return [element].concat($(element).nextSiblings()); + return []; + }, + + previousSiblings: function(element) { + return Element.recursivelyCollect(element, 'previousSibling'); + }, + + nextSiblings: function(element) { + return Element.recursivelyCollect(element, 'nextSibling'); + }, + + siblings: function(element) { + element = $(element); + return Element.previousSiblings(element).reverse() + .concat(Element.nextSiblings(element)); + }, + + match: function(element, selector) { + if (Object.isString(selector)) + selector = new Selector(selector); + return selector.match($(element)); + }, + + up: function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return $(element.parentNode); + var ancestors = Element.ancestors(element); + return Object.isNumber(expression) ? ancestors[expression] : + Selector.findElement(ancestors, expression, index); + }, + + down: function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return Element.firstDescendant(element); + return Object.isNumber(expression) ? Element.descendants(element)[expression] : + Element.select(element, expression)[index || 0]; + }, + + previous: function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element)); + var previousSiblings = Element.previousSiblings(element); + return Object.isNumber(expression) ? previousSiblings[expression] : + Selector.findElement(previousSiblings, expression, index); + }, + + next: function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element)); + var nextSiblings = Element.nextSiblings(element); + return Object.isNumber(expression) ? nextSiblings[expression] : + Selector.findElement(nextSiblings, expression, index); + }, + + + select: function(element) { + var args = Array.prototype.slice.call(arguments, 1); + return Selector.findChildElements(element, args); + }, + + adjacent: function(element) { + var args = Array.prototype.slice.call(arguments, 1); + return Selector.findChildElements(element.parentNode, args).without(element); + }, + + identify: function(element) { + element = $(element); + var id = Element.readAttribute(element, 'id'); + if (id) return id; + do { id = 'anonymous_element_' + Element.idCounter++ } while ($(id)); + Element.writeAttribute(element, 'id', id); + return id; + }, + + readAttribute: function(element, name) { + element = $(element); + if (Prototype.Browser.IE) { + var t = Element._attributeTranslations.read; + if (t.values[name]) return t.values[name](element, name); + if (t.names[name]) name = t.names[name]; + if (name.include(':')) { + return (!element.attributes || !element.attributes[name]) ? null : + element.attributes[name].value; + } + } + return element.getAttribute(name); + }, + + writeAttribute: function(element, name, value) { + element = $(element); + var attributes = { }, t = Element._attributeTranslations.write; + + if (typeof name == 'object') attributes = name; + else attributes[name] = Object.isUndefined(value) ? true : value; + + for (var attr in attributes) { + name = t.names[attr] || attr; + value = attributes[attr]; + if (t.values[attr]) name = t.values[attr](element, value); + if (value === false || value === null) + element.removeAttribute(name); + else if (value === true) + element.setAttribute(name, name); + else element.setAttribute(name, value); + } + return element; + }, + + getHeight: function(element) { + return Element.getDimensions(element).height; + }, + + getWidth: function(element) { + return Element.getDimensions(element).width; + }, + + classNames: function(element) { + return new Element.ClassNames(element); + }, + + hasClassName: function(element, className) { + if (!(element = $(element))) return; + var elementClassName = element.className; + return (elementClassName.length > 0 && (elementClassName == className || + new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName))); + }, + + addClassName: function(element, className) { + if (!(element = $(element))) return; + if (!Element.hasClassName(element, className)) + element.className += (element.className ? ' ' : '') + className; + return element; + }, + + removeClassName: function(element, className) { + if (!(element = $(element))) return; + element.className = element.className.replace( + new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip(); + return element; + }, + + toggleClassName: function(element, className) { + if (!(element = $(element))) return; + return Element[Element.hasClassName(element, className) ? + 'removeClassName' : 'addClassName'](element, className); + }, + + cleanWhitespace: function(element) { + element = $(element); + var node = element.firstChild; + while (node) { + var nextNode = node.nextSibling; + if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) + element.removeChild(node); + node = nextNode; + } + return element; + }, + + empty: function(element) { + return $(element).innerHTML.blank(); + }, + + descendantOf: function(element, ancestor) { + element = $(element), ancestor = $(ancestor); + + if (element.compareDocumentPosition) + return (element.compareDocumentPosition(ancestor) & 8) === 8; + + if (ancestor.contains) + return ancestor.contains(element) && ancestor !== element; + + while (element = element.parentNode) + if (element == ancestor) return true; + + return false; + }, + + scrollTo: function(element) { + element = $(element); + var pos = Element.cumulativeOffset(element); + window.scrollTo(pos[0], pos[1]); + return element; + }, + + getStyle: function(element, style) { + element = $(element); + style = style == 'float' ? 'cssFloat' : style.camelize(); + var value = element.style[style]; + if (!value || value == 'auto') { + var css = document.defaultView.getComputedStyle(element, null); + value = css ? css[style] : null; + } + if (style == 'opacity') return value ? parseFloat(value) : 1.0; + return value == 'auto' ? null : value; + }, + + getOpacity: function(element) { + return $(element).getStyle('opacity'); + }, + + setStyle: function(element, styles) { + element = $(element); + var elementStyle = element.style, match; + if (Object.isString(styles)) { + element.style.cssText += ';' + styles; + return styles.include('opacity') ? + element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element; + } + for (var property in styles) + if (property == 'opacity') element.setOpacity(styles[property]); + else + elementStyle[(property == 'float' || property == 'cssFloat') ? + (Object.isUndefined(elementStyle.styleFloat) ? 'cssFloat' : 'styleFloat') : + property] = styles[property]; + + return element; + }, + + setOpacity: function(element, value) { + element = $(element); + element.style.opacity = (value == 1 || value === '') ? '' : + (value < 0.00001) ? 0 : value; + return element; + }, + + getDimensions: function(element) { + element = $(element); + var display = Element.getStyle(element, 'display'); + if (display != 'none' && display != null) // Safari bug + return {width: element.offsetWidth, height: element.offsetHeight}; + + var els = element.style; + var originalVisibility = els.visibility; + var originalPosition = els.position; + var originalDisplay = els.display; + els.visibility = 'hidden'; + if (originalPosition != 'fixed') // Switching fixed to absolute causes issues in Safari + els.position = 'absolute'; + els.display = 'block'; + var originalWidth = element.clientWidth; + var originalHeight = element.clientHeight; + els.display = originalDisplay; + els.position = originalPosition; + els.visibility = originalVisibility; + return {width: originalWidth, height: originalHeight}; + }, + + makePositioned: function(element) { + element = $(element); + var pos = Element.getStyle(element, 'position'); + if (pos == 'static' || !pos) { + element._madePositioned = true; + element.style.position = 'relative'; + if (Prototype.Browser.Opera) { + element.style.top = 0; + element.style.left = 0; + } + } + return element; + }, + + undoPositioned: function(element) { + element = $(element); + if (element._madePositioned) { + element._madePositioned = undefined; + element.style.position = + element.style.top = + element.style.left = + element.style.bottom = + element.style.right = ''; + } + return element; + }, + + makeClipping: function(element) { + element = $(element); + if (element._overflow) return element; + element._overflow = Element.getStyle(element, 'overflow') || 'auto'; + if (element._overflow !== 'hidden') + element.style.overflow = 'hidden'; + return element; + }, + + undoClipping: function(element) { + element = $(element); + if (!element._overflow) return element; + element.style.overflow = element._overflow == 'auto' ? '' : element._overflow; + element._overflow = null; + return element; + }, + + cumulativeOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + } while (element); + return Element._returnOffset(valueL, valueT); + }, + + positionedOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + if (element) { + if (element.tagName.toUpperCase() == 'BODY') break; + var p = Element.getStyle(element, 'position'); + if (p !== 'static') break; + } + } while (element); + return Element._returnOffset(valueL, valueT); + }, + + absolutize: function(element) { + element = $(element); + if (Element.getStyle(element, 'position') == 'absolute') return element; + + var offsets = Element.positionedOffset(element); + var top = offsets[1]; + var left = offsets[0]; + var width = element.clientWidth; + var height = element.clientHeight; + + element._originalLeft = left - parseFloat(element.style.left || 0); + element._originalTop = top - parseFloat(element.style.top || 0); + element._originalWidth = element.style.width; + element._originalHeight = element.style.height; + + element.style.position = 'absolute'; + element.style.top = top + 'px'; + element.style.left = left + 'px'; + element.style.width = width + 'px'; + element.style.height = height + 'px'; + return element; + }, + + relativize: function(element) { + element = $(element); + if (Element.getStyle(element, 'position') == 'relative') return element; + + element.style.position = 'relative'; + var top = parseFloat(element.style.top || 0) - (element._originalTop || 0); + var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0); + + element.style.top = top + 'px'; + element.style.left = left + 'px'; + element.style.height = element._originalHeight; + element.style.width = element._originalWidth; + return element; + }, + + cumulativeScrollOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.scrollTop || 0; + valueL += element.scrollLeft || 0; + element = element.parentNode; + } while (element); + return Element._returnOffset(valueL, valueT); + }, + + getOffsetParent: function(element) { + if (element.offsetParent) return $(element.offsetParent); + if (element == document.body) return $(element); + + while ((element = element.parentNode) && element != document.body) + if (Element.getStyle(element, 'position') != 'static') + return $(element); + + return $(document.body); + }, + + viewportOffset: function(forElement) { + var valueT = 0, valueL = 0; + + var element = forElement; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + + if (element.offsetParent == document.body && + Element.getStyle(element, 'position') == 'absolute') break; + + } while (element = element.offsetParent); + + element = forElement; + do { + if (!Prototype.Browser.Opera || (element.tagName && (element.tagName.toUpperCase() == 'BODY'))) { + valueT -= element.scrollTop || 0; + valueL -= element.scrollLeft || 0; + } + } while (element = element.parentNode); + + return Element._returnOffset(valueL, valueT); + }, + + clonePosition: function(element, source) { + var options = Object.extend({ + setLeft: true, + setTop: true, + setWidth: true, + setHeight: true, + offsetTop: 0, + offsetLeft: 0 + }, arguments[2] || { }); + + source = $(source); + var p = Element.viewportOffset(source); + + element = $(element); + var delta = [0, 0]; + var parent = null; + if (Element.getStyle(element, 'position') == 'absolute') { + parent = Element.getOffsetParent(element); + delta = Element.viewportOffset(parent); + } + + if (parent == document.body) { + delta[0] -= document.body.offsetLeft; + delta[1] -= document.body.offsetTop; + } + + if (options.setLeft) element.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px'; + if (options.setTop) element.style.top = (p[1] - delta[1] + options.offsetTop) + 'px'; + if (options.setWidth) element.style.width = source.offsetWidth + 'px'; + if (options.setHeight) element.style.height = source.offsetHeight + 'px'; + return element; + } +}; + +Object.extend(Element.Methods, { + getElementsBySelector: Element.Methods.select, + + childElements: Element.Methods.immediateDescendants +}); + +Element._attributeTranslations = { + write: { + names: { + className: 'class', + htmlFor: 'for' + }, + values: { } + } +}; + +if (Prototype.Browser.Opera) { + Element.Methods.getStyle = Element.Methods.getStyle.wrap( + function(proceed, element, style) { + switch (style) { + case 'left': case 'top': case 'right': case 'bottom': + if (proceed(element, 'position') === 'static') return null; + case 'height': case 'width': + if (!Element.visible(element)) return null; + + var dim = parseInt(proceed(element, style), 10); + + if (dim !== element['offset' + style.capitalize()]) + return dim + 'px'; + + var properties; + if (style === 'height') { + properties = ['border-top-width', 'padding-top', + 'padding-bottom', 'border-bottom-width']; + } + else { + properties = ['border-left-width', 'padding-left', + 'padding-right', 'border-right-width']; + } + return properties.inject(dim, function(memo, property) { + var val = proceed(element, property); + return val === null ? memo : memo - parseInt(val, 10); + }) + 'px'; + default: return proceed(element, style); + } + } + ); + + Element.Methods.readAttribute = Element.Methods.readAttribute.wrap( + function(proceed, element, attribute) { + if (attribute === 'title') return element.title; + return proceed(element, attribute); + } + ); +} + +else if (Prototype.Browser.IE) { + Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap( + function(proceed, element) { + element = $(element); + try { element.offsetParent } + catch(e) { return $(document.body) } + var position = element.getStyle('position'); + if (position !== 'static') return proceed(element); + element.setStyle({ position: 'relative' }); + var value = proceed(element); + element.setStyle({ position: position }); + return value; + } + ); + + $w('positionedOffset viewportOffset').each(function(method) { + Element.Methods[method] = Element.Methods[method].wrap( + function(proceed, element) { + element = $(element); + try { element.offsetParent } + catch(e) { return Element._returnOffset(0,0) } + var position = element.getStyle('position'); + if (position !== 'static') return proceed(element); + var offsetParent = element.getOffsetParent(); + if (offsetParent && offsetParent.getStyle('position') === 'fixed') + offsetParent.setStyle({ zoom: 1 }); + element.setStyle({ position: 'relative' }); + var value = proceed(element); + element.setStyle({ position: position }); + return value; + } + ); + }); + + Element.Methods.cumulativeOffset = Element.Methods.cumulativeOffset.wrap( + function(proceed, element) { + try { element.offsetParent } + catch(e) { return Element._returnOffset(0,0) } + return proceed(element); + } + ); + + Element.Methods.getStyle = function(element, style) { + element = $(element); + style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize(); + var value = element.style[style]; + if (!value && element.currentStyle) value = element.currentStyle[style]; + + if (style == 'opacity') { + if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/)) + if (value[1]) return parseFloat(value[1]) / 100; + return 1.0; + } + + if (value == 'auto') { + if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none')) + return element['offset' + style.capitalize()] + 'px'; + return null; + } + return value; + }; + + Element.Methods.setOpacity = function(element, value) { + function stripAlpha(filter){ + return filter.replace(/alpha\([^\)]*\)/gi,''); + } + element = $(element); + var currentStyle = element.currentStyle; + if ((currentStyle && !currentStyle.hasLayout) || + (!currentStyle && element.style.zoom == 'normal')) + element.style.zoom = 1; + + var filter = element.getStyle('filter'), style = element.style; + if (value == 1 || value === '') { + (filter = stripAlpha(filter)) ? + style.filter = filter : style.removeAttribute('filter'); + return element; + } else if (value < 0.00001) value = 0; + style.filter = stripAlpha(filter) + + 'alpha(opacity=' + (value * 100) + ')'; + return element; + }; + + Element._attributeTranslations = (function(){ + + var classProp = 'className'; + var forProp = 'for'; + + var el = document.createElement('div'); + + el.setAttribute(classProp, 'x'); + + if (el.className !== 'x') { + el.setAttribute('class', 'x'); + if (el.className === 'x') { + classProp = 'class'; + } + } + el = null; + + el = document.createElement('label'); + el.setAttribute(forProp, 'x'); + if (el.htmlFor !== 'x') { + el.setAttribute('htmlFor', 'x'); + if (el.htmlFor === 'x') { + forProp = 'htmlFor'; + } + } + el = null; + + return { + read: { + names: { + 'class': classProp, + 'className': classProp, + 'for': forProp, + 'htmlFor': forProp + }, + values: { + _getAttr: function(element, attribute) { + return element.getAttribute(attribute); + }, + _getAttr2: function(element, attribute) { + return element.getAttribute(attribute, 2); + }, + _getAttrNode: function(element, attribute) { + var node = element.getAttributeNode(attribute); + return node ? node.value : ""; + }, + _getEv: (function(){ + + var el = document.createElement('div'); + el.onclick = Prototype.emptyFunction; + var value = el.getAttribute('onclick'); + var f; + + if (String(value).indexOf('{') > -1) { + f = function(element, attribute) { + attribute = element.getAttribute(attribute); + if (!attribute) return null; + attribute = attribute.toString(); + attribute = attribute.split('{')[1]; + attribute = attribute.split('}')[0]; + return attribute.strip(); + }; + } + else if (value === '') { + f = function(element, attribute) { + attribute = element.getAttribute(attribute); + if (!attribute) return null; + return attribute.strip(); + }; + } + el = null; + return f; + })(), + _flag: function(element, attribute) { + return $(element).hasAttribute(attribute) ? attribute : null; + }, + style: function(element) { + return element.style.cssText.toLowerCase(); + }, + title: function(element) { + return element.title; + } + } + } + } + })(); + + Element._attributeTranslations.write = { + names: Object.extend({ + cellpadding: 'cellPadding', + cellspacing: 'cellSpacing' + }, Element._attributeTranslations.read.names), + values: { + checked: function(element, value) { + element.checked = !!value; + }, + + style: function(element, value) { + element.style.cssText = value ? value : ''; + } + } + }; + + Element._attributeTranslations.has = {}; + + $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' + + 'encType maxLength readOnly longDesc frameBorder').each(function(attr) { + Element._attributeTranslations.write.names[attr.toLowerCase()] = attr; + Element._attributeTranslations.has[attr.toLowerCase()] = attr; + }); + + (function(v) { + Object.extend(v, { + href: v._getAttr2, + src: v._getAttr2, + type: v._getAttr, + action: v._getAttrNode, + disabled: v._flag, + checked: v._flag, + readonly: v._flag, + multiple: v._flag, + onload: v._getEv, + onunload: v._getEv, + onclick: v._getEv, + ondblclick: v._getEv, + onmousedown: v._getEv, + onmouseup: v._getEv, + onmouseover: v._getEv, + onmousemove: v._getEv, + onmouseout: v._getEv, + onfocus: v._getEv, + onblur: v._getEv, + onkeypress: v._getEv, + onkeydown: v._getEv, + onkeyup: v._getEv, + onsubmit: v._getEv, + onreset: v._getEv, + onselect: v._getEv, + onchange: v._getEv + }); + })(Element._attributeTranslations.read.values); + + if (Prototype.BrowserFeatures.ElementExtensions) { + (function() { + function _descendants(element) { + var nodes = element.getElementsByTagName('*'), results = []; + for (var i = 0, node; node = nodes[i]; i++) + if (node.tagName !== "!") // Filter out comment nodes. + results.push(node); + return results; + } + + Element.Methods.down = function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return element.firstDescendant(); + return Object.isNumber(expression) ? _descendants(element)[expression] : + Element.select(element, expression)[index || 0]; + } + })(); + } + +} + +else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) { + Element.Methods.setOpacity = function(element, value) { + element = $(element); + element.style.opacity = (value == 1) ? 0.999999 : + (value === '') ? '' : (value < 0.00001) ? 0 : value; + return element; + }; +} + +else if (Prototype.Browser.WebKit) { + Element.Methods.setOpacity = function(element, value) { + element = $(element); + element.style.opacity = (value == 1 || value === '') ? '' : + (value < 0.00001) ? 0 : value; + + if (value == 1) + if(element.tagName.toUpperCase() == 'IMG' && element.width) { + element.width++; element.width--; + } else try { + var n = document.createTextNode(' '); + element.appendChild(n); + element.removeChild(n); + } catch (e) { } + + return element; + }; + + Element.Methods.cumulativeOffset = function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + if (element.offsetParent == document.body) + if (Element.getStyle(element, 'position') == 'absolute') break; + + element = element.offsetParent; + } while (element); + + return Element._returnOffset(valueL, valueT); + }; +} + +if ('outerHTML' in document.documentElement) { + Element.Methods.replace = function(element, content) { + element = $(element); + + if (content && content.toElement) content = content.toElement(); + if (Object.isElement(content)) { + element.parentNode.replaceChild(content, element); + return element; + } + + content = Object.toHTML(content); + var parent = element.parentNode, tagName = parent.tagName.toUpperCase(); + + if (Element._insertionTranslations.tags[tagName]) { + var nextSibling = element.next(); + var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); + parent.removeChild(element); + if (nextSibling) + fragments.each(function(node) { parent.insertBefore(node, nextSibling) }); + else + fragments.each(function(node) { parent.appendChild(node) }); + } + else element.outerHTML = content.stripScripts(); + + content.evalScripts.bind(content).defer(); + return element; + }; +} + +Element._returnOffset = function(l, t) { + var result = [l, t]; + result.left = l; + result.top = t; + return result; +}; + +Element._getContentFromAnonymousElement = function(tagName, html) { + var div = new Element('div'), t = Element._insertionTranslations.tags[tagName]; + if (t) { + div.innerHTML = t[0] + html + t[1]; + t[2].times(function() { div = div.firstChild }); + } else div.innerHTML = html; + return $A(div.childNodes); +}; + +Element._insertionTranslations = { + before: function(element, node) { + element.parentNode.insertBefore(node, element); + }, + top: function(element, node) { + element.insertBefore(node, element.firstChild); + }, + bottom: function(element, node) { + element.appendChild(node); + }, + after: function(element, node) { + element.parentNode.insertBefore(node, element.nextSibling); + }, + tags: { + TABLE: ['<table>', '</table>', 1], + TBODY: ['<table><tbody>', '</tbody></table>', 2], + TR: ['<table><tbody><tr>', '</tr></tbody></table>', 3], + TD: ['<table><tbody><tr><td>', '</td></tr></tbody></table>', 4], + SELECT: ['<select>', '</select>', 1] + } +}; + +(function() { + var tags = Element._insertionTranslations.tags; + Object.extend(tags, { + THEAD: tags.TBODY, + TFOOT: tags.TBODY, + TH: tags.TD + }); +})(); + +Element.Methods.Simulated = { + hasAttribute: function(element, attribute) { + attribute = Element._attributeTranslations.has[attribute] || attribute; + var node = $(element).getAttributeNode(attribute); + return !!(node && node.specified); + } +}; + +Element.Methods.ByTag = { }; + +Object.extend(Element, Element.Methods); + +(function(div) { + + if (!Prototype.BrowserFeatures.ElementExtensions && div['__proto__']) { + window.HTMLElement = { }; + window.HTMLElement.prototype = div['__proto__']; + Prototype.BrowserFeatures.ElementExtensions = true; + } + + div = null; + +})(document.createElement('div')) + +Element.extend = (function() { + + function checkDeficiency(tagName) { + if (typeof window.Element != 'undefined') { + var proto = window.Element.prototype; + if (proto) { + var id = '_' + (Math.random()+'').slice(2); + var el = document.createElement(tagName); + proto[id] = 'x'; + var isBuggy = (el[id] !== 'x'); + delete proto[id]; + el = null; + return isBuggy; + } + } + return false; + } + + function extendElementWith(element, methods) { + for (var property in methods) { + var value = methods[property]; + if (Object.isFunction(value) && !(property in element)) + element[property] = value.methodize(); + } + } + + var HTMLOBJECTELEMENT_PROTOTYPE_BUGGY = checkDeficiency('object'); + + if (Prototype.BrowserFeatures.SpecificElementExtensions) { + if (HTMLOBJECTELEMENT_PROTOTYPE_BUGGY) { + return function(element) { + if (element && typeof element._extendedByPrototype == 'undefined') { + var t = element.tagName; + if (t && (/^(?:object|applet|embed)$/i.test(t))) { + extendElementWith(element, Element.Methods); + extendElementWith(element, Element.Methods.Simulated); + extendElementWith(element, Element.Methods.ByTag[t.toUpperCase()]); + } + } + return element; + } + } + return Prototype.K; + } + + var Methods = { }, ByTag = Element.Methods.ByTag; + + var extend = Object.extend(function(element) { + if (!element || typeof element._extendedByPrototype != 'undefined' || + element.nodeType != 1 || element == window) return element; + + var methods = Object.clone(Methods), + tagName = element.tagName.toUpperCase(); + + if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]); + + extendElementWith(element, methods); + + element._extendedByPrototype = Prototype.emptyFunction; + return element; + + }, { + refresh: function() { + if (!Prototype.BrowserFeatures.ElementExtensions) { + Object.extend(Methods, Element.Methods); + Object.extend(Methods, Element.Methods.Simulated); + } + } + }); + + extend.refresh(); + return extend; +})(); + +Element.hasAttribute = function(element, attribute) { + if (element.hasAttribute) return element.hasAttribute(attribute); + return Element.Methods.Simulated.hasAttribute(element, attribute); +}; + +Element.addMethods = function(methods) { + var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag; + + if (!methods) { + Object.extend(Form, Form.Methods); + Object.extend(Form.Element, Form.Element.Methods); + Object.extend(Element.Methods.ByTag, { + "FORM": Object.clone(Form.Methods), + "INPUT": Object.clone(Form.Element.Methods), + "SELECT": Object.clone(Form.Element.Methods), + "TEXTAREA": Object.clone(Form.Element.Methods) + }); + } + + if (arguments.length == 2) { + var tagName = methods; + methods = arguments[1]; + } + + if (!tagName) Object.extend(Element.Methods, methods || { }); + else { + if (Object.isArray(tagName)) tagName.each(extend); + else extend(tagName); + } + + function extend(tagName) { + tagName = tagName.toUpperCase(); + if (!Element.Methods.ByTag[tagName]) + Element.Methods.ByTag[tagName] = { }; + Object.extend(Element.Methods.ByTag[tagName], methods); + } + + function copy(methods, destination, onlyIfAbsent) { + onlyIfAbsent = onlyIfAbsent || false; + for (var property in methods) { + var value = methods[property]; + if (!Object.isFunction(value)) continue; + if (!onlyIfAbsent || !(property in destination)) + destination[property] = value.methodize(); + } + } + + function findDOMClass(tagName) { + var klass; + var trans = { + "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph", + "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList", + "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading", + "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote", + "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION": + "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD": + "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR": + "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET": + "FrameSet", "IFRAME": "IFrame" + }; + if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element'; + if (window[klass]) return window[klass]; + klass = 'HTML' + tagName + 'Element'; + if (window[klass]) return window[klass]; + klass = 'HTML' + tagName.capitalize() + 'Element'; + if (window[klass]) return window[klass]; + + var element = document.createElement(tagName); + var proto = element['__proto__'] || element.constructor.prototype; + element = null; + return proto; + } + + var elementPrototype = window.HTMLElement ? HTMLElement.prototype : + Element.prototype; + + if (F.ElementExtensions) { + copy(Element.Methods, elementPrototype); + copy(Element.Methods.Simulated, elementPrototype, true); + } + + if (F.SpecificElementExtensions) { + for (var tag in Element.Methods.ByTag) { + var klass = findDOMClass(tag); + if (Object.isUndefined(klass)) continue; + copy(T[tag], klass.prototype); + } + } + + Object.extend(Element, Element.Methods); + delete Element.ByTag; + + if (Element.extend.refresh) Element.extend.refresh(); + Element.cache = { }; +}; + + +document.viewport = { + + getDimensions: function() { + return { width: this.getWidth(), height: this.getHeight() }; + }, + + getScrollOffsets: function() { + return Element._returnOffset( + window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft, + window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop); + } +}; + +(function(viewport) { + var B = Prototype.Browser, doc = document, element, property = {}; + + function getRootElement() { + if (B.WebKit && !doc.evaluate) + return document; + + if (B.Opera && window.parseFloat(window.opera.version()) < 9.5) + return document.body; + + return document.documentElement; + } + + function define(D) { + if (!element) element = getRootElement(); + + property[D] = 'client' + D; + + viewport['get' + D] = function() { return element[property[D]] }; + return viewport['get' + D](); + } + + viewport.getWidth = define.curry('Width'); + + viewport.getHeight = define.curry('Height'); +})(document.viewport); + + +Element.Storage = { + UID: 1 +}; + +Element.addMethods({ + getStorage: function(element) { + if (!(element = $(element))) return; + + var uid; + if (element === window) { + uid = 0; + } else { + if (typeof element._prototypeUID === "undefined") + element._prototypeUID = [Element.Storage.UID++]; + uid = element._prototypeUID[0]; + } + + if (!Element.Storage[uid]) + Element.Storage[uid] = $H(); + + return Element.Storage[uid]; + }, + + store: function(element, key, value) { + if (!(element = $(element))) return; + + if (arguments.length === 2) { + Element.getStorage(element).update(key); + } else { + Element.getStorage(element).set(key, value); + } + + return element; + }, + + retrieve: function(element, key, defaultValue) { + if (!(element = $(element))) return; + var hash = Element.getStorage(element), value = hash.get(key); + + if (Object.isUndefined(value)) { + hash.set(key, defaultValue); + value = defaultValue; + } + + return value; + }, + + clone: function(element, deep) { + if (!(element = $(element))) return; + var clone = element.cloneNode(deep); + clone._prototypeUID = void 0; + if (deep) { + var descendants = Element.select(clone, '*'), + i = descendants.length; + while (i--) { + descendants[i]._prototypeUID = void 0; + } + } + return Element.extend(clone); + } +}); +/* Portions of the Selector class are derived from Jack Slocum's DomQuery, + * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style + * license. Please see http://www.yui-ext.com/ for more information. */ + +var Selector = Class.create({ + initialize: function(expression) { + this.expression = expression.strip(); + + if (this.shouldUseSelectorsAPI()) { + this.mode = 'selectorsAPI'; + } else if (this.shouldUseXPath()) { + this.mode = 'xpath'; + this.compileXPathMatcher(); + } else { + this.mode = "normal"; + this.compileMatcher(); + } + + }, + + shouldUseXPath: (function() { + + var IS_DESCENDANT_SELECTOR_BUGGY = (function(){ + var isBuggy = false; + if (document.evaluate && window.XPathResult) { + var el = document.createElement('div'); + el.innerHTML = '<ul><li></li></ul><div><ul><li></li></ul></div>'; + + var xpath = ".//*[local-name()='ul' or local-name()='UL']" + + "//*[local-name()='li' or local-name()='LI']"; + + var result = document.evaluate(xpath, el, null, + XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); + + isBuggy = (result.snapshotLength !== 2); + el = null; + } + return isBuggy; + })(); + + return function() { + if (!Prototype.BrowserFeatures.XPath) return false; + + var e = this.expression; + + if (Prototype.Browser.WebKit && + (e.include("-of-type") || e.include(":empty"))) + return false; + + if ((/(\[[\w-]*?:|:checked)/).test(e)) + return false; + + if (IS_DESCENDANT_SELECTOR_BUGGY) return false; + + return true; + } + + })(), + + shouldUseSelectorsAPI: function() { + if (!Prototype.BrowserFeatures.SelectorsAPI) return false; + + if (Selector.CASE_INSENSITIVE_CLASS_NAMES) return false; + + if (!Selector._div) Selector._div = new Element('div'); + + try { + Selector._div.querySelector(this.expression); + } catch(e) { + return false; + } + + return true; + }, + + compileMatcher: function() { + var e = this.expression, ps = Selector.patterns, h = Selector.handlers, + c = Selector.criteria, le, p, m, len = ps.length, name; + + if (Selector._cache[e]) { + this.matcher = Selector._cache[e]; + return; + } + + this.matcher = ["this.matcher = function(root) {", + "var r = root, h = Selector.handlers, c = false, n;"]; + + while (e && le != e && (/\S/).test(e)) { + le = e; + for (var i = 0; i<len; i++) { + p = ps[i].re; + name = ps[i].name; + if (m = e.match(p)) { + this.matcher.push(Object.isFunction(c[name]) ? c[name](m) : + new Template(c[name]).evaluate(m)); + e = e.replace(m[0], ''); + break; + } + } + } + + this.matcher.push("return h.unique(n);\n}"); + eval(this.matcher.join('\n')); + Selector._cache[this.expression] = this.matcher; + }, + + compileXPathMatcher: function() { + var e = this.expression, ps = Selector.patterns, + x = Selector.xpath, le, m, len = ps.length, name; + + if (Selector._cache[e]) { + this.xpath = Selector._cache[e]; return; + } + + this.matcher = ['.//*']; + while (e && le != e && (/\S/).test(e)) { + le = e; + for (var i = 0; i<len; i++) { + name = ps[i].name; + if (m = e.match(ps[i].re)) { + this.matcher.push(Object.isFunction(x[name]) ? x[name](m) : + new Template(x[name]).evaluate(m)); + e = e.replace(m[0], ''); + break; + } + } + } + + this.xpath = this.matcher.join(''); + Selector._cache[this.expression] = this.xpath; + }, + + findElements: function(root) { + root = root || document; + var e = this.expression, results; + + switch (this.mode) { + case 'selectorsAPI': + if (root !== document) { + var oldId = root.id, id = $(root).identify(); + id = id.replace(/([\.:])/g, "\\$1"); + e = "#" + id + " " + e; + } + + results = $A(root.querySelectorAll(e)).map(Element.extend); + root.id = oldId; + + return results; + case 'xpath': + return document._getElementsByXPath(this.xpath, root); + default: + return this.matcher(root); + } + }, + + match: function(element) { + this.tokens = []; + + var e = this.expression, ps = Selector.patterns, as = Selector.assertions; + var le, p, m, len = ps.length, name; + + while (e && le !== e && (/\S/).test(e)) { + le = e; + for (var i = 0; i<len; i++) { + p = ps[i].re; + name = ps[i].name; + if (m = e.match(p)) { + if (as[name]) { + this.tokens.push([name, Object.clone(m)]); + e = e.replace(m[0], ''); + } else { + return this.findElements(document).include(element); + } + } + } + } + + var match = true, name, matches; + for (var i = 0, token; token = this.tokens[i]; i++) { + name = token[0], matches = token[1]; + if (!Selector.assertions[name](element, matches)) { + match = false; break; + } + } + + return match; + }, + + toString: function() { + return this.expression; + }, + + inspect: function() { + return "#<Selector:" + this.expression.inspect() + ">"; + } +}); + +if (Prototype.BrowserFeatures.SelectorsAPI && + document.compatMode === 'BackCompat') { + Selector.CASE_INSENSITIVE_CLASS_NAMES = (function(){ + var div = document.createElement('div'), + span = document.createElement('span'); + + div.id = "prototype_test_id"; + span.className = 'Test'; + div.appendChild(span); + var isIgnored = (div.querySelector('#prototype_test_id .test') !== null); + div = span = null; + return isIgnored; + })(); +} + +Object.extend(Selector, { + _cache: { }, + + xpath: { + descendant: "//*", + child: "/*", + adjacent: "/following-sibling::*[1]", + laterSibling: '/following-sibling::*', + tagName: function(m) { + if (m[1] == '*') return ''; + return "[local-name()='" + m[1].toLowerCase() + + "' or local-name()='" + m[1].toUpperCase() + "']"; + }, + className: "[contains(concat(' ', @class, ' '), ' #{1} ')]", + id: "[@id='#{1}']", + attrPresence: function(m) { + m[1] = m[1].toLowerCase(); + return new Template("[@#{1}]").evaluate(m); + }, + attr: function(m) { + m[1] = m[1].toLowerCase(); + m[3] = m[5] || m[6]; + return new Template(Selector.xpath.operators[m[2]]).evaluate(m); + }, + pseudo: function(m) { + var h = Selector.xpath.pseudos[m[1]]; + if (!h) return ''; + if (Object.isFunction(h)) return h(m); + return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m); + }, + operators: { + '=': "[@#{1}='#{3}']", + '!=': "[@#{1}!='#{3}']", + '^=': "[starts-with(@#{1}, '#{3}')]", + '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']", + '*=': "[contains(@#{1}, '#{3}')]", + '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]", + '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]" + }, + pseudos: { + 'first-child': '[not(preceding-sibling::*)]', + 'last-child': '[not(following-sibling::*)]', + 'only-child': '[not(preceding-sibling::* or following-sibling::*)]', + 'empty': "[count(*) = 0 and (count(text()) = 0)]", + 'checked': "[@checked]", + 'disabled': "[(@disabled) and (@type!='hidden')]", + 'enabled': "[not(@disabled) and (@type!='hidden')]", + 'not': function(m) { + var e = m[6], p = Selector.patterns, + x = Selector.xpath, le, v, len = p.length, name; + + var exclusion = []; + while (e && le != e && (/\S/).test(e)) { + le = e; + for (var i = 0; i<len; i++) { + name = p[i].name + if (m = e.match(p[i].re)) { + v = Object.isFunction(x[name]) ? x[name](m) : new Template(x[name]).evaluate(m); + exclusion.push("(" + v.substring(1, v.length - 1) + ")"); + e = e.replace(m[0], ''); + break; + } + } + } + return "[not(" + exclusion.join(" and ") + ")]"; + }, + 'nth-child': function(m) { + return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m); + }, + 'nth-last-child': function(m) { + return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m); + }, + 'nth-of-type': function(m) { + return Selector.xpath.pseudos.nth("position() ", m); + }, + 'nth-last-of-type': function(m) { + return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m); + }, + 'first-of-type': function(m) { + m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m); + }, + 'last-of-type': function(m) { + m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m); + }, + 'only-of-type': function(m) { + var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m); + }, + nth: function(fragment, m) { + var mm, formula = m[6], predicate; + if (formula == 'even') formula = '2n+0'; + if (formula == 'odd') formula = '2n+1'; + if (mm = formula.match(/^(\d+)$/)) // digit only + return '[' + fragment + "= " + mm[1] + ']'; + if (mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b + if (mm[1] == "-") mm[1] = -1; + var a = mm[1] ? Number(mm[1]) : 1; + var b = mm[2] ? Number(mm[2]) : 0; + predicate = "[((#{fragment} - #{b}) mod #{a} = 0) and " + + "((#{fragment} - #{b}) div #{a} >= 0)]"; + return new Template(predicate).evaluate({ + fragment: fragment, a: a, b: b }); + } + } + } + }, + + criteria: { + tagName: 'n = h.tagName(n, r, "#{1}", c); c = false;', + className: 'n = h.className(n, r, "#{1}", c); c = false;', + id: 'n = h.id(n, r, "#{1}", c); c = false;', + attrPresence: 'n = h.attrPresence(n, r, "#{1}", c); c = false;', + attr: function(m) { + m[3] = (m[5] || m[6]); + return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}", c); c = false;').evaluate(m); + }, + pseudo: function(m) { + if (m[6]) m[6] = m[6].replace(/"/g, '\\"'); + return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m); + }, + descendant: 'c = "descendant";', + child: 'c = "child";', + adjacent: 'c = "adjacent";', + laterSibling: 'c = "laterSibling";' + }, + + patterns: [ + { name: 'laterSibling', re: /^\s*~\s*/ }, + { name: 'child', re: /^\s*>\s*/ }, + { name: 'adjacent', re: /^\s*\+\s*/ }, + { name: 'descendant', re: /^\s/ }, + + { name: 'tagName', re: /^\s*(\*|[\w\-]+)(\b|$)?/ }, + { name: 'id', re: /^#([\w\-\*]+)(\b|$)/ }, + { name: 'className', re: /^\.([\w\-\*]+)(\b|$)/ }, + { name: 'pseudo', re: /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/ }, + { name: 'attrPresence', re: /^\[((?:[\w-]+:)?[\w-]+)\]/ }, + { name: 'attr', re: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/ } + ], + + assertions: { + tagName: function(element, matches) { + return matches[1].toUpperCase() == element.tagName.toUpperCase(); + }, + + className: function(element, matches) { + return Element.hasClassName(element, matches[1]); + }, + + id: function(element, matches) { + return element.id === matches[1]; + }, + + attrPresence: function(element, matches) { + return Element.hasAttribute(element, matches[1]); + }, + + attr: function(element, matches) { + var nodeValue = Element.readAttribute(element, matches[1]); + return nodeValue && Selector.operators[matches[2]](nodeValue, matches[5] || matches[6]); + } + }, + + handlers: { + concat: function(a, b) { + for (var i = 0, node; node = b[i]; i++) + a.push(node); + return a; + }, + + mark: function(nodes) { + var _true = Prototype.emptyFunction; + for (var i = 0, node; node = nodes[i]; i++) + node._countedByPrototype = _true; + return nodes; + }, + + unmark: (function(){ + + var PROPERTIES_ATTRIBUTES_MAP = (function(){ + var el = document.createElement('div'), + isBuggy = false, + propName = '_countedByPrototype', + value = 'x' + el[propName] = value; + isBuggy = (el.getAttribute(propName) === value); + el = null; + return isBuggy; + })(); + + return PROPERTIES_ATTRIBUTES_MAP ? + function(nodes) { + for (var i = 0, node; node = nodes[i]; i++) + node.removeAttribute('_countedByPrototype'); + return nodes; + } : + function(nodes) { + for (var i = 0, node; node = nodes[i]; i++) + node._countedByPrototype = void 0; + return nodes; + } + })(), + + index: function(parentNode, reverse, ofType) { + parentNode._countedByPrototype = Prototype.emptyFunction; + if (reverse) { + for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) { + var node = nodes[i]; + if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++; + } + } else { + for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++) + if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++; + } + }, + + unique: function(nodes) { + if (nodes.length == 0) return nodes; + var results = [], n; + for (var i = 0, l = nodes.length; i < l; i++) + if (typeof (n = nodes[i])._countedByPrototype == 'undefined') { + n._countedByPrototype = Prototype.emptyFunction; + results.push(Element.extend(n)); + } + return Selector.handlers.unmark(results); + }, + + descendant: function(nodes) { + var h = Selector.handlers; + for (var i = 0, results = [], node; node = nodes[i]; i++) + h.concat(results, node.getElementsByTagName('*')); + return results; + }, + + child: function(nodes) { + var h = Selector.handlers; + for (var i = 0, results = [], node; node = nodes[i]; i++) { + for (var j = 0, child; child = node.childNodes[j]; j++) + if (child.nodeType == 1 && child.tagName != '!') results.push(child); + } + return results; + }, + + adjacent: function(nodes) { + for (var i = 0, results = [], node; node = nodes[i]; i++) { + var next = this.nextElementSibling(node); + if (next) results.push(next); + } + return results; + }, + + laterSibling: function(nodes) { + var h = Selector.handlers; + for (var i = 0, results = [], node; node = nodes[i]; i++) + h.concat(results, Element.nextSiblings(node)); + return results; + }, + + nextElementSibling: function(node) { + while (node = node.nextSibling) + if (node.nodeType == 1) return node; + return null; + }, + + previousElementSibling: function(node) { + while (node = node.previousSibling) + if (node.nodeType == 1) return node; + return null; + }, + + tagName: function(nodes, root, tagName, combinator) { + var uTagName = tagName.toUpperCase(); + var results = [], h = Selector.handlers; + if (nodes) { + if (combinator) { + if (combinator == "descendant") { + for (var i = 0, node; node = nodes[i]; i++) + h.concat(results, node.getElementsByTagName(tagName)); + return results; + } else nodes = this[combinator](nodes); + if (tagName == "*") return nodes; + } + for (var i = 0, node; node = nodes[i]; i++) + if (node.tagName.toUpperCase() === uTagName) results.push(node); + return results; + } else return root.getElementsByTagName(tagName); + }, + + id: function(nodes, root, id, combinator) { + var targetNode = $(id), h = Selector.handlers; + + if (root == document) { + if (!targetNode) return []; + if (!nodes) return [targetNode]; + } else { + if (!root.sourceIndex || root.sourceIndex < 1) { + var nodes = root.getElementsByTagName('*'); + for (var j = 0, node; node = nodes[j]; j++) { + if (node.id === id) return [node]; + } + } + } + + if (nodes) { + if (combinator) { + if (combinator == 'child') { + for (var i = 0, node; node = nodes[i]; i++) + if (targetNode.parentNode == node) return [targetNode]; + } else if (combinator == 'descendant') { + for (var i = 0, node; node = nodes[i]; i++) + if (Element.descendantOf(targetNode, node)) return [targetNode]; + } else if (combinator == 'adjacent') { + for (var i = 0, node; node = nodes[i]; i++) + if (Selector.handlers.previousElementSibling(targetNode) == node) + return [targetNode]; + } else nodes = h[combinator](nodes); + } + for (var i = 0, node; node = nodes[i]; i++) + if (node == targetNode) return [targetNode]; + return []; + } + return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : []; + }, + + className: function(nodes, root, className, combinator) { + if (nodes && combinator) nodes = this[combinator](nodes); + return Selector.handlers.byClassName(nodes, root, className); + }, + + byClassName: function(nodes, root, className) { + if (!nodes) nodes = Selector.handlers.descendant([root]); + var needle = ' ' + className + ' '; + for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) { + nodeClassName = node.className; + if (nodeClassName.length == 0) continue; + if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle)) + results.push(node); + } + return results; + }, + + attrPresence: function(nodes, root, attr, combinator) { + if (!nodes) nodes = root.getElementsByTagName("*"); + if (nodes && combinator) nodes = this[combinator](nodes); + var results = []; + for (var i = 0, node; node = nodes[i]; i++) + if (Element.hasAttribute(node, attr)) results.push(node); + return results; + }, + + attr: function(nodes, root, attr, value, operator, combinator) { + if (!nodes) nodes = root.getElementsByTagName("*"); + if (nodes && combinator) nodes = this[combinator](nodes); + var handler = Selector.operators[operator], results = []; + for (var i = 0, node; node = nodes[i]; i++) { + var nodeValue = Element.readAttribute(node, attr); + if (nodeValue === null) continue; + if (handler(nodeValue, value)) results.push(node); + } + return results; + }, + + pseudo: function(nodes, name, value, root, combinator) { + if (nodes && combinator) nodes = this[combinator](nodes); + if (!nodes) nodes = root.getElementsByTagName("*"); + return Selector.pseudos[name](nodes, value, root); + } + }, + + pseudos: { + 'first-child': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) { + if (Selector.handlers.previousElementSibling(node)) continue; + results.push(node); + } + return results; + }, + 'last-child': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) { + if (Selector.handlers.nextElementSibling(node)) continue; + results.push(node); + } + return results; + }, + 'only-child': function(nodes, value, root) { + var h = Selector.handlers; + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (!h.previousElementSibling(node) && !h.nextElementSibling(node)) + results.push(node); + return results; + }, + 'nth-child': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, formula, root); + }, + 'nth-last-child': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, formula, root, true); + }, + 'nth-of-type': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, formula, root, false, true); + }, + 'nth-last-of-type': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, formula, root, true, true); + }, + 'first-of-type': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, "1", root, false, true); + }, + 'last-of-type': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, "1", root, true, true); + }, + 'only-of-type': function(nodes, formula, root) { + var p = Selector.pseudos; + return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root); + }, + + getIndices: function(a, b, total) { + if (a == 0) return b > 0 ? [b] : []; + return $R(1, total).inject([], function(memo, i) { + if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i); + return memo; + }); + }, + + nth: function(nodes, formula, root, reverse, ofType) { + if (nodes.length == 0) return []; + if (formula == 'even') formula = '2n+0'; + if (formula == 'odd') formula = '2n+1'; + var h = Selector.handlers, results = [], indexed = [], m; + h.mark(nodes); + for (var i = 0, node; node = nodes[i]; i++) { + if (!node.parentNode._countedByPrototype) { + h.index(node.parentNode, reverse, ofType); + indexed.push(node.parentNode); + } + } + if (formula.match(/^\d+$/)) { // just a number + formula = Number(formula); + for (var i = 0, node; node = nodes[i]; i++) + if (node.nodeIndex == formula) results.push(node); + } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b + if (m[1] == "-") m[1] = -1; + var a = m[1] ? Number(m[1]) : 1; + var b = m[2] ? Number(m[2]) : 0; + var indices = Selector.pseudos.getIndices(a, b, nodes.length); + for (var i = 0, node, l = indices.length; node = nodes[i]; i++) { + for (var j = 0; j < l; j++) + if (node.nodeIndex == indices[j]) results.push(node); + } + } + h.unmark(nodes); + h.unmark(indexed); + return results; + }, + + 'empty': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) { + if (node.tagName == '!' || node.firstChild) continue; + results.push(node); + } + return results; + }, + + 'not': function(nodes, selector, root) { + var h = Selector.handlers, selectorType, m; + var exclusions = new Selector(selector).findElements(root); + h.mark(exclusions); + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (!node._countedByPrototype) results.push(node); + h.unmark(exclusions); + return results; + }, + + 'enabled': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (!node.disabled && (!node.type || node.type !== 'hidden')) + results.push(node); + return results; + }, + + 'disabled': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (node.disabled) results.push(node); + return results; + }, + + 'checked': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (node.checked) results.push(node); + return results; + } + }, + + operators: { + '=': function(nv, v) { return nv == v; }, + '!=': function(nv, v) { return nv != v; }, + '^=': function(nv, v) { return nv == v || nv && nv.startsWith(v); }, + '$=': function(nv, v) { return nv == v || nv && nv.endsWith(v); }, + '*=': function(nv, v) { return nv == v || nv && nv.include(v); }, + '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); }, + '|=': function(nv, v) { return ('-' + (nv || "").toUpperCase() + + '-').include('-' + (v || "").toUpperCase() + '-'); } + }, + + split: function(expression) { + var expressions = []; + expression.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) { + expressions.push(m[1].strip()); + }); + return expressions; + }, + + matchElements: function(elements, expression) { + var matches = $$(expression), h = Selector.handlers; + h.mark(matches); + for (var i = 0, results = [], element; element = elements[i]; i++) + if (element._countedByPrototype) results.push(element); + h.unmark(matches); + return results; + }, + + findElement: function(elements, expression, index) { + if (Object.isNumber(expression)) { + index = expression; expression = false; + } + return Selector.matchElements(elements, expression || '*')[index || 0]; + }, + + findChildElements: function(element, expressions) { + expressions = Selector.split(expressions.join(',')); + var results = [], h = Selector.handlers; + for (var i = 0, l = expressions.length, selector; i < l; i++) { + selector = new Selector(expressions[i].strip()); + h.concat(results, selector.findElements(element)); + } + return (l > 1) ? h.unique(results) : results; + } +}); + +if (Prototype.Browser.IE) { + Object.extend(Selector.handlers, { + concat: function(a, b) { + for (var i = 0, node; node = b[i]; i++) + if (node.tagName !== "!") a.push(node); + return a; + } + }); +} + +function $$() { + return Selector.findChildElements(document, $A(arguments)); +} + +var Form = { + reset: function(form) { + form = $(form); + form.reset(); + return form; + }, + + serializeElements: function(elements, options) { + if (typeof options != 'object') options = { hash: !!options }; + else if (Object.isUndefined(options.hash)) options.hash = true; + var key, value, submitted = false, submit = options.submit; + + var data = elements.inject({ }, function(result, element) { + if (!element.disabled && element.name) { + key = element.name; value = $(element).getValue(); + if (value != null && element.type != 'file' && (element.type != 'submit' || (!submitted && + submit !== false && (!submit || key == submit) && (submitted = true)))) { + if (key in result) { + if (!Object.isArray(result[key])) result[key] = [result[key]]; + result[key].push(value); + } + else result[key] = value; + } + } + return result; + }); + + return options.hash ? data : Object.toQueryString(data); + } +}; + +Form.Methods = { + serialize: function(form, options) { + return Form.serializeElements(Form.getElements(form), options); + }, + + getElements: function(form) { + var elements = $(form).getElementsByTagName('*'), + element, + arr = [ ], + serializers = Form.Element.Serializers; + for (var i = 0; element = elements[i]; i++) { + arr.push(element); + } + return arr.inject([], function(elements, child) { + if (serializers[child.tagName.toLowerCase()]) + elements.push(Element.extend(child)); + return elements; + }) + }, + + getInputs: function(form, typeName, name) { + form = $(form); + var inputs = form.getElementsByTagName('input'); + + if (!typeName && !name) return $A(inputs).map(Element.extend); + + for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) { + var input = inputs[i]; + if ((typeName && input.type != typeName) || (name && input.name != name)) + continue; + matchingInputs.push(Element.extend(input)); + } + + return matchingInputs; + }, + + disable: function(form) { + form = $(form); + Form.getElements(form).invoke('disable'); + return form; + }, + + enable: function(form) { + form = $(form); + Form.getElements(form).invoke('enable'); + return form; + }, + + findFirstElement: function(form) { + var elements = $(form).getElements().findAll(function(element) { + return 'hidden' != element.type && !element.disabled; + }); + var firstByIndex = elements.findAll(function(element) { + return element.hasAttribute('tabIndex') && element.tabIndex >= 0; + }).sortBy(function(element) { return element.tabIndex }).first(); + + return firstByIndex ? firstByIndex : elements.find(function(element) { + return /^(?:input|select|textarea)$/i.test(element.tagName); + }); + }, + + focusFirstElement: function(form) { + form = $(form); + form.findFirstElement().activate(); + return form; + }, + + request: function(form, options) { + form = $(form), options = Object.clone(options || { }); + + var params = options.parameters, action = form.readAttribute('action') || ''; + if (action.blank()) action = window.location.href; + options.parameters = form.serialize(true); + + if (params) { + if (Object.isString(params)) params = params.toQueryParams(); + Object.extend(options.parameters, params); + } + + if (form.hasAttribute('method') && !options.method) + options.method = form.method; + + return new Ajax.Request(action, options); + } +}; + +/*--------------------------------------------------------------------------*/ + + +Form.Element = { + focus: function(element) { + $(element).focus(); + return element; + }, + + select: function(element) { + $(element).select(); + return element; + } +}; + +Form.Element.Methods = { + + serialize: function(element) { + element = $(element); + if (!element.disabled && element.name) { + var value = element.getValue(); + if (value != undefined) { + var pair = { }; + pair[element.name] = value; + return Object.toQueryString(pair); + } + } + return ''; + }, + + getValue: function(element) { + element = $(element); + var method = element.tagName.toLowerCase(); + return Form.Element.Serializers[method](element); + }, + + setValue: function(element, value) { + element = $(element); + var method = element.tagName.toLowerCase(); + Form.Element.Serializers[method](element, value); + return element; + }, + + clear: function(element) { + $(element).value = ''; + return element; + }, + + present: function(element) { + return $(element).value != ''; + }, + + activate: function(element) { + element = $(element); + try { + element.focus(); + if (element.select && (element.tagName.toLowerCase() != 'input' || + !(/^(?:button|reset|submit)$/i.test(element.type)))) + element.select(); + } catch (e) { } + return element; + }, + + disable: function(element) { + element = $(element); + element.disabled = true; + return element; + }, + + enable: function(element) { + element = $(element); + element.disabled = false; + return element; + } +}; + +/*--------------------------------------------------------------------------*/ + +var Field = Form.Element; + +var $F = Form.Element.Methods.getValue; + +/*--------------------------------------------------------------------------*/ + +Form.Element.Serializers = { + input: function(element, value) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + return Form.Element.Serializers.inputSelector(element, value); + default: + return Form.Element.Serializers.textarea(element, value); + } + }, + + inputSelector: function(element, value) { + if (Object.isUndefined(value)) return element.checked ? element.value : null; + else element.checked = !!value; + }, + + textarea: function(element, value) { + if (Object.isUndefined(value)) return element.value; + else element.value = value; + }, + + select: function(element, value) { + if (Object.isUndefined(value)) + return this[element.type == 'select-one' ? + 'selectOne' : 'selectMany'](element); + else { + var opt, currentValue, single = !Object.isArray(value); + for (var i = 0, length = element.length; i < length; i++) { + opt = element.options[i]; + currentValue = this.optionValue(opt); + if (single) { + if (currentValue == value) { + opt.selected = true; + return; + } + } + else opt.selected = value.include(currentValue); + } + } + }, + + selectOne: function(element) { + var index = element.selectedIndex; + return index >= 0 ? this.optionValue(element.options[index]) : null; + }, + + selectMany: function(element) { + var values, length = element.length; + if (!length) return null; + + for (var i = 0, values = []; i < length; i++) { + var opt = element.options[i]; + if (opt.selected) values.push(this.optionValue(opt)); + } + return values; + }, + + optionValue: function(opt) { + return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text; + } +}; + +/*--------------------------------------------------------------------------*/ + + +Abstract.TimedObserver = Class.create(PeriodicalExecuter, { + initialize: function($super, element, frequency, callback) { + $super(callback, frequency); + this.element = $(element); + this.lastValue = this.getValue(); + }, + + execute: function() { + var value = this.getValue(); + if (Object.isString(this.lastValue) && Object.isString(value) ? + this.lastValue != value : String(this.lastValue) != String(value)) { + this.callback(this.element, value); + this.lastValue = value; + } + } +}); + +Form.Element.Observer = Class.create(Abstract.TimedObserver, { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.Observer = Class.create(Abstract.TimedObserver, { + getValue: function() { + return Form.serialize(this.element); + } +}); + +/*--------------------------------------------------------------------------*/ + +Abstract.EventObserver = Class.create({ + initialize: function(element, callback) { + this.element = $(element); + this.callback = callback; + + this.lastValue = this.getValue(); + if (this.element.tagName.toLowerCase() == 'form') + this.registerFormCallbacks(); + else + this.registerCallback(this.element); + }, + + onElementEvent: function() { + var value = this.getValue(); + if (this.lastValue != value) { + this.callback(this.element, value); + this.lastValue = value; + } + }, + + registerFormCallbacks: function() { + Form.getElements(this.element).each(this.registerCallback, this); + }, + + registerCallback: function(element) { + if (element.type) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + Event.observe(element, 'click', this.onElementEvent.bind(this)); + break; + default: + Event.observe(element, 'change', this.onElementEvent.bind(this)); + break; + } + } + } +}); + +Form.Element.EventObserver = Class.create(Abstract.EventObserver, { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.EventObserver = Class.create(Abstract.EventObserver, { + getValue: function() { + return Form.serialize(this.element); + } +}); +(function() { + + var Event = { + KEY_BACKSPACE: 8, + KEY_TAB: 9, + KEY_RETURN: 13, + KEY_ESC: 27, + KEY_LEFT: 37, + KEY_UP: 38, + KEY_RIGHT: 39, + KEY_DOWN: 40, + KEY_DELETE: 46, + KEY_HOME: 36, + KEY_END: 35, + KEY_PAGEUP: 33, + KEY_PAGEDOWN: 34, + KEY_INSERT: 45, + + cache: {} + }; + + var docEl = document.documentElement; + var MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED = 'onmouseenter' in docEl + && 'onmouseleave' in docEl; + + var _isButton; + if (Prototype.Browser.IE) { + var buttonMap = { 0: 1, 1: 4, 2: 2 }; + _isButton = function(event, code) { + return event.button === buttonMap[code]; + }; + } else if (Prototype.Browser.WebKit) { + _isButton = function(event, code) { + switch (code) { + case 0: return event.which == 1 && !event.metaKey; + case 1: return event.which == 1 && event.metaKey; + default: return false; + } + }; + } else { + _isButton = function(event, code) { + return event.which ? (event.which === code + 1) : (event.button === code); + }; + } + + function isLeftClick(event) { return _isButton(event, 0) } + + function isMiddleClick(event) { return _isButton(event, 1) } + + function isRightClick(event) { return _isButton(event, 2) } + + function element(event) { + event = Event.extend(event); + + var node = event.target, type = event.type, + currentTarget = event.currentTarget; + + if (currentTarget && currentTarget.tagName) { + if (type === 'load' || type === 'error' || + (type === 'click' && currentTarget.tagName.toLowerCase() === 'input' + && currentTarget.type === 'radio')) + node = currentTarget; + } + + if (node.nodeType == Node.TEXT_NODE) + node = node.parentNode; + + return Element.extend(node); + } + + function findElement(event, expression) { + var element = Event.element(event); + if (!expression) return element; + var elements = [element].concat(element.ancestors()); + return Selector.findElement(elements, expression, 0); + } + + function pointer(event) { + return { x: pointerX(event), y: pointerY(event) }; + } + + function pointerX(event) { + var docElement = document.documentElement, + body = document.body || { scrollLeft: 0 }; + + return event.pageX || (event.clientX + + (docElement.scrollLeft || body.scrollLeft) - + (docElement.clientLeft || 0)); + } + + function pointerY(event) { + var docElement = document.documentElement, + body = document.body || { scrollTop: 0 }; + + return event.pageY || (event.clientY + + (docElement.scrollTop || body.scrollTop) - + (docElement.clientTop || 0)); + } + + + function stop(event) { + Event.extend(event); + event.preventDefault(); + event.stopPropagation(); + + event.stopped = true; + } + + Event.Methods = { + isLeftClick: isLeftClick, + isMiddleClick: isMiddleClick, + isRightClick: isRightClick, + + element: element, + findElement: findElement, + + pointer: pointer, + pointerX: pointerX, + pointerY: pointerY, + + stop: stop + }; + + + var methods = Object.keys(Event.Methods).inject({ }, function(m, name) { + m[name] = Event.Methods[name].methodize(); + return m; + }); + + if (Prototype.Browser.IE) { + function _relatedTarget(event) { + var element; + switch (event.type) { + case 'mouseover': element = event.fromElement; break; + case 'mouseout': element = event.toElement; break; + default: return null; + } + return Element.extend(element); + } + + Object.extend(methods, { + stopPropagation: function() { this.cancelBubble = true }, + preventDefault: function() { this.returnValue = false }, + inspect: function() { return '[object Event]' } + }); + + Event.extend = function(event, element) { + if (!event) return false; + if (event._extendedByPrototype) return event; + + event._extendedByPrototype = Prototype.emptyFunction; + var pointer = Event.pointer(event); + + Object.extend(event, { + target: event.srcElement || element, + relatedTarget: _relatedTarget(event), + pageX: pointer.x, + pageY: pointer.y + }); + + return Object.extend(event, methods); + }; + } else { + Event.prototype = window.Event.prototype || document.createEvent('HTMLEvents').__proto__; + Object.extend(Event.prototype, methods); + Event.extend = Prototype.K; + } + + function _createResponder(element, eventName, handler) { + var registry = Element.retrieve(element, 'prototype_event_registry'); + + if (Object.isUndefined(registry)) { + CACHE.push(element); + registry = Element.retrieve(element, 'prototype_event_registry', $H()); + } + + var respondersForEvent = registry.get(eventName); + if (Object.isUndefined(respondersForEvent)) { + respondersForEvent = []; + registry.set(eventName, respondersForEvent); + } + + if (respondersForEvent.pluck('handler').include(handler)) return false; + + var responder; + if (eventName.include(":")) { + responder = function(event) { + if (Object.isUndefined(event.eventName)) + return false; + + if (event.eventName !== eventName) + return false; + + Event.extend(event, element); + handler.call(element, event); + }; + } else { + if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED && + (eventName === "mouseenter" || eventName === "mouseleave")) { + if (eventName === "mouseenter" || eventName === "mouseleave") { + responder = function(event) { + Event.extend(event, element); + + var parent = event.relatedTarget; + while (parent && parent !== element) { + try { parent = parent.parentNode; } + catch(e) { parent = element; } + } + + if (parent === element) return; + + handler.call(element, event); + }; + } + } else { + responder = function(event) { + Event.extend(event, element); + handler.call(element, event); + }; + } + } + + responder.handler = handler; + respondersForEvent.push(responder); + return responder; + } + + function _destroyCache() { + for (var i = 0, length = CACHE.length; i < length; i++) { + Event.stopObserving(CACHE[i]); + CACHE[i] = null; + } + } + + var CACHE = []; + + if (Prototype.Browser.IE) + window.attachEvent('onunload', _destroyCache); + + if (Prototype.Browser.WebKit) + window.addEventListener('unload', Prototype.emptyFunction, false); + + + var _getDOMEventName = Prototype.K; + + if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED) { + _getDOMEventName = function(eventName) { + var translations = { mouseenter: "mouseover", mouseleave: "mouseout" }; + return eventName in translations ? translations[eventName] : eventName; + }; + } + + function observe(element, eventName, handler) { + element = $(element); + + var responder = _createResponder(element, eventName, handler); + + if (!responder) return element; + + if (eventName.include(':')) { + if (element.addEventListener) + element.addEventListener("dataavailable", responder, false); + else { + element.attachEvent("ondataavailable", responder); + element.attachEvent("onfilterchange", responder); + } + } else { + var actualEventName = _getDOMEventName(eventName); + + if (element.addEventListener) + element.addEventListener(actualEventName, responder, false); + else + element.attachEvent("on" + actualEventName, responder); + } + + return element; + } + + function stopObserving(element, eventName, handler) { + element = $(element); + + var registry = Element.retrieve(element, 'prototype_event_registry'); + + if (Object.isUndefined(registry)) return element; + + if (eventName && !handler) { + var responders = registry.get(eventName); + + if (Object.isUndefined(responders)) return element; + + responders.each( function(r) { + Element.stopObserving(element, eventName, r.handler); + }); + return element; + } else if (!eventName) { + registry.each( function(pair) { + var eventName = pair.key, responders = pair.value; + + responders.each( function(r) { + Element.stopObserving(element, eventName, r.handler); + }); + }); + return element; + } + + var responders = registry.get(eventName); + + if (!responders) return; + + var responder = responders.find( function(r) { return r.handler === handler; }); + if (!responder) return element; + + var actualEventName = _getDOMEventName(eventName); + + if (eventName.include(':')) { + if (element.removeEventListener) + element.removeEventListener("dataavailable", responder, false); + else { + element.detachEvent("ondataavailable", responder); + element.detachEvent("onfilterchange", responder); + } + } else { + if (element.removeEventListener) + element.removeEventListener(actualEventName, responder, false); + else + element.detachEvent('on' + actualEventName, responder); + } + + registry.set(eventName, responders.without(responder)); + + return element; + } + + function fire(element, eventName, memo, bubble) { + element = $(element); + + if (Object.isUndefined(bubble)) + bubble = true; + + if (element == document && document.createEvent && !element.dispatchEvent) + element = document.documentElement; + + var event; + if (document.createEvent) { + event = document.createEvent('HTMLEvents'); + event.initEvent('dataavailable', true, true); + } else { + event = document.createEventObject(); + event.eventType = bubble ? 'ondataavailable' : 'onfilterchange'; + } + + event.eventName = eventName; + event.memo = memo || { }; + + if (document.createEvent) + element.dispatchEvent(event); + else + element.fireEvent(event.eventType, event); + + return Event.extend(event); + } + + + Object.extend(Event, Event.Methods); + + Object.extend(Event, { + fire: fire, + observe: observe, + stopObserving: stopObserving + }); + + Element.addMethods({ + fire: fire, + + observe: observe, + + stopObserving: stopObserving + }); + + Object.extend(document, { + fire: fire.methodize(), + + observe: observe.methodize(), + + stopObserving: stopObserving.methodize(), + + loaded: false + }); + + if (window.Event) Object.extend(window.Event, Event); + else window.Event = Event; +})(); + +(function() { + /* Support for the DOMContentLoaded event is based on work by Dan Webb, + Matthias Miller, Dean Edwards, John Resig, and Diego Perini. */ + + var timer; + + function fireContentLoadedEvent() { + if (document.loaded) return; + if (timer) window.clearTimeout(timer); + document.loaded = true; + document.fire('dom:loaded'); + } + + function checkReadyState() { + if (document.readyState === 'complete') { + document.stopObserving('readystatechange', checkReadyState); + fireContentLoadedEvent(); + } + } + + function pollDoScroll() { + try { document.documentElement.doScroll('left'); } + catch(e) { + timer = pollDoScroll.defer(); + return; + } + fireContentLoadedEvent(); + } + + if (document.addEventListener) { + document.addEventListener('DOMContentLoaded', fireContentLoadedEvent, false); + } else { + document.observe('readystatechange', checkReadyState); + if (window == top) + timer = pollDoScroll.defer(); + } + + Event.observe(window, 'load', fireContentLoadedEvent); +})(); + +Element.addMethods(); + +/*------------------------------- DEPRECATED -------------------------------*/ + +Hash.toQueryString = Object.toQueryString; + +var Toggle = { display: Element.toggle }; + +Element.Methods.childOf = Element.Methods.descendantOf; + +var Insertion = { + Before: function(element, content) { + return Element.insert(element, {before:content}); + }, + + Top: function(element, content) { + return Element.insert(element, {top:content}); + }, + + Bottom: function(element, content) { + return Element.insert(element, {bottom:content}); + }, + + After: function(element, content) { + return Element.insert(element, {after:content}); + } +}; + +var $continue = new Error('"throw $continue" is deprecated, use "return" instead'); + +var Position = { + includeScrollOffsets: false, + + prepare: function() { + this.deltaX = window.pageXOffset + || document.documentElement.scrollLeft + || document.body.scrollLeft + || 0; + this.deltaY = window.pageYOffset + || document.documentElement.scrollTop + || document.body.scrollTop + || 0; + }, + + within: function(element, x, y) { + if (this.includeScrollOffsets) + return this.withinIncludingScrolloffsets(element, x, y); + this.xcomp = x; + this.ycomp = y; + this.offset = Element.cumulativeOffset(element); + + return (y >= this.offset[1] && + y < this.offset[1] + element.offsetHeight && + x >= this.offset[0] && + x < this.offset[0] + element.offsetWidth); + }, + + withinIncludingScrolloffsets: function(element, x, y) { + var offsetcache = Element.cumulativeScrollOffset(element); + + this.xcomp = x + offsetcache[0] - this.deltaX; + this.ycomp = y + offsetcache[1] - this.deltaY; + this.offset = Element.cumulativeOffset(element); + + return (this.ycomp >= this.offset[1] && + this.ycomp < this.offset[1] + element.offsetHeight && + this.xcomp >= this.offset[0] && + this.xcomp < this.offset[0] + element.offsetWidth); + }, + + overlap: function(mode, element) { + if (!mode) return 0; + if (mode == 'vertical') + return ((this.offset[1] + element.offsetHeight) - this.ycomp) / + element.offsetHeight; + if (mode == 'horizontal') + return ((this.offset[0] + element.offsetWidth) - this.xcomp) / + element.offsetWidth; + }, + + + cumulativeOffset: Element.Methods.cumulativeOffset, + + positionedOffset: Element.Methods.positionedOffset, + + absolutize: function(element) { + Position.prepare(); + return Element.absolutize(element); + }, + + relativize: function(element) { + Position.prepare(); + return Element.relativize(element); + }, + + realOffset: Element.Methods.cumulativeScrollOffset, + + offsetParent: Element.Methods.getOffsetParent, + + page: Element.Methods.viewportOffset, + + clone: function(source, target, options) { + options = options || { }; + return Element.clonePosition(target, source, options); + } +}; + +/*--------------------------------------------------------------------------*/ + +if (!document.getElementsByClassName) document.getElementsByClassName = function(instanceMethods){ + function iter(name) { + return name.blank() ? null : "[contains(concat(' ', @class, ' '), ' " + name + " ')]"; + } + + instanceMethods.getElementsByClassName = Prototype.BrowserFeatures.XPath ? + function(element, className) { + className = className.toString().strip(); + var cond = /\s/.test(className) ? $w(className).map(iter).join('') : iter(className); + return cond ? document._getElementsByXPath('.//*' + cond, element) : []; + } : function(element, className) { + className = className.toString().strip(); + var elements = [], classNames = (/\s/.test(className) ? $w(className) : null); + if (!classNames && !className) return elements; + + var nodes = $(element).getElementsByTagName('*'); + className = ' ' + className + ' '; + + for (var i = 0, child, cn; child = nodes[i]; i++) { + if (child.className && (cn = ' ' + child.className + ' ') && (cn.include(className) || + (classNames && classNames.all(function(name) { + return !name.toString().blank() && cn.include(' ' + name + ' '); + })))) + elements.push(Element.extend(child)); + } + return elements; + }; + + return function(className, parentElement) { + return $(parentElement || document.body).getElementsByClassName(className); + }; +}(Element.Methods); + +/*--------------------------------------------------------------------------*/ + +Element.ClassNames = Class.create(); +Element.ClassNames.prototype = { + initialize: function(element) { + this.element = $(element); + }, + + _each: function(iterator) { + this.element.className.split(/\s+/).select(function(name) { + return name.length > 0; + })._each(iterator); + }, + + set: function(className) { + this.element.className = className; + }, + + add: function(classNameToAdd) { + if (this.include(classNameToAdd)) return; + this.set($A(this).concat(classNameToAdd).join(' ')); + }, + + remove: function(classNameToRemove) { + if (!this.include(classNameToRemove)) return; + this.set($A(this).without(classNameToRemove).join(' ')); + }, + + toString: function() { + return $A(this).join(' '); + } +}; + +Object.extend(Element.ClassNames.prototype, Enumerable); + +/*--------------------------------------------------------------------------*/ \ No newline at end of file diff --git a/browserid/static/funcunit/test/protodrag/scriptaculous.js b/browserid/static/funcunit/test/protodrag/scriptaculous.js new file mode 100644 index 0000000000000000000000000000000000000000..6bf437acc4b26a9071ba329c6ecb4e3b79c2c857 --- /dev/null +++ b/browserid/static/funcunit/test/protodrag/scriptaculous.js @@ -0,0 +1,68 @@ +// script.aculo.us scriptaculous.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009 + +// Copyright (c) 2005-2009 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// For details, see the script.aculo.us web site: http://script.aculo.us/ + +var Scriptaculous = { + Version: '1.8.3', + require: function(libraryName) { + try{ + // inserting via DOM fails in Safari 2.0, so brute force approach + document.write('<script type="text/javascript" src="'+libraryName+'"><\/script>'); + } catch(e) { + // for xhtml+xml served content, fall back to DOM methods + var script = document.createElement('script'); + script.type = 'text/javascript'; + script.src = libraryName; + document.getElementsByTagName('head')[0].appendChild(script); + } + }, + REQUIRED_PROTOTYPE: '1.6.0.3', + load: function() { + function convertVersionString(versionString) { + var v = versionString.replace(/_.*|\./g, ''); + v = parseInt(v + '0'.times(4-v.length)); + return versionString.indexOf('_') > -1 ? v-1 : v; + } + + if((typeof Prototype=='undefined') || + (typeof Element == 'undefined') || + (typeof Element.Methods=='undefined') || + (convertVersionString(Prototype.Version) < + convertVersionString(Scriptaculous.REQUIRED_PROTOTYPE))) + throw("script.aculo.us requires the Prototype JavaScript framework >= " + + Scriptaculous.REQUIRED_PROTOTYPE); + + var js = /scriptaculous\.js(\?.*)?$/; + $$('head script[src]').findAll(function(s) { + return s.src.match(js); + }).each(function(s) { + var path = s.src.replace(js, ''), + includes = s.src.match(/\?.*load=([a-z,]*)/); + (includes ? includes[1] : 'builder,effects,dragdrop,controls,slider,sound').split(',').each( + function(include) { Scriptaculous.require(path+include+'.js') }); + }); + } +}; + +Scriptaculous.load(); \ No newline at end of file diff --git a/browserid/static/funcunit/test/qunit/qunit.js b/browserid/static/funcunit/test/qunit/qunit.js new file mode 100644 index 0000000000000000000000000000000000000000..5ad5365446be5d18e1e72a44b0c7da693237ddda --- /dev/null +++ b/browserid/static/funcunit/test/qunit/qunit.js @@ -0,0 +1,3 @@ +//we probably have to have this only describing where the tests are +steal + .plugins("funcunit/syn/test/qunit") \ No newline at end of file diff --git a/browserid/static/funcunit/test/run.js b/browserid/static/funcunit/test/run.js new file mode 100644 index 0000000000000000000000000000000000000000..e67d9a4bccea11766cd0e78ec7b55fec04788eff --- /dev/null +++ b/browserid/static/funcunit/test/run.js @@ -0,0 +1,7 @@ +//integration point for JavaScriptMVC testing + +print("========================== funcunit ============================") + +load('steal/rhino/steal.js'); +load('funcunit/loader.js'); +FuncUnit.load('funcunit/funcunit.html'); \ No newline at end of file diff --git a/browserid/static/funcunit/update b/browserid/static/funcunit/update new file mode 100644 index 0000000000000000000000000000000000000000..d8e46265cd642265010e8efea3c8bf392d9a7688 --- /dev/null +++ b/browserid/static/funcunit/update @@ -0,0 +1,5 @@ +load('steal/rhino/steal.js') + +steal('//steal/get/get', function(s){ + s.get('http://github.com/jupiterjs/funcunit/', {name: 'funcunit'}); +}) diff --git a/browserid/static/ietest.html b/browserid/static/ietest.html new file mode 100644 index 0000000000000000000000000000000000000000..92de8c770a4db098b4d20741336937001d41ca84 --- /dev/null +++ b/browserid/static/ietest.html @@ -0,0 +1,25 @@ +<!doctype html> +<html> + <head> + <meta charset="utf-8"> + <title>IE test</title> + </head> + <body> + some content + <iframe src="https://dev.diresworb.org/relay.html"></iframe> + + <button id="addFrame">Add Frame</button> + + <script type="text/javascript"> + var addFrame = document.querySelector('#addFrame'); + addFrame.addEventListener("click", function(event) { + event.preventDefault(); + + var iframe = document.createElement('iframe'); + iframe.src = "https://dev.diresworb.org/relay"; + document.body.appendChild(iframe); + + }, false); + </script> + </body> +</html> diff --git a/browserid/static/include.js b/browserid/static/include.js index b2083c867a5507225c4e0c476b86d9f629f7ed27..5d076054711de0e0f196bfc3c487aaaf0b069f65 100644 --- a/browserid/static/include.js +++ b/browserid/static/include.js @@ -572,23 +572,35 @@ if (!navigator.id.getVerifiedEmail || navigator.id._getVerifiedEmailIsShimmed) return iframe; } + function _open_relay_frame(doc) { + var iframe = doc.createElement("iframe"); + iframe.setAttribute('name', 'browserid_relay'); + iframe.setAttribute('src', ipServer + "/relay.html"); + iframe.style.display = "none"; + doc.body.appendChild(iframe); + return iframe; + } + function _open_window() { return window.open( - ipServer + "/sign_in", "_mozid_signin", + ipServer + "/sign_in#host=" + document.location.host, "_mozid_signin", isMobile ? undefined : "menubar=0,location=0,resizable=0,scrollbars=0,status=0,dialog=1,width=520,height=350"); } navigator.id.getVerifiedEmail = function(callback) { + var doc = window.document; var w = _open_window(); + var iframe = _open_relay_frame(doc); // clean up a previous channel that never was reaped if (chan) chan.destroy(); - chan = Channel.build({window: w, origin: ipServer, scope: "mozid"}); + chan = Channel.build({window: iframe.contentWindow, origin: ipServer, scope: "mozid"}); function cleanup() { chan.destroy(); chan = undefined; w.close(); + iframe.parentNode.removeChild(iframe); } chan.call({ @@ -645,11 +657,13 @@ if (!navigator.id.getVerifiedEmail || navigator.id._getVerifiedEmailIsShimmed) // if we have a token, we should not be opening a window, rather we should be // able to do this entirely through IFRAMEs + var w; if (token) { var iframe = _create_iframe(doc); - var w = iframe.contentWindow; + w = iframe.contentWindow; } else { - var w = _open_window(); + _open_window(); + _open_relay_frame(doc); } // clean up a previous channel that never was reaped diff --git a/browserid/static/jquery/buildAll.js b/browserid/static/jquery/buildAll.js index 06cbf45eec6ece65b35eb2df94a313d83b37248d..0cbd3408f1b9c8b60e7e8eae33b60ca255b18098 100644 --- a/browserid/static/jquery/buildAll.js +++ b/browserid/static/jquery/buildAll.js @@ -75,6 +75,7 @@ steal.plugins('steal/build/pluginify','steal/build/apps','steal/build/scripts'). } }) + steal.File("jquery/dist/standalone").mkdir(); steal.File("jquery/dist/standalone/dependencies.json").save($.toJSON(files)); //get each file ... print("Creating jquery/dist/standalone/") diff --git a/browserid/static/jquery/class/class.js b/browserid/static/jquery/class/class.js index 31db76ee6b3c846b1f370d0f182daf45f1003a84..9433e9b7701a7e93ffe65206ff243e3b53231c03 100644 --- a/browserid/static/jquery/class/class.js +++ b/browserid/static/jquery/class/class.js @@ -3,22 +3,32 @@ // http://ejohn.org/blog/simple-javascript-inheritance/ // It provides class level inheritance and callbacks. //@steal-clean -steal.plugins("jquery").then(function( $ ) { +steal.plugins("jquery","jquery/lang").then(function( $ ) { // if we are initializing a new class var initializing = false, - + makeArray = $.makeArray, + isFunction = $.isFunction, + isArray = $.isArray, + extend = $.extend, + concatArgs = function(arr, args){ + return arr.concat(makeArray(args)); + }, // tests if we can get super in .toString() fnTest = /xyz/.test(function() { xyz; }) ? /\b_super\b/ : /.*/, - // overwrites an object with methods, sets up _super + // newProps - new properties + // oldProps - where the old properties might be + // addTo - what we are adding to inheritProps = function( newProps, oldProps, addTo ) { addTo = addTo || newProps for ( var name in newProps ) { // Check if we're overwriting an existing function - addTo[name] = typeof newProps[name] == "function" && typeof oldProps[name] == "function" && fnTest.test(newProps[name]) ? (function( name, fn ) { + addTo[name] = isFunction(newProps[name]) && + isFunction(oldProps[name]) && + fnTest.test(newProps[name]) ? (function( name, fn ) { return function() { var tmp = this._super, ret; @@ -35,7 +45,7 @@ steal.plugins("jquery").then(function( $ ) { }; })(name, newProps[name]) : newProps[name]; } - }; + }, /** @@ -44,42 +54,44 @@ steal.plugins("jquery").then(function( $ ) { * @tag core * @download dist/jquery/jquery.class.js * @test jquery/class/qunit.html - * Class provides simulated inheritance in JavaScript. Use $.Class to bridge the gap between - * jQuery's functional programming style and Object Oriented Programming. - * It is based off John Resig's [http://ejohn.org/blog/simple-javascript-inheritance/|Simple Class] + * + * Class provides simulated inheritance in JavaScript. Use clss to bridge the gap between + * jQuery's functional programming style and Object Oriented Programming. It + * is based off John Resig's [http://ejohn.org/blog/simple-javascript-inheritance/|Simple Class] * Inheritance library. Besides prototypal inheritance, it includes a few important features: - * <ul> - * <li>Static inheritance</li> - * <li>Introspection</li> - * <li>Namespaces</li> - * <li>Setup and initialization methods</li> - * <li>Easy callback function creation</li> - * </ul> - * <h2>Static v. Prototype</h2> - * <p>Before learning about Class, it's important to + * + * - Static inheritance + * - Introspection + * - Namespaces + * - Setup and initialization methods + * - Easy callback function creation + * + * + * ## Static v. Prototype + * + * Before learning about Class, it's important to * understand the difference between - * a class's <b>static</b> and <b>prototype</b> properties. - * </p> - * @codestart - * //STATIC - * MyClass.staticProperty //shared property - * - * //PROTOTYPE - * myclass = new MyClass() - * myclass.prototypeMethod() //instance method - * @codeend - * <p>A static (or class) property is on the Class constructor + * a class's __static__ and __prototype__ properties. + * + * //STATIC + * MyClass.staticProperty //shared property + * + * //PROTOTYPE + * myclass = new MyClass() + * myclass.prototypeMethod() //instance method + * + * A static (or class) property is on the Class constructor * function itself - * and can be thought of being shared by all instances of the Class. - * Prototype propertes are available only on instances of the Class. - * </p> - * <h2>A Basic Class</h2> - * <p>The following creates a Monster class with a + * and can be thought of being shared by all instances of the + * Class. Prototype propertes are available only on instances of the Class. + * + * ## A Basic Class + * + * The following creates a Monster class with a * name (for introspection), static, and prototype members. * Every time a monster instance is created, the static * count is incremented. * - * </p> * @codestart * $.Class.extend('Monster', * /* @static *| @@ -121,48 +133,52 @@ steal.plugins("jquery").then(function( $ ) { * * @codeend * - * <p> + * * Notice that the prototype <b>init</b> function is called when a new instance of Monster is created. - * </p> - * <h2>Inheritance</h2> - * <p>When a class is extended, all static and prototype properties are available on the new class. + * + * + * ## Inheritance + * + * When a class is extended, all static and prototype properties are available on the new class. * If you overwrite a function, you can call the base class's function by calling * <code>this._super</code>. Lets create a SeaMonster class. SeaMonsters are less * efficient at eating small children, but more powerful fighters. - * </p> - * @codestart - * Monster.extend("SeaMonster",{ - * eat: function( smallChildren ) { - * this._super(smallChildren / 2); - * }, - * fight: function() { - * this.health -= 1; - * } - * }); - * - * lockNess = new SeaMonster('Lock Ness'); - * lockNess.eat(4); //health = 12 - * lockNess.fight(); //health = 11 - * @codeend - * <h3>Static property inheritance</h3> + * + * + * Monster.extend("SeaMonster",{ + * eat: function( smallChildren ) { + * this._super(smallChildren / 2); + * }, + * fight: function() { + * this.health -= 1; + * } + * }); + * + * lockNess = new SeaMonster('Lock Ness'); + * lockNess.eat(4); //health = 12 + * lockNess.fight(); //health = 11 + * + * ### Static property inheritance + * * You can also inherit static properties in the same way: - * @codestart - * $.Class.extend("First", - * { - * staticMethod: function() { return 1;} - * },{}) + * + * $.Class.extend("First", + * { + * staticMethod: function() { return 1;} + * },{}) * - * First.extend("Second",{ - * staticMethod: function() { return this._super()+1;} - * },{}) + * First.extend("Second",{ + * staticMethod: function() { return this._super()+1;} + * },{}) * - * Second.staticMethod() // -> 2 - * @codeend - * <h2>Namespaces</h2> - * <p>Namespaces are a good idea! We encourage you to namespace all of your code. + * Second.staticMethod() // -> 2 + * + * ## Namespaces + * + * Namespaces are a good idea! We encourage you to namespace all of your code. * It makes it possible to drop your code into another app without problems. * Making a namespaced class is easy: - * </p> + * * @codestart * $.Class.extend("MyNamespace.MyClass",{},{}); * @@ -279,14 +295,14 @@ steal.plugins("jquery").then(function( $ ) { * */ - jQuery.Class = function() { + clss = $.Class = function() { if (arguments.length) { - jQuery.Class.extend.apply(jQuery.Class, arguments); + clss.extend.apply(clss, arguments); } }; /* @Static*/ - $.extend($.Class, { + extend(clss, { /** * @function callback * Returns a callback function for a function on this Class. @@ -342,25 +358,25 @@ steal.plugins("jquery").then(function( $ ) { callback: function( funcs ) { //args that should be curried - var args = jQuery.makeArray(arguments), + var args = makeArray(arguments), self; funcs = args.shift(); - if (!jQuery.isArray(funcs) ) { + if (!isArray(funcs) ) { funcs = [funcs]; } self = this; //@steal-remove-start for( var i =0; i< funcs.length;i++ ) { - if(typeof funcs[i] == "string" && typeof this[funcs[i]] !== 'function'){ + if(typeof funcs[i] == "string" && !isFunction(this[funcs[i]])){ throw ("class.js "+( this.fullName || this.Class.fullName)+" does not have a "+funcs[i]+"method!"); } } //@steal-remove-end return function class_cb() { - var cur = args.concat(jQuery.makeArray(arguments)), + var cur = concatArgs(args, arguments), isString, length = funcs.length, f = 0, @@ -378,7 +394,7 @@ steal.plugins("jquery").then(function( $ ) { } cur = (isString ? self[func] : func).apply(self, cur || []); if ( f < length - 1 ) { - cur = !jQuery.isArray(cur) || cur._use_call ? [cur] : cur + cur = !isArray(cur) || cur._use_call ? [cur] : cur } } return cur; @@ -397,15 +413,7 @@ steal.plugins("jquery").then(function( $ ) { * @param {Object} [current=window] the object you want to look in. * @return {Object} the object you are looking for. */ - getObject: function( objectName, current ) { - var current = current || window, - parts = objectName ? objectName.split(/\./) : [], - i = 0; - for (; i < parts.length; i++ ) { - current = current[parts[i]] || (current[parts[i]] = {}) - } - return current; - }, + getObject: $.String.getObject, /** * @function newInstance * Creates a new instance of the class. This method is useful for creating new instances @@ -424,19 +432,38 @@ steal.plugins("jquery").then(function( $ ) { args = inst.setup.apply(inst, arguments); } if ( inst.init ) { - inst.init.apply(inst, $.isArray(args) ? args : arguments); + inst.init.apply(inst, isArray(args) ? args : arguments); } return inst; }, /** - * Copy and overwrite options from old class - * @param {Object} oldClass - * @param {String} fullName - * @param {Object} staticProps - * @param {Object} protoProps + * Setup gets called on the inherting class with the base class followed by the + * inheriting class's raw properties. + * + * Setup will deeply extend a static defaults property on the base class with + * properties on the base class. For example: + * + * $.Class("MyBase",{ + * defaults : { + * foo: 'bar' + * } + * },{}) + * + * MyBase("Inheriting",{ + * defaults : { + * newProp : 'newVal' + * } + * },{} + * + * Inheriting.defaults -> {foo: 'bar', 'newProp': 'newVal'} + * + * @param {Object} baseClass the base class that is being inherited from + * @param {String} fullName the name of the new class + * @param {Object} staticProps the static properties of the new class + * @param {Object} protoProps the prototype properties of the new class */ - setup: function( oldClass, fullName ) { - this.defaults = $.extend(true, {}, oldClass.defaults, this.defaults); + setup: function( baseClass, fullName ) { + this.defaults = extend(true, {}, baseClass.defaults, this.defaults); return arguments; }, rawInstance: function() { @@ -500,12 +527,12 @@ steal.plugins("jquery").then(function( $ ) { } // Copy old stuff onto class for ( name in this ) { - if ( this.hasOwnProperty(name) && $.inArray(name, ['prototype', 'defaults', 'getObject']) == -1 ) { + if ( this.hasOwnProperty(name) ) { Class[name] = this[name]; } } - // do static inheritance + // copy new props on class inheritProps(klass, this, Class); // do namespace stuff @@ -513,12 +540,12 @@ steal.plugins("jquery").then(function( $ ) { var parts = fullName.split(/\./), shortName = parts.pop(), - current = $.Class.getObject(parts.join('.')), + current = clss.getObject(parts.join('.'), window, true), namespace = current; //@steal-remove-start if (!Class.nameOk ) { - steal.dev.isHappyName(fullName) + //steal.dev.isHappyName(fullName) } if(current[shortName]){ steal.dev.warn("class.js There's already something called "+fullName) @@ -528,7 +555,7 @@ steal.plugins("jquery").then(function( $ ) { } // set things that can't be overwritten - $.extend(Class, { + extend(Class, { prototype: prototype, namespace: namespace, shortName: shortName, @@ -550,7 +577,7 @@ steal.plugins("jquery").then(function( $ ) { * @codeend */ - var args = Class.setup.apply(Class, [_super_class].concat($.makeArray(arguments))); + var args = Class.setup.apply(Class, concatArgs([_super_class],arguments)); if ( Class.init ) { Class.init.apply(Class, args || []); @@ -560,17 +587,28 @@ steal.plugins("jquery").then(function( $ ) { return Class; /** * @function setup - * Called with the same arguments as new Class(arguments ...) when a new instance is created. - * @codestart - * $.Class.extend("MyClass", - * { - * setup: function( val ) { - * this.val = val; - * } - * }) - * var mc = new MyClass("Check Check") - * mc.val //-> 'Check Check' - * @codeend + * If a setup method is provided, it is called when a new + * instances is created. It gets passed the same arguments that + * were given to the Class constructor function (<code> new Class( arguments ... )</code>). + * + * $.Class("MyClass", + * { + * setup: function( val ) { + * this.val = val; + * } + * }) + * var mc = new MyClass("Check Check") + * mc.val //-> 'Check Check' + * + * Setup is called before [jQuery.Class.prototype.init init]. If setup + * return an array, those arguments will be used for init. + * + * $.Class("jQuery.Controller",{ + * setup : function(htmlElement, rawOptions){ + * return [$(htmlElement), + * $.extend({}, this.Class.defaults, rawOptions )] + * } + * }) * * <div class='whisper'>PRO TIP: * Setup functions are used to normalize constructor arguments and provide a place for @@ -578,23 +616,38 @@ steal.plugins("jquery").then(function( $ ) { * run. * </div> * + * Setup is not defined on $.Class itself, so calling super in inherting classes + * will break. Don't do the following: + * + * $.Class("Thing",{ + * setup : function(){ + * this._super(); // breaks! + * } + * }) + * * @return {Array|undefined} If an array is return, [jQuery.Class.prototype.init] is * called with those arguments; otherwise, the original arguments are used. */ //break up /** * @function init - * Called with the same arguments as new Class(arguments ...) when a new instance is created. - * @codestart - * $.Class.extend("MyClass", - * { - * init: function( val ) { - * this.val = val; - * } - * }) - * var mc = new MyClass("Check Check") - * mc.val //-> 'Check Check' - * @codeend + * If an <code>init</code> method is provided, it gets called when a new instance + * is created. Init gets called after [jQuery.Class.prototype.setup setup], typically with the + * same arguments passed to the Class + * constructor: (<code> new Class( arguments ... )</code>). + * + * $.Class("MyClass", + * { + * init: function( val ) { + * this.val = val; + * } + * }) + * var mc = new MyClass(1) + * mc.val //-> 1 + * + * [jQuery.Class.prototype.setup Setup] is able to modify the arguments passed to init. Read + * about it there. + * */ //Breaks up code /** @@ -625,7 +678,7 @@ steal.plugins("jquery").then(function( $ ) { - jQuery.Class.prototype. + clss.prototype. /** * @function callback * Returns a callback function. This does the same thing as and is described better in [jQuery.Class.static.callback]. @@ -636,7 +689,7 @@ steal.plugins("jquery").then(function( $ ) { * next function. * @return {Function} the callback function */ - callback = jQuery.Class.callback; + callback = clss.callback; })(); \ No newline at end of file diff --git a/browserid/static/jquery/class/test/qunit/class_test.js b/browserid/static/jquery/class/class_test.js similarity index 88% rename from browserid/static/jquery/class/test/qunit/class_test.js rename to browserid/static/jquery/class/class_test.js index e3c86cce19d6cdf278b87855b7b844a527027eaa..89dc0495b1b00763877e36a4bbb4644345e83e86 100644 --- a/browserid/static/jquery/class/test/qunit/class_test.js +++ b/browserid/static/jquery/class/class_test.js @@ -1,3 +1,7 @@ +steal + .plugins("jquery/class") //load your app + .plugins('funcunit/qunit').then(function(){ + module("jquery/class"); test("Creating", function(){ @@ -46,7 +50,7 @@ test("Creating", function(){ new Dog(); new Animal(); new Animal(); - ajax = new Ajax(1000); + var ajax = new Ajax(1000); equals(2, Animal.count, "right number of animals"); equals(1, Dog.count, "right number of animals") @@ -178,3 +182,25 @@ test("Creating without extend", function(){ }); new Foo().dude(true); }) + + +/* Not sure I want to fix this yet. +test("Super in derived when parent doesn't have init", function(){ + $.Class("Parent",{ + }); + + Parent("Derived",{ + init : function(){ + this._super(); + } + }); + + try { + new Derived(); + ok(true, "Can call super in init safely") + } catch (e) { + ok(false, "Failed to call super in init with error: " + e) + } +})*/ + +}); \ No newline at end of file diff --git a/browserid/static/jquery/class/qunit.html b/browserid/static/jquery/class/qunit.html index 255812e463aaaaecd1d7b6da8c584c65ba376869..503f699fd1c3c30446d7a2a2f9b994a6e1f02b5f 100644 --- a/browserid/static/jquery/class/qunit.html +++ b/browserid/static/jquery/class/qunit.html @@ -10,6 +10,6 @@ <h2 id="qunit-userAgent"></h2> <ol id="qunit-tests"></ol> <div id="qunit-test-area"></div> - <script type='text/javascript' src='../../steal/steal.js?steal[app]=jquery/class/test/qunit'></script> + <script type='text/javascript' src='../../steal/steal.js?jquery/class/class_test.js'></script> </body> </html> \ No newline at end of file diff --git a/browserid/static/jquery/controller/controller.html b/browserid/static/jquery/controller/controller.html index e22531663edce2e7ea5c12a40a519fe97a99e79d..43604741f11ec731540561054325ab8d308da34a 100644 --- a/browserid/static/jquery/controller/controller.html +++ b/browserid/static/jquery/controller/controller.html @@ -54,7 +54,7 @@ </script> <script type='text/javascript' id="demo-source"> // create a new Tabs class -$.Controller.extend("Tabs",{ +$.Controller("Tabs",{ // initialize widget init : function(el){ diff --git a/browserid/static/jquery/controller/controller.js b/browserid/static/jquery/controller/controller.js index 2937bd3b6fa072735db377d80bfdcd76c113c2d8..3aac62d8d4eb6c46b3708ef2fde3723d9f6639a8 100644 --- a/browserid/static/jquery/controller/controller.js +++ b/browserid/static/jquery/controller/controller.js @@ -3,26 +3,30 @@ steal.plugins('jquery/class', 'jquery/lang', 'jquery/event/destroyed').then(func // ------- helpers ------ // Binds an element, returns a function that unbinds var bind = function( el, ev, callback ) { - var wrappedCallback; + var wrappedCallback, + binder = el.bind && el.unbind ? el : $(isFunction(el) ? [el] : el); //this is for events like >click. if ( ev.indexOf(">") === 0 ) { ev = ev.substr(1); wrappedCallback = function( event ) { if ( event.target === el ) { callback.apply(this, arguments); - } else { - event.handled = null; - } + } }; } - $(el).bind(ev, wrappedCallback || callback); + binder.bind(ev, wrappedCallback || callback); // if ev name has >, change the name and bind // in the wrapped callback, check that the element matches the actual element return function() { - $(el).unbind(ev, wrappedCallback || callback); + binder.unbind(ev, wrappedCallback || callback); el = ev = callback = wrappedCallback = null; }; }, + makeArray = $.makeArray, + isArray = $.isArray, + isFunction = $.isFunction, + extend = $.extend, + Str = $.String, // Binds an element, returns a function that unbinds delegate = function( el, selector, ev, callback ) { $(el).delegate(selector, ev, callback); @@ -39,7 +43,7 @@ steal.plugins('jquery/class', 'jquery/lang', 'jquery/event/destroyed').then(func */ shifter = function shifter(cb) { return function() { - return cb.apply(null, [$(this)].concat(Array.prototype.slice.call(arguments, 0))); + return cb.apply(null, [this.nodeName ? $(this) : this].concat(Array.prototype.slice.call(arguments, 0))); }; }, // matches dots @@ -48,17 +52,19 @@ steal.plugins('jquery/class', 'jquery/lang', 'jquery/event/destroyed').then(func controllersReg = /_?controllers?/ig, //used to remove the controller from the name underscoreAndRemoveController = function( className ) { - return $.String.underscore(className.replace("jQuery.", "").replace(dotsReg, '_').replace(controllersReg, "")); + return Str.underscore(className.replace("jQuery.", "").replace(dotsReg, '_').replace(controllersReg, "")); }, // checks if it looks like an action actionMatcher = /[^\w]/, - // gets jus the event - eventCleaner = /^(>?default\.)|(>)/, // handles parameterized action names parameterReplacer = /\{([^\}]+)\}/g, breaker = /^(?:(.*?)\s)?([\w\.\:>]+)$/, - basicProcessor; + basicProcessor, + data = function(el, data){ + return $.data(el, "controllers", data) + }; /** + * @class jQuery.Controller * @tag core * @plugin jquery/controller * @download http://jmvcsite.heroku.com/pluginify?plugins[]=jquery/controller/controller.js @@ -293,7 +299,7 @@ steal.plugins('jquery/class', 'jquery/lang', 'jquery/event/destroyed').then(func * $(".special").foo("bar","something I want to pass") * @codeend */ - $.Class.extend("jQuery.Controller", + $.Class("jQuery.Controller", /** * @Static */ @@ -328,13 +334,13 @@ steal.plugins('jquery/class', 'jquery/lang', 'jquery/event/destroyed').then(func if (!$.fn[pluginname] ) { $.fn[pluginname] = function( options ) { - var args = $.makeArray(arguments), + var args = makeArray(arguments), //if the arg is a method on this controller - isMethod = typeof options == "string" && $.isFunction(controller.prototype[options]), + isMethod = typeof options == "string" && isFunction(controller.prototype[options]), meth = args[0]; - this.each(function() { + return this.each(function() { //check if created - var controllers = $.data(this, "controllers"), + var controllers = data(this), //plugin is actually the controller instance plugin = controllers && controllers[pluginname]; @@ -352,14 +358,12 @@ steal.plugins('jquery/class', 'jquery/lang', 'jquery/event/destroyed').then(func controller.newInstance.apply(controller, [this].concat(args)); } }); - //always return the element - return this; }; } // make sure listensTo is an array //@steal-remove-start - if (!$.isArray(this.listensTo) ) { + if (!isArray(this.listensTo) ) { throw "listensTo is not an array in " + this.fullName; } //@steal-remove-end @@ -367,13 +371,11 @@ steal.plugins('jquery/class', 'jquery/lang', 'jquery/event/destroyed').then(func this.actions = {}; for ( funcName in this.prototype ) { - if ( this.prototype.hasOwnProperty(funcName) ) { - if (!$.isFunction(this.prototype[funcName]) ) { - continue; - } - if ( this._isAction(funcName) ) { - this.actions[funcName] = this._getAction(funcName); - } + if (funcName == 'constructor' || !isFunction(this.prototype[funcName]) ) { + continue; + } + if ( this._isAction(funcName) ) { + this.actions[funcName] = this._action(funcName); } } @@ -398,8 +400,7 @@ steal.plugins('jquery/class', 'jquery/lang', 'jquery/event/destroyed').then(func if ( actionMatcher.test(methodName) ) { return true; } else { - var cleanedEvent = methodName.replace(eventCleaner, ""); - return $.inArray(cleanedEvent, this.listensTo) > -1 || $.event.special[cleanedEvent] || $.Controller.processors[cleanedEvent]; + return $.inArray(methodName, this.listensTo) > -1 || $.event.special[methodName] || processors[methodName]; } }, @@ -410,19 +411,21 @@ steal.plugins('jquery/class', 'jquery/lang', 'jquery/event/destroyed').then(func * @return {Object} null or the processor and pre-split parts. * The processor is what does the binding/subscribing. */ - _getAction: function( methodName, options ) { + _action: function( methodName, options ) { //if we don't have a controller instance, we'll break this guy up later parameterReplacer.lastIndex = 0; if (!options && parameterReplacer.test(methodName) ) { return null; } - var convertedName = options ? $.String.sub(methodName, options) : methodName, - parts = convertedName.match(breaker), + var convertedName = options ? Str.sub(methodName, [options, window]) : methodName, + arr = isArray(convertedName), + parts = (arr ? convertedName[1] : convertedName).match(breaker), event = parts[2], - processor = this.processors[event] || basicProcessor; + processor = processors[event] || basicProcessor; return { processor: processor, - parts: parts + parts: parts, + delegate : arr ? convertedName[0] : undefined }; }, /** @@ -444,12 +447,64 @@ steal.plugins('jquery/class', 'jquery/lang', 'jquery/event/destroyed').then(func * The processor must return a function that when called, * unbinds the event handler. * + * Controller already has processors for the following events: + * + * - change + * - click + * - contextmenu + * - dblclick + * - focusin + * - focusout + * - keydown + * - keyup + * - keypress + * - mousedown + * - mouseenter + * - mouseleave + * - mousemove + * - mouseout + * - mouseover + * - mouseup + * - reset + * - resize + * - scroll + * - select + * - submit + * + * The following processors always listen on the window or document: + * + * - windowresize + * - windowscroll + * - load + * - unload + * - hashchange + * - ready + * + * Which means anytime the window is resized, the following controller will listen to it: + * + * $.Controller('Sized',{ + * windowresize : function(){ + * this.element.width(this.element.parent().width() / 2); + * } + * }); + * + * $('.foo').sized(); */ processors: {}, /** * @attribute listensTo * A list of special events this controller listens too. You only need to add event names that * are whole words (ie have no special characters). + * + * $.Controller('TabPanel',{ + * listensTo : ['show'] + * },{ + * 'show' : function(){ + * this.element.show(); + * } + * }) + * + * $('.foo').tab_panel().trigger("show"); */ listensTo: [], /** @@ -518,7 +573,7 @@ steal.plugins('jquery/class', 'jquery/lang', 'jquery/event/destroyed').then(func this.element = $(element).addClass(cls._fullName); //set in data - ($.data(element, "controllers") || $.data(element, "controllers", {}))[cls._fullName] = this; + (data(element) || data(element, {}))[cls._fullName] = this; //adds bindings this._bindings = []; @@ -547,14 +602,14 @@ steal.plugins('jquery/class', 'jquery/lang', 'jquery/event/destroyed').then(func * * */ - this.options = $.extend($.extend(true, {}, cls.defaults), options); + this.options = extend( extend(true, {}, cls.defaults), options); //go through the cached list of actions and use the processor to bind for ( funcName in cls.actions ) { if ( cls.actions.hasOwnProperty(funcName) ) { - ready = cls.actions[funcName] || cls._getAction(funcName, this.options); + ready = cls.actions[funcName] || cls._action(funcName, this.options); this._bindings.push( - ready.processor(element, ready.parts[2], ready.parts[1], this.callback(funcName), this)); + ready.processor(ready.delegate || element, ready.parts[2], ready.parts[1], this.callback(funcName), this)); } } @@ -572,7 +627,7 @@ steal.plugins('jquery/class', 'jquery/lang', 'jquery/event/destroyed').then(func var destroyCB = shifter(this.callback("destroy")); this.element.bind("destroyed", destroyCB); this._bindings.push(function( el ) { - destroyCB.removed = true; + //destroyCB.removed = true; $(element).unbind("destroyed", destroyCB); }); @@ -712,7 +767,7 @@ steal.plugins('jquery/class', 'jquery/lang', 'jquery/event/destroyed').then(func * @param {Object} options */ update: function( options ) { - $.extend(this.options, options); + extend(this.options, options); }, /** * Destroy unbinds and undelegates all event handlers on this controller, @@ -748,18 +803,13 @@ steal.plugins('jquery/class', 'jquery/lang', 'jquery/event/destroyed').then(func this.element.removeClass(fname); $.each(this._bindings, function( key, value ) { - if ( $.isFunction(value) ) { - value(self.element[0]); - } + value(self.element[0]); }); delete this._actions; - - controllers = this.element.data("controllers"); - if ( controllers && controllers[fname] ) { - delete controllers[fname]; - } + delete this.element.data("controllers")[fname]; + $(this).triggerHandler("destroyed"); //in case we want to know if the controller is removed this.element = null; }, @@ -780,6 +830,7 @@ steal.plugins('jquery/class', 'jquery/lang', 'jquery/event/destroyed').then(func _set_called: true }); + var processors = $.Controller.processors, //------------- PROCESSSORS ----------------------------- //processors do the binding. They return a function that @@ -789,45 +840,23 @@ steal.plugins('jquery/class', 'jquery/lang', 'jquery/event/destroyed').then(func var c = controller.Class; // document controllers use their name as an ID prefix. - if ( c.onDocument && !/^Main(Controller)?$/.test(c.shortName) ) { //prepend underscore name if necessary + if ( c.onDocument && !/^Main(Controller)?$/.test(c.shortName) && el === controller.element[0]) { //prepend underscore name if necessary selector = selector ? "#" + c._shortName + " " + selector : "#" + c._shortName; } return binder(el, event, shifter(cb), selector); }; - var processors = $.Controller.processors, - //a window event only happens on the window - windowEvent = function( el, event, selector, cb ) { - return binder(window, event.replace(/window/, ""), shifter(cb)); - }; + //set commong events to be processed as a basicProcessor - $.each("change click contextmenu dblclick keydown keyup keypress mousedown mousemove mouseout mouseover mouseup reset windowresize resize windowscroll scroll select submit dblclick focusin focusout load unload ready hashchange mouseenter mouseleave".split(" "), function( i, v ) { + $.each("change click contextmenu dblclick keydown keyup keypress mousedown mousemove mouseout mouseover mouseup reset resize scroll select submit focusin focusout mouseenter mouseleave".split(" "), function( i, v ) { processors[v] = basicProcessor; }); - $.each(["windowresize", "windowscroll", "load", "ready", "unload", "hashchange"], function( i, v ) { - processors[v] = windowEvent; - }); - //the ready processor happens on the document - processors.ready = function( el, event, selector, cb ) { - $(shifter(cb)); //cant really unbind - }; /** * @add jQuery.fn */ - $.fn.mixin = function() { - //create a bunch of controllers - var controllers = $.makeArray(arguments), - forLint; - return this.each(function() { - for ( var i = 0; i < controllers.length; i++ ) { - forLint = new controllers[i](this); - } - - }); - }; //used to determine if a controller instance is one of controllers //controllers can be strings or classes var i, isAControllerOf = function( instance, controllers ) { @@ -845,17 +874,13 @@ steal.plugins('jquery/class', 'jquery/lang', 'jquery/event/destroyed').then(func * @return {Array} an array of controller instances. */ $.fn.controllers = function() { - var controllerNames = $.makeArray(arguments), + var controllerNames = makeArray(arguments), instances = [], - controllers; + controllers, c, cname; //check if arguments this.each(function() { - var c, cname; controllers = $.data(this, "controllers"); - if (!controllers ) { - return; - } for ( cname in controllers ) { if ( controllers.hasOwnProperty(cname) ) { c = controllers[cname]; diff --git a/browserid/static/jquery/controller/test/qunit/controller_test.js b/browserid/static/jquery/controller/controller_test.js similarity index 72% rename from browserid/static/jquery/controller/test/qunit/controller_test.js rename to browserid/static/jquery/controller/controller_test.js index 210685a00456dde2d73e02bc6a2aeee6bbc80a09..ffbfa087b1c0f6422f49ff40a91d7d112cd08697 100644 --- a/browserid/static/jquery/controller/test/qunit/controller_test.js +++ b/browserid/static/jquery/controller/controller_test.js @@ -1,3 +1,8 @@ +steal + .plugins("jquery/controller",'jquery/controller/subscribe') //load your app + .plugins('funcunit/qunit') //load qunit + .then(function(){ + module("jquery/controller") test("subscribe testing works", function(){ @@ -141,7 +146,7 @@ test("parameterized actions", function(){ test("windowresize", function(){ var called = false; jQuery.Controller.extend("WindowBind",{ - "windowresize" : function() { + "{window} resize" : function() { called = true; } }) @@ -168,3 +173,67 @@ test("delegate", function(){ ok(called, "delegate works") $("#qunit-test-area").html("") }) + +test("inherit", function(){ + var called = false; + $.Controller.extend( "Parent", { + click: function(){ + called = true; + } + }) + Parent.extend( "Child", { + + }) + var els = $("<div><span><a href='#'>click me</a></span></div>").appendTo($("#qunit-test-area")) + els.child(); + els.find("a").trigger('click') + ok(called, "inherited the click method") + $("#qunit-test-area").html("") +}); + +test("objects in action", function(){ + $.Controller('Thing',{ + "{item} someEvent" : function(thing, ev){ + ok(true, "called"); + equals(ev.type, "someEvent","correct event") + equals(this.Class.fullName, "Thing", "This is a controller isntance") + equals(thing.name,"Justin","Raw, not jQuery wrapped thing") + } + }); + + var thing1 = {name: "Justin"}; + + var ta = $("<div/>").appendTo( $("#qunit-test-area") ) + ta.thing({item : thing1}); + + $(thing1).trigger("someEvent"); + + $("#qunit-test-area").html(""); + +}); + +test("dot",function(){ + $.Controller("Dot",{ + "foo.bar" : function(){ + ok(true,'called') + } + }); + + var ta = $("<div/>").appendTo( $("#qunit-test-area") ); + ta.dot().trigger("foo.bar"); + $("#qunit-test-area").html(""); +}) + +// HTMLFormElement[0] breaks +test("the right element", 1, function(){ + $.Controller('FormTester',{ + init : function(){ + equals(this.element[0].nodeName.toLowerCase(), "form" ) + } + }) + $("<form><input name='one'/></form>").appendTo( $("#qunit-test-area") ) + .form_tester(); + $("#qunit-test-area").html("") +}) + +}); diff --git a/browserid/static/jquery/controller/history/history.js b/browserid/static/jquery/controller/history/history.js index c17d85264f052be1af52e5c7b4abd2dc44a97d23..95befeb1d75a4e1bcbd617166ee144c6e4efd86f 100644 --- a/browserid/static/jquery/controller/history/history.js +++ b/browserid/static/jquery/controller/history/history.js @@ -1,5 +1,6 @@ steal.plugins('jquery/controller/subscribe', - 'jquery/event/hashchange').then(function($){ + 'jquery/event/hashchange', + 'jquery/lang/deparam').then(function($){ /** * @page jquery.controller.history History Events @@ -8,6 +9,8 @@ steal.plugins('jquery/controller/subscribe', * The jquery/controller/history plugin adds * browser hash (#) based history support. * + * It allows you to listen to hashchange events with OpenAjax.hub. + * * Typically you subscribe to a history event in your controllers: * * $.Controller("MyHistory",{ @@ -16,40 +19,61 @@ steal.plugins('jquery/controller/subscribe', * } * }) * + * ## Event Names + * + * When a history event happens, an OpenAjax message is produced that + * starts with "history.". The remainder of the message name depends on the + * value of the "hash". + * * The following shows hash values and * the corresponding published message and data. * * "#foo=bar" -> "history.index" {foo: bar} * "#foo/bar" -> "history.foo.bar" {} * "#foo&bar=baz" -> "history.foo" {bar: baz} - * + * + * Essentially, if the hash starts with something like #foo/bar, this gets + * added to the message name as "foo.bar". Once "&" is found, it adds the remainder + * as name-value pairs to the message data. + * + * ## Controller Helper Functions + * + * The methods on the left are added to Controller.prototype and make it easier to + * make changes to history. + * */ var keyBreaker = /([^\[\]]+)|(\[\])/g; $.Controller.History = { /** - * + * @hide * returns the pathname part * - * @codestart - * "#foo/bar&foo=bar" -> 'foo/bar' - * @codeend + * // if the url is "#foo/bar&foo=bar" + * $.Controller.History.pathname() -> 'foo/bar' + * */ pathname : function(path) { var parts = path.match(/#([^&]*)/); return parts ? parts[1] : null }, /** + * @hide * returns the search part, but without the first & - * @codestart - * "#foo/bar&foo=bar" -> 'foo=barr' - * @codeend + * + * // if the url is "#foo/bar&foo=bar" + * $.Controller.History.search() -> 'foo=bar' */ search : function(path) { var parts = path.match(/#[^&]*&(.*)/); return parts ? parts[1] : null }, + /** + * @hide + * Returns the data + * @param {Object} path + */ getData: function(path) { var search = $.Controller.History.search(path), digitTest = /^\d+$/; @@ -60,39 +84,7 @@ $.Controller.History = { // Support the legacy format that used MVC.Object.to_query_string that used %20 for // spaces and not the '+' sign; search = search.replace(/\+/g,"%20") - - var data = {}, - pairs = search.split('&'), - current; - - for(var i=0; i < pairs.length; i++){ - current = data; - var pair = pairs[i].split('='); - - // if we find foo=1+1=2 - if(pair.length != 2) { - pair = [pair[0], pair.slice(1).join("=")] - } - - var key = decodeURIComponent(pair[0]), - value = decodeURIComponent(pair[1]), - parts = key.match(keyBreaker); - - for ( var j = 0; j < parts.length - 1; j++ ) { - var part = parts[j]; - if (!current[part] ) { - current[part] = digitTest.test(part) || parts[j+1] == "[]" ? [] : {} - } - current = current[part]; - } - lastPart = parts[parts.length - 1]; - if(lastPart == "[]"){ - current.push(value) - }else{ - current[lastPart] = value; - } - } - return data; + return $.String.deparam(search); } }; @@ -117,10 +109,13 @@ jQuery(function($) { $(window).trigger('hashchange') },1) //immediately after ready }) - +/** + * @add jQuery.Controller.prototype + */ $.extend($.Controller.prototype, { /** + * @parent jquery.controller.history * Redirects to another page. * @plugin 'dom/history' * @param {Object} options an object that will turned into a url like #controller/action¶m1=value1 @@ -130,6 +125,7 @@ $.extend($.Controller.prototype, { location.hash = point; }, /** + * @parent jquery.controller.history * Redirects to another page by replacing current URL with the given one. This * call will not create a new entry in the history. * @plugin 'dom/history' @@ -140,6 +136,7 @@ $.extend($.Controller.prototype, { location.replace(location.href.split('#')[0] + point); }, /** + * @parent jquery.controller.history * Adds history point to browser history. * @plugin 'dom/history' * @param {Object} options an object that will turned into a url like #controller/action¶m1=value1 @@ -150,6 +147,8 @@ $.extend($.Controller.prototype, { location.hash = point; }, /** + * @hide + * @parent jquery.controller.history * Creates a history point from given options. Resultant history point is like #controller/action¶m1=value1 * @plugin 'dom/history' * @param {Object} options an object that will turned into history point @@ -172,6 +171,7 @@ $.extend($.Controller.prototype, { }, /** + * @parent jquery.controller.history * Provides current window.location parameters as object properties. * @plugin 'dom/history' */ diff --git a/browserid/static/jquery/controller/history/html5/html5.js b/browserid/static/jquery/controller/history/html5/html5.js new file mode 100644 index 0000000000000000000000000000000000000000..c2c940758065c16eae7afed56a6c54047b24fe5f --- /dev/null +++ b/browserid/static/jquery/controller/history/html5/html5.js @@ -0,0 +1,31 @@ +steal.plugins('jquery/controller/subscribe').then(function($){ + + var hasHistoryManagementSupport = !!(window.history && history.pushState); + + if (hasHistoryManagementSupport) { + steal.dev.log("WARNING: The current browser does not support HTML5 History Management."); + } else { + window.onpopstate = function(event) { + OpenAjax.hub.publish("history."+location.href, (event && event.state) || {}); + }; + + setTimeout(function(){ + window.onpopstate(); + }, 1); // immediately after ready + + $.extend($.Controller.prototype, { + redirectTo: function(url, data, title) { + data = data || {}; + window.history.pushState(data, title, url); + this.publish("history." + url, data); + } + }); + + $.Controller.processors["windowpopstate"] = function(el, event, selector, cb) { + $(window).bind("popstate", cb); + return function(){ + $(window).unbind("popstate", cb); + } + }; + } +}) diff --git a/browserid/static/jquery/controller/history/html5/qunit.html b/browserid/static/jquery/controller/history/html5/qunit.html new file mode 100644 index 0000000000000000000000000000000000000000..7274e70224d91af7b917e46e017d3b3cfc10af66 --- /dev/null +++ b/browserid/static/jquery/controller/history/html5/qunit.html @@ -0,0 +1,21 @@ +<html> + <head> + <link rel="stylesheet" type="text/css" href="../../../../funcunit/qunit/qunit.css" /> + <style> + body { + margin: 0px; padding: 0px; + } + </style> + <script type='text/javascript' src='../../../../steal/steal.js?steal[app]=jquery/controller/history/html5/qunit'></script> + </head> + <body> + + <h1 id="qunit-header">HTML5 History Test Suite</h1> + <h2 id="qunit-banner"></h2> + <div id="qunit-testrunner-toolbar"></div> + <h2 id="qunit-userAgent"></h2> + <div id="test-content"></div> + <ol id="qunit-tests"></ol> + <div id="qunit-test-area"></div> + </body> +</html> diff --git a/browserid/static/jquery/controller/history/html5/qunit/qunit.js b/browserid/static/jquery/controller/history/html5/qunit/qunit.js new file mode 100755 index 0000000000000000000000000000000000000000..1687eaefb7aae80ba33f1298851b6a961f99b0cb --- /dev/null +++ b/browserid/static/jquery/controller/history/html5/qunit/qunit.js @@ -0,0 +1,82 @@ +steal.plugins("funcunit/qunit", "jquery/controller/history/html5").then(function($){ + +module("jquery/controller/history/html5",{ + setup: function(){ + + } +}) + + +$.Controller.extend("HTML5HistoryTestController", { +}, +{ + "history.** subscribe": function(event_name, params) { + this["gotHistory"](event_name.replace("history.", ""), params); + }, + + gotHistory: $.noop, + + "window windowpopstate": function(ev) { + this["gotPopState"](location.href, (ev.originalEvent && ev.originalEvent.state) || {}); + }, + + gotPopState: $.noop +}); + +var originalLocation = location.href; + +asyncTest("Controller redirect should work", function(){ + expect(1); + var testController = new HTML5HistoryTestController($("<div />").get(0)); + + var testLocation = "/test/location"; + testController["gotHistory"] = function(location, state) { + start(); + equals(location, testLocation); + testController["gotHistory"] = $.noop; + testController.redirectTo(originalLocation); + }; + + stop(); + testController.redirectTo(testLocation); +}); + +asyncTest("State data should persist", function(){ + expect(1); + var testController = new HTML5HistoryTestController($("<div />").get(0)); + + testController["gotHistory"] = function(location, state) { + start(); + equals(state.hi, "mom"); + testController["gotHistory"] = $.noop; + testController.redirectTo(originalLocation); + }; + + stop(); + testController.redirectTo("/test/location", { hi: "mom" }); +}); + +asyncTest("Should listen to windowpopstate", function(){ + expect(2); + var testController = new HTML5HistoryTestController($("<div />").get(0)); + + testController["gotPopState"] = function(location, state) { + start(); + ok(location.indexOf("/test/location") !== -1); + equals(state.hi, "mom"); + testController["gotPopState"] = $.noop; + testController.redirectTo(originalLocation); + }; + + stop(); + testController.redirectTo("/test/location", { hi: "mom" }); + + testController["gotHistory"] = function(location, state) { + testController["gotHistory"] = $.noop; + window.history.back(); + }; + + testController.redirectTo("/test/location2", { hi: "mom2" }); +}); + +}); diff --git a/browserid/static/jquery/controller/pages/listening.js b/browserid/static/jquery/controller/pages/listening.js index cbd8bf674f2c8ff29c694ffe1ab52bacba0d6b01..054f144030d4364a10a43d1cb4e324091b9f4411 100644 --- a/browserid/static/jquery/controller/pages/listening.js +++ b/browserid/static/jquery/controller/pages/listening.js @@ -2,17 +2,19 @@ @page jquery.controller.listening Listening To Events @parent jQuery.Controller -Controllers organize event handlers and make listening to -events really easy. +Controllers make creating and tearing down event handlers extremely +easy. The tearingdown of event handlers is especially important +in preventing memory leaks in long lived applications. ## Automatic Binding When a [jQuery.Controller.prototype.setup new controller is created], -contoller checks its methods for functions that are named like -an event handler. It automatically binds these functions to the -controller's [jQuery.Controller.prototype.element element] with event delegation. When +contoller checks its prototype methods for functions that are named like +event handlers. It binds these functions to the +controller's [jQuery.Controller.prototype.element element] with +event delegation. When the controller is destroyed (or it's element is removed from the page), controller -will unbind all its event handlers automatically. +will unbind its event handlers automatically. For example, each of the following controller's functions will automatically bound: @@ -20,14 +22,14 @@ bound: $.Controller("Crazy",{ // listens to all clicks on this element - "click" : function(){}, + "click" : function(el, ev){}, // listens to all mouseovers on // li elements withing this controller - "li mouseover" : function(){} + "li mouseover" : function(el, ev){} // listens to the window being resized - "windowresize" : function(){} + "{window} resize" : function(window, ev){} }) Controller will bind function names with spaces, standard DOM events, and @@ -37,7 +39,8 @@ In general, Controller will know automatically when to bind event handler functi one case - event names without selectors that are not in $.event.special. But to correct for this, you just need to add the -function to the listensTo property. Here's how: +function to the [jQuery.Controller.static.listensTo listensTo] +property. Here's how: $.Controller.extend("MyShow",{ listensTo: ["show"] @@ -65,31 +68,106 @@ as parameters. <b>this</b> refers to the controller instance. For example: } }) -## Parameterized Event Bindings +## Templated Event Bindings -Controller lets you parameterize event names and selectors. The following -makes 2 buttons. One says hello on click, the other on mouseenter. +One of Controller's most powerful features is templated event +handlers. You can parameterize the event name, +the selector, or event the root element. - $.Controller("Hello",{ - "{helloEvent}" : function(){ - alert('hello') +### Templating event names and selectors: + +Often, you want to make a widget's behavior +configurable. A common example is configuring which event +a menu should show a sub-menu (ex: on click or mouseenter). The +following controller lets you configure when a menu should show +sub-menus: + +The following makes two buttons. One says hello on click, +the other on a 'tap' event. + + $.Controller("Menu",{ + "li {showEvent}" : function(el){ + el.children('ul').show() } }) - $("#clickMe").hello({helloEvent : "click"}); - $("#touchMe").hello({helloEvent : "mouseenter"}); + $("#clickMe").menu({showEvent : "click"}); + $("#touchMe").menu({showEvent : "mouseenter"}); + +$.Controller replaces value in <code>{}</code> with +values in a +controller's [jQuery.Controller.prototype.options options]. This means +we can easily provide a default <code>showEvent</code> value and create +a menu without providing a value like: + + $.Controller("Menu", + { + defaults : { + showEvent : "click" + } + }, + { + "li {showEvent}" : function(el){ + el.children('ul').show() + } + }); + + $("#clickMe").menu(); //defaults to using click -You can parameterize any part of the method name. The following makes two -lists. One listens for clicks on divs, the other on lis. +Sometimes, we might might want to configure our widget to +use different elements. The following makes the menu widget's +<code>button</code> elements configurable: - $.Controller("List",{ - "{listItem} click" : function(){ - //do something! + $.Controller("Menu",{ + "{button} {showEvent}" : function(el){ + el.children('ul').show() } }) + + $('#buttonMenu').menu({button: "button"}); + +### Templating the root element. + +Finally, controller lets you bind to objects outside +of the [jQuery.Controller.prototype.element controller's element]. + +The following listens to clicks on the window: + + $.Controller("HideOnClick",{ + "{window} click" : function(){ + this.element.hide() + } + }) + +The following listens to Todos being created: + + $.Controller("NewTodos",{ + "{App.Models.Todo} created" : function(Todo, ev, newTodo){ + this.element.append("newTodos.ejs", newTodo) + } + }); + +But instead of making NewTodos only work with the Todo model, +we can make it configurable: + + $.Controller("Newbie",{ + "{model} created" : function(Model, ev, newItem){ + this.element.append(this.options.view, newItem) + } + }); + + $('#newItems').newbie({ + model: App.Models.Todo, + view: "newTodos.ejs" + }) - $("#divs").list({listItem : "div"}); - $("#lis").list({listItem : "li"}); +### How Templated events work + +When looking up a value to replace <code>{}</code>, +controller first looks up the item in the options, then it looks +up the value in the window object. It does not use eval to look up the +object. Instead it uses [jQuery.String.getObject]. + ## Subscribing to OpenAjax messages and custom bindings diff --git a/browserid/static/jquery/controller/pages/plugin.js b/browserid/static/jquery/controller/pages/plugin.js index d82e324abc7d65efa9de6aebb0ffa71bd186f400..6c21eb1ebae5ba7ffaa21bbc157de2587afdfdca 100644 --- a/browserid/static/jquery/controller/pages/plugin.js +++ b/browserid/static/jquery/controller/pages/plugin.js @@ -17,7 +17,7 @@ For example, the following controller: } }) -creates a <code>jQuery.fn.my_tabs</code> method that you can use like: +creates a <code>jQuery.fn.my_widget</code> method that you can use like: // create my_widget on each .thing $(".thing").my_widget({message : "Hello"}) diff --git a/browserid/static/jquery/controller/qunit.html b/browserid/static/jquery/controller/qunit.html index 909f3130bc03cbd220c71789ab576c6a32c36b1a..ae8fad54a3cf8af1f0b3be8650e267ae2249ca9a 100644 --- a/browserid/static/jquery/controller/qunit.html +++ b/browserid/static/jquery/controller/qunit.html @@ -6,7 +6,7 @@ margin: 0px; padding: 0px; } </style> - <script type='text/javascript' src='../../steal/steal.js?steal[app]=jquery/controller/test/qunit'></script> + <script type='text/javascript' src='../../steal/steal.js?jquery/controller/controller_test.js'></script> </head> <body> diff --git a/browserid/static/jquery/controller/test/qunit/qunit.js b/browserid/static/jquery/controller/test/qunit/qunit.js deleted file mode 100644 index 611a85199d4813a68e7c21b330c1b3a4cc72746c..0000000000000000000000000000000000000000 --- a/browserid/static/jquery/controller/test/qunit/qunit.js +++ /dev/null @@ -1,9 +0,0 @@ -//we probably have to have this only describing where the tests are -steal - .plugins("jquery/controller",'jquery/controller/subscribe') //load your app - .plugins('funcunit/qunit') //load qunit - .then("controller_test") - -if(steal.browser.rhino){ - steal.plugins('funcunit/qunit/env') -} \ No newline at end of file diff --git a/browserid/static/jquery/controller/view/qunit.html b/browserid/static/jquery/controller/view/qunit.html index cfb5649af91d98a0a9d321976f889ff4fe73f63e..cc57a785595c5a76649c2edfb5a95feeb55469fe 100644 --- a/browserid/static/jquery/controller/view/qunit.html +++ b/browserid/static/jquery/controller/view/qunit.html @@ -6,7 +6,7 @@ margin: 0px; padding: 0px; } </style> - <script type='text/javascript' src='../../../steal/steal.js?steal[app]=jquery/controller/view/test/qunit'></script> + <script type='text/javascript' src='../../../steal/steal.js?jquery/controller/view/test/qunit/controller_view_test.js'></script> </head> <body> diff --git a/browserid/static/jquery/controller/view/test/qunit/controller_view_test.js b/browserid/static/jquery/controller/view/test/qunit/controller_view_test.js index d35ce47c2984585fcbdeb897aac8c7610d073cc7..82188116c9a51d0156cd3492010dc2d0a48e659d 100644 --- a/browserid/static/jquery/controller/view/test/qunit/controller_view_test.js +++ b/browserid/static/jquery/controller/view/test/qunit/controller_view_test.js @@ -1,15 +1,22 @@ -module("jquery/controller/view") -test("this.view", function(){ +steal.plugins('jquery/controller/view','jquery/view/micro','funcunit/qunit') //load qunit + .then(function(){ - $.Controller.extend("jquery.Controller.View.Test.Qunit",{ - init: function() { - this.element.html(this.view()) - } - }) - jQuery.View.ext = ".micro"; - $("#qunit-test-area").append("<div id='cont_view'/>"); + module("jquery/controller/view"); - new jquery.Controller.View.Test.Qunit( $('#cont_view') ); + test("this.view", function(){ + + $.Controller.extend("jquery.Controller.View.Test.Qunit",{ + init: function() { + this.element.html(this.view()) + } + }) + jQuery.View.ext = ".micro"; + $("#qunit-test-area").append("<div id='cont_view'/>"); + + new jquery.Controller.View.Test.Qunit( $('#cont_view') ); + + ok(/Hello World/i.test($('#cont_view').text()),"view rendered") + }); - ok(/Hello World/i.test($('#cont_view').text()),"view rendered") -}); \ No newline at end of file +}); + diff --git a/browserid/static/jquery/dom/compare/compare.js b/browserid/static/jquery/dom/compare/compare.js index 76813043e9c8bf18f98ebf534baa9b4ec1ee5704..a7e80ddf0425043f10ff0d8bc45a240c1304e9e3 100644 --- a/browserid/static/jquery/dom/compare/compare.js +++ b/browserid/static/jquery/dom/compare/compare.js @@ -6,57 +6,71 @@ steal.plugins('jquery/dom').then(function($){ * @function compare * @parent dom * @download http://jmvcsite.heroku.com/pluginify?plugins[]=jquery/dom/compare/compare.js + * * Compares the position of two nodes and returns a bitmask detailing how they are positioned - * relative to each other. You can expect it to return the same results as + * relative to each other. + * + * $('#foo').compare($('#bar')) //-> Number + * + * You can expect it to return the same results as * [http://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-compareDocumentPosition | compareDocumentPosition]. * Parts of this documentation and source come from [http://ejohn.org/blog/comparing-document-position | John Resig]. - * <h2>Demo</h2> + * + * ## Demo * @demo jquery/dom/compare/compare.html * @test jquery/dom/compare/qunit.html * @plugin dom/compare - * @param {HTMLElement} a the first node - * @param {HTMLElement} b the second node - * @return {Number} A bitmap with the following digit values: + * + * + * @param {HTMLElement|jQuery} element an element or jQuery collection to compare against. + * @return {Number} A bitmap number representing how the elements are positioned from each other. + * + * If the code looks like: + * + * $('#foo').compare($('#bar')) //-> Number + * + * Number is a bitmap with with the following values: * <table class='options'> * <tr><th>Bits</th><th>Number</th><th>Meaning</th></tr> * <tr><td>000000</td><td>0</td><td>Elements are identical.</td></tr> - * <tr><td>000001</td><td>1</td><td>The nodes are in different documents (or one is outside of a document).</td></tr> - * <tr><td>000010</td><td>2</td><td>Node B precedes Node A.</td></tr> - * <tr><td>000100</td><td>4</td><td>Node A precedes Node B.</td></tr> - * <tr><td>001000</td><td>8</td><td>Node B contains Node A.</td></tr> - * <tr><td>010000</td><td>16</td><td>Node A contains Node B.</td></tr> + * <tr><td>000001</td><td>1</td><td>The nodes are in different + * documents (or one is outside of a document).</td></tr> + * <tr><td>000010</td><td>2</td><td>#bar precedes #foo.</td></tr> + * <tr><td>000100</td><td>4</td><td>#foo precedes #bar.</td></tr> + * <tr><td>001000</td><td>8</td><td>#bar contains #foo.</td></tr> + * <tr><td>010000</td><td>16</td><td>#foo contains #bar.</td></tr> * </table> */ -jQuery.fn.compare = function(b){ //usually - //b is usually a relatedTarget, but b/c it is we have to avoid a few FF errors +jQuery.fn.compare = function(element){ //usually + //element is usually a relatedTarget, but element/c it is we have to avoid a few FF errors try{ //FF3 freaks out with XUL - b = b.jquery ? b[0] : b; + element = element.jquery ? element[0] : element; }catch(e){ return null; } if (window.HTMLElement) { //make sure we aren't coming from XUL element - var s = HTMLElement.prototype.toString.call(b) + var s = HTMLElement.prototype.toString.call(element) if (s == '[xpconnect wrapped native prototype]' || s == '[object XULElement]') return null; } if(this[0].compareDocumentPosition){ - return this[0].compareDocumentPosition(b); + return this[0].compareDocumentPosition(element); } - if(this[0] == document && b != document) return 8; - var number = (this[0] !== b && this[0].contains(b) && 16) + (this[0] != b && b.contains(this[0]) && 8), + if(this[0] == document && element != document) return 8; + var number = (this[0] !== element && this[0].contains(element) && 16) + (this[0] != element && element.contains(this[0]) && 8), docEl = document.documentElement; if(this[0].sourceIndex){ - number += (this[0].sourceIndex < b.sourceIndex && 4) - number += (this[0].sourceIndex > b.sourceIndex && 2) - number += (this[0].ownerDocument !== b.ownerDocument || + number += (this[0].sourceIndex < element.sourceIndex && 4) + number += (this[0].sourceIndex > element.sourceIndex && 2) + number += (this[0].ownerDocument !== element.ownerDocument || (this[0] != docEl && this[0].sourceIndex <= 0 ) || - (b != docEl && b.sourceIndex <= 0 )) && 1 + (element != docEl && element.sourceIndex <= 0 )) && 1 }else{ var range = document.createRange(), sourceRange = document.createRange(), compare; range.selectNode(this[0]); - sourceRange.selectNode(b); + sourceRange.selectNode(element); compare = range.compareBoundaryPoints(Range.START_TO_START, sourceRange); } diff --git a/browserid/static/jquery/dom/compare/test/qunit/compare_test.js b/browserid/static/jquery/dom/compare/compare_test.js similarity index 88% rename from browserid/static/jquery/dom/compare/test/qunit/compare_test.js rename to browserid/static/jquery/dom/compare/compare_test.js index 056bf7b5c22b4aa99bd81ae7e54253b7c582da93..f005176c327140d2ed1c4c96670cbdae8d4731a5 100644 --- a/browserid/static/jquery/dom/compare/test/qunit/compare_test.js +++ b/browserid/static/jquery/dom/compare/compare_test.js @@ -1,3 +1,7 @@ +steal + .plugins("jquery/dom/compare") //load your app + .plugins('funcunit/qunit').then(function(){ + module("jquery/dom/compare") test("Compare cases", function(){ $(document.body).append("<div id='outer'><div class='first'></div><div class='second'></div>") @@ -16,4 +20,6 @@ test("Compare cases", function(){ equals(first.compare(second), 4, "A sibling elements"); equals(second.compare(first), 2, "A sibling elements"); outer.remove() -}) \ No newline at end of file +}); + +}); \ No newline at end of file diff --git a/browserid/static/jquery/dom/compare/qunit.html b/browserid/static/jquery/dom/compare/qunit.html index 1f2208f1cedb254789ecb1a6856aacfecaa7c8a9..d7c40442029a754edc6b1a5401445aa02896d5ac 100644 --- a/browserid/static/jquery/dom/compare/qunit.html +++ b/browserid/static/jquery/dom/compare/qunit.html @@ -15,6 +15,6 @@ <h2 id="qunit-userAgent"></h2> <ol id="qunit-tests"></ol> - <script type='text/javascript' src='../../../steal/steal.js?steal[app]=jquery/dom/compare/test/qunit'></script> + <script type='text/javascript' src='../../../steal/steal.js?jquery/dom/compare/compare_test.js'></script> </body> </html> \ No newline at end of file diff --git a/browserid/static/jquery/dom/compare/test/qunit/qunit.js b/browserid/static/jquery/dom/compare/test/qunit/qunit.js deleted file mode 100644 index 8ac2c35feef50280e654084338293e8924e4c9f7..0000000000000000000000000000000000000000 --- a/browserid/static/jquery/dom/compare/test/qunit/qunit.js +++ /dev/null @@ -1,5 +0,0 @@ -//we probably have to have this only describing where the tests are -steal - .plugins("jquery/dom/compare") //load your app - .plugins('funcunit/qunit') //load qunit - .then("compare_test") \ No newline at end of file diff --git a/browserid/static/jquery/dom/cur_styles/test/qunit/cur_styles_test.js b/browserid/static/jquery/dom/cur_styles/cur_styles_test.js similarity index 70% rename from browserid/static/jquery/dom/cur_styles/test/qunit/cur_styles_test.js rename to browserid/static/jquery/dom/cur_styles/cur_styles_test.js index ce5ac756a67baa59e971f9fda8132eaf205ee3a2..e279172acf501f3ecd1ba5cab9593b446d326e5f 100644 --- a/browserid/static/jquery/dom/cur_styles/test/qunit/cur_styles_test.js +++ b/browserid/static/jquery/dom/cur_styles/cur_styles_test.js @@ -1,8 +1,13 @@ +steal + .plugins("jquery/dom/dimensions",'jquery/view/micro') //load your app + .plugins('funcunit/qunit').then(function(){ + module("jquery/dom/curStyles"); test("reading", function(){ - $("#qunit-test-area").html("//jquery/dom/cur_styles/test/qunit/curStyles.micro",{}) + + $("#qunit-test-area").html("//jquery/dom/cur_styles/test/curStyles.micro",{}) var res = $.curStyles( $("#styled")[0], ["padding-left", @@ -18,5 +23,7 @@ test("reading", function(){ equals(res.paddingLeft, "5px","padding left"); equals(res.position, "relative","position"); $("#qunit-test-area").html("") +}); + }) diff --git a/browserid/static/jquery/dom/cur_styles/qunit.html b/browserid/static/jquery/dom/cur_styles/qunit.html index 813417974e6c7215e7d20a9cbb7e9ee417cc105d..736ae9d64d3298a50db4b5c946756f5b8d2d22a6 100644 --- a/browserid/static/jquery/dom/cur_styles/qunit.html +++ b/browserid/static/jquery/dom/cur_styles/qunit.html @@ -7,7 +7,7 @@ margin: 0px; padding: 0px; } </style> - <script type='text/javascript' src='../../../steal/steal.js?steal[app]=jquery/dom/cur_styles/test/qunit'></script> + <script type='text/javascript' src='../../../steal/steal.js?jquery/dom/cur_styles/cur_styles_test.js'></script> </head> <body> diff --git a/browserid/static/jquery/dom/cur_styles/test/qunit/curStyles.micro b/browserid/static/jquery/dom/cur_styles/test/curStyles.micro similarity index 100% rename from browserid/static/jquery/dom/cur_styles/test/qunit/curStyles.micro rename to browserid/static/jquery/dom/cur_styles/test/curStyles.micro diff --git a/browserid/static/jquery/dom/cur_styles/test/qunit/qunit.js b/browserid/static/jquery/dom/cur_styles/test/qunit/qunit.js deleted file mode 100644 index e51bcaaf704ce23cdb4342a93a4756939fe9a502..0000000000000000000000000000000000000000 --- a/browserid/static/jquery/dom/cur_styles/test/qunit/qunit.js +++ /dev/null @@ -1,4 +0,0 @@ -steal - .plugins("jquery/dom/dimensions",'jquery/view/micro') //load your app - .plugins('funcunit/qunit') //load qunit - .then("cur_styles_test") \ No newline at end of file diff --git a/browserid/static/jquery/dom/dom.js b/browserid/static/jquery/dom/dom.js index f964832fa0752f94a2be656486aed408c351e3ac..e2204e8a69eea0127c7eec836457ec13bf898509 100644 --- a/browserid/static/jquery/dom/dom.js +++ b/browserid/static/jquery/dom/dom.js @@ -1,7 +1,73 @@ /** - * @page dom DOM Helpers - * @tag core - * JavaScriptMVC adds a bunch of useful jQuery extensions for the dom. Check them out on the left. - * - */ +@page dom DOM Helpers +@tag core +JavaScriptMVC adds a bunch of useful +jQuery extensions for the dom. Check them out on the left. + +## [dimensions Dimensions] + +Set and animate the inner and outer height and width of elements. + + $('#foo').outerWidth(100); + $('#bar').animate({innerWidth: 500}); + +This is great when you want to include padding and margin in +setting the dimensions of elements. + +## [jQuery.cookie Cookie] + +Set and get cookie values: + + $.cookie('cookie','value'); + +## [jQuery.fixture Fixture] + +Simulate Ajax responses. + + $.fixture("/services/tasks.php','fixtures/tasks.json'); + +Works with jQuery's Ajax converters! + +## [jQuery.fn.compare Compare] + +Compare the location of two elements rapidly. + + $('#foo').compare($('#bar')) & 2 // true if #bar is before #foo + +## [jQuery.fn.curStyles CurStyles] + +Get multiple css properties quickly. + + $('#foo').curStyles('left','top') //-> {left:'20px',top:'10px'} + +## [jQuery.fn.formParams FormParams] + +Serializes a form into a JSON-like object: + + $('form').formParams() //-> {name: 'Justin', favs: ['JS','Ruby']} + +## [jQuery.fn.selection Selection] + +Gets or sets the current text selection. + + // gets selection info + $('pre').selection() //-> {start: 22, end: 57, range: range} + + // sets the selection + $('div').selection(20,22) + +## [jQuery.fn.within Within] + +Returns elements that have a point within their boundaries. + + $('.drop').within(200,200) //-> drops that touch 200,200 + +## [jQuery.Range Range] + +Text range utilities. + + $('#copy').range() //-> text range that has copy selected + + +*/ steal.plugins('jquery'); \ No newline at end of file diff --git a/browserid/static/jquery/dom/fixture/fixture.js b/browserid/static/jquery/dom/fixture/fixture.js index 787fae0066c2f5336648dc453beb0010d6ee50a9..f1ceb37a19c11f380084bfc87efde20f02b13917 100644 --- a/browserid/static/jquery/dom/fixture/fixture.js +++ b/browserid/static/jquery/dom/fixture/fixture.js @@ -1,6 +1,176 @@ steal.plugins('jquery/dom').then(function( $ ) { + + // the pre-filter needs to re-route the url + $.ajaxPrefilter( function( settings, originalOptions, jqXHR ) { + // if fixtures are on + if(! $.fixture.on) { + return; + } + + // add the fixture option if programmed in + overwrite(settings); + + // if we don't have a fixture, do nothing + if(!settings.fixture){ + return; + } + + //if referencing something else, update the fixture option + if ( typeof settings.fixture === "string" && $.fixture[settings.fixture] ) { + settings.fixture = $.fixture[settings.fixture]; + } + + // if a string, we just point to the right url + if ( typeof settings.fixture == "string" ) { + var url = settings.fixture; + + if (/^\/\//.test(url) ) { + url = steal.root.join(settings.fixture.substr(2)); + } + //@steal-remove-start + steal.dev.log("looking for fixture in " + url); + //@steal-remove-end + settings.url = url; + settings.data = null; + settings.type = "GET"; + if (!settings.error ) { + settings.error = function( xhr, error, message ) { + throw "fixtures.js Error " + error + " " + message; + }; + } + + }else { + //@steal-remove-start + steal.dev.log("using a dynamic fixture for " + settings.url); + //@steal-remove-end + + //it's a function ... add the fixture datatype so our fixture transport handles it + // TODO: make everything go here for timing and other fun stuff + settings.dataTypes.splice(0,0,"fixture") + } + + }); + + + $.ajaxTransport( "fixture", function( s, original ) { + + // remove the fixture from the datatype + s.dataTypes.shift(); + + //we'll return the result of the next data type + var next = s.dataTypes[0], + timeout; + + return { + + send: function( headers , callback ) { + + // callback after a timeout + timeout = setTimeout(function() { + + // get the callback data from the fixture function + var response = s.fixture(original, s, headers); + + // normalize the fixture data into a response + if(!$.isArray(response)){ + var tmp = [{}]; + tmp[0][next] = response + response = tmp; + } + if(typeof response[0] != 'number'){ + response.unshift(200,"success") + } + + // make sure we provide a response type that matches the first datatype (typically json) + if(!response[2] || !response[2][next]){ + var tmp = {} + tmp[next] = response[2]; + response[2] = tmp; + } + + // pass the fixture data back to $.ajax + callback.apply(null, response ); + }, $.fixture.delay); + }, + + abort: function() { + clearTimeout(timeout) + } + }; + + }); + + + + var typeTest = /^(script|json|test|jsonp)$/, + // a list of 'overwrite' settings object + overwrites = [], + // checks if an overwrite matches ajax settings + isSimilar = function(settings, overwrite, exact){ + + settings = $.extend({}, settings) + + for(var prop in overwrite){ + if(prop === 'fixture'){ + + } else if(overwrite[prop] !== settings[prop]){ + return false; + } + if(exact){ + delete settings[prop] + } + } + if(exact){ + for(var name in settings){ + return false + } + } + return true; + }, + // returns the index of an overwrite function + find = function(settings, exact){ + for(var i =0; i < overwrites.length; i++){ + if(isSimilar(settings, overwrites[i], exact)){ + return i; + } + } + return -1; + }, + // overwrites the settings fixture if an overwrite matches + overwrite = function(settings){ + var index = find(settings); + if(index > -1){ + settings.fixture = overwrites[index].fixture; + } + + }, + /** + * Makes an attempt to guess where the id is at in the url and returns it. + * @param {Object} settings + */ + getId = function(settings){ + var id = settings.data.id; + + if(id === undefined){ + settings.url.replace(/\/(\d+)(\/|$)/g, function(all, num){ + id = num; + }); + } + + if(id === undefined){ + id = settings.url.replace(/\/(\w+)(\/|$)/g, function(all, num){ + if(num != 'update'){ + id = num; + } + }) + } + + if(id === undefined){ // if still not set, guess a random number + id = Math.round(Math.random()*1000) + } - var ajax = $.ajax; + return id; + }; /** * @class jQuery.fixture @@ -9,128 +179,104 @@ steal.plugins('jquery/dom').then(function( $ ) { * @test jquery/dom/fixture/qunit.html * @parent dom * - * Fixtures simulate AJAX responses by overwriting - * [jQuery.ajax $.ajax], - * [jQuery.get $.get], and - * [jQuery.post $.post]. - * Instead of making a request to a server, fixtures simulate - * the repsonse with a file or function. - * - * They are a great technique when you want to develop JavaScript + * Fixtures simulate AJAX responses. Instead of making + * a request to a server, fixtures simulate + * the response with a file or function. They are a great technique when you want to develop JavaScript * independently of the backend. * - * <h3>Quick Example</h3> - * <p>Instead of making a request to <code>/tasks.json</code>, - * $.ajax will look in <code>fixtures/tasks.json</code>. - * It's expected that a static <code>fixtures/tasks.json</code> - * file exists relative to the current page. - * </p> - * @codestart - * $.ajax({url: "/tasks.json", - * dataType: "json", - * type: "get", - * fixture: "fixtures/tasks.json", - * success: myCallback}); - * @codeend - * <h2>Using Fixtures</h2> - * To enable fixtures, you must add this plugin to your page and - * set the fixture property. + * ### Two Quick Examples * - * The fixture property is set as ... - * @codestart - * //... a property with $.ajax - * $.ajax({fixture: FIXTURE_VALUE}) + * There are two common ways of using fixtures. The first is to + * map Ajax requests to another file or function. The following + * intercepts requests to <code>/tasks.json</code> and directs them + * to <code>fixtures/tasks.json</code>: + * + * $.fixture("/tasks.json","fixtures/tasks.json"); + * + * You can also add a fixture option directly to $.ajax like: + * + * $.ajax({url: "/tasks.json", + * dataType: "json", + * type: "get", + * fixture: "fixtures/tasks.json", + * success: myCallback + * }); + * + * The first technique keeps fixture logic out of your Ajax + * requests. However, if your service urls are changing __a lot__ + * the second technique means you only have to change the service + * url in one spot. + * + * + * + * ## Types of Fixtures + * + * There are 2 types of fixtures: + * - __Static__ - the response is in a file. + * - __Dynamic__ - the response is generated by a function. * - * //... a parameter in $.get and $.post - * $.get ( url, data, callback, type, FIXTURE_VALUE ) - * $.post( url, data, callback, type, FIXTURE_VALUE ) - * @codeend - * <h3>Turning Off Fixtures</h3> - * <p>To turn off fixtures, simply remove the fixture plugin from - * your page. The Ajax methods will ignore <code>FIXTURE_VALUE</code> - * and revert to their normal behavior. If you want to ignore a single - * fixture, we suggest commenting it out. - * </p> - * <div class='whisper'> - * PRO TIP: Don't worry about leaving the fixture values in your source. - * They don't take up many characters and won't impact how jQuery makes - * requests. They can be useful even after the service they simulate - * is created. - * </div> - * <h2>Types of Fixtures</h2> - * <p>There are 2 types of fixtures</p> - * <ul> - * <li><b>Static</b> - the response is in a file. - * </li> - * <li> - * <b>Dynamic</b> - the response is generated by a function. - * </li> - * </ul> * There are different ways to lookup static and dynamic fixtures. - * <h3>Static Fixtures</h3> - * Static fixture locations can be calculated: - * @codestart - * // looks in test/fixtures/tasks/1.get - * $.ajax({type:"get", - * url: "tasks/1", - * fixture: true}) - * @codeend - * Or provided: - * @codestart - * // looks in fixtures/tasks1.json relative to page - * $.ajax({type:"get", - * url: "tasks/1", - * fixture: "fixtures/task1.json"}) - * - * // looks in fixtures/tasks1.json relative to jmvc root - * // this assumes you are using steal - * $.ajax({type:"get", - * url: "tasks/1", - * fixture: "//fixtures/task1.json"})` - * @codeend - * <div class='whisper'> - * PRO TIP: Use provided fixtures. It's easier to understand what it is going. - * Also, create a fixtures folder in your app to hold your fixtures. - * </div> - * <h3>Dynamic Fixtures</h3> - * <p>Dynamic Fixtures are functions that return the arguments the $.ajax callbacks - * (<code>beforeSend</code>, <code>success</code>, <code>complete</code>, - * <code>error</code>) expect. </p> - * <p>For example, the "<code>success</code>" of a json request is called with - * <code>[data, textStatus, XMLHttpRequest].</p> - * <p>There are 2 ways to lookup dynamic fixtures.<p> - * They can provided: - * @codestart - * //just use a function as the fixture property - * $.ajax({ - * type: "get", - * url: "tasks", - * data: {id: 5}, - * dataType: "json", - * fixture: function( settings, callbackType ) { - * var xhr = {responseText: "{id:"+settings.data.id+"}"} - * switch(callbackType){ - * case "success": - * return [{id: settings.data.id},"success",xhr] - * case "complete": - * return [xhr,"success"] - * } - * } - * }) - * @codeend + * + * ### Static Fixtures + * + * Static fixtures use an alternate url as the response of the Ajax request. + * + * // looks in fixtures/tasks1.json relative to page + * $.fixture("tasks/1", "fixtures/task1.json"); + * + * $.ajax({type:"get", + * url: "tasks/1", + * fixture: "fixtures/task1.json"}) + * + * // looks in fixtures/tasks1.json relative to jmvc root + * // this assumes you are using steal + * $.fixture("tasks/1", "//fixtures/task1.json"); + * + * $.ajax({type:"get", + * url: "tasks/1", + * fixture: "//fixtures/task1.json"})` + * + * ### Dynamic Fixtures + * + * Dynamic Fixtures are functions that return the arguments the $.ajax callbacks + * (<code>beforeSend</code>, <code>success</code>, <code>complete</code>, + * <code>error</code>) expect. + * + * For example, the "<code>success</code>" of a json request is called with + * <code>[data, textStatus, XMLHttpRequest]. + * + * There are 2 ways to lookup dynamic fixtures. They can provided: + * + * //just use a function as the fixture property + * $.ajax({ + * type: "get", + * url: "tasks", + * data: {id: 5}, + * dataType: "json", + * fixture: function( settings, callbackType ) { + * var xhr = {responseText: "{id:"+settings.data.id+"}"} + * switch(callbackType){ + * case "success": + * return [{id: settings.data.id},"success",xhr] + * case "complete": + * return [xhr,"success"] + * } + * } + * }) + * * Or found by name on $.fixture: - * @codestart - * // add your function on $.fixture - * // We use -FUNC by convention - * $.fixture["-myGet"] = function(settings, cbType){...} - * - * // reference it - * $.ajax({ - * type:"get", - * url: "tasks/1", - * dataType: "json", - * fixture: "-myGet"}) - * @codeend + * + * // add your function on $.fixture + * // We use -FUNC by convention + * $.fixture["-myGet"] = function(settings, cbType){...} + * + * // reference it + * $.ajax({ + * type:"get", + * url: "tasks/1", + * dataType: "json", + * fixture: "-myGet"}) + * * <p>Dynamic fixture functions are called with:</p> * <ul> * <li> settings - the settings data passed to <code>$.ajax()</code> @@ -146,94 +292,84 @@ steal.plugins('jquery/dom').then(function( $ ) { * What to see what the app feels like when a request takes 5 seconds to return? Set * [jQuery.fixture.delay] to 5000. * </div> - * <h2>Helpers</h2> - * <p>The fixture plugin comes with a few ready-made dynamic fixtures and + * + * ## Helpers + * + * The fixture plugin comes with a few ready-made dynamic fixtures and * fixture helpers:</p> + * * <ul> * <li>[jQuery.fixture.make] - creates fixtures for findAll, findOne.</li> * <li>[jQuery.fixture.-restCreate] - a fixture for restful creates.</li> * <li>[jQuery.fixture.-restDestroy] - a fixture for restful updates.</li> * <li>[jQuery.fixture.-restUpdate] - a fixture for restful destroys.</li> * </ul> + * * @demo jquery/dom/fixture/fixture.html * @constructor * Takes an ajax settings and returns a url to look for a fixture. Overwrite this if you want a custom lookup method. * @param {Object} settings * @return {String} the url that will be used for the fixture */ - $.fixture = function( settings ) { - var url = settings.url, - match, left, right; - url = url.replace(/%2F/g, "~").replace(/%20/g, "_"); - - if ( settings.data && settings.processData && typeof settings.data !== "string" ) { - settings.data = jQuery.param(settings.data); - } - - - if ( settings.data && settings.type.toLowerCase() == "get" ) { - url += ($.String.include(url, '?') ? '&' : '?') + settings.data; - } - - match = url.match(/^(?:https?:\/\/[^\/]*)?\/?([^\?]*)\??(.*)?/); - left = match[1]; - - right = settings.type ? '.' + settings.type.toLowerCase() : '.post'; - if ( match[2] ) { - left += '/'; - right = match[2].replace(/\#|&/g, '-').replace(/\//g, '~') + right; + $.fixture = function( settings , fixture) { + // if we provide a fixture ... + if(fixture !== undefined){ + if(typeof settings == 'string'){ + // handle url strings + settings ={ + url : settings + }; + } + + //handle removing. An exact match if fixture was provided, otherwise, anything similar + var index = find(settings, !!fixture); + if(index >= -1){ + overwrites.splice(index,1) + } + if(fixture == null){ + return + } + + settings.fixture = fixture; + overwrites.push(settings) } - return left + right; }; - $.extend($.fixture, { + $.extend($.fixture, { /** * Provides a rest update fixture function */ - "-restUpdate": function( settings, cbType ) { - switch ( cbType ) { - case "success": - return [$.extend({ - id: parseInt(settings.url, 10) - }, settings.data), "success", $.fixture.xhr()]; - case "complete": - return [$.fixture.xhr(), "success"]; - } + "-restUpdate": function( settings ) { + return [{ + id: getId(settings) + },{ + location: settings.url+"/"+getId(settings) + }]; }, + /** * Provides a rest destroy fixture function */ "-restDestroy": function( settings, cbType ) { - switch ( cbType ) { - case "success": - return [true, "success", $.fixture.xhr()]; - case "complete": - return [$.fixture.xhr(), "success"]; - } + return {}; }, + /** * Provides a rest create fixture function */ "-restCreate": function( settings, cbType ) { - switch ( cbType ) { - case "success": - return [{ - id: parseInt(Math.random() * 1000, 10) - }, "success", $.fixture.xhr()]; - case "complete": - return [$.fixture.xhr({ - getResponseHeader: function() { - return settings.url + "/" + parseInt(Math.random() * 1000, 10); - } - }), "success"]; - } - - + var id = parseInt(Math.random() * 100000, 10); + return [{ + id: id + },{ + location: settings.url+"/"+id + }]; }, + /** * Used to make fixtures for findAll / findOne style requests. * @codestart - * //makes a threaded list of messages + * //makes a nested list of messages * $.fixture.make(["messages","message"],1000, function(i, messages){ * return { * subject: "This is message "+i, @@ -248,20 +384,47 @@ steal.plugins('jquery/dom').then(function( $ ) { * data:{ * offset: 100, * limit: 50, - * order: "date ASC", + * order: ["date ASC"], * parentId: 5}, * }, * fixture: "-messages", * success: function( messages ) { ... } * }); * @codeend - * @param {Array} types An array of the fixture names + * @param {Array|String} types An array of the fixture names or the singular fixture name. + * If an array, the first item is the plural fixture name (prefixed with -) and the second + * item is the singular name. If a string, it's assumed to be the singular fixture name. Make + * will simply add s to the end of it for the plural name. * @param {Number} count the number of items to create - * @param {Function} make a function that will return json data representing the object. + * @param {Function} make a function that will return json data representing the object. The + * make function is called back with the id and the current array of items. + * @param {Function} filter (optional) a function used to further filter results. Used for to simulate + * server params like searchText or startDate. The function should return true if the item passes the filter, + * false otherwise. For example: + * + * @codestart + * function(item, settings){ + if(settings.data.searchText){ + var regex = new RegExp("^"+settings.data.searchText) + return regex.test(item.name); + } + * } + * @codeend */ - make: function( types, count, make ) { + make: function( types, count, make, filter ) { + if(typeof types === "string"){ + types = [types+"s",types ] + } // make all items - var items = ($.fixture["~" + types[0]] = []); + var items = ($.fixture["~" + types[0]] = []), // TODO: change this to a hash + findOne = function(id){ + for ( var i = 0; i < items.length; i++ ) { + if ( id == items[i].id ) { + return items[i]; + } + } + }; + for ( var i = 0; i < (count); i++ ) { //call back provided make var item = make(i, items); @@ -283,10 +446,22 @@ steal.plugins('jquery/dom').then(function( $ ) { var split = name.split(" "); retArr = retArr.sort(function( a, b ) { if ( split[1].toUpperCase() !== "ASC" ) { - return a[split[0]] < b[split[0]]; + if( a[split[0]] < b[split[0]] ) { + return 1; + } else if(a[split[0]] == b[split[0]]){ + return 0 + } else { + return -1; + } } else { - return a[split[0]] > b[split[0]]; + if( a[split[0]] < b[split[0]] ) { + return -1; + } else if(a[split[0]] == b[split[0]]){ + return 0 + } else { + return 1; + } } }); }); @@ -306,7 +481,9 @@ steal.plugins('jquery/dom').then(function( $ ) { //filter results if someone added an attr like parentId for ( var param in settings.data ) { - if ( param.indexOf("Id") != -1 || param.indexOf("_id") != -1 ) { + i=0; + if ( settings.data[param] && // don't do this if the value of the param is null (ignore it) + (param.indexOf("Id") != -1 || param.indexOf("_id") != -1) ) { while ( i < retArr.length ) { if ( settings.data[param] != retArr[i][param] ) { retArr.splice(i, 1); @@ -316,6 +493,18 @@ steal.plugins('jquery/dom').then(function( $ ) { } } } + + + if( filter ) { + i = 0; + while (i < retArr.length) { + if (!filter(retArr[i], settings)) { + retArr.splice(i, 1); + } else { + i++; + } + } + } //return data spliced with limit and offset return [{ @@ -325,19 +514,48 @@ steal.plugins('jquery/dom').then(function( $ ) { "data": retArr.slice(offset, offset + limit) }]; }; - + // findOne $.fixture["-" + types[1]] = function( settings ) { - for ( var i = 0; i < (count); i++ ) { - if ( settings.data.id == items[i].id ) { - return [items[i]]; + return [findOne(settings.data.id)]; + }; + // update + $.fixture["-" + types[1]+"Update"] = function( settings, cbType ) { + var id = getId(settings); + + // TODO: make it work with non-linear ids .. + $.extend(findOne(id), settings.data); + return $.fixture["-restUpdate"](settings, cbType) + }; + $.fixture["-" + types[1]+"Destroy"] = function( settings, cbType ) { + var id = getId(settings); + for(var i = 0; i < items.length; i ++ ){ + if(items[i].id == id){ + items.splice(i, 1); + break; } } + + // TODO: make it work with non-linear ids .. + $.extend(findOne(id), settings.data); + return $.fixture["-restDestroy"](settings, cbType) + }; + $.fixture["-" + types[1]+"Create"] = function( settings, cbType ) { + var item = make(items.length, items); + $.extend(item, settings.data); + + if(!item.id){ + item.id = items.length; + } + + items.push(item); + return $.fixture["-restCreate"](settings, cbType) }; - }, /** * Use $.fixture.xhr to create an object that looks like an xhr object. - * <h3>Example</h3> + * + * ## Example + * * The following example shows how the -restCreate fixture uses xhr to return * a simulated xhr object: * @codestart @@ -381,7 +599,17 @@ steal.plugins('jquery/dom').then(function( $ ) { status: 200, statusText: "OK" }, xhr); - } + }, + /** + * @attribute on + * On lets you programatically turn off fixtures. This is mostly used for testing. + * + * $.fixture.on = false + * Task.findAll({}, function(){ + * $.fixture.on = true; + * }) + */ + on : true }); /** * @attribute delay @@ -417,50 +645,6 @@ steal.plugins('jquery/dom').then(function( $ ) { /** * @add jQuery */ - // break - $. - /** - * Adds the fixture option to settings. If present, loads from fixture location instead - * of provided url. This is useful for simulating ajax responses before the server is done. - * @param {Object} settings - */ - ajax = function( settings ) { - var func = $.fixture; - if (!settings.fixture ) { - return ajax.apply($, arguments); - } - if ( $.fixture["-handleFunction"](settings) ) { - return; - } - if ( typeof settings.fixture == "string" ) { - var url = settings.fixture; - if (/^\/\//.test(url) ) { - url = steal.root.join(settings.fixture.substr(2)); - } - //@steal-remove-start - steal.dev.log("looking for fixture in " + url); - //@steal-remove-end - settings.url = url; - settings.data = null; - settings.type = "GET"; - if (!settings.error ) { - settings.error = function( xhr, error, message ) { - throw "fixtures.js Error " + error + " " + message; - }; - } - return ajax(settings); - - } - settings = jQuery.extend(true, settings, jQuery.extend(true, {}, jQuery.ajaxSettings, settings)); - - settings.url = steal.root.join('test/fixtures/' + func(settings)); // convert settings - settings.data = null; - settings.type = 'GET'; - return ajax(settings); - }; - - $.extend($.ajax, ajax); - $. /** * Adds a fixture param. @@ -472,6 +656,14 @@ steal.plugins('jquery/dom').then(function( $ ) { */ get = function( url, data, callback, type, fixture ) { // shift arguments if data argument was ommited + if ( jQuery.isFunction(data) ) { + if(!typeTest.test(type||"")){ + fixture = type; + type = callback; + } + callback = data; + data = null; + } if ( jQuery.isFunction(data) ) { fixture = type; type = callback; @@ -500,11 +692,13 @@ steal.plugins('jquery/dom').then(function( $ ) { */ post = function( url, data, callback, type, fixture ) { if ( jQuery.isFunction(data) ) { - fixture = type; - type = callback; - callback = data; - data = {}; - } + if(!typeTest.test(type||"")){ + fixture = type; + type = callback; + } + callback = data; + data = {}; + } return jQuery.ajax({ type: "POST", @@ -515,4 +709,92 @@ steal.plugins('jquery/dom').then(function( $ ) { fixture: fixture }); }; + + /** + * @page jquery.fixture.0organizing Organizing Fixtures + * @parent jQuery.fixture + * + * The __best__ way of organizing fixtures is to have a 'fixtures.js' file that steals + * <code>jquery/dom/fixture</code> and defines all your fixtures. For example, + * if you have a 'todo' application, you might + * have <code>todo/fixtures/fixtures.js</code> look like: + * + * steal({ + * path: '//jquery/dom/fixture.js', + * ignore: true + * }) + * .then(function(){ + * + * $.fixture({ + * type: 'get', + * url: '/services/todos.json' + * }, + * '//todo/fixtures/todos.json'); + * + * $.fixture({ + * type: 'post', + * url: '/services/todos.json' + * }, + * function(settings){ + * return {id: Math.random(), + * name: settings.data.name} + * }); + * + * }) + * + * __Notice__: We used steal's ignore option to prevent + * loading the fixture plugin in production. + * + * Finally, we steal <code>todo/fixtures/fixtures.js</code> in the + * app file (<code>todo/todo.js</code>) like: + * + * + * steal({path: '//todo/fixtures/fixtures.js',ignore: true}); + * + * //start of your app's steals + * steal.plugins( ... ) + * + * We typically keep it a one liner so it's easy to comment out. + * + * ## Switching Between Sets of Fixtures + * + * If you are using fixtures for testing, you often want to use different + * sets of fixtures. You can add something like the following to your fixtures.js file: + * + * if( /fixtureSet1/.test( window.location.search) ){ + * $.fixture("/foo","//foo/fixtures/foo1.json'); + * } else if(/fixtureSet2/.test( window.location.search)){ + * $.fixture("/foo","//foo/fixtures/foo1.json'); + * } else { + * // default fixtures (maybe no fixtures) + * } + * + */ + // + /** + * @add jQuery.fixture + */ + // + /** + * @page jquery.fixture.1errors Simulating Errors + * @parent jQuery.fixture + * + * The following simulates an unauthorized request + * to <code>/foo</code>. + * + * $.fixture("/foo", function(){ + * return [401,"{type: 'unauthorized'}"] + * }); + * + * This could be received by the following Ajax request: + * + * $.ajax({ + * url: '/foo', + * error : function(jqXhr, status, statusText){ + * // status === 'error' + * // statusText === "{type: 'unauthorized'}" + * } + * }) + * + */ }); \ No newline at end of file diff --git a/browserid/static/jquery/dom/fixture/fixture_test.js b/browserid/static/jquery/dom/fixture/fixture_test.js new file mode 100644 index 0000000000000000000000000000000000000000..fb08fe991d5eeddf1ad9662e97ef6f19e80e6c55 --- /dev/null +++ b/browserid/static/jquery/dom/fixture/fixture_test.js @@ -0,0 +1,170 @@ +steal + .plugins("jquery/dom/fixture", "jquery/model") + .plugins('funcunit/qunit').then(function(){ + +module("jquery/dom/fixture"); + + +test("static fixtures", function(){ + stop(); + $.get("something",function(data){ + equals(data.sweet,"ness","$.get works"); + $.post("something",function(data){ + + equals(data.sweet,"ness","$.post works"); + + $.ajax({ + url: "something", + dataType: "json", + success: function( data ) { + equals(data.sweet,"ness","$.ajax works"); + start(); + }, + fixture: "//jquery/dom/fixture/fixtures/test.json" + }) + + },"json","//jquery/dom/fixture/fixtures/test.json"); + },'json',"//jquery/dom/fixture/fixtures/test.json"); +}) + +test("dynamic fixtures",function(){ + stop(); + $.fixture.delay = 10; + var fix = function(){ + return [{sweet: "ness"}] + } + $.get("something",function(data){ + equals(data.sweet,"ness","$.get works"); + $.post("something",function(data){ + + equals(data.sweet,"ness","$.post works"); + + $.ajax({ + url: "something", + dataType: "json", + success: function( data ) { + equals(data.sweet,"ness","$.ajax works"); + start(); + }, + fixture: fix + }) + + },"json",fix); + },'json',fix); +}); + +test("fixture function", 3, function(){ + + stop(); + var url = steal.root.join("jquery/dom/fixture/fixtures/foo.json"); + $.fixture(url,"//jquery/dom/fixture/fixtures/foobar.json" ); + + $.get(url,function(data){ + equals(data.sweet,"ner","url passed works"); + + $.fixture(url,"//jquery/dom/fixture/fixtures/test.json" ); + + $.get(url,function(data){ + + equals(data.sweet,"ness","replaced"); + + $.fixture(url, null ); + + $.get(url,function(data){ + + equals(data.a,"b","removed"); + + start(); + + },'json') + + + },'json') + + + + },"json"); + +}); + + +test("fixtures with converters", function(){ + + stop(); + $.ajax( { + url : steal.root.join("jquery/dom/fixture/fixtures/foobar.json"), + dataType: "json fooBar", + converters: { + "json fooBar": function( data ) { + // Extract relevant text from the xml document + return "Mr. "+data.name; + } + }, + fixture : function(){ + return { + name : "Justin" + } + }, + success : function(prettyName){ + start(); + equals(prettyName, "Mr. Justin") + } + }); +}) + +test("$.fixture.make fixtures",function(){ + stop(); + $.fixture.make('thing', 1000, function(i){ + return { + id: i, + name: "thing "+i + } + }, + function(item, settings){ + if(settings.data.searchText){ + var regex = new RegExp("^"+settings.data.searchText) + return regex.test(item.name); + } + }) + $.ajax({ + url: "things", + type: "json", + data: { + offset: 100, + limit: 200, + order: ["name ASC"], + searchText: "thing 2" + }, + fixture: "-things", + success: function(things){ + equals(things.data[0].name, "thing 29", "first item is correct") + equals(things.data.length, 11, "there are 11 items") + start(); + } + }) +}); + +test("simulating an error", function(){ + var st = '{type: "unauthorized"}'; + + $.fixture("/foo", function(){ + return [401,st] + }); + stop(); + + $.ajax({ + url : "/foo", + success : function(){ + ok(false, "success called"); + start(); + }, + error : function(jqXHR, status, statusText){ + ok(true, "error called"); + equals(statusText, st); + start(); + } + }) +}) + + +}); diff --git a/browserid/static/jquery/dom/fixture/fixtures/foo.json b/browserid/static/jquery/dom/fixture/fixtures/foo.json new file mode 100644 index 0000000000000000000000000000000000000000..9fbc82d018ad8ead460e350f774d4971c6dee33f --- /dev/null +++ b/browserid/static/jquery/dom/fixture/fixtures/foo.json @@ -0,0 +1 @@ +{"a" : "b"} diff --git a/browserid/static/jquery/dom/fixture/fixtures/foobar.json b/browserid/static/jquery/dom/fixture/fixtures/foobar.json new file mode 100644 index 0000000000000000000000000000000000000000..a50afea1d3f3d22ee0a40343c29026d9b9a378b6 --- /dev/null +++ b/browserid/static/jquery/dom/fixture/fixtures/foobar.json @@ -0,0 +1,3 @@ +{ + "sweet" :"ner" +} diff --git a/browserid/static/jquery/dom/fixture/qunit.html b/browserid/static/jquery/dom/fixture/qunit.html index 9a6ba526a24a3850919c5f512e5e98c9f137ff49..5207a04ce02defe7423d5510d03c6c53aaecde0d 100644 --- a/browserid/static/jquery/dom/fixture/qunit.html +++ b/browserid/static/jquery/dom/fixture/qunit.html @@ -7,7 +7,7 @@ margin: 0px; padding: 0px; } </style> - <script type='text/javascript' src='../../../steal/steal.js?steal[app]=jquery/dom/fixture/test/qunit'></script> + <script type='text/javascript' src='../../../steal/steal.js?jquery/dom/fixture/fixture_test.js'></script> </head> <body> diff --git a/browserid/static/jquery/dom/fixture/test/qunit/fixture_test.js b/browserid/static/jquery/dom/fixture/test/qunit/fixture_test.js deleted file mode 100644 index a6499ae174912e7658f782f7dedffa2c7b3182c9..0000000000000000000000000000000000000000 --- a/browserid/static/jquery/dom/fixture/test/qunit/fixture_test.js +++ /dev/null @@ -1,50 +0,0 @@ -module("jquery/dom/fixture"); - - -test("static fixtures", function(){ - stop(); - $.get("something",function(data){ - equals(data.sweet,"ness","$.get works"); - $.post("something",function(data){ - - equals(data.sweet,"ness","$.post works"); - - $.ajax({ - url: "something", - dataType: "json", - success: function( data ) { - equals(data.sweet,"ness","$.ajax works"); - start(); - }, - fixture: "//jquery/dom/fixture/fixtures/test.json" - }) - - },"json","//jquery/dom/fixture/fixtures/test.json"); - },'json',"//jquery/dom/fixture/fixtures/test.json"); -}) - -test("dynamic fixtures",function(){ - stop(); - $.fixture.delay = 10; - var fix = function(){ - return [{sweet: "ness"}] - } - $.get("something",function(data){ - equals(data.sweet,"ness","$.get works"); - $.post("something",function(data){ - - equals(data.sweet,"ness","$.post works"); - - $.ajax({ - url: "something", - dataType: "json", - success: function( data ) { - equals(data.sweet,"ness","$.ajax works"); - start(); - }, - fixture: fix - }) - - },"json",fix); - },'json',fix); -}) diff --git a/browserid/static/jquery/dom/fixture/test/qunit/qunit.js b/browserid/static/jquery/dom/fixture/test/qunit/qunit.js deleted file mode 100644 index 9750a4ae9241d4ce13cb8751ba302d0668b2c796..0000000000000000000000000000000000000000 --- a/browserid/static/jquery/dom/fixture/test/qunit/qunit.js +++ /dev/null @@ -1,4 +0,0 @@ -steal - .plugins("jquery/dom/fixture") //load your app - .plugins('funcunit/qunit') //load qunit - .then("fixture_test") \ No newline at end of file diff --git a/browserid/static/jquery/dom/form_params/form_params.js b/browserid/static/jquery/dom/form_params/form_params.js index 7197c7f10c23c9683c1663b6ed542c66780e2165..1ae3319dc915b85f49768cba3ece90528e4d2710 100644 --- a/browserid/static/jquery/dom/form_params/form_params.js +++ b/browserid/static/jquery/dom/form_params/form_params.js @@ -65,7 +65,7 @@ steal.plugins("jquery/dom").then(function( $ ) { } var key = el.name, - value = $.fn.val.call([el]) || $.data(el, "value"), + value = $.data(el, "value") || $.fn.val.call([el]), isRadioCheck = radioCheck.test(el.type), parts = key.match(keyBreaker), write = !isRadioCheck || !! el.checked, diff --git a/browserid/static/jquery/dom/range/qunit.html b/browserid/static/jquery/dom/range/qunit.html new file mode 100644 index 0000000000000000000000000000000000000000..c437ef7736106031c4309eb88208b755079759f7 --- /dev/null +++ b/browserid/static/jquery/dom/range/qunit.html @@ -0,0 +1,20 @@ +<html> + <head> + <link rel="stylesheet" type="text/css" href="../../../funcunit/qunit/qunit.css" /> + <title>selection QUnit Test</title> + <script type='text/javascript'> + steal = {ignoreControllers: true} + </script> + <script type='text/javascript' src='../../../steal/steal.js?jquery/dom/selection/test/qunit'></script> + </head> + <body> + + <h1 id="qunit-header">selection Test Suite</h1> + <h2 id="qunit-banner"></h2> + <div id="qunit-testrunner-toolbar"></div> + <h2 id="qunit-userAgent"></h2> + <div id="test-content"></div> + <ol id="qunit-tests"></ol> + <div id="qunit-test-area"></div> + </body> +</html> \ No newline at end of file diff --git a/browserid/static/jquery/dom/range/range.html b/browserid/static/jquery/dom/range/range.html new file mode 100644 index 0000000000000000000000000000000000000000..f076f19651fef36d48ded1434a99eb4bfc61b68e --- /dev/null +++ b/browserid/static/jquery/dom/range/range.html @@ -0,0 +1,62 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" + "http://www.w3.org/TR/html4/strict.dtd"> +<html lang="en"> + <head> + <title>Range</title> + <style type='text/css'> + body {font-family: verdana} + .error {border: solid 1px red;} + .error_text { color: red; font-size: 10px;} + td {padding: 3px;} + .start { + border-left: solid 3px green; + } + .end { + border-right: solid 3px red; + } + .parent { + outline: dotted 2px gray; + } + #box { + width: 400px; + } + </style> + </head> + <body> + <div id='box'> + <h1>The Range Plugin</h1> + <p>Select a range of text on the page and get useful info about it!</p> + <p>We'll figure out what to do with form elements later.</p> + <pre id='info'></pre> + </div> + <script type='text/javascript' + src='../../../steal/steal.js?jquery/dom/range'> + + </script> + <script type='text/javascript'> + var curStart = $(), + curEnd = $(), + curParent = $(), + htmlEl = function(el){ + return el.nodeType === 3 || el.nodeType === 4 ? + el.parentNode : el; + }; + $('body').mouseup(function(){ + var range = $.Range.current(); + var start = range.start(), + end = range.end(); + curStart.removeClass('start'); + curEnd.removeClass('end'); + curParent.removeClass('parent') + var starter = htmlEl(start.container); + var ender = htmlEl(end.container), + parenter = range.parent(); + + curStart = $(starter).addClass('start'); + curEnd = $(ender).addClass('end'); + curParent = $(parenter).addClass('parent') + $("#info").html("startOffset = "+start.offset+"\nendOffset = "+end.offset) + }) + </script> + </body> +</html> \ No newline at end of file diff --git a/browserid/static/jquery/dom/range/range.js b/browserid/static/jquery/dom/range/range.js new file mode 100644 index 0000000000000000000000000000000000000000..29307367b1a0d84107415212fa5154332ec95318 --- /dev/null +++ b/browserid/static/jquery/dom/range/range.js @@ -0,0 +1,523 @@ +steal.plugins('jquery','jquery/dom/compare').then(function($){ +// TODOS ... +// Ad + +/** + * @function jQuery.fn.range + * + * Returns a jQuery.Range. + */ +$.fn.range = function(){ + return $.Range(this[0]) +} + +var convertType = function(type){ + return type.replace(/([a-z])([a-z]+)/gi, function(all,first, next){ + return first+next.toLowerCase() + }).replace(/_/g,""); +}, +reverse = function(type){ + return type.replace(/^([a-z]+)_TO_([a-z]+)/i, function(all, first, last){ + return last+"_TO_"+first; + }); +}, +getWindow = function( element ) { + return element ? element.ownerDocument.defaultView || element.ownerDocument.parentWindow : window +}, +bisect = function(el, start, end){ + //split the start and end ... figure out who is touching ... + if(end-start == 1){ + return + } +} +/** + * @Class jQuery.Range + * @parent dom + * @tag alpha + * + * Provides text range helpers for creating, moving, and comparing ranges. + * + * @constructor + * + * Returns a jQuery range object. + * + * @param {TextRange|Node|Point} [range] An object specifiying a + * range. Depending on the object, the selected text will be different. $.Range supports the + * following types + * + * - __undefined or null__ - returns a range with nothing selected + * - __Node__ - returns a range with the node's text selected + * - __Point__ - returns a range at the point on the screen. The point can be specified like: + * + * //client coordinates + * {clientX: 200, clientY: 300} + * + * //page coordinates + * {pageX: 200, pageY: 300} + * {top: 200, left: 300} + * + * - __TextRange__ a raw text range object. + */ +$.Range = function(range){ + if(this.constructor !== $.Range){ + return new $.Range(range); + } + if(range && range.jquery){ + range = range[0]; + } + // create one + if(!range || range.nodeType){ + this.win = getWindow(range) + if(this.win.document.createRange){ + this.range = this.win.document.createRange() + }else{ + this.range = this.win.document.body.createTextRange() + } + if(range){ + this.select(range) + } + + } else if (range.clientX || range.pageX || range.left) { + this.rangeFromPoint(range) + } else { + this.range = range; + } +}; +/** + * @static + */ +// +/** + * Gets the current range. + * + * $.Range.current() //-> jquery.range + * + * @param {HTMLElement} [el] an optional element used to get selection for a given window. + * @return {jQuery.Range} a jQuery.Range wrapped range. + */ +$.Range.current = function(el){ + var win = getWindow(el), + selection; + if(win.getSelection){ + selection = win.getSelection() + return new $.Range( selection.rangeCount ? selection.getRangeAt(0) : win.document.createRange()) + }else{ + return new $.Range( win.document.selection.createRange() ); + } +}; + + + + +$.extend($.Range.prototype,{ + rangeFromPoint : function(point){ + var clientX = point.clientX, clientY = point.clientY + if(!clientX){ + var off = scrollOffset(); + clientX = (off.pageX || off.left || 0 ) - off.left; + clientY = (off.pageY || off.top || 0 ) - off.top; + } + + // it's some text node in this range ... + var parent = document.elementFromPoint(clientX, clientY); + + //typically it will be 'on' text + for(var n=0; n < parent.childNodes.length; n++){ + var node = parent.childNodes[n]; + if(node.nodeType === 3 || node.nodeType === 4){ + var range = $.Range(node), + length = range.toString().length; + + + // now lets start moving the end until the boundingRect is within our range + + for(var i = 1; i < length+1; i++){ + var rect = range.end(i).rect(); + if(rect.left <= clientX && rect.left+rect.width >= clientX && + rect.top <= clientY && rect.top+rect.height >= clientY ){ + range.start(i-1); + this.range = range.range; + return; + } + } + } + } + + // if not 'on' text, recursively go through and find out when we shift to next + // 'line' + var previous; + iterate(parent.childNodes, function(textNode){ + var range = $.Range(textNode); + if(range.rect().top > point.clientY){ + return false; + }else{ + previous = range; + } + }); + if(previous){ + previous.start(previous.toString().length); + this.range = previous.range; + }else{ + this.range = $.Range(parent).range + } + + }, + + window : function(){ + return this.win || window; + }, + /** + * Return true if any portion of these two ranges overlap. + * + * var foo = document.getElementById('foo'); + * + * $.Range(foo.childNodes[0]).compare(foo.childNodes[1]) //-> false + * + * @param {jQuery.Range} elRange + * @return {Boolean} true if part of the ranges overlap, false if otherwise. + */ + overlaps : function(elRange){ + if(elRange.nodeType){ + elRange = $.Range(elRange).select(elRange); + } + //if the start is within the element ... + var startToStart = this.compare("START_TO_START", elRange), + endToEnd = this.compare("END_TO_END", elRange) + + // if we wrap elRange + if(startToStart <=0 && endToEnd >=0){ + return true; + } + // if our start is inside of it + if( startToStart >= 0 && + this.compare("START_TO_END", elRange) <= 0 ) { + return true; + } + // if our end is inside of elRange + if(this.compare("END_TO_START", elRange) >= 0 && + endToEnd <= 0 ) { + return true; + } + return false; + }, + /** + * Collapses a range + * + * $('#foo').range().collapse() + * + * @param {Boolean} [toStart] true if to the start of the range, false if to the + * end. Defaults to false. + * @return {Range} returns the range for chaining. + */ + collapse : function(toStart){ + this.range.collapse(toStart === undefined ? true : toStart); + return this; + }, + toString : function(){ + return typeof this.range.text == "string" ? this.range.text : this.range.toString(); + }, + /** + * Gets or sets the start of the range. + * + * If a value is not provided, start returns the range's starting container and offset like: + * + * $('#foo').range().start() //-> {container: fooElement, offset: 0 } + * + * If a set value is provided, it can set the range. The start of the range is set differently + * depending on the type of set value: + * + * - __Object__ - an object with the new starting container and offset is provided like + * + * $.Range().start({container: $('#foo')[0], offset: 20}) + * + * - __Number__ - the new offset value. The container is kept the same. + * + * - __String__ - adjusts the offset by converting the string offset to a number and adding it to the current + * offset. For example, the following moves the offset forward four characters: + * + * $('#foo').range().start("+4") + * + * + * @param {Object|String|Number} [set] a set value if setting the start of the range or nothing if reading it. + * @return {jQuery.Range|Object} if setting the start, the range is returned for chaining, otherwise, the + * start offset and container are returned. + */ + start : function(set){ + if(set === undefined){ + if(this.range.startContainer){ + return { + container : this.range.startContainer, + offset : this.range.startOffset + } + }else{ + var start = this.clone().collapse().parent(); + var startRange = $.Range(start).select(start).collapse(); + startRange.move("END_TO_START", this); + return { + container : start, + offset : startRange.toString().length + } + } + } else { + if (this.range.setStart) { + if(typeof set == 'number'){ + this.range.setStart(this.range.startContainer, set) + } else if(typeof set == 'string') { + this.range.setStart(this.range.startContainer, this.range.startOffset+ parseInt(set,10) ); + } else { + this.range.setStart(set.container, set.offset) + } + } else { + throw 'todo' + } + return this; + } + + + }, + /** + * Sets or gets the end of the range. It takes similar options as [jQuery.Range.prototype.get]. + * @param {Object} [set] + */ + end : function(set){ + if (set === undefined) { + if (this.range.startContainer) { + return { + container: this.range.endContainer, + offset: this.range.endOffset + } + } + else { + var end = this.clone().collapse(false).parent(); + var endRange = $.Range(end).select(end).collapse(); + endRange.move("END_TO_END", this); + return { + container: end, + offset: endRange.toString().length + } + } + } else { + if (this.range.setEnd) { + if(typeof set == 'number'){ + this.range.setEnd(this.range.endContainer, set) + } else { + this.range.setEnd(set.container, set.offset) + } + } else { + throw 'todo' + } + return this; + } + }, + /** + * returns the most common ancestor element of the endpoints in the range. + */ + parent : function(){ + return this.range.parentElement || this.range.commonAncestorContainer + }, + /** + * Returns the bounding rectangle of this range. + * + * @param {String} [from] - where the coordinates should be + * positioned from. By default, coordinates are given from the client viewport. + * But if 'page' is given, they are provided relative to the page. + * + * @return {TextRectangle} - The client rects. + */ + rect : function(from){ + var rect = this.range.getBoundingClientRect() + if(from === 'page'){ + var off = scrollOffset(); + rect = $.extend({}, rect); + rect.top += off.top; + rect.left += off.left; + } + return rect; + }, + /** + * Returns client rects + * @param {String} [from] how the rects coordinates should be given (viewport or page). Provide 'page' for + * rect coordinates from the page. + */ + rects : function(from){ + var rects = $.makeArray( this.range.getClientRects() ).sort(function(rect1, rect2){ + return rect2.width*rect2.height - rect1.width*rect1.height; + }), + i=0,j, + len = rects.length; + //return rects; + //rects are sorted, largest to smallest + while(i < rects.length){ + var cur = rects[i], + found = false; + + j = i+1; + for(j = i+1; j < rects.length; j++){ + if( withinRect(cur, rects[j] ) ) { + found = rects[j]; + break; + } + } + if(found){ + rects.splice(i,1) + }else{ + i++; + } + + + } + // safari will be return overlapping ranges ... + if(from == 'page'){ + var off = scrollOffset(); + return $.map(rects, function(item){ + var i = $.extend({}, item) + i.top += off.top; + i.left += off.left; + return i; + }) + } + + + return rects; + } + +}); +(function(){ + //method branching .... + var fn = $.Range.prototype, + range = $.Range().range; + + /** + * @function compare + * Compares one range to another range. This is different from the spec b/c the spec is confusing. + * + * source.compare("START_TO_END", toRange); + * + * This returns -1 if source's start is before toRange's end. + * @param {Object} type + * @param {Object} range + */ + fn.compare = range.compareBoundaryPoints ? + function(type, range){ + return this.range.compareBoundaryPoints(this.window().Range[reverse( type )], range.range) + }: + function(type, range){ + return this.range.compareEndPoints(convertType(type), range.range) + } + + /** + * @function move + * Move the endpoints of a range + * @param {Object} type + * @param {Object} range + */ + fn.move = range.setStart ? + function(type, range){ + + var rangesRange = range.range; + switch(type){ + case "START_TO_END" : + this.range.setStart(rangesRange.endContainer, rangesRange.endOffset) + break; + case "START_TO_START" : + this.range.setStart(rangesRange.startContainer, rangesRange.startOffset) + break; + case "END_TO_END" : + this.range.setEnd(rangesRange.endContainer, rangesRange.endOffset) + break; + case "END_TO_START" : + this.range.setEnd(rangesRange.startContainer, rangesRange.startOffset) + break; + } + + return this; + }: + function(type, range){ + this.range.setEndPoint(convertType(type), range.range) + return this; + }; + var cloneFunc = range.cloneRange ? "cloneRange" : "duplicate", + selectFunc = range.selectNodeContents ? "selectNodeContents" : "moveToElementText"; + + /** + * Clones the range and returns a new $.Range object. + */ + fn.clone = function(){ + return $.Range( this.range[cloneFunc]() ); + }; + + /** + * Selects an element with this range + * @param {HTMLElement} el + */ + fn.select = function(el){ + this.range[selectFunc](el); + return this; + }; + +})(); + + +// helpers ----------------- + +// iterates through a list of elements, calls cb on every text node +// if cb returns false, exits the iteration +var iterate = function(elems, cb){ + var elem, start; + for (var i = 0; elems[i]; i++) { + elem = elems[i]; + // Get the text from text nodes and CDATA nodes + if (elem.nodeType === 3 || elem.nodeType === 4) { + if (cb(elem) === false) { + return false; + } + // Traverse everything else, except comment nodes + } + else + if (elem.nodeType !== 8) { + if (iterate(elem.childNodes, cb) === false) { + return false; + } + } + } +}, +// if a point is within a rectangle +within = function(rect, point){ + + return rect.left <= point.clientX && rect.left + rect.width >= point.clientX && + rect.top <= point.clientY && + rect.top + rect.height >= point.clientY +}, +// if a rectangle is within another rectangle +withinRect = function(outer, inner){ + return within(outer, { + clientX: inner.left, + clientY: inner.top + }) && //top left + within(outer, { + clientX: inner.left + inner.width, + clientY: inner.top + }) && //top right + within(outer, { + clientX: inner.left, + clientY: inner.top + inner.height + }) && //bottom left + within(outer, { + clientX: inner.left + inner.width, + clientY: inner.top + inner.height + }) //bottom right +}, +// gets the scroll offset from a window +scrollOffset = function( win){ + var win = win ||window; + doc = win.document.documentElement, body = win.document.body; + + return { + left: (doc && doc.scrollLeft || body && body.scrollLeft || 0) + (doc.clientLeft || 0), + top: (doc && doc.scrollTop || body && body.scrollTop || 0) + (doc.clientTop || 0) + }; +}; + + + +}); \ No newline at end of file diff --git a/browserid/static/jquery/dom/cur_styles/test/qunit/outer.micro b/browserid/static/jquery/dom/range/range_test.js similarity index 100% rename from browserid/static/jquery/dom/cur_styles/test/qunit/outer.micro rename to browserid/static/jquery/dom/range/range_test.js diff --git a/browserid/static/jquery/dom/selection/qunit.html b/browserid/static/jquery/dom/selection/qunit.html new file mode 100644 index 0000000000000000000000000000000000000000..b99867fd678fb621474906fc22709bbc1168234b --- /dev/null +++ b/browserid/static/jquery/dom/selection/qunit.html @@ -0,0 +1,20 @@ +<html> + <head> + <link rel="stylesheet" type="text/css" href="../../../funcunit/qunit/qunit.css" /> + <title>selection QUnit Test</title> + <script type='text/javascript'> + steal = {ignoreControllers: true} + </script> + <script type='text/javascript' src='../../../steal/steal.js?jquery/dom/selection/selection_test.js'></script> + </head> + <body> + + <h1 id="qunit-header">selection Test Suite</h1> + <h2 id="qunit-banner"></h2> + <div id="qunit-testrunner-toolbar"></div> + <h2 id="qunit-userAgent"></h2> + <div id="test-content"></div> + <ol id="qunit-tests"></ol> + <div id="qunit-test-area"></div> + </body> +</html> \ No newline at end of file diff --git a/browserid/static/jquery/dom/selection/selection.html b/browserid/static/jquery/dom/selection/selection.html new file mode 100644 index 0000000000000000000000000000000000000000..20840c29aaabb91d2a278f0edcd37880a5b0615a --- /dev/null +++ b/browserid/static/jquery/dom/selection/selection.html @@ -0,0 +1,78 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" + "http://www.w3.org/TR/html4/strict.dtd"> +<html lang="en"> + <head> + <title>selection</title> + <style type='text/css'> + body {font-family: verdana} + .error {border: solid 1px red;} + .error_text { color: red; font-size: 10px;} + td {padding: 3px;} + </style> + </head> + <body> + <table> + <tr> + <td> <a href="javascript://" id='select_textarea'>Select Textarea</a></td> + <td><textarea id=''>012 +456</textarea></td> + </tr> + <tr> + <td><a href="javascript://" id='select_input'>Select Input</a></td> + <td><input text='text' value='0123456789'/></td> + </tr> + <tr> + <td><a href="javascript://" id='select_p'>Select Within One Element</a></td> + <td><p id='1'>0123456789</p></td> + </tr> + <tr> + <td><a href="javascript://" id='select_multi'>Select Across Multiple Elements</a></td> + <td><div id='2'>012<div>3<span>4</span>5</div></div></td> + </tr> + </table> + + + <div id='selectionArea'><p>Hello <b>World</b>! how are you today?</p> + <p>I am good, thank you.</p> + </div> + + + <script type='text/javascript' + src='../../../steal/steal.js?jquery/dom/selection'> + $('#select_textarea').click(function(){ + $('textarea').selection(1,5); + }) + $('#select_input').click(function(){ + $('input').selection(1,5); + }) + $('#select_p').click(function(){ + $('#1').selection(1,5); + }) + $('#select_multi').click(function(){ + $('#2').selection(1,5); + }) + var selects = ['textarea', 'input', '#1','#2'] + + setTimeout(function(){ + var next = selects.shift(); + if(next){ + $(next).selection(1,5); + setTimeout(arguments.callee, 300) + } + },100) + + $(document.body).mouseup(function(){ + var s = $("#selectionArea").selection() + if(s){ + console.log( s.start, s.end) + }else{ + console.log('nothing') + } + + }); + </script> + <script type='text/javascript'> + + </script> + </body> +</html> \ No newline at end of file diff --git a/browserid/static/jquery/dom/selection/selection.js b/browserid/static/jquery/dom/selection/selection.js new file mode 100644 index 0000000000000000000000000000000000000000..7a224c51f8b112ff57b18371d319198832c6193f --- /dev/null +++ b/browserid/static/jquery/dom/selection/selection.js @@ -0,0 +1,236 @@ +steal.plugins('jquery','jquery/dom/range').then(function($){ +var convertType = function(type){ + return type.replace(/([a-z])([a-z]+)/gi, function(all,first, next){ + return first+next.toLowerCase() + }).replace(/_/g,""); +}, +reverse = function(type){ + return type.replace(/^([a-z]+)_TO_([a-z]+)/i, function(all, first, last){ + return last+"_TO_"+first; + }); +}, +getWindow = function( element ) { + return element ? element.ownerDocument.defaultView || element.ownerDocument.parentWindow : window +}, +// A helper that uses range to abstract out getting the current start and endPos. +getElementsSelection = function(el, win){ + var current = $.Range.current(el).clone(), + entireElement = $.Range(el).select(el); + if(!current.overlaps(entireElement)){ + return null; + } + // we need to check if it starts before our element ... + if(current.compare("START_TO_START", entireElement) < 1){ + startPos = 0; + // we should move current ... + current.move("START_TO_START",entireElement); + }else{ + fromElementToCurrent =entireElement.clone(); + fromElementToCurrent.move("END_TO_START", current); + startPos = fromElementToCurrent.toString().length + } + + // now we need to make sure current isn't to the right of us ... + if(current.compare("END_TO_END", entireElement) >= 0){ + endPos = entireElement.toString().length + }else{ + endPos = startPos+current.toString().length + } + return { + start: startPos, + end : endPos + }; +}, +getSelection = function(el){ + // use selectionStart if we can. + var win = getWindow(el); + + if (el.selectionStart !== undefined) { + + if(document.activeElement + && document.activeElement != el + && el.selectionStart == el.selectionEnd + && el.selectionStart == 0){ + return {start: el.value.length, end: el.value.length}; + } + return {start: el.selectionStart, end: el.selectionEnd} + } else if(win.getSelection){ + return getElementsSelection(el, win) + } else{ + + try { + //try 2 different methods that work differently + // one should only work for input elements, but sometimes doesn't + // I don't know why this is, or what to detect + if (el.nodeName.toLowerCase() == 'input') { + var real = getWindow(el).document.selection.createRange(), r = el.createTextRange(); + r.setEndPoint("EndToStart", real); + + var start = r.text.length + return { + start: start, + end: start + real.text.length + } + } + else { + var res = getElementsSelection(el,win) + if(!res){ + return res; + } + // we have to clean up for ie's textareas + var current = $.Range.current().clone(), + r2 = current.clone().collapse().range, + r3 = current.clone().collapse(false).range; + + r2.moveStart('character', -1) + r3.moveStart('character', -1) + // if we aren't at the start, but previous is empty, we are at start of newline + if (res.startPos != 0 && r2.text == "") { + res.startPos += 2; + } + // do a similar thing for the end of the textarea + if (res.endPos != 0 && r3.text == "") { + res.endPos += 2; + } + + return res + } + }catch(e){ + return {start: el.value.length, end: el.value.length}; + } + } +}, +select = function( el, start, end ) { + var win = getWindow(el) + if(el.setSelectionRange){ + if(end === undefined){ + el.focus(); + el.setSelectionRange(start, start); + } else { + el.select(); + el.selectionStart = start; + el.selectionEnd = end; + } + } else if (el.createTextRange) { + //el.focus(); + var r = el.createTextRange(); + r.moveStart('character', start); + end = end || start; + r.moveEnd('character', end - el.value.length); + + r.select(); + } else if(win.getSelection){ + var doc = win.document, + sel = win.getSelection(), + range = doc.createRange(), + ranges = [start, end !== undefined ? end : start]; + getCharElement([el],ranges); + range.setStart(ranges[0].el, ranges[0].count); + range.setEnd(ranges[1].el, ranges[1].count); + + // removeAllRanges is suprisingly necessary for webkit ... BOOO! + sel.removeAllRanges(); + sel.addRange(range); + + } else if(win.document.body.createTextRange){ //IE's weirdness + var range = document.body.createTextRange(); + range.moveToElementText(el); + range.collapse() + range.moveStart('character', start) + range.moveEnd('character', end !== undefined ? end : start) + range.select(); + } + +}, +/* + * If one of the range values is within start and len, replace the range + * value with the element and its offset. + */ +replaceWithLess = function(start, len, range, el){ + if(typeof range[0] === 'number' && range[0] < len){ + range[0] = { + el: el, + count: range[0] - start + }; + } + if(typeof range[1] === 'number' && range[1] <= len){ + range[1] = { + el: el, + count: range[1] - start + };; + } +}, +getCharElement = function( elems , range, len ) { + var elem, + start; + + len = len || 0; + for ( var i = 0; elems[i]; i++ ) { + elem = elems[i]; + // Get the text from text nodes and CDATA nodes + if ( elem.nodeType === 3 || elem.nodeType === 4 ) { + start = len + len += elem.nodeValue.length; + //check if len is now greater than what's in counts + replaceWithLess(start, len, range, elem ) + // Traverse everything else, except comment nodes + } else if ( elem.nodeType !== 8 ) { + len = getCharElement( elem.childNodes, range, len ); + } + } + return len; +}; +/** + * @parent dom + * @tag beta + * + * Gets or sets the current text selection. + * + * ## Getting + * + * Gets the current selection in the context of an element. For example: + * + * $('textarea').selection() // -> { .... } + * + * returns an object with: + * + * - __start__ - The number of characters from the start of the element to the start of the selection. + * _ __end__ - The number of characters from teh start of the element to the end of the selection. + * _ __range__ - A [jQuery.Range] that represents the current selection. + * + * This lets you do: + * + * var textarea = $('textarea') + * selection = textarea.selection(), + * selected = textarea.val().substr(selection.start, selection.end); + * + * alert('You selected '+selected+'.'); + * + * Selection works with all elements. If you want to get selection information on the page: + * + * $(document.body).selection(); + * + * ## Setting + * + * By providing a start and end offset, you can select text within a given element. + * + * $('#rte').selection(30, 40) + * + * @param {Number} [start] - Start of the range + * @param {Number} [end] - End of the range + * @return {Object|jQuery} - returns the selection information or the jQuery collection for + * chaining. + */ +$.fn.selection = function(start, end){ + if(start !== undefined){ + return this.each(function(){ + select(this, start, end) + }) + }else{ + return getSelection(this[0]) + } +}; +// for testing +$.fn.selection.getCharElement = getCharElement; + +}); \ No newline at end of file diff --git a/browserid/static/jquery/dom/selection/selection_test.js b/browserid/static/jquery/dom/selection/selection_test.js new file mode 100644 index 0000000000000000000000000000000000000000..d6ccd7472ec25c362c0149a6c87df98bcb044921 --- /dev/null +++ b/browserid/static/jquery/dom/selection/selection_test.js @@ -0,0 +1,40 @@ +steal + .plugins("funcunit/qunit", "jquery/dom/selection").then(function(){ + +module("jquery/dom/selection"); + +test("getCharElement", function(){ + $("#qunit-test-area") + .html("<textarea>012\n456</textarea>"+ + "<input text='text' value='01234567' id='inp'/>"+ + "<p id='1'>0123456789</p>"+ + "<div id='2'>012<div>3<span>4</span>5</div></div>"); + stop(); + setTimeout(function(){ + var types = ['textarea','#inp','#1','#2']; + for(var i =0; i< types.length; i++){ + console.log(types[i]) + $(types[i]).selection(1,5); + } + /* + $('textarea').selection(1,5); + $('input').selection(1,5); + $('#1').selection(1,5); + $('#2').selection(1,5); + */ + var res = []; + for(var i =0; i< types.length; i++){ + res.push( $(types[i]).selection() ); + } + + + + for(var i =0; i< res.length; i++){ + same(res[i],{start: 1, end: 5},types[i]) + } + + start(); + },1000) +}); + +}); \ No newline at end of file diff --git a/browserid/static/jquery/dom/within/within.js b/browserid/static/jquery/dom/within/within.js index b846f4b1b85d9a315ed7ab6b5d0ee9ce9c9db221..bea9a8bed1a10ee8a9f8285593942941c1aa6e35 100644 --- a/browserid/static/jquery/dom/within/within.js +++ b/browserid/static/jquery/dom/within/within.js @@ -12,27 +12,31 @@ steal.plugins('jquery/dom').then(function($){ * @function within * @parent dom * Returns if the elements are within the position - * @param {Object} x - * @param {Object} y - * @param {Object} cache + * @param {Number} left the position + * @param {Number} top + * @param {Boolean} [useOffsetCache] */ -$.fn.within= function(x, y, cache) { +$.fn.within= function(left, top, useOffsetCache) { var ret = [] this.each(function(){ var q = jQuery(this); - if(this == document.documentElement) return ret.push(this); - - var offset = cache ? jQuery.data(this,"offset", q.offset()) : q.offset(); + if (this == document.documentElement) { + return ret.push(this); + } + var offset = useOffsetCache ? + jQuery.data(this,"offsetCache") || jQuery.data(this,"offsetCache", q.offset()) : + q.offset(); - var res = withinBox(x, y, - offset.left, offset.top, - this.offsetWidth, this.offsetHeight ); + var res = withinBox(left, top, offset.left, offset.top, + this.offsetWidth, this.offsetHeight ); - if(res) ret.push(this); + if (res) { + ret.push(this); + } }); - return this.pushStack( jQuery.unique( ret ), "within", x+","+y ); + return this.pushStack( jQuery.unique( ret ), "within", left+","+top ); } diff --git a/browserid/static/jquery/download/dependencies.json b/browserid/static/jquery/download/dependencies.json new file mode 100644 index 0000000000000000000000000000000000000000..2f6c21e5e418958f408cc58c5f178d13f960485b --- /dev/null +++ b/browserid/static/jquery/download/dependencies.json @@ -0,0 +1 @@ +{"steal/dev/dev.js":[],"jquery/class/class.js":["jquery/jquery.js","jquery/lang/lang.js"],"jquery/jquery.js":[],"jquery/lang/lang.js":["jquery/jquery.js"],"jquery/controller/controller.js":["jquery/class/class.js","jquery/lang/lang.js","jquery/event/destroyed/destroyed.js"],"jquery/event/destroyed/destroyed.js":["jquery/event/event.js"],"jquery/event/event.js":["jquery/jquery.js"],"jquery/controller/history/history.js":["jquery/controller/subscribe/subscribe.js","jquery/event/hashchange/hashchange.js","jquery/lang/deparam/deparam.js"],"jquery/controller/subscribe/subscribe.js":["jquery/controller/controller.js","jquery/lang/openajax/openajax.js"],"jquery/lang/openajax/openajax.js":[],"jquery/event/hashchange/hashchange.js":[],"jquery/lang/deparam/deparam.js":["jquery/jquery.js"],"jquery/controller/history/html5/html5.js":["jquery/controller/subscribe/subscribe.js"],"jquery/controller/view/view.js":["jquery/controller/controller.js","jquery/view/view.js"],"jquery/view/view.js":["jquery/jquery.js"],"jquery/dom/dom.js":["jquery/jquery.js"],"jquery/dom/closest/closest.js":["jquery/dom/dom.js"],"jquery/dom/compare/compare.js":["jquery/dom/dom.js"],"jquery/dom/cookie/cookie.js":["jquery/lang/json/json.js"],"jquery/lang/json/json.js":["jquery/jquery.js"],"jquery/dom/cur_styles/cur_styles.js":["jquery/dom/dom.js"],"jquery/dom/dimensions/dimensions.js":["jquery/dom/cur_styles/cur_styles.js"],"jquery/dom/fixture/fixture.js":["jquery/dom/dom.js"],"jquery/dom/form_params/form_params.js":["jquery/dom/dom.js"],"jquery/dom/range/range.js":["jquery/jquery.js","jquery/dom/compare/compare.js"],"jquery/dom/selection/selection.js":["jquery/jquery.js","jquery/dom/range/range.js"],"jquery/dom/within/within.js":["jquery/dom/dom.js"],"jquery/download/download.js":[],"jquery/event/default/default.js":["jquery/event/event.js"],"jquery/event/drag/drag.js":["jquery/event/event.js","jquery/lang/vector/vector.js","jquery/event/livehack/livehack.js"],"jquery/lang/vector/vector.js":["jquery/jquery.js"],"jquery/event/livehack/livehack.js":["jquery/event/event.js"],"jquery/event/drag/limit/limit.js":["jquery/event/drag/drag.js","jquery/dom/cur_styles/cur_styles.js"],"jquery/event/drag/scroll/scroll.js":["jquery/event/drop/drop.js"],"jquery/event/drop/drop.js":["jquery/event/drag/drag.js","jquery/dom/within/within.js","jquery/dom/compare/compare.js"],"jquery/event/drag/step/step.js":["jquery/event/drag/drag.js","jquery/dom/cur_styles/cur_styles.js"],"jquery/event/hover/hover.js":["jquery/event/event.js","jquery/event/livehack/livehack.js"],"jquery/event/key/key.js":["jquery/event/event.js"],"jquery/event/pause/pause.js":["jquery/event/livehack/livehack.js"],"jquery/event/resize/resize.js":["jquery/event/event.js"],"jquery/event/selection/selection.js":["jquery/dom/range/range.js","jquery/controller/controller.js","jquery/event/livehack/livehack.js"],"jquery/event/swipe/swipe.js":["jquery/event/livehack/livehack.js"],"jquery/lang/rsplit/rsplit.js":["jquery/lang/lang.js"],"jquery/model/model.js":["jquery/class/class.js","jquery/lang/lang.js"],"jquery/model/associations/associations.js":["jquery/model/model.js"],"jquery/model/backup/backup.js":["jquery/model/model.js"],"jquery/model/guesstype/guesstype.js":["jquery/model/model.js"],"jquery/model/list/list.js":["jquery/model/model.js"],"jquery/model/list/cookie/cookie.js":["jquery/dom/cookie/cookie.js","jquery/model/list/list.js"],"jquery/model/list/local/local.js":["jquery/dom/cookie/cookie.js","jquery/model/list/list.js"],"jquery/model/service/service.js":["jquery/model/model.js"],"jquery/model/service/json_rest/json_rest.js":["jquery/model/service/service.js"],"jquery/model/service/twitter/twitter.js":["jquery/model/service/service.js"],"jquery/model/service/yql/yql.js":["jquery/model/service/service.js"],"jquery/model/validations/validations.js":["jquery/model/model.js"],"jquery/tie/tie.js":["jquery/controller/controller.js"],"jquery/view/ejs/ejs.js":["jquery/view/view.js","jquery/lang/rsplit/rsplit.js"],"jquery/view/helpers/helpers.js":["jquery/view/ejs/ejs.js"],"jquery/view/jaml/jaml.js":["jquery/view/view.js"],"jquery/view/micro/micro.js":["jquery/view/view.js"],"jquery/view/tmpl/tmpl.js":["jquery/view/view.js"]} \ No newline at end of file diff --git a/browserid/static/jquery/download/download.js b/browserid/static/jquery/download/download.js index f1c37cc21fa8be927468f9e1c04cf19bd82d29b1..c6e5ef0be353548eed50d25cb8533abd81a7448b 100644 --- a/browserid/static/jquery/download/download.js +++ b/browserid/static/jquery/download/download.js @@ -41,13 +41,17 @@ } this.dependencies = []; var $form = $target.closest('form'), - params = $form.formParams(), i; + params = $form.formParams(), i, queryVal; for(i=0; i<params.plugins.length; i++){ this._pushPlugins(this._getDependencies(params.plugins[i])); } $('#pluginForm input[type=checkbox]').attr('checked', false); for(i=0; i<this.dependencies.length; i++){ - $('input[value='+this.dependencies[i]+']').attr('checked', true); + queryVal = this.dependencies[i] + .replace(new RegExp("/", "g"), "\\/") + .replace(new RegExp("\\.", "g"), "\\."); + $('input[value='+queryVal+']') + .attr('checked', true); } }, /** diff --git a/browserid/static/jquery/event/default/default.js b/browserid/static/jquery/event/default/default.js index 2e6a62055700337873d7fa96ac546542dc02c1de..d87d8606fef90758fb670d1d31fe67c806284b6e 100644 --- a/browserid/static/jquery/event/default/default.js +++ b/browserid/static/jquery/event/default/default.js @@ -1,10 +1,67 @@ + +steal.plugins('jquery/event').then(function($){ + +$.fn. /** - * @add jQuery.event.special + * @function jQuery.fn.triggerAsync + * @plugin jquery/event/default + * @parent jquery.event.pause + * + * Triggers an event and calls success when the event has finished propagating through the DOM and + * preventDefault is not called. + * + * $('#panel').triggerAsync('show', function(){ + * $('#panel').show(); + * }) + * + * You can also provide a callback that gets called if preventDefault was called on the event: + * + * $('#panel').triggerAsync('show', function(){ + * $('#panel').show(); + * },function(){ + * $('#other').addClass('error'); + * }) + * + * triggerAsync is designed to work with the [jquery.event.pause] plugin although it is defined in + * <code>jquery/event/default</code> + * + * ## API + * + * + * @param {String} type The type of event + * @param {Object} data The data for the event + * @param {Function} success(event) a callback function + * @param {Function} prevented(event) called if preventDefault is called on the */ -steal.plugins('jquery/event').then(function($){ +triggerAsync = function(type, data, success, prevented){ + if(typeof data == 'function'){ + success = data; + data = undefined; + } + + if ( this[0] ) { + var event = $.Event( type ), + old = event.preventDefault; + + event.preventDefault = function(){ + old.apply(this, arguments); + prevented && prevented(this) + } + //event._success= success; + jQuery.event.trigger( {type: type, _success: success}, data, this[0] ); + } else{ + success.call(this); + } + return this; +} + + +/** + * @add jQuery.event.special + */ //cache default types for performance -var types = {}, rnamespaces= /\.(.*)$/; +var types = {}, rnamespaces= /\.(.*)$/, $event = $.event; /** * @attribute default * @parent specialevents @@ -12,47 +69,44 @@ var types = {}, rnamespaces= /\.(.*)$/; * @download http://jmvcsite.heroku.com/pluginify?plugins[]=jquery/event/default/default.js * @test jquery/event/default/qunit.html * Allows you to perform default actions as a result of an event. - * <p> + * * Event based APIs are a powerful way of exposing functionality of your widgets. It also fits in * quite nicely with how the DOM works. - * </p> - * <p> + * + * * Like default events in normal functions (e.g. submitting a form), synthetic default events run after * all event handlers have been triggered and no event handler has called * preventDefault or returned false. - * </p> - * <p>To listen for a default event, just prefix the event with default.</p> - * @codestart - * $("div").bind("default.show", function(ev){ ... }); - * $("ul").delegate("li","default.activate", function(ev){ ... }); - * @codeend - * <p> - * The default plugin also adds the [jQuery.fn.triggerDefault triggerDefault] and [jQuery.fn.triggerDefaults triggerDefaults] methods. These are used to trigger - * an event and report back whether preventDefault was called on the event. The only difference is [jQuery.fn.triggerDefault triggerDefault] - * doesn't bubble. - * </p> - * <h2>Example</h2> - * <p>Lets look at how you could build a simple tabs widget with default events. - * First with just jQuery:</p> - * <p> + * + * To listen for a default event, just prefix the event with default. + * + * $("div").bind("default.show", function(ev){ ... }); + * $("ul").delegate("li","default.activate", function(ev){ ... }); + * + * + * ## Example + * + * Lets look at how you could build a simple tabs widget with default events. + * First with just jQuery: + * * Default events are useful in cases where you want to provide an event based * API for users of your widgets. Users can simply listen to your synthetic events and * prevent your default functionality by calling preventDefault. - * </p> - * <p> + * * In the example below, the tabs widget provides a show event. Users of the * tabs widget simply listen for show, and if they wish for some reason, call preventDefault * to avoid showing the tab. - * </p> - * <p> + * * In this case, the application developer doesn't want to show the second * tab until the checkbox is checked. - * </p> + * * @demo jquery/event/default/defaultjquery.html - * <p>Lets see how we would build this with JavaScriptMVC:</p> + * + * Lets see how we would build this with JavaScriptMVC: + * * @demo jquery/event/default/default.html */ -$.event.special["default"] = { +$event.special["default"] = { add: function( handleObj ) { //save the type types[handleObj.namespace.replace(rnamespaces,"")] = true; @@ -66,126 +120,99 @@ $.event.special["default"] = { ev._defaultActions.push({element: this, handler: origHandler, event: ev, data: data, currentTarget: ev.currentTarget}) } }, - setup: function() {return true} -} - -// overwrite trigger to allow default types -var oldTrigger = $.event.trigger; -$.event.trigger = function defaultTriggerer( event, data, elem, bubbling){ - //always need to convert here so we know if we have default actions - var type = event.type || event - - if ( !bubbling ) { - event = typeof event === "object" ? - // jQuery.Event object - event[$.expando] ? event : - // Object literal - jQuery.extend( jQuery.Event(type), event ) : - // Just the event type (string) - jQuery.Event(type); - - if ( type.indexOf("!") >= 0 ) { - event.type = type = type.slice(0, -1); - event.exclusive = true; - } - event._defaultActions = []; //set depth for possibly reused events - } - - var defaultGetter = jQuery.Event("default."+event.type), - res; + setup: function() {return true}, + triggerDefault : function(event, elem){ - $.extend(defaultGetter,{ - target: elem, - _defaultActions: event._defaultActions, - exclusive : true - }); - - defaultGetter.stopPropagation(); - - //default events only work on elements - if(elem){ - oldTrigger.call($.event, defaultGetter, [defaultGetter, data], elem, true); - } - - //fire old trigger, this will call back here - res = oldTrigger.call($.event, event, data, elem, bubbling); - - //fire if there are default actions to run && - // we have not prevented default && - // propagation has been stopped or we are at the document element - // we have reached the document - if (!event.isDefaultPrevented() && - event._defaultActions && - ( ( event.isPropagationStopped() ) || - ( !elem.parentNode && !elem.ownerDocument ) ) - - ) { - - // put event back - event.namespace= event.type; - event.type = "default"; - event.liveFired = null; + var defaultGetter = jQuery.Event("default."+event.type); + + $.extend(defaultGetter,{ + target: elem, + _defaultActions: event._defaultActions, + exclusive : true + }); - // call each event handler - for(var i = 0 ; i < event._defaultActions.length; i++){ - var a = event._defaultActions[i], - oldHandle = event.handled; - event.currentTarget = a.currentTarget; - a.handler.call(a.element, event, a.data); - event.handled = event.handled === null ? oldHandle : true; - } - event._defaultActions = null; //set to null so everyone else on this element ignores it - } -} -/** - * @add jQuery.fn - */ -$.fn. -/** - * Triggers the event, stops the event from propagating through the DOM, and - * returns whether or not the event's default action was prevented. - * If true, the default action was not prevented. If false, the - * default action was prevented. This is the same as triggerDefaults, but - * the event doesn't bubble. Use these methods to easily determine if default was - * prevented, and proceed accordingly. - * - * <p>Widget developers might use this method to perform additional logic if an event - * handler doesn't prevent the default action. For example, a tabs widget might - * hide the currently shown tab if the application developer doesn't prevent default.</p> - * @param {Object} type The type of event to trigger. - * @param {Object} data Some data to pass to callbacks listening to this - * event. - */ -triggerDefault = function(type, data){ - if ( this[0] ) { - var event = $.Event( type ); - event.stopPropagation(); - jQuery.event.trigger( event, data, this[0] ); - return !event.isDefaultPrevented(); + defaultGetter.stopPropagation(); + + //default events only work on elements + if(elem){ + $event.handle.call(elem, defaultGetter); + } + }, + checkAndRunDefaults : function(event, elem){ + //fire if there are default actions to run && + // we have not prevented default && + // propagation has been stopped or we are at the document element + // we have reached the document + if (!event.isDefaultPrevented() && + (!event.isPaused || !event.isPaused()) && // no paused function or it's not paused + event._defaultActions && + ( ( event.isPropagationStopped() ) || + ( !elem.parentNode && !elem.ownerDocument ) ) + + ) { + + // put event back + event.namespace= event.type; + event.type = "default"; + event.liveFired = null; + + // call each event handler + for(var i = 0 ; i < event._defaultActions.length; i++){ + var a = event._defaultActions[i], + oldHandle = event.handled; + event.currentTarget = a.currentTarget; + a.handler.call(a.element, event, a.data); + event.handled = event.handled === null ? oldHandle : true; + } + + event._defaultActions = null; //set to null so everyone else on this element ignores it + + if(event._success){ + event._success(event); + } + } } - return true; } -$.fn. -/** - * Triggers the event and returns whether or not the event's - * default action was prevented. If true, the default action was not - * prevented. If false, the default action was prevented. This is the same - * as triggerDefault, but the event bubbles. Use these methods to easily determine if default was - * prevented, and proceed accordingly. - * @param {Object} type The type of event to trigger. - * @param {Object} data Some data to pass to callbacks listening to this - * event. - */ -triggerDefaults = function(type, data){ - if ( this[0] ) { - var event = $.Event( type ); - jQuery.event.trigger( event, data, this[0] ); - return !event.isDefaultPrevented(); + +// overwrite trigger to allow default types +var oldTrigger = $event.trigger, + triggerDefault = $event.special['default'].triggerDefault, + checkAndRunDefaults = $event.special['default'].checkAndRunDefaults, + oldData = jQuery._data; + +$._data = function(elem, name, data){ + // always need to supply a function to call for handle + if(!data && name === "handle"){ + var func = oldData.apply(this, arguments); + return function(e){ + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ? + jQuery.event.handle.apply( this, arguments ) : + undefined; + } } - return true; + return oldData.apply(this, arguments) } + +$event.trigger = function defaultTriggerer( event, data, elem, onlyHandlers){ + // Event object or event type + var type = event.type || event, + namespaces = [], + + // Caller can pass in an Event, Object, or just an event type string + event = typeof event === "object" ? + // jQuery.Event object + event[ jQuery.expando ] ? event : + // Object literal + new jQuery.Event( type, event ) : + // Just the event type (string) + new jQuery.Event( type ); + + event._defaultActions = []; //set depth for possibly reused events - + oldTrigger.call($.event, event, data, elem, onlyHandlers); +}; diff --git a/browserid/static/jquery/event/default/default_pause_test.html b/browserid/static/jquery/event/default/default_pause_test.html new file mode 100644 index 0000000000000000000000000000000000000000..dfc3f0a49752936217558531daa7c393f83b9cef --- /dev/null +++ b/browserid/static/jquery/event/default/default_pause_test.html @@ -0,0 +1,22 @@ +<html> + <head> + <title>Default Test Suite</title> + <link rel="stylesheet" type="text/css" href="../../../funcunit/qunit/qunit.css" /> + <style> + body { + margin: 0px; padding: 0px; + } + </style> + <script type='text/javascript' src='../../../steal/steal.js?jquery/event/default/default_pause_test.js'></script> + </head> + <body> + + <h1 id="qunit-header">Default Test Suite</h1> + <h2 id="qunit-banner"></h2> + <div id="qunit-testrunner-toolbar"></div> + <h2 id="qunit-userAgent"></h2> + <div id="test-content"></div> + <ol id="qunit-tests"></ol> + <div id="qunit-test-area"></div> + </body> +</html> \ No newline at end of file diff --git a/browserid/static/jquery/event/default/default_pause_test.js b/browserid/static/jquery/event/default/default_pause_test.js new file mode 100644 index 0000000000000000000000000000000000000000..9ffa00f39a7a67ab22fa1c0bec1250fb06c6b2a3 --- /dev/null +++ b/browserid/static/jquery/event/default/default_pause_test.js @@ -0,0 +1,98 @@ +steal.plugins('funcunit/qunit','jquery/event/default','jquery/event/pause').then(function(){ + +module("jquery/event/default_pause"); + + +test("default and pause with delegate", function(){ + var order = []; + stop(); + $("#qunit-test-area").html("<div id='foo'><p id='bar'>hello</p></div>") + + $("#foo").delegate("#bar","default.show", function(){ + order.push("default") + }); + + $("#foo").delegate("#bar","show", function(ev){ + order.push('show') + ev.pause(); + setTimeout(function(){ + ev.resume(); + + setTimeout(function(){ + start(); + same(order,['show','default']) + },30) + + },50) + }); + + + $("#bar").trigger("show") + +}); + +test("default and pause with live", function(){ + $("#qunit-test-area").html("<div id='foo'>hello</div>") + + var order = []; + stop(); + + $("#foo").live("default.show", function(){ + order.push("default") + }); + $("#foo").live("show", function(ev){ + order.push('show') + ev.pause(); + setTimeout(function(){ + ev.resume(); + setTimeout(function(){ + start(); + same(order,['show','default']) + $("#foo").die("show"); + $("#foo").die("default.show"); + },30) + },50) + }); + + + $("#foo").trigger("show") + +}); + + +test("triggerAsync", function(){ + $("#qunit-test-area").html("<div id='foo'>hello</div>") + + var order = []; + stop(); + + $("#foo").live("default.show", function(){ + order.push("default") + }); + $("#foo").live("show", function(ev){ + order.push('show') + ev.pause(); + setTimeout(function(){ + ev.resume(); + setTimeout(function(){ + start(); + $("#foo").die() + same(order,['show','default','async']) + },30) + },50) + }); + + + $("#foo").triggerAsync("show", function(){ + order.push("async") + }) +}); + +test("triggerAsync with nothing", function(){ + $("#fool").triggerAsync("show", function(){ + ok(true) + }) +}); + + +}); \ No newline at end of file diff --git a/browserid/static/jquery/event/default/test/qunit/default_test.js b/browserid/static/jquery/event/default/default_test.js similarity index 72% rename from browserid/static/jquery/event/default/test/qunit/default_test.js rename to browserid/static/jquery/event/default/default_test.js index 269af1c21f6081d52bbb945bf67647edeef89168..8183ac391b003a95ef87c09a17996359069d29e0 100644 --- a/browserid/static/jquery/event/default/test/qunit/default_test.js +++ b/browserid/static/jquery/event/default/default_test.js @@ -1,3 +1,5 @@ +steal.plugins('funcunit/qunit','jquery/event/default').then(function(){ + module("jquery/event/default") test("namespaced with same function", function(){ @@ -13,9 +15,10 @@ test("namespaced with same function", function(){ test("triggering defaults", function(){ - $("#qunit-test-area").html("//jquery/event/default/test/qunit/html.micro",{}) - + $("#qunit-test-area").html( + "<div id='bigwrapper'><div id='wrap1'><div id='touchme1'>ClickMe</div></div>"+ + "<div id='wrap2'><div id='touchme2'>ClickMe</a></div></div>") var count1 = 0, defaultNum, touchNum, num = 0;; @@ -64,7 +67,10 @@ test("triggering defaults", function(){ test("live on default events", function(){ - $("#qunit-test-area").html("//jquery/event/default/test/qunit/html.micro",{}) + $("#qunit-test-area").html( + + "<div id='bigwrapper'><div id='wrap1'><div id='touchme1'>ClickMe</div></div>"+ + "<div id='wrap2'><div id='touchme2'>ClickMe</a></div></div>") var bw = $("#bigwrapper"), count1 = 0, count2 = 0, @@ -101,4 +107,25 @@ test("live on default events", function(){ $("#qunit-test-area").html("") -}) +}); + + +test("default and live order", function(){ + var order = []; + $("#qunit-test-area").html("<div id='foo'></div>") + + $("#foo").live("default.show", function(){ + order.push("default") + }); + $("#foo").live("show", function(){ + order.push("show") + }); + + $("#foo").trigger("show") + + same(order, ['show','default'],"show then default") + $("#foo").die() +}); + + +}); diff --git a/browserid/static/jquery/event/default/qunit.html b/browserid/static/jquery/event/default/qunit.html index 207b0e31c2d000c7db0b3f27b849ff3d4fbc13cc..13d292988af1eea5da9ea89c45c6d95b616be0ab 100644 --- a/browserid/static/jquery/event/default/qunit.html +++ b/browserid/static/jquery/event/default/qunit.html @@ -7,7 +7,7 @@ margin: 0px; padding: 0px; } </style> - <script type='text/javascript' src='../../../steal/steal.js?steal[app]=jquery/event/default/test/qunit'></script> + <script type='text/javascript' src='../../../steal/steal.js?jquery/event/default/default_test.js'></script> </head> <body> diff --git a/browserid/static/jquery/event/default/test/qunit/html.micro b/browserid/static/jquery/event/default/test/qunit/html.micro deleted file mode 100644 index e10f1199b92640452b8160dc401c61b57a813588..0000000000000000000000000000000000000000 --- a/browserid/static/jquery/event/default/test/qunit/html.micro +++ /dev/null @@ -1,8 +0,0 @@ -<div id="bigwrapper"> - <div id='wrap1'> - <div id="touchme1">ClickMe</div> - </div> - <div id='wrap2'> - <div id='touchme2'>ClickMe</a> - </div> -</div> \ No newline at end of file diff --git a/browserid/static/jquery/event/default/test/qunit/qunit.js b/browserid/static/jquery/event/default/test/qunit/qunit.js deleted file mode 100644 index cf8ac90b5dff931a1565e5028fac435df8d56bb7..0000000000000000000000000000000000000000 --- a/browserid/static/jquery/event/default/test/qunit/qunit.js +++ /dev/null @@ -1,6 +0,0 @@ -//we probably have to have this only describing where the tests are -steal - .plugins("jquery/event/default") //load your app - .plugins('funcunit/qunit','jquery/view/micro') //load qunit - .then("default_test") - diff --git a/browserid/static/jquery/event/drag/drag.html b/browserid/static/jquery/event/drag/drag.html index f9437d58b4a1cee7227b0d9078a25ff20cc45f8b..17556909931f89a96d9a7368985ae943897d65a4 100644 --- a/browserid/static/jquery/event/drag/drag.html +++ b/browserid/static/jquery/event/drag/drag.html @@ -113,8 +113,6 @@ $("#scroll-drag").bind("draginit",function(ev, drag){drag.scrolls( $("#scrollare $("#form-drag").bind("dragdown",function(ev, drag){ if(ev.target.nodeName.toLowerCase() == 'input'){ drag.cancel(); - }else{ - ev.preventDefault(); } }) </script> diff --git a/browserid/static/jquery/event/drag/drag.js b/browserid/static/jquery/event/drag/drag.js index 5c6b6f844781174c87fb84fa08edff3bbb0fb4e0..3264540c5e8d0e8183931928b370816f5df01259 100644 --- a/browserid/static/jquery/event/drag/drag.js +++ b/browserid/static/jquery/event/drag/drag.js @@ -8,7 +8,10 @@ steal.plugins('jquery/event', 'jquery/lang/vector', 'jquery/event/livehack').the return method.apply(object, args2); }; }, - event = $.event; + event = $.event, + clearSelection = window.getSelection ? function(){ + window.getSelection().removeAllRanges() + } : function(){}; // var handle = event.handle; //unused /** * @class jQuery.Drag @@ -21,8 +24,10 @@ steal.plugins('jquery/event', 'jquery/lang/vector', 'jquery/event/livehack').the * as a parameter to the drag event callbacks. By calling * methods on the drag event, you can alter the drag's * behavior. - * <h2>Drag Events</h2> + * ## Drag Events + * * The drag plugin allows you to listen to the following events: + * * <ul> * <li><code>dragdown</code> - the mouse cursor is pressed down</li> * <li><code>draginit</code> - the drag motion is started</li> @@ -31,23 +36,27 @@ steal.plugins('jquery/event', 'jquery/lang/vector', 'jquery/event/livehack').the * <li><code>dragover</code> - the drag is over a drop point</li> * <li><code>dragout</code> - the drag moved out of a drop point</li> * </ul> - * <p>Just by binding or delegating on one of these events, you make + * + * Just by binding or delegating on one of these events, you make * the element dragable. You can change the behavior of the drag * by calling methods on the drag object passed to the callback. - * <h3>Example</h3> + * + * ### Example + * * Here's a quick example: - * @codestart - * //makes the drag vertical - * $(".drags").live("draginit", function(event, drag){ - * drag.vertical(); - * }) - * //gets the position of the drag and uses that to set the width - * //of an element - * $(".resize").live("dragmove",function(event, drag){ - * $(this).width(drag.position.left() - $(this).offset().left ) - * }) - * @codeend - * <h2>Drag Object</h2> + * + * //makes the drag vertical + * $(".drags").live("draginit", function(event, drag){ + * drag.vertical(); + * }) + * //gets the position of the drag and uses that to set the width + * //of an element + * $(".resize").live("dragmove",function(event, drag){ + * $(this).width(drag.position.left() - $(this).offset().left ) + * }) + * + * ## Drag Object + * * <p>The drag object is passed after the event to drag * event callback functions. By calling methods * and changing the properties of the drag object, @@ -81,6 +90,7 @@ steal.plugins('jquery/event', 'jquery/lang/vector', 'jquery/event/livehack').the $.extend($.Drag, { lowerName: "drag", current: null, + distance: 0, /** * Called when someone mouses down on a draggable object. * Gathers all callback functions and creates a new Draggable. @@ -104,6 +114,7 @@ steal.plugins('jquery/event', 'jquery/lang/vector', 'jquery/event/livehack').the delegate: ev.liveFired || element, selector: ev.handleObj.selector, moved: false, + distance: this.distance, callbacks: { dragdown: event.find(delegate, ["dragdown"], selector), draginit: event.find(delegate, ["draginit"], selector), @@ -118,17 +129,12 @@ steal.plugins('jquery/event', 'jquery/lang/vector', 'jquery/event/livehack').the }, ev); } }); - - - - - + /** * @Prototype */ $.extend($.Drag.prototype, { setup: function( options, ev ) { - //this.noSelection(); $.extend(this, options); this.element = $(this.element); this.event = ev; @@ -138,11 +144,15 @@ steal.plugins('jquery/event', 'jquery/lang/vector', 'jquery/event/livehack').the mouseup = bind(this, this.mouseup); this._mousemove = mousemove; this._mouseup = mouseup; + this._distance = options.distance ? options.distance : 0; + $(document).bind('mousemove', mousemove); $(document).bind('mouseup', mouseup); if (!this.callEvents('down', this.element, ev) ) { - ev.preventDefault(); + this.noSelection(this.delegate); + //this is for firefox + clearSelection(); } }, /** @@ -155,11 +165,17 @@ steal.plugins('jquery/event', 'jquery/lang/vector', 'jquery/event/livehack').the if (!this.moved ) { this.event = this.element = null; } - //this.selection(); + + this.selection(this.delegate); this.destroyed(); }, mousemove: function( docEl, ev ) { if (!this.moved ) { + var dist = Math.pow( ev.pageX - this.event.pageX, 2 ) + Math.pow( ev.pageY - this.event.pageY, 2 ); + if(dist < this._distance){ + return false; + } + this.init(this.element, ev); this.moved = true; } @@ -171,6 +187,7 @@ steal.plugins('jquery/event', 'jquery/lang/vector', 'jquery/event/livehack').the //e.preventDefault(); this.draw(pointer, ev); }, + mouseup: function( docEl, event ) { //if there is a current, we should call its dragstop if ( this.moved ) { @@ -178,18 +195,48 @@ steal.plugins('jquery/event', 'jquery/lang/vector', 'jquery/event/livehack').the } this.destroy(); }, - noSelection: function() { + + /** + * noSelection method turns off text selection during a drag event. + * This method is called by default unless a event is listening to the 'dragdown' event. + * + * ## Example + * + * $('div.drag').bind('dragdown', function(elm,event,drag){ + * drag.noSelection(); + * }); + * + * @param [elm] an element to prevent selection on. Defaults to the dragable element. + */ + noSelection: function(elm) { + elm = elm || this.delegate + document.documentElement.onselectstart = function() { return false; }; document.documentElement.unselectable = "on"; - $(document.documentElement).css('-moz-user-select', 'none'); + this.selectionDisabled = (this.selectionDisabled ? this.selectionDisabled.add(elm) : $(elm)); + this.selectionDisabled.css('-moz-user-select', '-moz-none'); }, - selection: function() { - document.documentElement.onselectstart = function() {}; - document.documentElement.unselectable = "off"; - $(document.documentElement).css('-moz-user-select', ''); + + /** + * selection method turns on text selection that was previously turned off during the drag event. + * This method is called by default in 'destroy' unless a event is listening to the 'dragdown' event. + * + * ## Example + * + * $('div.drag').bind('dragdown', function(elm,event,drag){ + * drag.noSelection(); + * }); + */ + selection: function(elm) { + if(this.selectionDisabled){ + document.documentElement.onselectstart = function() {}; + document.documentElement.unselectable = "off"; + this.selectionDisabled.css('-moz-user-select', ''); + } }, + init: function( element, event ) { element = $(element); var startElement = (this.movingElement = (this.element = $(element))); //the element that has been clicked on @@ -256,13 +303,27 @@ steal.plugins('jquery/event', 'jquery/lang/vector', 'jquery/event/livehack').the if ( this._cancelled ) { return; } + clearSelection(); /** * @attribute location * The location of where the element should be in the page. This * takes into account the start position of the cursor on the element. + * + * If the drag is going to be moved to an unacceptable location, you can call preventDefault in + * dragmove to prevent it from being moved there. + * + * $('.mover').bind("dragmove", function(ev, drag){ + * if(drag.location.top() < 100){ + * ev.preventDefault() + * } + * }); + * + * You can also set the location to where it should be on the page. */ this.location = pointer.minus(this.mouseElementPosition); // the offset between the mouse pointer and the representative that the user asked for // position = mouse - (dragOffset - dragTopLeft) - mousePosition + + // call move events this.move(event); if ( this._cancelled ) { return; @@ -347,7 +408,9 @@ steal.plugins('jquery/event', 'jquery/lang/vector', 'jquery/event/livehack').the this.movingElement.css({ zIndex: this.oldZIndex }); - if ( this.movingElement[0] !== this.element[0] ) { + if ( this.movingElement[0] !== this.element[0] && + !this.movingElement.has(this.element[0]).length && + !this.element.has(this.movingElement[0]).length ) { this.movingElement.css({ display: 'none' }); @@ -382,6 +445,7 @@ steal.plugins('jquery/event', 'jquery/lang/vector', 'jquery/event/livehack').the // store the original element and make the ghost the dragged element this.movingElement = ghost; + this.noSelection(ghost) this._removeMovingElement = true; return ghost; }, @@ -404,7 +468,7 @@ steal.plugins('jquery/event', 'jquery/lang/vector', 'jquery/event/livehack').the display: 'block', position: 'absolute' }).show(); - + this.noSelection(this.movingElement) this.mouseElementPosition = new $.Vector(this._offsetX, this._offsetY); }, /** @@ -417,7 +481,7 @@ steal.plugins('jquery/event', 'jquery/lang/vector', 'jquery/event/livehack').the * @param {Boolean} [val] optional, set to false if you don't want to revert. */ revert: function( val ) { - this._revert = val === null ? true : val; + this._revert = val === undefined ? true : val; }, /** * Isolates the drag to vertical movement. @@ -438,6 +502,19 @@ steal.plugins('jquery/event', 'jquery/lang/vector', 'jquery/event/livehack').the */ only: function( only ) { return (this._only = (only === undefined ? true : only)); + }, + + /** + * Sets the distance from the mouse before the item begins dragging. + * @param {Number} val + */ + distance:function(val){ + if(val !== undefined){ + this._distance = val; + return this; + }else{ + return this._distance + } } }); @@ -493,8 +570,4 @@ steal.plugins('jquery/event', 'jquery/lang/vector', 'jquery/event/livehack').the $.Drag.mousedown.call($.Drag, e, this); }); - - - - }); \ No newline at end of file diff --git a/browserid/static/jquery/event/drag/test/qunit/drag_test.js b/browserid/static/jquery/event/drag/drag_test.js similarity index 79% rename from browserid/static/jquery/event/drag/test/qunit/drag_test.js rename to browserid/static/jquery/event/drag/drag_test.js index 4e8b8524ef61cba67069722519f438954fc13151..efaf8a7195d7f0e7c1fe5e40865eba726b755b50 100644 --- a/browserid/static/jquery/event/drag/test/qunit/drag_test.js +++ b/browserid/static/jquery/event/drag/drag_test.js @@ -1,3 +1,7 @@ +steal.plugins("jquery/event/drop", + 'funcunit/qunit', + 'funcunit/syn').then("//jquery/event/drop/drop_test",function(){ + module("jquery/event/drag",{ makePoints : function(){ var div = $("<div>"+ @@ -24,8 +28,7 @@ test("dragging an element", function(){ "<div id='midpoint'></div>"+ "<div id='drop'></div>"+ "</div>"); - - div.appendTo($("#qunit-test-area")); + $("#qunit-test-area").html(div); var basicCss = { width: "20px", height: "20px", @@ -181,9 +184,53 @@ test("dragdown" , function(){ var offset2 = $('#dragger').offset(); equals(offset.top+20, offset2.top, "top") equals(offset.left+20, offset2.left, "left") - ok(draginpfocused, "First input was allowed to be focused correctly"); + // IE doesn't respect preventDefault on text inputs (http://www.quirksmode.org/dom/events/click.html) + if(!$.browser.msie) + ok(draginpfocused, "First input was allowed to be focused correctly"); //ok(!dragnopreventfocused, "Second input was not allowed to focus"); start(); }) }) + +test("dragging child element (a handle)" , function(){ + var div = $("<div>"+ + "<div id='dragger'>"+ + "<div id='dragged'>Place to drag</div>"+ + "</div>"+ + "</div>"); + + $("#qunit-test-area").html(div); + $("#dragger").css({ + position: "absolute", + backgroundColor : "blue", + border: "solid 1px black", + top: "0px", + left: "0px", + width: "200px", + height: "200px" + }); + + var dragged = $('#dragged'); + + $('#dragger').bind("draginit", function(ev, drag){ + drag.only(); + drag.representative(dragged); + }) + + stop(); + + var offset = $('#dragger').offset(); + + Syn.drag("+20 +20","dragged", function() { + var offset2 = $('#dragger').offset(); + equals(offset.top, offset2.top, "top") + equals(offset.left, offset2.left, "left") + + ok(dragged.is(':visible'), "Handle should be visible"); + + start(); + }); +}); + +}); \ No newline at end of file diff --git a/browserid/static/jquery/event/drag/limit/limit.html b/browserid/static/jquery/event/drag/limit/limit.html index 604dbf74e7a91a18e9a4b5a8f287500d0d31e270..19d009b33d3480170de9ca480627af2294ff0461 100644 --- a/browserid/static/jquery/event/drag/limit/limit.html +++ b/browserid/static/jquery/event/drag/limit/limit.html @@ -56,7 +56,9 @@ $("#ondoc .handle").live("mouesdown", function(){ //console.log("mousedowned") }) - + $("#internal").delegate(".handle","draginit", function(ev, drag){ + drag.limit($("#internal"), true) + }) //do internal var jq = $() diff --git a/browserid/static/jquery/event/drag/limit/limit.js b/browserid/static/jquery/event/drag/limit/limit.js index 9398e83f6b104eb161063bb70e959e9945b90855..40fe874f36661ba4b9b81a23e0410a6f0a2aaad5 100644 --- a/browserid/static/jquery/event/drag/limit/limit.js +++ b/browserid/static/jquery/event/drag/limit/limit.js @@ -12,9 +12,11 @@ steal.plugins('jquery/event/drag', 'jquery/dom/cur_styles').then(function( $ ) { * @download http://jmvcsite.heroku.com/pluginify?plugins[]=jquery/event/event/drag/limit/limit.js * limits the drag to a containing element * @param {jQuery} container + * @param {Object} [center] can set the limit to the center of the object. Can be + * 'x', 'y' or 'both' * @return {$.Drag} */ - .limit = function( container ) { + .limit = function( container, center ) { //on draws ... make sure this happens var styles = container.curStyles('borderTopWidth', 'paddingTop', 'borderLeftWidth', 'paddingLeft'), paddingBorder = new $.Vector( @@ -22,7 +24,8 @@ steal.plugins('jquery/event/drag', 'jquery/dom/cur_styles').then(function( $ ) { this._limit = { offset: container.offsetv().plus(paddingBorder), - size: container.dimensionsv() + size: container.dimensionsv(), + center : center }; return this; }; @@ -31,28 +34,32 @@ steal.plugins('jquery/event/drag', 'jquery/dom/cur_styles').then(function( $ ) { $.Drag.prototype.position = function( offsetPositionv ) { //adjust required_css_position accordingly if ( this._limit ) { - var movingSize = this.movingElement.dimensionsv('outer'), - lot = this._limit.offset.top(), - lof = this._limit.offset.left(), - height = this._limit.size.height(), - width = this._limit.size.width(); + var limit = this._limit, + center = limit.center && limit.center.toLowerCase(), + movingSize = this.movingElement.dimensionsv('outer'), + halfHeight = center && center != 'x' ? movingSize.height() / 2 : 0, + halfWidth = center && center != 'y' ? movingSize.width() / 2 : 0, + lot = limit.offset.top(), + lof = limit.offset.left(), + height = limit.size.height(), + width = limit.size.width(); //check if we are out of bounds ... //above - if ( offsetPositionv.top() < lot ) { - offsetPositionv.top(lot); + if ( offsetPositionv.top()+halfHeight < lot ) { + offsetPositionv.top(lot - halfHeight); } //below - if ( offsetPositionv.top() + movingSize.height() > lot + height ) { - offsetPositionv.top(lot + height - movingSize.height()); + if ( offsetPositionv.top() + movingSize.height() - halfHeight > lot + height ) { + offsetPositionv.top(lot + height - movingSize.height() + halfHeight); } //left - if ( offsetPositionv.left() < lof ) { - offsetPositionv.left(lof); + if ( offsetPositionv.left()+halfWidth < lof ) { + offsetPositionv.left(lof - halfWidth); } //right - if ( offsetPositionv.left() + movingSize.width() > lof + width ) { - offsetPositionv.left(lof + width - movingSize.left()); + if ( offsetPositionv.left() + movingSize.width() -halfWidth > lof + width ) { + offsetPositionv.left(lof + width - movingSize.left()+halfWidth); } } diff --git a/browserid/static/jquery/event/drag/qunit.html b/browserid/static/jquery/event/drag/qunit.html index 4ffe9031c8b346ffd041502b1e7510ed652bf1f2..484912f64d85a64baf354bd4eee10743ef5dcfa6 100644 --- a/browserid/static/jquery/event/drag/qunit.html +++ b/browserid/static/jquery/event/drag/qunit.html @@ -11,6 +11,6 @@ <ol id="qunit-tests"></ol> <div id="qunit-test-area"></div> - <script type='text/javascript' src='../../../steal/steal.js?steal[app]=jquery/event/drag/test/qunit'></script> + <script type='text/javascript' src='../../../steal/steal.js?jquery/event/drag/drag_test.js'></script> </body> </html> \ No newline at end of file diff --git a/browserid/static/jquery/event/drag/scroll/scroll.js b/browserid/static/jquery/event/drag/scroll/scroll.js index 75a427eac83017d6034158347970d8c415daf667..928249950751735ece70c1e29c717ed5c9e7f74c 100644 --- a/browserid/static/jquery/event/drag/scroll/scroll.js +++ b/browserid/static/jquery/event/drag/scroll/scroll.js @@ -11,8 +11,10 @@ $.Drag.prototype. * @param {jQuery} elements to scroll. The window can be in this array. */ scrolls = function(elements){ + var elements = $(elements); + for(var i = 0 ; i < elements.length; i++){ - this.constructor.responder._responders.push( new $.Scrollable(elements[i]) ) + this.constructor.responder._elements.push( elements.eq(i).data("_dropData", new $.Scrollable(elements[i]) )[0] ) } }, diff --git a/browserid/static/jquery/event/drag/step/step.html b/browserid/static/jquery/event/drag/step/step.html index 43b6991bbe8a1c6ebb5b1e9484f78cea30f56eaa..e8fbc4dbf7ee41121eee1f9747c101e4dce992d0 100644 --- a/browserid/static/jquery/event/drag/step/step.html +++ b/browserid/static/jquery/event/drag/step/step.html @@ -9,8 +9,8 @@ .error_text { color: red; font-size: 10px;} td {padding: 3px;} .handle { - width: 300px; - height: 40px; + width: 98px; + height: 38px; border: dashed 1px red; cursor : pointer; } @@ -23,33 +23,45 @@ margin-top: 50px; } #ondoc { - border: solid 5px red; - padding: 20px; - height: 300px; - width: 600px; - margin: 10px; + border: solid 5px green; + padding: 0px; + height: 240px; + width: 420px; + + } + #demo-html{ + padding: 30px 0px 0px 30px; } </style> </head> <body> - - <div id="ondoc"> - <div class='handle'>handle</div> - <div class='handle'>handle</div> - </div> +<div id='demo-html'> +<div id="ondoc"> + <div id='handle' class='handle'>top left</div> + <div id='handle2' class='handle'>horizontal</div> + <div id='handle3' class='handle'>vertical</div> + <div id='handle4' class='handle'>horizontal vertical</div> +</div> +</div> <script type='text/javascript' - src='../../../../steal/steal.js?steal[app]=jquery/event/drag/step&steal[env]=development' - package='main.js' - compress='false'> + src='../../../../steal/steal.js?jquery/event/drag/step'> </script> - <script type='text/javascript'> - $("#ondoc .handle").live("draginit", function(ev, drag){ - drag.step(40,$("#ondoc")) - }) - - - </script> +<script type='text/javascript' id='demo-source'> +$("#ondoc") + .delegate('#handle',"draginit", function(ev, drag){ + drag.step(40,$("#ondoc")) + }) + .delegate('#handle2',"draginit", function(ev, drag){ + drag.step(40,$("#ondoc"),"x") + }) + .delegate('#handle3',"draginit", function(ev, drag){ + drag.step(40,$("#ondoc"),"y") + }) + .delegate('#handle4',"draginit", function(ev, drag){ + drag.step(40,$("#ondoc"),"xy") + }) +</script> </body> diff --git a/browserid/static/jquery/event/drag/step/step.js b/browserid/static/jquery/event/drag/step/step.js index 855e13b8886e40fbeb807c85ad31541b5408f9a6..51318548e34d1b559abbcef9993a719dcf86177e 100644 --- a/browserid/static/jquery/event/drag/step/step.js +++ b/browserid/static/jquery/event/drag/step/step.js @@ -13,14 +13,26 @@ steal.plugins('jquery/event/drag', 'jquery/dom/cur_styles').then(function( $ ) { * @plugin jquery/event/drag/step * @download http://jmvcsite.heroku.com/pluginify?plugins[]=jquery/event/drag/step/step.js * makes the drag move in steps of amount pixels. - * @codestart - * drag.step({x: 5}, $('foo')) - * @codeend - * @param {number} amount - * @param {jQuery} container - * @return {jQuery.Drag} the drag + * + * drag.step({x: 5}, $('foo'), "xy") + * + * ## Demo + * + * @demo jquery/event/drag/step/step.html + * + * @param {number|Object} amount make the drag move X amount in pixels from the top-left of container. + * @param {jQuery} [container] the container to move in reference to. If not provided, the document is used. + * @param {String} [center] Indicates how to position the drag element in relationship to the container. + * + * - If nothing is provided, places the top left corner of the drag element at + * 'amount' intervals from the top left corner of the container. + * - If 'x' is provided, it centers the element horizontally on the top-left corner. + * - If 'y' is provided, it centers the element vertically on the top-left corner of the container. + * - If 'xy' is provided, it centers the element on the top-left corner of the container. + * + * @return {jQuery.Drag} the drag object for chaining. */ - step = function( amount, container ) { + step = function( amount, container, center ) { //on draws ... make sure this happens if ( typeof amount == 'number' ) { amount = { @@ -32,10 +44,11 @@ steal.plugins('jquery/event/drag', 'jquery/dom/cur_styles').then(function( $ ) { this._step = amount; var styles = container.curStyles("borderTopWidth", "paddingTop", "borderLeftWidth", "paddingLeft"); - var left = parseInt(styles.borderTopWidth) + parseInt(styles.paddingTop), - top = parseInt(styles.borderLeftWidth) + parseInt(styles.paddingLeft); + var top = parseInt(styles.borderTopWidth) + parseInt(styles.paddingTop), + left = parseInt(styles.borderLeftWidth) + parseInt(styles.paddingLeft); this._step.offset = container.offsetv().plus(left, top); + this._step.center = center; return this; }; @@ -44,9 +57,11 @@ steal.plugins('jquery/event/drag', 'jquery/dom/cur_styles').then(function( $ ) { $.Drag.prototype.position = function( offsetPositionv ) { //adjust required_css_position accordingly if ( this._step ) { - var movingSize = this.movingElement.dimensionsv('outer'), - lot = this._step.offset.top(), - lof = this._step.offset.left(); + var step = this._step, + center = step.center && step.center.toLowerCase(), + movingSize = this.movingElement.dimensionsv('outer'), + lot = step.offset.top()- (center && center != 'x' ? movingSize.height() / 2 : 0), + lof = step.offset.left() - (center && center != 'y' ? movingSize.width() / 2 : 0); if ( this._step.x ) { offsetPositionv.left(Math.round(lof + round(offsetPositionv.left() - lof, this._step.x))) diff --git a/browserid/static/jquery/event/drag/test/qunit/qunit.js b/browserid/static/jquery/event/drag/test/qunit/qunit.js deleted file mode 100644 index 2fa324dd5d42cc4d02e5f3d462a007039b113d72..0000000000000000000000000000000000000000 --- a/browserid/static/jquery/event/drag/test/qunit/qunit.js +++ /dev/null @@ -1,4 +0,0 @@ -steal - .plugins("jquery/event/drop",'funcunit/syn') //load your app - .plugins('funcunit/qunit' ) //load qunit - .then("drag_test") \ No newline at end of file diff --git a/browserid/static/jquery/event/drop/drop.js b/browserid/static/jquery/event/drop/drop.js index 632f9211263a799e5e2707a48773d9842777cf11..34c22dfeb2452b5dd017168015db09bec98ed83a 100644 --- a/browserid/static/jquery/event/drop/drop.js +++ b/browserid/static/jquery/event/drop/drop.js @@ -78,13 +78,33 @@ steal.plugins('jquery/event/drag','jquery/dom/within','jquery/dom/compare').then * @codeend * A bit more complex example: * @demo jquery/event/drop/drop.html 1000 + * + * + * + * ## How it works + * + * 1. When you bind on a drop event, it adds that element to the list of rootElements. + * RootElements might be drop points, or might have delegated drop points in them. + * + * 2. When a drag motion is started, each rootElement is queried for the events listening on it. + * These events might be delegated events so we need to query for the drop elements. + * + * 3. With each drop element, we add a Drop object with all the callbacks for that element. + * Each element might have multiple event provided by different rootElements. We merge + * callbacks into the Drop object if there is an existing Drop object. + * + * 4. Once Drop objects have been added to all elements, we go through them and call draginit + * if available. + * + * * @constructor * The constructor is never called directly. */ $.Drop = function(callbacks, element){ jQuery.extend(this,callbacks); - this.element = element; + this.element = $(element); } + // add the elements ... $.each(eventNames, function(){ event.special[this] = { add: function( handleObj ) { @@ -103,24 +123,27 @@ steal.plugins('jquery/event/drag','jquery/dom/within','jquery/dom/compare').then } } } - }) + }); + $.extend($.Drop,{ lowerName: "drop", - _elements: [], //elements that are listening for drops - _responders: [], //potential drop points + _rootElements: [], //elements that are listening for drops + _elements: $(), //elements that can be dropped on last_active: [], endName: "dropon", + // adds an element as a 'root' element + // this element might have events that we need to respond to addElement: function( el ) { //check other elements - for(var i =0; i < this._elements.length ; i++ ){ - if(el ==this._elements[i]) return; + for(var i =0; i < this._rootElements.length ; i++ ){ + if(el ==this._rootElements[i]) return; } - this._elements.push(el); + this._rootElements.push(el); }, removeElement: function( el ) { - for(var i =0; i < this._elements.length ; i++ ){ - if(el == this._elements[i]){ - this._elements.splice(i,1) + for(var i =0; i < this._rootElements.length ; i++ ){ + if(el == this._rootElements[i]){ + this._rootElements.splice(i,1) return; } } @@ -140,7 +163,7 @@ steal.plugins('jquery/event/drag','jquery/dom/within','jquery/dom/compare').then * Tests if a drop is within the point. */ isAffected: function( point, moveable, responder ) { - return ((responder.element != moveable.element) && (responder.element.within(point[0], point[1], responder).length == 1)); + return ((responder.element != moveable.element) && (responder.element.within(point[0], point[1], responder._cache).length == 1)); }, /** * @hide @@ -169,60 +192,156 @@ steal.plugins('jquery/event/drag','jquery/dom/within','jquery/dom/compare').then responder.callHandlers(this.lowerName+'move',responder.element[0], event, mover) }, /** - * Gets all elements that are droppable, adds them + * Gets all elements that are droppable and adds them to a list. + * + * This should be called if and when new drops are added to the page + * during the motion of a single drag. + * + * This is called by default when a drag motion starts. + * + * ## Use + * + * After adding an element or drop, call compile. + * + * $("#midpoint").bind("dropover",function(){ + * // when a drop hovers over midpoint, + * // make drop a drop. + * $("#drop").bind("dropover", function(){ + * + * }); + * $.Drop.compile(); + * }); */ compile: function( event, drag ) { - var el, drops, selector, sels; - this.last_active = []; - for(var i=0; i < this._elements.length; i++){ //for each element - el = this._elements[i] + // if we called compile w/o a current drag + if(!this.dragging && !drag){ + return; + }else if(!this.dragging){ + this.dragging = drag; + this.last_active = []; + //this._elements = $(); + } + var el, + drops, + selector, + dropResponders, + newEls = [], + dragging = this.dragging; + + // go to each root element and look for drop elements + for(var i=0; i < this._rootElements.length; i++){ //for each element + el = this._rootElements[i] + + // gets something like {"": ["dropinit"], ".foo" : ["dropover","dropmove"] } var drops = $.event.findBySelector(el, eventNames) - for(selector in drops){ //find the selectors - sels = selector ? jQuery(selector, el) : [el]; - for(var e= 0; e < sels.length; e++){ //for each found element, create a drop point - jQuery.removeData(sels[e],"offset"); - this.add(sels[e], new this(drops[selector]), event, drag); + // get drop elements by selector + for(selector in drops){ + + + dropResponders = selector ? jQuery(selector, el) : [el]; + + // for each drop element + for(var e= 0; e < dropResponders.length; e++){ + + // add the callbacks to the element's Data + // there already might be data, so we merge it + if( this.addCallbacks(dropResponders[e], drops[selector], dragging) ){ + newEls.push(dropResponders[e]) + }; } } } + // once all callbacks are added, call init on everything ... + // todo ... init could be called more than once? + this.add(newEls, event, dragging) + }, + // adds the drag callbacks object to the element or merges other callbacks ... + // returns true or false if the element is new ... + // onlyNew lets only new elements add themselves + addCallbacks : function(el, callbacks, onlyNew){ + var origData = $.data(el,"_dropData"); + if(!origData){ + $.data(el,"_dropData", new $.Drop(callbacks, el)); + //this._elements.push(el); + return true; + }else if(!onlyNew){ + var origCbs = origData; + // merge data + for(var eventName in callbacks){ + origCbs[eventName] = origCbs[eventName] ? + origCbs[eventName].concat(callbacks[eventName]) : + callbacks[eventName]; + } + return false; + } }, - add: function( element, callbacks, event, drag ) { - element = jQuery(element); - var responder = new $.Drop(callbacks, element); - responder.callHandlers(this.lowerName+'init', element[0], event, drag) - if(!responder._canceled){ - this._responders.push(responder); + // calls init on each element's drags. + // if its cancelled it's removed + // adds to the current elements ... + add: function( newEls, event, drag , dragging) { + var i = 0, + drop; + + while(i < newEls.length){ + drop = $.data(newEls[i],"_dropData"); + drop.callHandlers(this.lowerName+'init', newEls[i], event, drag) + if(drop._canceled){ + newEls.splice(i,1) + }else{ + i++; + } } + this._elements.push.apply(this._elements, newEls) }, show: function( point, moveable, event ) { var element = moveable.element; - if(!this._responders.length) return; + if(!this._elements.length) return; var respondable, affected = [], propagate = true, - i,j, la, toBeActivated, aff, - oldLastActive = this.last_active; + i = 0, + j, + la, + toBeActivated, + aff, + oldLastActive = this.last_active, + responders = [], + self = this, + drag; - for(var d =0 ; d < this._responders.length; d++ ){ + //what's still affected ... we can also move element out here + while( i < this._elements.length){ + drag = $.data(this._elements[i],"_dropData"); - if(this.isAffected(point, moveable, this._responders[d])){ - affected.push(this._responders[d]); + if (!drag) { + this._elements.splice(i, 1) + } + else { + i++; + if (self.isAffected(point, moveable, drag)) { + affected.push(drag); + } } - } + + affected.sort(this.sortByDeepestChild); //we should only trigger on lowest children event.stopRespondPropagate = function(){ propagate = false; } - //deactivate everything in last_active that isn't active + toBeActivated = affected.slice(); + + // all these will be active this.last_active = affected; + + //deactivate everything in last_active that isn't active for (j = 0; j < oldLastActive.length; j++) { - la = oldLastActive[j] + la = oldLastActive[j]; i = 0; while((aff = toBeActivated[i])){ if(la == aff){ @@ -249,9 +368,9 @@ steal.plugins('jquery/event/drag','jquery/dom/within','jquery/dom/compare').then } }, end: function( event, moveable ) { - var responder, la; - for(var r =0; r<this._responders.length; r++){ - this._responders[r].callHandlers(this.lowerName+'end', null, event, moveable); + var responder, la, endName = this.lowerName+'end'; + for(var r =0; r<this._elements.length; r++){ + $.data(this._elements[r],"_dropData").callHandlers(endName, null, event, moveable); } //go through the actives ... if you are over one, call dropped on it for(var i = 0; i < this.last_active.length; i++){ @@ -269,8 +388,12 @@ steal.plugins('jquery/event/drag','jquery/dom/within','jquery/dom/compare').then * @hide */ clear: function() { - - this._responders = []; + this._elements.each(function(){ + $.removeData(this,"_dropData") + }) + this._elements = $(); + delete this.dragging; + //this._responders = []; } }) $.Drag.responder = $.Drop; diff --git a/browserid/static/jquery/event/drop/drop_test.js b/browserid/static/jquery/event/drop/drop_test.js new file mode 100644 index 0000000000000000000000000000000000000000..9ea6839edd0b486e201b67a72f7e1015e6c95758 --- /dev/null +++ b/browserid/static/jquery/event/drop/drop_test.js @@ -0,0 +1,41 @@ +steal.plugins('funcunit','funcunit/syn').then(function(){ + +module("jquery/event/drop"); + +test("new drop added",2, function(){ + var div = $("<div>"+ + "<div id='drag'></div>"+ + "<div id='midpoint'></div>"+ + "<div id='drop'></div>"+ + "</div>"); + + div.appendTo($("#qunit-test-area")); + var basicCss = { + width: "20px", + height: "20px", + position: "absolute", + border: "solid 1px black" + } + $("#drag").css(basicCss).css({top: "0px", left: "0px", zIndex: 1000, backgroundColor: "red"}) + $("#midpoint").css(basicCss).css({top: "0px", left: "30px"}) + $("#drop").css(basicCss).css({top: "0px", left: "60px"}); + + $('#drag').bind("draginit", function(){}); + + $("#midpoint").bind("dropover",function(){ + ok(true, "midpoint called"); + + $("#drop").bind("dropover", function(){ + ok(true, "drop called"); + }); + $.Drop.compile(); + }); + stop(); + Syn.drag({to: "#drop"},"drag", function(){ + start(); + }); +}); + + + +}) diff --git a/browserid/static/jquery/event/event.js b/browserid/static/jquery/event/event.js index 42422936b05fd13d2304068e33aff0a1e0c31344..c355532125537ea891f7b846eae576c24aeeefae 100644 --- a/browserid/static/jquery/event/event.js +++ b/browserid/static/jquery/event/event.js @@ -1,6 +1,40 @@ /** * @page specialevents Special Events * @tag core - * JavaScriptMVC adds a bunch of useful jQuery extensions for the dom. Check them out on the left. + * JavaScriptMVC provides a bunch of useful special events. Find out more info on the left. The following is a + * brief summary: + * + * ## [jQuery.event.special.default Default Events] + * + * Lets you supply default behavior for an event that is preventable + * with event.preventDefault(). This is extremely useful for providing DOM-like api's for your widgets. + * + * $("#tabs").delegate(".panel","default.open", function(){ + * $(this).show() + * }) + * + * ## [jQuery.event.special.destroyed Destroyed Events] + * + * Know if an element has been removed from the page. + * + * $("#contextMenu").bind("destroyed", function(){ + * // cleanup + * $(document.body).unbind("click.contextMenu"); + * }) + * + * ## [jQuery.Drag Drag] and [jQuery.Drop Drop] Events + * + * Listen to drag-drop events with event delegation. + * + * $(".item").live("dragover", function(ev, drag){ + * // let user know that the item can be dropped + * $(this).addClass("canDrop"); + * }).live("dropover", function(ev, drop, drag){ + * // let user know that the item can be dropped on + * $(this).addClass('drop-able') + * }) + * + * ## + * */ steal.plugins('jquery'); \ No newline at end of file diff --git a/browserid/static/jquery/event/handle/handle.js b/browserid/static/jquery/event/handle/handle.js new file mode 100644 index 0000000000000000000000000000000000000000..d4c2591e04994fe177bd80e15be5e96cd2646b5d --- /dev/null +++ b/browserid/static/jquery/event/handle/handle.js @@ -0,0 +1,85 @@ +steal.plugins("jquery").then(function(){ + +var $event = $.event, + oldTrigger = $event.trigger; +// a copy of $'s handle function that goes until it finds +$.event.handle = function( event ) { + var args = $.makeArray( arguments ); + // Event object or event type + var type = event.type || event, + namespaces = [], + exclusive; + + if ( type.indexOf("!") >= 0 ) { + // Exclusive events trigger only for the exact event (no namespaces) + type = type.slice(0, -1); + exclusive = true; + } + + if ( type.indexOf(".") >= 0 ) { + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split("."); + type = namespaces.shift(); + namespaces.sort(); + } + event.type = type; + event.exclusive = exclusive; + + + event = jQuery.event.fix( event || window.event ); + // Snapshot the handlers list since a called handler may add/remove events. + var handlers = ((jQuery._data( this, "events" ) || {})[ event.type ] || []).slice(0), + run_all = !event.exclusive && !event.namespace, + args = Array.prototype.slice.call( arguments, 0 ); + + // Use the fix-ed Event rather than the (read-only) native event + args[0] = event; + event.currentTarget = this; + + // JMVC CHANGED + var oldType = type; + if (event.type !== "default" && $event.special['default']) { + $event.special['default'].triggerDefault(event, this); + } + event.type = oldType; + + for ( var j = 0, l = handlers.length; j < l; j++ ) { + var handleObj = handlers[ j ]; + if( event.firstPass ){ + event.firstPass = false; + continue; + } + + // Triggered event must 1) be non-exclusive and have no namespace, or + // 2) have namespace(s) a subset or equal to those in the bound event. + if ( run_all || event.namespace_re.test( handleObj.namespace ) ) { + // Pass in a reference to the handler function itself + // So that we can later remove it + event.handler = handleObj.handler; + event.data = handleObj.data; + event.handleObj = handleObj; + + var ret = handleObj.handler.apply( this, args ); + + + if ( ret !== undefined ) { + event.result = ret; + if ( ret === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + + if ( event.isImmediatePropagationStopped() ) { + break; + } + } + } + + // JMVC CHANGED + if (event.type !== "default" && $event.special['default']) { + $event.special['default'].checkAndRunDefaults(event, this); + } + return event.result; +} +}) diff --git a/browserid/static/jquery/event/hover/hover.html b/browserid/static/jquery/event/hover/hover.html index a128aa4523cbf96829b4e3013b483aa30dfce6f8..441c37494f23172a91376113dd634ecd06e99b28 100644 --- a/browserid/static/jquery/event/hover/hover.html +++ b/browserid/static/jquery/event/hover/hover.html @@ -8,7 +8,7 @@ .error {border: solid 1px red;} .error_text { color: red; font-size: 10px;} td {padding: 3px;} - .hover, .hovers { + .hover, .hovers, .hoverleave { border: solid 1px green; } .hoverstate { @@ -27,6 +27,8 @@ <h4>Bound Directly</h4> <div class='hovers'>hover me for a second</div> <div class='hovers'>hover me for a second</div> +<h4>HoverLeave</h4> +<div class='hoverleave'>Leave and don't return for a half second</div> </div> <script type='text/javascript' src='../../../steal/steal.js?steal[app]=jquery/event/hover&steal[env]=development'> @@ -51,6 +53,10 @@ $('.hovers').bind('hoverinit',function(ev, hovered){ }) $('.hovers').bind('hoverenter',add) $('.hovers').bind('hoverleave', remove) + +$('.hoverleave').bind('hoverinit',function(ev, hovered){ + hovered.leave(500) +}).bind('hoverenter',add).bind('hoverleave', remove) </script> </body> </html> \ No newline at end of file diff --git a/browserid/static/jquery/event/hover/hover.js b/browserid/static/jquery/event/hover/hover.js index 948a6d41d36b85bc18117787da449148d1bc0f5b..5de4d993af1a2c2a5f8b699be23d6a6da0a2f903 100644 --- a/browserid/static/jquery/event/hover/hover.js +++ b/browserid/static/jquery/event/hover/hover.js @@ -59,14 +59,15 @@ steal.plugins('jquery/event','jquery/event/livehack').then(function($){ * @constructor Creates a new hover. This is never * called directly. */ -jQuery.Hover = function(){ - this._delay = jQuery.Hover.delay; - this._distance = jQuery.Hover.distance; +$.Hover = function(){ + this._delay = $.Hover.delay; + this._distance = $.Hover.distance; + this._leave = $.Hover.leave }; /** * @Static */ -$.extend(jQuery.Hover,{ +$.extend($.Hover,{ /** * @attribute delay * A hover is activated if it moves less than distance in this time. @@ -78,13 +79,14 @@ $.extend(jQuery.Hover,{ * A hover is activated if it moves less than this distance in delay time. * Set this value as a global default. */ - distance: 10 + distance: 10, + leave : 0 }) /** * @Prototype */ -$.extend(jQuery.Hover.prototype,{ +$.extend($.Hover.prototype,{ /** * Sets the delay for this hover. This method should * only be used in hoverinit. @@ -93,6 +95,7 @@ $.extend(jQuery.Hover.prototype,{ */ delay: function( delay ) { this._delay = delay; + return this; }, /** * Sets the distance for this hover. This method should @@ -101,60 +104,89 @@ $.extend(jQuery.Hover.prototype,{ */ distance: function( distance ) { this._distance = distance; + return this; + }, + leave : function(leave){ + this._leave = leave; + return this; } }) -var $ = jQuery, - event = jQuery.event, +var event = $.event, handle = event.handle, onmouseenter = function(ev){ //now start checking mousemoves to update location var delegate = ev.liveFired || ev.currentTarget; var selector = ev.handleObj.selector; + //prevents another mouseenter until current has run its course + if($.data(delegate,"_hover"+selector)){ + return; + } + $.data(delegate,"_hover"+selector, true) var loc = { pageX : ev.pageX, pageY : ev.pageY }, dist = 0, timer, - entered = this, - called = false, + enteredEl = this, + hovered = false, lastEv = ev, - hover = new jQuery.Hover(); - - $(entered).bind("mousemove.specialMouseEnter", {}, function(ev){ - dist += Math.pow( ev.pageX-loc.pageX, 2 ) + Math.pow( ev.pageY-loc.pageY, 2 ); - loc = { - pageX : ev.pageX, - pageY : ev.pageY - } - lastEv = ev - }).bind("mouseleave.specialMouseLeave",{}, function(ev){ - clearTimeout(timer); - if(called){ + hover = new $.Hover(), + leaveTimer, + callHoverLeave = function(){ $.each(event.find(delegate, ["hoverleave"], selector), function(){ - this.call(entered, ev) + this.call(enteredEl, ev, hover) }) - } - $(entered).unbind("mouseleave.specialMouseLeave") - }) + cleanUp(); + }, + mouseenter = function(ev){ + clearTimeout(leaveTimer); + dist += Math.pow( ev.pageX-loc.pageX, 2 ) + Math.pow( ev.pageY-loc.pageY, 2 ); + loc = { + pageX : ev.pageX, + pageY : ev.pageY + } + lastEv = ev + }, + mouseleave = function(ev){ + clearTimeout(timer); + // go right away + if(hovered){ + if(hover._leave === 0){ + callHoverLeave(); + }else{ + clearTimeout(leaveTimer); + leaveTimer = setTimeout(function(){ + callHoverLeave(); + }, hover._leave) + } + }else{ + cleanUp(); + } + }, + cleanUp = function(){ + $(enteredEl).unbind("mouseleave",mouseleave) + $(enteredEl).unbind("mousemove",mouseenter); + $.removeData(delegate,"_hover"+selector) + }; + + $(enteredEl).bind("mousemove",mouseenter).bind("mouseleave", mouseleave); $.each(event.find(delegate, ["hoverinit"], selector), function(){ - this.call(entered, ev, hover) + this.call(enteredEl, ev, hover) }) + timer = setTimeout(function(){ //check that we aren't moveing around - if(dist < hover._distance && $(entered).queue().length == 0){ + if(dist < hover._distance && $(enteredEl).queue().length == 0){ $.each(event.find(delegate, ["hoverenter"], selector), function(){ - this.call(entered, lastEv, hover) + this.call(enteredEl, lastEv, hover) }) - called = true; - $(entered).unbind("mousemove.specialMouseEnter") - + hovered = true; + return; }else{ dist = 0; timer = setTimeout(arguments.callee, hover._delay) } - - }, hover._delay) }; diff --git a/browserid/static/jquery/event/key/key.html b/browserid/static/jquery/event/key/key.html new file mode 100644 index 0000000000000000000000000000000000000000..a2cda5bb3ed959bd2b954ed711431334220ec873 --- /dev/null +++ b/browserid/static/jquery/event/key/key.html @@ -0,0 +1,32 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" + "http://www.w3.org/TR/html4/strict.dtd"> +<html lang="en"> + <head> + <title>jQuery Event Key</title> + <style type='text/css'> + body {font-family: verdana} + </style> + </head> + <body> +<div id='demo-html'> +<label for='key'>Type in input:</label><input id='key' /> +<div>Keydown:<span id='keydown'></span></div> +<div>Keypress:<span id='keypress'></span></div> +<div>Keyup:<span id='keyup'></span></div> +</div> +<script type='text/javascript' + src='../../../steal/steal.js?jquery/event/key' + id='demo-source'> + var readable = { + "\r" : "\\r", + "\b" : "\\b", + "\t" : "\\t" + }, + printKey = function(ev){ + var key = ev.key(); + $("#"+ev.type).html(readable[key] || key) + }; + $('#key').keydown(printKey).keypress(printKey).keyup(printKey) +</script> + </body> +</html> \ No newline at end of file diff --git a/browserid/static/jquery/event/key/key.js b/browserid/static/jquery/event/key/key.js new file mode 100644 index 0000000000000000000000000000000000000000..97fed9cac85d428928cc6c58524fbe84decf7962 --- /dev/null +++ b/browserid/static/jquery/event/key/key.js @@ -0,0 +1,160 @@ +steal.plugins('jquery/event').then(function($){ + var keymap = {}, + reverseKeyMap = {}; + + /** + * @function jQuery.event.key + * @parent jQuery.Event.prototype.key + * + * Allows you to set alternate key maps or overwrite existing key codes. + * For example:: + * + * $.event.key({"~" : 177}); + * + * @param {Object} map A map of character - keycode pairs. + */ + $.event.key = function(map){ + $.extend(keymap, map); + for(var name in map){ + reverseKeyMap[map[name]] = name; + } + }; + + $.event.key({ + //backspace + '\b':'8', + + //tab + '\t':'9', + + //enter + '\r':'13', + + //special + 'shift':'16','ctrl':'17','alt':'18', + + //weird + 'pause-break':'19', + 'caps':'20', + 'escape':'27', + 'num-lock':'144', + 'scroll-lock':'145', + 'print' : '44', + + //navigation + 'page-up':'33','page-down':'34','end':'35','home':'36', + 'left':'37','up':'38','right':'39','down':'40','insert':'45','delete':'46', + + //normal characters + ' ':'32', + '0':'48','1':'49','2':'50','3':'51','4':'52','5':'53','6':'54','7':'55','8':'56','9':'57', + 'a':'65','b':'66','c':'67','d':'68','e':'69','f':'70','g':'71','h':'72','i':'73','j':'74','k':'75','l':'76','m':'77', + 'n':'78','o':'79','p':'80','q':'81','r':'82','s':'83','t':'84','u':'85','v':'86','w':'87','x':'88','y':'89','z':'90', + //normal-characters, numpad + 'num0':'96','num1':'97','num2':'98','num3':'99','num4':'100','num5':'101','num6':'102','num7':'103','num8':'104','num9':'105', + '*':'106','+':'107','-':'109','.':'110', + //normal-characters, others + '/':'111', + ';':'186', + '=':'187', + ',':'188', + '-':'189', + '.':'190', + '/':'191', + '`':'192', + '[':'219', + '\\':'220', + ']':'221', + "'":'222', + + //ignore these, you shouldn't use them + 'left window key':'91','right window key':'92','select key':'93', + + + 'f1':'112','f2':'113','f3':'114','f4':'115','f5':'116','f6':'117', + 'f7':'118','f8':'119','f9':'120','f10':'121','f11':'122','f12':'123' + }); + + /** + * @parent specialevents + * @plugin jquery/event/key + * + * Returns a string representation of the key pressed. The following + * listens to and prevents backspaces being pressed in inputs: + * + * $("input").keypress(function(ev){ + * if(ev.key() == '\b') { + * ev.preventDefault(); + * } + * }); + * + * ## Keys + * + * The following describes the key values returned by [jQuery.Event.prototype.key]. + * + * - \b - backspace + * - \t - tab + * - \r - enter key + * - shift, ctrl, alt + * - pause-break, caps, escape, num-lock, scroll-loc, print + * - page-up, page-down, end, home, left, up, right, down, insert, delete + * - ' ' - space + * - 0-9 - number key pressed + * - a-z - alpha key pressed + * - num0-9 - number pad key pressed + * - / ; : = , - . / ` [ \\ ] ' " + * - f1-12 - function keys pressed + * + * ## Alternate keys + * + * Use [jQuery.event.key] to set alternate key mappings for other locales. + * + * @return {String} The string representation of of the key pressed. + */ + jQuery.Event.prototype.key = function(){ + var event = this, + keycode, + test = /\w/; + + var key_Key = reverseKeyMap[(event.keyCode || event.which)+""], + char_Key = String.fromCharCode(event.keyCode || event.which), + key_Char = event.charCode && reverseKeyMap[event.charCode+""], + char_Char = event.charCode && String.fromCharCode(event.charCode); + + if( char_Char && test.test(char_Char) ) { + return char_Char.toLowerCase() + } + if( key_Char && test.test(key_Char) ) { + return char_Char.toLowerCase() + } + if( char_Key && test.test(char_Key) ) { + return char_Key.toLowerCase() + } + if( key_Key && test.test(key_Key) ) { + return key_Key.toLowerCase() + } + //console.log(this.type, key_Key, char_Key, key_Char, char_Char); + //if IE + //if ($.browser.msie){ + if (event.type == 'keypress'){ + return event.keyCode ? String.fromCharCode(event.keyCode) : String.fromCharCode(event.which) + } /*else if (event.type == 'keydown') { + // IE only recognizes the backspace and delete keys in the keydown event, not keypress + keycode = reverseKeyMap[event.keyCode]; + + if (keycode === '\b' || keycode === 'delete'){ + return keycode; + } + } */ + //} + + + if (!event.keyCode && event.which) { + return String.fromCharCode(event.which) + } + + return reverseKeyMap[event.keyCode+""] + } + + +}) \ No newline at end of file diff --git a/browserid/static/jquery/event/key/key_test.js b/browserid/static/jquery/event/key/key_test.js new file mode 100644 index 0000000000000000000000000000000000000000..1e2765a52d36921f5ab3ce7c853bdaa09c7210bb --- /dev/null +++ b/browserid/static/jquery/event/key/key_test.js @@ -0,0 +1,26 @@ +steal.plugins('funcunit/qunit','funcunit/syn','jquery/event/key').then(function(){ + +module('jquery/event/key'); + +test("type some things", function(){ + $("#qunit-test-area").append("<input id='key' />") + var keydown, keypress, keyup; + $('#key').keydown(function(ev){ + keydown = ev.key(); + }).keypress(function(ev){ + keypress = ev.key(); + }).keyup(function(ev){ + keyup = ev.key(); + }); + + stop(); + + Syn.key("a","key", function(){ + equals(keydown, "a","keydown"); + equals(keypress,"a","keypress"); + equals(keyup, "a","keyup"); + start(); + }); +}) + +}) diff --git a/browserid/static/jquery/event/key/qunit.html b/browserid/static/jquery/event/key/qunit.html new file mode 100644 index 0000000000000000000000000000000000000000..608329d0f0653564cb9391987c6cb1e70c12e087 --- /dev/null +++ b/browserid/static/jquery/event/key/qunit.html @@ -0,0 +1,16 @@ +<html> + <head> + <link rel="stylesheet" type="text/css" href="../../../funcunit/qunit/qunit.css" /> + <script type='text/javascript' src='../../../steal/steal.js?jquery/event/key/key_test.js'></script> + </head> + <body> + + <h1 id="qunit-header">Key Test Suite</h1> + <h2 id="qunit-banner"></h2> + <div id="qunit-testrunner-toolbar"></div> + <h2 id="qunit-userAgent"></h2> + <div id="test-content"></div> + <ol id="qunit-tests"></ol> + <div id="qunit-test-area"></div> + </body> +</html> \ No newline at end of file diff --git a/browserid/static/jquery/event/livehack/livehack.js b/browserid/static/jquery/event/livehack/livehack.js index 19de3c6656f4ec8661e6daf666bbab5d7b397ec0..f1a8e6dadd865c215c1bb23c68e9a5565c4c0569 100644 --- a/browserid/static/jquery/event/livehack/livehack.js +++ b/browserid/static/jquery/event/livehack/livehack.js @@ -61,9 +61,9 @@ steal.plugins('jquery/event').then(function() { return handlers; }; /** - * Finds - * @param {HTMLElement} el - * @param {Array} types + * Finds all events. Group by selector. + * @param {HTMLElement} el the element + * @param {Array} types event types */ event.findBySelector = function( el, types ) { var events = $.data(el, "events"), @@ -91,6 +91,8 @@ steal.plugins('jquery/event').then(function() { return selectors; }; + event.supportTouch = "ontouchend" in document; + $.fn.respondsTo = function( events ) { if (!this.length ) { return false; diff --git a/browserid/static/jquery/event/offline/offline.js b/browserid/static/jquery/event/offline/offline.js deleted file mode 100644 index 43452276983fe74dd9ea00a9025b92bf8ac10baa..0000000000000000000000000000000000000000 --- a/browserid/static/jquery/event/offline/offline.js +++ /dev/null @@ -1,36 +0,0 @@ -steal.plugins('jquery/event').then(function($){ - - - - - var support = $.support; - support.online = ("onLine" in window.navigator) - - - - - //support.offlineEvents = eventSupported("online",document.documentElement) - $(function(){ - support.onlineEvents = ("ononline" in document.body) - if(!support.onlineEvents){ - document.body.setAttribute("ononline","") - support.onlineEvents = ("ononline" in window) - } - if(support.onlineEvents){ - return; - } - var lastStatus = navigator.onLine; - setInterval(function(){ - if(lastStatus !== navigator.onLine){ - lastStatus = navigator.onLine - $(document.body).trigger(lastStatus ? "online" : "offline") - $(window).triggerHandle(lastStatus ? "online" : "offline") - } - },100) - - }) - - - - -}) diff --git a/browserid/static/jquery/event/pause/pause.html b/browserid/static/jquery/event/pause/pause.html new file mode 100644 index 0000000000000000000000000000000000000000..03f30272a675a3797a15ade2eed050ad07350ead --- /dev/null +++ b/browserid/static/jquery/event/pause/pause.html @@ -0,0 +1,216 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" + "http://www.w3.org/TR/html4/strict.dtd"> +<html lang="en"> + <head> + <title>Pause Example</title> + <style type='text/css'> + body {font-family: verdana} + .tabs { + padding: 0px; margin: 0px; + } + .tabs li { + float: left; + padding: 10px; + background-color: #F6F6F6; + list-style: none; + margin-left: 10px; + } + .tabs li a { + color: #1C94C4; + font-weight: bold; + text-decoration: none; + } + .tabs li.active a { + color: #F6A828; + cursor: default; + } + .tab { + border: solid 2px #F6A828; + width: 360px; + height: 100px; + padding: 20px; + } + .dirty { + border: solid 2px red; + } + /* clearfix from jQueryUI */ + .ui-helper-clearfix:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; } + .ui-helper-clearfix { display: inline-block; } + /* required comment for clearfix to work in Opera \*/ + * html .ui-helper-clearfix { height:1%; } + .ui-helper-clearfix { display:block; } + /* end clearfix */ + #modal { + position: absolute; + top: 0px; left: 0px; + height: 100%; width: 100%; + display: none; + } + #background { + background-color: gray; + top: 0px; left: 0px; + height: 100%; width: 100%; + opacity: 0.5; + } + #box { + position: absolute; + margin: -55px 0px 0px -105px; + top: 50%; + left: 50%; + border: solid 1px gray; + background-color: white; + opacity: 1; + width: 200px; + height: 100px; + padding: 10px; + } + span {color: Red;} + </style> + </head> + <body> +<div id="demo-html"> +<ul id='tabs' class='ui-helper-clearfix''> + <li><a href='#tab1'>Tab 1</a></li> + <li><a href='#tab2'>Tab 2</a></li> + <li><a href='#tab3'>Tab 3</a></li> +</ul> +<div id='panels'> + <form id='tab1' class='tab'> + Name: <input name='name'/> + Age: <select name='age'><option>10</option><option>20</option></select> + </form> + <form id='tab2' class='tab'> + Yard: <select name='yard'><option>1</option><option>2</option></select> + Team: <input name='team'/> + </form> + <form id='tab3' class='tab'> + Price: <input name='price'/> + Comment: <textarea name='comment'></textarea> + </form> +</div> +</div> +<div id='modal'> + <div id='background'></div> + <div id='box'><p>Do you wish to save?</p> + <a href='javascript://' id='yes'>Yes</a> + <a href='javascript://' id='no'>No</a> + <a href='javascript://' id='cancel'>Cancel</a> + </div> +</div> + +<script type='text/javascript' src='../../../steal/steal.js'></script> +<script type='text/javascript'> + steal.plugins("jquery/controller",'jquery/event/pause','jquery/event/default').start(); +</script> +<script type='text/javascript' id="demo-source"> +$.Controller("Tabs",{ + init : function(el){ + $(el).children("li:first").addClass('active'); + var tab = this.tab; + this.element.children("li:gt(0)").each(function(){ + tab($(this)).hide() + }) + }, + tab : function(li){ + return $(li.find("a").attr("href").match(/#.*/)[0]) + }, + "li click" : function(el, ev){ + ev.preventDefault(); + var active = this.find('.active') + old = this.tab(active), + cur = this.tab(el); + old.triggerAsync('hide', function(){ + active.removeClass('active') + old.slideUp(function(){ + el.addClass('active') + cur.slideDown() + }); + }) + } +}) + +// adds the controller to the element +$("#tabs").tabs(); + +// create a widget listens for change and marks as dirty +$.Controller("Dirtybit",{ + listensTo : ['set'] +},{ + init : function(){ + this.element.data('formData',this.element.serialize()) + }, + "change" : function(el){ + this.check() + }, + keyup : function(el){ + this.check() + }, + click : function(el){ + this.check() + }, + check : function(){ + var el = this.element; + if(el.serialize() == el.data('formData')){ + el.removeClass('dirty') + }else{ + el.addClass('dirty') + } + }, + "set" : function(){ + this.element.data('formData',this.element.serialize()) + .removeClass('dirty'); + } +}) + +// a modal width +$.Controller("Modal",{ + init : function(){ + this.element.show(); + }, + "a click" : function(a){ + this.element.hide(); + this.options[a.attr('id')](); + + this.destroy(); + } +}) + + +// create a saver widget +$.Controller("Saver",{ + listensTo : ['hide'] +},{ + "hide": function(el, ev){ + if (el.hasClass('dirty')) { + ev.pause() + $('#modal').modal({ + yes: function(){ + var save = $('<span>Saving</span>').appendTo(el); + $.post("/update", el.serialize(), function(){ + save.remove(); + el.trigger('set'); + ev.resume(); + }) + }, + no: function(){ + ev.resume(); + }, + cancel: function(){ + ev.preventDefault(); + ev.resume(); + } + }) + } + } +}); + +$("#panels .tab").dirtybit().saver(); + +// a fake post method +$.post = function(url,data, success){ + setTimeout(success,500) +} + +</script> + </body> +</html> \ No newline at end of file diff --git a/browserid/static/jquery/event/pause/pause.js b/browserid/static/jquery/event/pause/pause.js new file mode 100644 index 0000000000000000000000000000000000000000..c489f1d478b22971e55134de080cecdc03f4df0f --- /dev/null +++ b/browserid/static/jquery/event/pause/pause.js @@ -0,0 +1,227 @@ +steal.plugins('jquery/event/livehack', 'jquery/event/handle').then(function($){ + +var current, + rnamespaces = /\.(.*)$/, + returnFalse = function(){return false}, + returnTrue = function(){return true}; + +/** + * @page jquery.event.pause Pause-Resume + * @plugin jquery/event/pause + * @parent specialevents + * The jquery/event/pause plugin adds the ability to pause and + * resume events. + * + * $('#todos').bind('show', function(ev){ + * ev.pause(); + * + * $(this).load('todos.html', function(){ + * ev.resume(); + * }); + * }) + * + * When an event is paused, stops calling other event handlers for the + * event (similar to event.stopImmediatePropagation() ). But when + * resume is called on the event, it will begin calling events on event handlers + * after the 'paused' event handler. + * + * + * Pause-able events complement the [jQuery.event.special.default default] + * events plugin, providing the ability to easy create widgets with + * an asynchronous API. + * + * ## Example + * + * Consider a basic tabs widget that: + * + * - trigger's a __show__ event on panels when they are to be displayed + * - shows the panel after the show event. + * + * The sudo code for this controller might look like: + * + * $.Controller('Tabs',{ + * ".button click" : function( el ){ + * var panel = this.getPanelFromButton( el ); + * panel.triggerAsync('show', function(){ + * panel.show(); + * }) + * } + * }) + * + * Someone using this plugin would be able to delay the panel showing until ready: + * + * $('#todos').bind('show', function(ev){ + * ev.pause(); + * + * $(this).load('todos.html', function(){ + * ev.resume(); + * }); + * }) + * + * Or prevent the panel from showing at all: + * + * $('#todos').bind('show', function(ev){ + * if(! isReady()){ + * ev.preventDefault(); + * } + * }) + * + * ## Limitations + * + * The element and event handler that the <code>pause</code> is within can not be removed before + * resume is called. + * + * ## Big Example + * + * The following example shows a tabs widget where the user is prompted to save, ignore, or keep editing + * a tab when a new tab is clicked. + * + * @demo jquery/event/pause/pause.html + * + * It's a long, but great example of how to do some pretty complex state management with JavaScriptMVC. + * + */ +$.Event.prototype.isPaused = returnFalse + +/** + * @function + * @parent jquery.event.pause + * Pauses an event (to be resumed later); + */ +$.Event.prototype.pause = function(){ + current = this; + this.stopImmediatePropagation(); + this.isPaused = returnTrue; +}; +/** + * @function + * @parent jquery.event.pause + * + * Resumes an event + */ +$.Event.prototype.resume = function(){ + this.isPaused = this.isImmediatePropagationStopped = this.isPropagationStopped = returnFalse; + + var el = this.liveFired || this.currentTarget || this.target, + defult = $.event.special['default'], + oldType = this.type; + + // if we were in a 'live' -> run our liveHandler + if(this.handleObj.origHandler){ + var cur = this.currentTarget; + this.currentTarget = this.liveFired; + this.liveFired = undefined; + + liveHandler.call(el, this, cur ); + el = cur; + } + if(this.isImmediatePropagationStopped()){ + return false; + } + + // skip the event the first pass because we've already handled it + this.firstPass = true; + + if(!this.isPropagationStopped()){ + $.event.trigger(this, [this.handleObj], el, false); + } + +}; + + +function liveHandler( event, after ) { + var stop, maxLevel, related, match, handleObj, elem, j, i, l, data, close, namespace, ret, + elems = [], + selectors = [], + events = jQuery._data( this, "events" ); + + // Make sure we avoid non-left-click bubbling in Firefox (#3861) and disabled elements in IE (#6911) + if ( event.liveFired === this || !events || !events.live || event.target.disabled || event.button && event.type === "click" ) { + return; + } + + if ( event.namespace ) { + namespace = new RegExp("(^|\\.)" + event.namespace.split(".").join("\\.(?:.*\\.)?") + "(\\.|$)"); + } + + event.liveFired = this; + + var live = events.live.slice(0); + + for ( j = 0; j < live.length; j++ ) { + handleObj = live[j]; + + if ( handleObj.origType.replace( rnamespaces, "" ) === event.type ) { + selectors.push( handleObj.selector ); + + } else { + live.splice( j--, 1 ); + } + } + + match = jQuery( event.target ).closest( selectors, event.currentTarget ); + + for ( i = 0, l = match.length; i < l; i++ ) { + close = match[i]; + + for ( j = 0; j < live.length; j++ ) { + handleObj = live[j]; + + if ( close.selector === handleObj.selector && (!namespace || namespace.test( handleObj.namespace )) && !close.elem.disabled ) { + elem = close.elem; + related = null; + + // Those two events require additional checking + if ( handleObj.preType === "mouseenter" || handleObj.preType === "mouseleave" ) { + event.type = handleObj.preType; + related = jQuery( event.relatedTarget ).closest( handleObj.selector )[0]; + + // Make sure not to accidentally match a child element with the same selector + if ( related && jQuery.contains( elem, related ) ) { + related = elem; + } + } + + if ( !related || related !== elem ) { + elems.push({ elem: elem, handleObj: handleObj, level: close.level }); + } + } + } + } + + for ( i = 0, l = elems.length; i < l; i++ ) { + match = elems[i]; + // inserted to only call elements after this point ... + if(after) { + if(after === match.elem){ + after = undefined; + } + continue; + } + + if ( maxLevel && match.level > maxLevel ) { + break; + } + + event.currentTarget = match.elem; + event.data = match.handleObj.data; + event.handleObj = match.handleObj; + + ret = match.handleObj.origHandler.apply( match.elem, arguments ); + + if ( ret === false || event.isPropagationStopped() ) { + maxLevel = match.level; + + if ( ret === false ) { + stop = false; + } + if ( event.isImmediatePropagationStopped() ) { + break; + } + } + } + + return stop; +} + +}); \ No newline at end of file diff --git a/browserid/static/jquery/event/pause/pause_test.js b/browserid/static/jquery/event/pause/pause_test.js new file mode 100644 index 0000000000000000000000000000000000000000..6928204c4e01b3c1bde22e59d1718e9778bb5f5e --- /dev/null +++ b/browserid/static/jquery/event/pause/pause_test.js @@ -0,0 +1,51 @@ +steal.plugins('funcunit/qunit','funcunit/syn','jquery/event/pause').then(function(){ + +module("jquery/event/pause", {setup : function(){ + $("#qunit-test-area").html("") + var div = $("<div id='wrapper'><ul id='ul'>"+ + "<li><p>Hello</p>"+ + "<ul><li><p id='foo'>Foo Bar</p></li></ul>"+ + "</li></ul></div>").appendTo($("#qunit-test-area")); + +}}); + +test("basics",3, function(){ + + var calls =0, + lastTime, + space = function(){ + if(lastTime){ + + ok(new Date - lastTime > 35,"space between times "+(new Date - lastTime)) + } + lastTime = new Date() + }; + + $('#ul').delegate("li", "show",function(ev){ + calls++; + space(); + + ev.pause(); + + setTimeout(function(){ + ev.resume(); + },100) + + }) + + $('#wrapper').bind('show', function(){ + space() + equals(calls, 2, "both lis called"); + start() + }); + stop(); + $('#foo').trigger("show") +}); + + + + + + + +}); \ No newline at end of file diff --git a/browserid/static/jquery/event/pause/qunit.html b/browserid/static/jquery/event/pause/qunit.html new file mode 100644 index 0000000000000000000000000000000000000000..c9434a5ea01bf7b848656ff6dba48c4a8f219bd3 --- /dev/null +++ b/browserid/static/jquery/event/pause/qunit.html @@ -0,0 +1,20 @@ +<html> + <head> + <link rel="stylesheet" type="text/css" href="../../../funcunit/qunit/qunit.css" /> + <title>swipe QUnit Test</title> + <script type='text/javascript'> + steal = {ignoreControllers: true} + </script> + <script type='text/javascript' src='../../../steal/steal.js?jquery/event/pause/pause_test.js'></script> + </head> + <body> + + <h1 id="qunit-header">swipe Test Suite</h1> + <h2 id="qunit-banner"></h2> + <div id="qunit-testrunner-toolbar"></div> + <h2 id="qunit-userAgent"></h2> + <div id="test-content"></div> + <ol id="qunit-tests"></ol> + <div id="qunit-test-area"></div> + </body> +</html> \ No newline at end of file diff --git a/browserid/static/jquery/event/resize/demo.html b/browserid/static/jquery/event/resize/demo.html new file mode 100644 index 0000000000000000000000000000000000000000..ab44a6351002404f9371bd6ab9473d4c74af9f93 --- /dev/null +++ b/browserid/static/jquery/event/resize/demo.html @@ -0,0 +1,139 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" + "http://www.w3.org/TR/html4/strict.dtd"> +<html lang="en"> + <head> + <title>selection</title> + <style type='text/css'> + body {font-family: verdana} + .error {border: solid 1px red;} + .error_text { color: red; font-size: 10px;} + td {padding: 3px;} + .resizable {border: solid 1px red; + + } + body { + margin: 0px; + padding: 0px; + } + h1 { + border: solid 2px green; + } + #content { + border: solid 2px red; + } + #out { + padding: 10px; + } + #split { + border: solid 1px gray; + } + h3 { + background-color: yellow; + padding: 0px; + margin: 0px; + } + .top { + border: dotted 1px orange; + } + + </style> + <link type="text/css" href="../../../mxui/data/grid/grid.css" rel="stylesheet" /> + </head> + <body> + <div id='out'> + <h1>Here is some content that can fold over the page strangely</h1> + <div id='content'> + Some more content + <div id='split'> + <div class='panel' style="width:200px;position:absolute;left:0;" id='leftPanel'> + <h3>I am above the splitter.</h3> + <div id='vsplit'> + <div class='top'> + TOP + </div> + <div class='bottom'> + Bottom + </div> + </div> + </div> + <div class='panel' style="width:200px;position:absolute;left:200;"> + <div id='forever'></div> + </div> + </div> + </div> + </div> + <script type='text/javascript' + src='../../../steal/steal.js'> + + + + </script> + <script type='text/ejs' id='rowEjs'> + <td><%= name %></td> + <td><%= birthday %></td> + <td><%= foo %></td> + </script> + <script type='text/javascript'> + steal.plugins('mxui/data/grid','mxui/layout/split', + 'jquery/dom/fixture').then(function(){ + + $('#content').mxui_layout_fill({ + parent : $(document.body) + }) + + + $('#split').mxui_layout_fill({ + parent : $('#content') + }).mxui_layout_split({ direction: "vertical", panelClass: "panel" }) + + + $('#vsplit').mxui_layout_fill({ + parent : $('#leftPanel') + }).mxui_layout_split({ direction: "horizontal" }); + + + + $.Model('Thing',{ + attributes : {birthday: "date"} + },{}) + $.fixture.make('thing', 1000, function(i){ + return { + id: i, + name : "thing "+i, + birthday : Math.round(new Date()*Math.random()), + foo : "bar" + } + }) + + + + // we need to also provide the grid with data ... + + var paramsScroll = new Mxui.Data({limit : 50, offset: 0}) + + // Forever Scroll + $("#forever").mxui_data_grid({ + model : Thing, + params : paramsScroll, + columns: { + name: "Name", + birthday: "Birthday", + foo : "Thisisaverylongtitle" + }, + row : "rowEjs", + offsetEmpties: false + }) + + $("#forever .scrollBody").bind("scroll", function(){ + if(this.clientHeight+this.scrollTop + 70 >= this.scrollHeight && !paramsScroll.updating){ + + paramsScroll.attr('offset', paramsScroll.offset + 30) + } + }) + + + }).start() + + </script> + </body> +</html> \ No newline at end of file diff --git a/browserid/static/jquery/event/resize/qunit.html b/browserid/static/jquery/event/resize/qunit.html new file mode 100644 index 0000000000000000000000000000000000000000..97dcdbc3e62df5f3e3c19c519718847223fe4b0d --- /dev/null +++ b/browserid/static/jquery/event/resize/qunit.html @@ -0,0 +1,17 @@ +<html> + <head> + <link rel="stylesheet" type="text/css" href="../../../funcunit/qunit/qunit.css" /> + <title>Resize QUnit Test</title> + <script type='text/javascript' src='../../../steal/steal.js?jquery/event/resize/resize_test.js'></script> + </head> + <body> + + <h1 id="qunit-header">selection Test Suite</h1> + <h2 id="qunit-banner"></h2> + <div id="qunit-testrunner-toolbar"></div> + <h2 id="qunit-userAgent"></h2> + <div id="test-content"></div> + <ol id="qunit-tests"></ol> + <div id="qunit-test-area"></div> + </body> +</html> \ No newline at end of file diff --git a/browserid/static/jquery/event/resize/resize.html b/browserid/static/jquery/event/resize/resize.html new file mode 100644 index 0000000000000000000000000000000000000000..6848c2487156884d7480826efc0006944037d36b --- /dev/null +++ b/browserid/static/jquery/event/resize/resize.html @@ -0,0 +1,47 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" + "http://www.w3.org/TR/html4/strict.dtd"> +<html lang="en"> + <head> + <title>selection</title> + <style type='text/css'> + body {font-family: verdana} + .error {border: solid 1px red;} + .error_text { color: red; font-size: 10px;} + td {padding: 3px;} + .resizable {border: solid 1px red; + + } + </style> + </head> + <body> + <h1>Click an element to see who gets resized as a result. Elements with red borders are + resizable.</h1> + <ul class='resizable'> + <li>Hello World</li> + <li><p>Hello World2</p> + <ul> + <li class='resizable'>Another Item</li> + </ul> + </li> + <li>JavaScriptMVC is just WAY smart</li> + </ul> + <script type='text/javascript' + src='../../../steal/steal.js?jquery/event/resize'> + + $('.resizable').bind('resize', function(){ + var self = $(this); + self.css("border-color","blue"); + setTimeout(function(){ + self.css("border-color","red"); + },1000); + }); + $(window).click(function(ev){ + $(ev.target).trigger("resize") + }) + + </script> + <script type='text/javascript'> + + </script> + </body> +</html> \ No newline at end of file diff --git a/browserid/static/jquery/event/resize/resize.js b/browserid/static/jquery/event/resize/resize.js index 7c874ba9d3cd105632f0f01a2f95e289849dc127..4012d30deb7cfd88c170041259ad80079e07cbc2 100644 --- a/browserid/static/jquery/event/resize/resize.js +++ b/browserid/static/jquery/event/resize/resize.js @@ -1,50 +1,153 @@ -steal.plugins('jquery/event').then(function($){ +steal.plugins('jquery/event').then(function( $ ) { /** * @add jQuery.event.special */ - var resizeCount = 0, + var resizers = $(), + resizeCount = 0, + // bind on the window window resizes to happen win = $(window), - windowWidth = win.width(), - windowHeight = win.height(), + windowWidth = 0, + windowHeight = 0, timer; + + $(function() { + windowWidth = win.width(); + windowHeight = win.height(); + }) + /** * @attribute resize * @parent specialevents - * Normalizes resize events cross browser. - * <p>This only allows native resize events on the window and prevents them from being called - * indefinitely. - * </p> + * + * The resize event is useful for updating elements dimensions when a parent element + * has been resized. It allows you to only resize elements that need to be resized + * in the 'right order'. + * + * By listening to a resize event, you will be alerted whenever a parent + * element has a <code>resize</code> event triggered on it. For example: + * + * $('#foo').bind('resize', function(){ + * // adjust #foo's dimensions + * }) + * + * $(document.body).trigger("resize"); + * + * ## The 'Right Order' + * + * When a control changes size, typically, you want only internal controls to have to adjust their + * dimensions. Furthermore, you want to adjust controls from the 'outside-in', meaning + * that the outermost control adjusts its dimensions before child controls adjust theirs. + * + * Resize calls resize events in exactly this manner. + * + * When you trigger a resize event, it will propagate up the DOM until it reaches + * an element with the first resize event + * handler. There it will move the event in the opposite direction, calling the element's + * children's resize event handlers. + * + * ## Stopping Children Updates + * + * If your element doesn't need to change it's dimensions as a result of the parent element, it should + * call ev.stopPropagation(). This will only stop resize from being sent to child elements of the current element. + * + * */ $.event.special.resize = { + setup: function( handleObj ) { + // add and sort the resizers array + // don't add window because it can't be compared easily + if ( this !== window ) { + resizers.push(this); + $.unique(resizers); + } + // returns false if the window + return this !== window; + }, + teardown: function() { + // we shouldn't have to sort + resizers = resizers.not(this); + + // returns false if the window + return this !== window; + }, add: function( handleObj ) { - //jQuery.event.add( this, handleObj.origType, jQuery.extend({}, handleObj, {handler: liveHandler}) ); - + // increment the number of resizer elements + //$.data(this, "jquery.dom.resizers", ++$.data(this, "jquery.dom.resizers") ); var origHandler = handleObj.handler; handleObj.origHandler = origHandler; - - handleObj.handler = function(ev, data){ - if((this !== window) || (resizeCount === 0 && !ev.originalEvent)){ - resizeCount++; - handleObj.origHandler.call(this, ev, data); - resizeCount--; - } - var width = win.width(); - var height = win.height(); - if(resizeCount === 0 && (width != windowWidth ||height != windowHeight)){ - windowWidth = width; - windowHeight = height; - clearTimeout(timer) - timer = setTimeout(function(){ - win.triggerHandler("resize"); - },1) - - } + + handleObj.handler = function( ev, data ) { + var isWindow = this === window; + + // if we are the window and a real resize has happened + // then we check if the dimensions actually changed + // if they did, we will wait a brief timeout and + // trigger resize on the window + // this is for IE, to prevent window resize 'infinate' loop issues + if ( isWindow && ev.originalEvent ) { + var width = win.width(), + height = win.height(); + + + if ((width != windowWidth || height != windowHeight)) { + //update the new dimensions + windowWidth = width; + windowHeight = height; + clearTimeout(timer) + timer = setTimeout(function() { + win.trigger("resize"); + }, 1); + + } + return; + } + + // if this is the first handler for this event ... + if ( resizeCount === 0 ) { + // prevent others from doing what we are about to do + resizeCount++; + + //trigger all this element's handlers + $.event.handle.call(this, ev); + if ( ev.isPropagationStopped() ) { + resizeCount--; + return; + } + + // get all other elements within this element that listen to resize + // and trigger their resize events + var index = resizers.index(this), + length = resizers.length, + child, sub; + + // if index == -1 it's the window + while (++index < length && (child = resizers[index]) && (isWindow || $.contains(this, child)) ) { + + // call the event + $.event.handle.call(child, ev); + + if ( ev.isPropagationStopped() ) { + // move index until the item is not in the current child + while (++index < length && (sub = resizers[index]) ) { + if (!$.contains(child, sub) ) { + // set index back one + index--; + break + } + } + } + } + + // prevent others from responding + ev.stopImmediatePropagation(); + resizeCount--; + } else { + handleObj.origHandler.call(this, ev, data); + } } - }, - - setup: function() { - return this !== window; } - } -}) + }; + // automatically bind on these + $([document, window]).bind('resize', function() {}) +}) \ No newline at end of file diff --git a/browserid/static/jquery/event/resize/resize_test.js b/browserid/static/jquery/event/resize/resize_test.js new file mode 100644 index 0000000000000000000000000000000000000000..6fa1bf502c9c1ebda337b7f04041f779a27d67d2 --- /dev/null +++ b/browserid/static/jquery/event/resize/resize_test.js @@ -0,0 +1,55 @@ +steal.plugins('funcunit/qunit','jquery/event/resize').then(function(){ + +module("jquery/event/resize") + + +test("resize hits only children in order", function(){ + var ids = [] + record = function(ev){ + ids.push(this.id ? this.id : this) + }, + divs = $("#qunit-test-area") + .html("<div id='1'><div id='1.1'></div><div id='1.2'></div></div><div id='2'></div>") + .find('div').bind('resize',record); + + $(document.body).bind('resize',record); + + $("#qunit-test-area").children().eq(0).trigger("resize"); + + same(ids, ['1','1.1','1.2']) + + ids= []; + $("#qunit-test-area").trigger("resize"); + same(ids, [document.body, '1','1.1','1.2','2']); + + ids= []; + $(window).trigger("resize"); + same(ids, [document.body, '1','1.1','1.2','2']); + + $(document.body).unbind('resize',record); +}); + +test("resize stopping prop", function(){ + var ids = [] + record = function(ev){ + + ids.push(this.id ? this.id : this) + if(this.id == '1'){ + ev.stopPropagation(); + } + }, + divs = $("#qunit-test-area") + .html("<div id='1'><div id='1.1'></div><div id='1.2'></div></div><div id='2'></div>") + .find('div').bind('resize',record); + + $(document.body).bind('resize',record); + + $(window).trigger("resize"); + same(ids, [document.body, '1','2']); + + $(document.body).unbind('resize',record); +}); + + + +}) diff --git a/browserid/static/jquery/event/select/select.html b/browserid/static/jquery/event/select/select.html deleted file mode 100644 index 3cfc3d12adda714b2ba6b733d94690812448043c..0000000000000000000000000000000000000000 --- a/browserid/static/jquery/event/select/select.html +++ /dev/null @@ -1,63 +0,0 @@ -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" - "http://www.w3.org/TR/html4/strict.dtd"> -<html lang="en"> - <head> - <title>select</title> - <style type='text/css'> - body {font-family: verdana} - .error {border: solid 1px red;} - .error_text { color: red; font-size: 10px;} - td {padding: 3px;} - .selected { - background-color: blue; - } - </style> - </head> - <body> - <div class='select'> - this is selectable - </div> - <div class='select'> - this is selectable - </div> - <p> - This is not selectable - </p> - <div id='container'> - <div class='selectme' tabindex='0'> - Select me - </div> - <div class='selectme' tabindex='0'> - Select me - </div> - </div> - <a href='#' id='remove'>Remove</a> - <script type='text/javascript' - src='../../../steal/steal.js?steal[app]=jquery/event/select&steal[env]=development'> - </script> - <script type='text/javascript'> - var s = { - 'selectin' : function(ev, previous){ - console.log('selectin',ev, previous) - $(this).addClass('selected') - }, - 'selectout' : function(ev, to){ - console.log('selectout',ev, to) - $(this).removeClass('selected') - } - } - $(".select").bind(s) - $("#container").delegate('.selectme','selectin',function(ev, previous){ - console.log('selectin',ev, previous) - $(this).addClass('selected') - }); - $("#container").delegate('.selectme','selectout',function(ev, previous){ - console.log('selectout',ev, previous) - $(this).removeClass('selected') - }); - $("#remove").click(function(){ - $("#container").remove(); - }) - </script> - </body> -</html> \ No newline at end of file diff --git a/browserid/static/jquery/event/select/select.js b/browserid/static/jquery/event/select/select.js deleted file mode 100644 index 00506172e53ca16ff8dece122af705a3f8b808f8..0000000000000000000000000000000000000000 --- a/browserid/static/jquery/event/select/select.js +++ /dev/null @@ -1,65 +0,0 @@ -steal.plugins('jquery/event').then(function($){ - var currentSelected = null, - currentTimer, - pieces, - focusin = function(ev){ - clearTimeout(currentTimer); - ev.stopPropagation(); //prevent others from handling focusin - var so = $.Event('selectout'); - so.relatedTarget = this; - - $(currentSelected).trigger(so); - - var si = $.Event('selectin'); - si.relatedTarget = currentSelected; - si.byFocus = true; - $(ev.target).trigger(si ); - currentSelected = null; - - }, - focusout = function(ev){ - ev.stopPropagation(); - currentSelected = ev.currentTarget; - clearTimeout(currentTimer); - currentTimer = setTimeout(function(){ - $(currentSelected).trigger('selectout'); - currentSelected = null; - }, 100) - }, - focusBubble = 'focusin', - blurBubble = 'focusout'; - - - if(document.addEventListener){ - document.addEventListener('focus', function(ev){ - jQuery.event.trigger( 'focusbubble', null, ev.target ) - },true); - document.addEventListener('blur', function(ev){ - jQuery.event.trigger( 'blurbubble', null, ev.target ) - },true); - focusBubble = 'focusbubble', - blurBubble = 'blurbubble'; - } - - $.event.special.selectin = { - add: function( handleObj ) { - if(handleObj.selector){ - $(this).delegate(handleObj.selector,focusBubble, focusin) - $(this).delegate(handleObj.selector,blurBubble, focusout) - }else{ - $(this).bind(focusBubble, focusin). - bind(blurBubble, focusout) - } - }, - remove: function( handleObj ) { - if(handleObj.selector){ - $(this).undelegate(handleObj.selector,focusBubble, focusin) - $(this).undelegate(handleObj.selector,blurBubble, focusout) - }else{ - $(this).unbind(focusBubble, focusin). - unbind(blurBubble, focusout) - } - } - } - -}) diff --git a/browserid/static/jquery/event/selection/qunit.html b/browserid/static/jquery/event/selection/qunit.html new file mode 100644 index 0000000000000000000000000000000000000000..8a555e7b41c424cb318d0ebf14fb08d80635bc1e --- /dev/null +++ b/browserid/static/jquery/event/selection/qunit.html @@ -0,0 +1,20 @@ +<html> + <head> + <link rel="stylesheet" type="text/css" href="../../../funcunit/qunit/qunit.css" /> + <title>selection QUnit Test</title> + <script type='text/javascript'> + steal = {ignoreControllers: true} + </script> + <script type='text/javascript' src='../../../steal/steal.js?jquery/event/selection/selection_test.js'></script> + </head> + <body> + + <h1 id="qunit-header">selection Test Suite</h1> + <h2 id="qunit-banner"></h2> + <div id="qunit-testrunner-toolbar"></div> + <h2 id="qunit-userAgent"></h2> + <div id="test-content"></div> + <ol id="qunit-tests"></ol> + <div id="qunit-test-area"></div> + </body> +</html> \ No newline at end of file diff --git a/browserid/static/jquery/event/selection/selection.html b/browserid/static/jquery/event/selection/selection.html new file mode 100644 index 0000000000000000000000000000000000000000..f71dfd2cd0840c028cdc719c852da1c63295b674 --- /dev/null +++ b/browserid/static/jquery/event/selection/selection.html @@ -0,0 +1,101 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" + "http://www.w3.org/TR/html4/strict.dtd"> +<html lang="en"> + <head> + <title>selection</title> + <style type='text/css'> + body {font-family: verdana} + .error {border: solid 1px red;} + .error_text { color: red; font-size: 10px;} + td {padding: 3px;} + </style> + </head> + <body> + <table> + <tr> + <td> <a href="javascript://" id='select_textarea'>Select Textarea</a></td> + <td><textarea id=''>012 +456</textarea></td> + </tr> + <tr> + <td><a href="javascript://" id='select_input'>Select Input</a></td> + <td><input text='text' value='0123456789'/></td> + </tr> + <tr> + <td><a href="javascript://" id='select_p'>Select Within One Element</a></td> + <td><p id='1'>0123456789</p></td> + </tr> + <tr> + <td><a href="javascript://" id='select_multi'>Select Across Multiple Elements</a></td> + <td><div id='2'>012<div>3<span>4</span>5</div></div></td> + </tr> + </table> + + + <div id='selectionArea'><p>Hello <b>World</b>! how are you today?</p> + <p>I am good, thank you.</p> + </div> + + + <script type='text/javascript' + src='../../../steal/steal.js?jquery/event/selection'> + + (function(){ + $.event.selection.preventDefault = true; + + var selection = $("<div/>").css({ + opacity: 0.5, + backgroundColor: 'blue', + position: 'absolute', + zIndex: 1000, + top: '0px', + left: '0px' + }).appendTo(document.body), + startRange, + highlight = function(entire){ + var rects = entire.rects(), + childs = selection.children(); + + for(var i =0; i < rects.length; i++){ + + var child = childs.eq(i).length ? childs.eq(i) : $('<div/>').css({ + backgroundColor: 'blue', + position: 'absolute', + zIndex: 1000 + }) + //.text(rects[i].left) + .appendTo(selection); + + child.css({ + left: rects[i].left+"px", + top: rects[i].top+"px" + }).width(rects[i].width) + .height(rects[i].height); + } + + childs.slice(i).remove(); + + selection.show(); + }; + + $(document.body).bind('mousedown', function(){ + selection.hide(); + }) + .bind('selectionStart', function(ev, range){ + highlight(range) + }) + .bind('selectionMoving', function(){ + selection.hide(); + }) + .bind('selectionMove', function(ev, range){ + highlight(range); + }) + + })(); + + </script> + <script type='text/javascript'> + + </script> + </body> +</html> \ No newline at end of file diff --git a/browserid/static/jquery/event/selection/selection.js b/browserid/static/jquery/event/selection/selection.js new file mode 100644 index 0000000000000000000000000000000000000000..7a3962116299792c32989d69fd2fa7b2fc9ce113 --- /dev/null +++ b/browserid/static/jquery/event/selection/selection.js @@ -0,0 +1,96 @@ +// a text selection event that is useful in mobile safari + +steal.plugins('jquery/dom/range','jquery/controller','jquery/event/livehack').then(function($){ + + + var event = $.event; + + event.selection = { + delay : 300, + preventDefault : event.supportTouch + }; + + event.setupHelper( ["selectionStart","selectionEnd","selectionEnding","selectionMoving","selectionMove"], "mousedown", function(ev){ + //now start checking mousemoves to update location + var delegate = ev.liveFired || ev.currentTarget, + selector = ev.handleObj.selector, + ready = false, + el = this, + startRange = $.Range(ev), + getRange = function(ev){ + var range = $.Range(ev), + pos = startRange.compare("START_TO_START", range), + entire; + if(pos == -1 || pos == 0){ + return startRange.clone().move("END_TO_END", range) + } else { + return range.clone().move("END_TO_END", startRange) + } + }, + cleanUp = function(){ + $(delegate).unbind('mousemove', mousemove) + .unbind('mouseup',mouseup); + clearTimeout(moveTimer); + startRange = null; + }, + mouseup = function(moveev){ + + if(!ready){ + cleanUp(); + return + } + $.each(event.find(delegate, ["selectionMoving"], selector), function(){ + this.call(el, moveev, range) + }); + var range = getRange(moveev); + cleanUp(); + $.each(event.find(delegate, ["selectionEnd"], selector), function(){ + this.call(el, ev, range); + }); + + }, + mousemove = function(moveev){ + // safari keeps triggering moves even if we haven't moved + if(moveev.clientX == ev.clientX && moveev.clientY == ev.clientY){ + return; + } + + if(!ready){ + return cleanUp(); + } + $.each(event.find(delegate, ["selectionMoving"], selector), function(){ + this.call(el, moveev, range) + }); + var range = getRange(moveev); + $.each(event.find(delegate, ["selectionMove"], selector), function(){ + this.call(el, moveev, range) + }); + }, + start = function(){ + ready = true; + var startEv = event.selection.preventDefault ? $.Event('selectionStart') : ev; + startEv.target = startEv.target || ev.target; + $.each(event.find(delegate, ["selectionStart"], selector), function(){ + this.call(el, startEv, startRange) + }); + + if(event.selection.preventDefault && startEv.isDefaultPrevented()){ + ready = false; + cleanUp(); + } + }, + moveTimer; + + if(event.selection.preventDefault){ + ev.preventDefault(); + moveTimer = setTimeout(start, event.selection.delay); + } else { + start(); + } + + + $(delegate).bind('mousemove', mousemove) + .bind('mouseup',mouseup) + }); + +}); \ No newline at end of file diff --git a/browserid/static/jquery/event/selection/selection_test.js b/browserid/static/jquery/event/selection/selection_test.js new file mode 100644 index 0000000000000000000000000000000000000000..d6ccd7472ec25c362c0149a6c87df98bcb044921 --- /dev/null +++ b/browserid/static/jquery/event/selection/selection_test.js @@ -0,0 +1,40 @@ +steal + .plugins("funcunit/qunit", "jquery/dom/selection").then(function(){ + +module("jquery/dom/selection"); + +test("getCharElement", function(){ + $("#qunit-test-area") + .html("<textarea>012\n456</textarea>"+ + "<input text='text' value='01234567' id='inp'/>"+ + "<p id='1'>0123456789</p>"+ + "<div id='2'>012<div>3<span>4</span>5</div></div>"); + stop(); + setTimeout(function(){ + var types = ['textarea','#inp','#1','#2']; + for(var i =0; i< types.length; i++){ + console.log(types[i]) + $(types[i]).selection(1,5); + } + /* + $('textarea').selection(1,5); + $('input').selection(1,5); + $('#1').selection(1,5); + $('#2').selection(1,5); + */ + var res = []; + for(var i =0; i< types.length; i++){ + res.push( $(types[i]).selection() ); + } + + + + for(var i =0; i< res.length; i++){ + same(res[i],{start: 1, end: 5},types[i]) + } + + start(); + },1000) +}); + +}); \ No newline at end of file diff --git a/browserid/static/jquery/event/swipe/qunit.html b/browserid/static/jquery/event/swipe/qunit.html new file mode 100644 index 0000000000000000000000000000000000000000..e65f52bdad4a44bcc90be202260b4718bfe030f7 --- /dev/null +++ b/browserid/static/jquery/event/swipe/qunit.html @@ -0,0 +1,20 @@ +<html> + <head> + <link rel="stylesheet" type="text/css" href="../../../funcunit/qunit/qunit.css" /> + <title>swipe QUnit Test</title> + <script type='text/javascript'> + steal = {ignoreControllers: true} + </script> + <script type='text/javascript' src='../../../steal/steal.js?jquery/event/swipe/swipe_test.js'></script> + </head> + <body> + + <h1 id="qunit-header">swipe Test Suite</h1> + <h2 id="qunit-banner"></h2> + <div id="qunit-testrunner-toolbar"></div> + <h2 id="qunit-userAgent"></h2> + <div id="test-content"></div> + <ol id="qunit-tests"></ol> + <div id="qunit-test-area"></div> + </body> +</html> \ No newline at end of file diff --git a/browserid/static/jquery/event/swipe/swipe.html b/browserid/static/jquery/event/swipe/swipe.html new file mode 100644 index 0000000000000000000000000000000000000000..131bde68047b76e0d6134f78517bda2f425cc626 --- /dev/null +++ b/browserid/static/jquery/event/swipe/swipe.html @@ -0,0 +1,52 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" + "http://www.w3.org/TR/html4/strict.dtd"> +<html lang="en"> + <head> + <title>swipe</title> + <style type='text/css'> + body {font-family: verdana} + .error {border: solid 1px red;} + .error_text { color: red; font-size: 10px;} + td {padding: 3px;} + #swipe { + width: 500px; + height: 100px; + background-color: green; + } + li { + border: solid 5px green; + width: 200px; + } + </style> + </head> + <body> + <div id='swipe'>Swipe Me</div> + <div id='swipes'>Swipes + <ul> + <li>Swipe Me</li> + <li>Swipe Me</li> + <li>Swipe Me</li> + <li>Swipe Me</li> + </ul> + </div> + <script type='text/javascript' + src='../../../steal/steal.js?jquery/event/swipe'> + </script> + <script type='text/javascript'> + $('#swipe').bind('swipe', function(){ + console.log('swipe1') + }).bind("swiperight", function(){ + console.log('right') + }).bind('swipeleft', function(){ + console.log('left') + }).bind('swipeup', function(){ + console.log('up') + }).bind('swipedown', function(){ + console.log('down') + }) + $('#swipes').delegate('li','swipe', function(){ + console.log('swipe2') + }) + </script> + </body> +</html> \ No newline at end of file diff --git a/browserid/static/jquery/event/swipe/swipe.js b/browserid/static/jquery/event/swipe/swipe.js new file mode 100644 index 0000000000000000000000000000000000000000..b740f790f7012e3d2aeb4bbfe9604036892f59b0 --- /dev/null +++ b/browserid/static/jquery/event/swipe/swipe.js @@ -0,0 +1,101 @@ +steal.plugins('jquery/event/livehack').then(function($){ +var supportTouch = "ontouchend" in document, + scrollEvent = "touchmove scroll", + touchStartEvent = supportTouch ? "touchstart" : "mousedown", + touchStopEvent = supportTouch ? "touchend" : "mouseup", + touchMoveEvent = supportTouch ? "touchmove" : "mousemove", + data = function(event){ + var d = event.originalEvent.touches ? + event.originalEvent.touches[ 0 ] : + event; + return { + time: (new Date).getTime(), + coords: [ d.pageX, d.pageY ], + origin: $( event.target ) + }; + }; + +/** + * @class jQuery.event.swipe + * @parent specialevents + * @plugin jquery/event/swipe + * + * Swipe provides cross browser swipe events. On mobile devices, swipe uses touch events. On desktop browsers, + * swipe uses mouseevents. + * + * A swipe happens when a touch or drag moves + */ +var swipe = $.event.swipe = { + /** + * @attribute delay + * Delay is the upper limit of time the swipe motion can take in milliseconds. This defaults to 1000. + * + * A user must perform the swipe motion in this much time. + */ + delay : 500, + /** + * @attribute max + * The maximum distance the pointer must travel in pixels. The default is 75 pixels. + */ + max : 75, + /** + * @attribute min + * The minimum distance the pointer must travel in pixesl. The default is 30 pixels. + */ + min : 30 +}; + + +$.event.setupHelper( [ + + +"swipe",'swipeleft','swiperight','swipeup','swipedown'], touchStartEvent, function(ev){ + //listen to mouseup + var start = data(ev), + stop, + delegate = ev.liveFired || ev.currentTarget, + selector = ev.handleObj.selector, + entered = this; + + function moveHandler(event){ + if ( !start ) { + return; + } + stop = data(event); + + // prevent scrolling + if ( Math.abs( start.coords[0] - stop.coords[0] ) > 10 ) { + event.preventDefault(); + } + }; + $(document.documentElement).bind(touchMoveEvent,moveHandler ) + .one(touchStopEvent, function(event){ + $(this).unbind( touchMoveEvent, moveHandler ); + if ( start && stop ) { + var deltaX = Math.abs(start.coords[0] - stop.coords[0]), + deltaY = Math.abs(start.coords[1] - stop.coords[1]), + distance = Math.sqrt(deltaX*deltaX+deltaY*deltaY); + console.log(stop.time - start.time, swipe.delay, distance , swipe.min) + if ( stop.time - start.time < swipe.delay && distance >= swipe.min ) { + + var events = ['swipe'] + if( deltaX >= swipe.min && deltaY < swipe.min) { + events.push( start.coords[0] > stop.coords[0] ? "swipeleft" : "swiperight" ); + }else if(deltaY >= swipe.min && deltaX < swipe.min){ + events.push( start.coords[1] < stop.coords[1] ? "swipedown" : "swipeup" ); + } + + + + //trigger swipe events on this guy + $.each($.event.find(delegate, events, selector), function(){ + this.call(entered, ev, {start : start, end: stop}) + }) + + } + } + start = stop = undefined; + }) +}); + +}); \ No newline at end of file diff --git a/browserid/static/jquery/event/swipe/swipe_test.js b/browserid/static/jquery/event/swipe/swipe_test.js new file mode 100644 index 0000000000000000000000000000000000000000..89d968359a9289a64cbb63b8084429fbfba68d71 --- /dev/null +++ b/browserid/static/jquery/event/swipe/swipe_test.js @@ -0,0 +1,110 @@ +steal.plugins('funcunit/qunit','funcunit/syn','jquery/event/swipe').then(function(){ + +module("jquery/swipe", {setup : function(){ + $("#qunit-test-area").html("") + var div = $("<div id='outer'>"+ + "<div id='inner1'>one</div>"+ + "<div id='inner2'>two<div id='inner3'>three</div></div>"+ + "</div>"); + + div.appendTo($("#qunit-test-area")); + var basicCss = { + position: "absolute", + border: "solid 1px black" + } + $("#outer").css(basicCss).css({top: "10px", left: "10px", + zIndex: 1000, backgroundColor: "green", width: "200px", height: "200px"}) +}}); + +test("swipe right event",2, function(){ + + $("#outer").bind("swipe",function(){ + ok(true,"swipe called"); + }).bind("swipeleft", function(){ + ok(false, "swipe left") + }).bind("swiperight", function(){ + ok(true, "swiperight") + }); + stop(); + Syn.drag({ + from: "20x20", + to: "50x20", + duration: 100, + },"outer", function(){ + start(); + }) + +}); + + +test("swipe left event",2, function(){ + + $("#outer").bind("swipe",function(){ + ok(true,"swipe called"); + }).bind("swipeleft", function(){ + ok(true, "swipe left") + }).bind("swiperight", function(){ + ok(false, "swiperight") + }); + stop(); + Syn.drag({ + from: "50x20", + to: "20x20", + duration: 100, + },"outer", function(){ + start(); + }) + +}); + + +test("swipe up event",2, function(){ + + $("#outer").bind("swipe",function(){ + ok(true,"swipe called"); + }).bind("swipeup", function(){ + ok(true, "swipe left") + }).bind("swiperight", function(){ + ok(false, "swiperight") + }).bind("swipedown", function(){ + ok(false, "swipedown") + }); + stop(); + Syn.drag({ + from: "20x50", + to: "20x20", + duration: 100, + },"outer", function(){ + start(); + }) + +}); + +test("swipe down event",2, function(){ + + $("#outer").bind("swipe",function(){ + ok(true,"swipe called"); + }).bind("swipeup", function(){ + ok(false, "swipe left") + }).bind("swiperight", function(){ + ok(false, "swiperight") + }).bind("swipedown", function(){ + ok(true, "swipedown") + }); + stop(); + Syn.drag({ + from: "20x20", + to: "20x50", + duration: 100, + },"outer", function(){ + start(); + }) + +}); + + + + + + +}) \ No newline at end of file diff --git a/browserid/static/jquery/generate/controller b/browserid/static/jquery/generate/controller index b48b7f16df80e797128edfd8efb2ef0192ede6f7..214902a7de4fa45b6d0a8c0c86e12434b3006f2d 100644 --- a/browserid/static/jquery/generate/controller +++ b/browserid/static/jquery/generate/controller @@ -1,7 +1,7 @@ if (_args.length < 1) { - print("USAGE : steal/js steal/generate/controller YourController") - print("EX : steal/js steal/generate/model Cookbook.Controllers.Recipe"); - print(" > cookbook/controller/recipe.js") + print("USAGE : steal/js steal/generate/controller Company.Widget") + print("EX : steal/js steal/generate/controller Company.WidgetName"); + print(" > company/widget_name/widget_name.js") print(); quit(); } @@ -11,11 +11,25 @@ load('steal/rhino/steal.js'); steal( '//steal/generate/generate', '//steal/generate/system', function(steal){ - var md = steal.generate.convert(_args[0]); + var upper = function(parts){ + for(var i =0; i < parts.length; i++){ + parts[i] = parts[i].charAt(0).toUpperCase()+parts[i].substr(1) + } + return parts + } - md.appPath = md.path.replace(/\/controllers$/,""); - - steal.generate("jquery/generate/templates/controller",md.appPath,md) + if(_args[0].charAt(0) !== _args[0].charAt(0).toUpperCase()){ + var caps = upper( _args[0].split(/_|-/) ).join(''), + name = upper(caps.split("/")).join('.'); + + print(" Creating "+name); + _args[0] = name; + } + + var md = steal.generate.convert(_args[0]), + path = _args[0].toLowerCase().replace('.',"/"); + md.path_to_steal = new steal.File(path).pathToRoot() + steal.generate("jquery/generate/templates/controller",md.path+"/"+md.underscore,md) }); diff --git a/browserid/static/jquery/generate/funcunit b/browserid/static/jquery/generate/funcunit new file mode 100644 index 0000000000000000000000000000000000000000..3eb62b3a76a17db8eda1158c1d79c59757876e1f --- /dev/null +++ b/browserid/static/jquery/generate/funcunit @@ -0,0 +1,20 @@ +if (_args.length < 1) { + print("USAGE : steal/js steal/generate/funcunit Myapp") + print(" > Myapp/funcunit.html") + print(); + quit(); +} + +load('steal/rhino/steal.js'); + +steal( '//steal/generate/generate', + '//steal/generate/system', +function(steal){ + var path = _args[0], + split = _args[0].split("/"), + name = split[split.length-1]; + md.path_to_steal = new steal.File(path).pathToRoot() + steal.generate("jquery/generate/templates/funcunit", path, {path: path, name: name}) + +}); + diff --git a/browserid/static/jquery/event/offline/offline.html b/browserid/static/jquery/generate/templates/controller/(underscore).html.ejs similarity index 56% rename from browserid/static/jquery/event/offline/offline.html rename to browserid/static/jquery/generate/templates/controller/(underscore).html.ejs index 280d91ce436df711b10f7df13134cf045251c88a..5915944a4dd2146779d16867dfb481a5a7df27d7 100644 --- a/browserid/static/jquery/event/offline/offline.html +++ b/browserid/static/jquery/generate/templates/controller/(underscore).html.ejs @@ -2,7 +2,7 @@ "http://www.w3.org/TR/html4/strict.dtd"> <html lang="en"> <head> - <title>offline</title> + <title><%= name %></title> <style type='text/css'> body {font-family: verdana} .error {border: solid 1px red;} @@ -11,16 +11,13 @@ </style> </head> <body> - <div id='log'></div> + <h1><%= name %> Demo</h1> + <div id='<%= underscore %>'></div> <script type='text/javascript' - src='../../../steal/steal.js?steal[app]=jquery/event/offline&steal[env]=development'> + src='<%= path_to_steal %>/steal/steal.js?<%= path %>/<%= underscore %>'> </script> <script type='text/javascript'> - var logger = function(ev){ - $('#log').append("<p>"+ev.type+":"+(this == window ? "window" : this.nodeName)+"</p>") - } - $(document.body).bind("offline", logger) - $(document.body).bind("online", logger) + $('#<%= underscore %>').<%= path %>_<%= underscore %>(); </script> </body> </html> \ No newline at end of file diff --git a/browserid/static/jquery/generate/templates/controller/(underscore).js.ejs b/browserid/static/jquery/generate/templates/controller/(underscore).js.ejs new file mode 100644 index 0000000000000000000000000000000000000000..6ce019b5f9a0cbec9c960b3b4ff6c84da3fe3804 --- /dev/null +++ b/browserid/static/jquery/generate/templates/controller/(underscore).js.ejs @@ -0,0 +1,20 @@ +steal.plugins('jquery/controller').then(function($){ + +/** + * @class <%= name %> + */ +$.Controller('<%= name %>', +/* @Static */ +{ + defaults : { + + } +}, +/* @Prototype */ +{ + init : function(){ + this.element.html("Hello World!"); + } +}) + +}); \ No newline at end of file diff --git a/browserid/static/jquery/generate/templates/controller/(underscore)_test.js.ejs b/browserid/static/jquery/generate/templates/controller/(underscore)_test.js.ejs new file mode 100644 index 0000000000000000000000000000000000000000..963d96d22ce72d7b6c33fd1eba237d799cf4600a --- /dev/null +++ b/browserid/static/jquery/generate/templates/controller/(underscore)_test.js.ejs @@ -0,0 +1,14 @@ +steal.plugins('funcunit').then(function(){ + +module("<%= fullName %>", { + setup: function(){ + S.open("//<%= path %>/<%=underscore%>/<%=underscore%>.html"); + } +}); + +test("Text Test", function(){ + equals(S("h1").text(), "<%= fullName %> Demo","demo text"); +}); + + +}); \ No newline at end of file diff --git a/browserid/static/jquery/generate/templates/controller/controllers/(underscore)_controller.js.ejs b/browserid/static/jquery/generate/templates/controller/controllers/(underscore)_controller.js.ejs deleted file mode 100644 index be4e663b37d90e4cc3bcd39302948e4670c19cc8..0000000000000000000000000000000000000000 --- a/browserid/static/jquery/generate/templates/controller/controllers/(underscore)_controller.js.ejs +++ /dev/null @@ -1,12 +0,0 @@ -/** - * @tag controllers, home - */ -jQuery.Controller.extend('<%=name.replace("Models","Controllers")%>', -/* @Static */ -{ - -}, -/* @Prototype */ -{ - -}); \ No newline at end of file diff --git a/browserid/static/jquery/generate/templates/controller/funcunit.html.ejs b/browserid/static/jquery/generate/templates/controller/funcunit.html.ejs new file mode 100644 index 0000000000000000000000000000000000000000..1b1888e0500a4122b3dee8339eda44c65ffdbec6 --- /dev/null +++ b/browserid/static/jquery/generate/templates/controller/funcunit.html.ejs @@ -0,0 +1,15 @@ +<html> + <head> + <link rel="stylesheet" type="text/css" href="<%= path_to_steal %>/funcunit/qunit/qunit.css" /> + <title><%= name %> FuncUnit Test</title> + <script type='text/javascript' src='<%= path_to_steal %>/steal/steal.js?<%=path%>/<%= underscore %>/<%= underscore %>_test.js'></script> + </head> + <body> + + <h1 id="qunit-header"><%= name %> Test Suite</h1> + <h2 id="qunit-banner"></h2> + <div id="qunit-testrunner-toolbar"></div> + <h2 id="qunit-userAgent"></h2> + <ol id="qunit-tests"></ol> + </body> +</html> \ No newline at end of file diff --git a/browserid/static/jquery/generate/templates/controller/views/.ignore b/browserid/static/jquery/generate/templates/controller/views/.ignore new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/browserid/static/jquery/generate/templates/scaffold/controllers/(underscore)_controller.js.ejs b/browserid/static/jquery/generate/templates/scaffold/controllers/(underscore)_controller.js.ejs index 9a1062088dc72342d542b9435bfea488e0ebcb53..ced082b089426e4de0d2f1d835a7fd030b9dc8dc 100644 --- a/browserid/static/jquery/generate/templates/scaffold/controllers/(underscore)_controller.js.ejs +++ b/browserid/static/jquery/generate/templates/scaffold/controllers/(underscore)_controller.js.ejs @@ -15,7 +15,7 @@ $.Controller.extend('<%=name.replace("Models","Controllers")%>', /** * When the page loads, gets all <%= plural %> to be displayed. */ - load: function(){ + "{window} load": function(){ if(!$("#<%= underscore %>").length){ $(document.body).append($('<div/>').attr('id','<%= underscore %>')); <%= name %>.findAll({}, this.callback('list')); diff --git a/browserid/static/jquery/generate/templates/scaffold/test/qunit/(underscore)_test.js.ejs b/browserid/static/jquery/generate/templates/scaffold/test/qunit/(underscore)_test.js.ejs index 0b5640a435b962eed249e1c7d87a291b6f38e8de..1962db8550a4f656acb907381127e73d55cf298a 100644 --- a/browserid/static/jquery/generate/templates/scaffold/test/qunit/(underscore)_test.js.ejs +++ b/browserid/static/jquery/generate/templates/scaffold/test/qunit/(underscore)_test.js.ejs @@ -1,45 +1,45 @@ module("Model: <%= name %>") -test("findAll", function(){ +asyncTest("findAll", function(){ stop(2000); <%= name %>.findAll({}, function(<%= plural %>){ - start() ok(<%= plural %>) ok(<%= plural %>.length) ok(<%= plural %>[0].name) ok(<%= plural %>[0].description) + start() }); }) -test("create", function(){ +asyncTest("create", function(){ stop(2000); new <%= name %>({name: "dry cleaning", description: "take to street corner"}).save(function(<%= underscore %>){ - start(); ok(<%= underscore %>); ok(<%= underscore %>.id); equals(<%= underscore %>.name,"dry cleaning") <%= underscore %>.destroy() + start(); }) }) -test("update" , function(){ +asyncTest("update" , function(){ stop(); new <%= name %>({name: "cook dinner", description: "chicken"}). save(function(<%= underscore %>){ equals(<%= underscore %>.description,"chicken"); <%= underscore %>.update({description: "steak"},function(<%= underscore %>){ - start() equals(<%= underscore %>.description,"steak"); <%= underscore %>.destroy(); + start() }) }) }); -test("destroy", function(){ +asyncTest("destroy", function(){ stop(2000); new <%= name %>({name: "mow grass", description: "use riding mower"}). destroy(function(<%= underscore %>){ - start(); ok( true ,"Destroy called" ) + start(); }) }) \ No newline at end of file diff --git a/browserid/static/jquery/jquery.js b/browserid/static/jquery/jquery.js index 615f7f488b013215c2c7d53d4748dfb43af007bb..a2bf53029406db542615654b26f06dcc4568f30a 100644 --- a/browserid/static/jquery/jquery.js +++ b/browserid/static/jquery/jquery.js @@ -1,28 +1,30 @@ /*! - * jQuery JavaScript Library v1.4.4 + * jQuery JavaScript Library v1.6.1 * http://jquery.com/ * - * Copyright 2010, John Resig + * Copyright 2011, John Resig * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * * Includes Sizzle.js * http://sizzlejs.com/ - * Copyright 2010, The Dojo Foundation + * Copyright 2011, The Dojo Foundation * Released under the MIT, BSD, and GPL Licenses. * - * Date: Thu Nov 11 19:04:53 2010 -0500 + * Date: Thu May 12 15:04:36 2011 -0400 */ (function( window, undefined ) { // Use the correct document accordingly with window argument (sandbox) -var document = window.document; +var document = window.document, + navigator = window.navigator, + location = window.location; var jQuery = (function() { // Define a local copy of jQuery var jQuery = function( selector, context ) { // The jQuery object is actually just the init constructor 'enhanced' - return new jQuery.fn.init( selector, context ); + return new jQuery.fn.init( selector, context, rootjQuery ); }, // Map over jQuery in case of overwrite @@ -36,22 +38,15 @@ var jQuery = function( selector, context ) { // A simple way to check for HTML strings or ID strings // (both of which we optimize for) - quickExpr = /^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]+)$)/, - - // Is it a simple selector - isSimple = /^.[^:#\[\.,]*$/, + quickExpr = /^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/, // Check if a string has a non-whitespace character in it rnotwhite = /\S/, - rwhite = /\s/, // Used for trimming whitespace trimLeft = /^\s+/, trimRight = /\s+$/, - // Check for non-word characters - rnonword = /\W/, - // Check for digits rdigit = /\d/, @@ -75,12 +70,9 @@ var jQuery = function( selector, context ) { // For matching the engine and version of the browser browserMatch, - - // Has the ready events already been bound? - readyBound = false, - - // The functions to execute on DOM ready - readyList = [], + + // The deferred used on DOM ready + readyList, // The ready event handler DOMContentLoaded, @@ -92,12 +84,13 @@ var jQuery = function( selector, context ) { slice = Array.prototype.slice, trim = String.prototype.trim, indexOf = Array.prototype.indexOf, - + // [[Class]] -> type pairs class2type = {}; jQuery.fn = jQuery.prototype = { - init: function( selector, context ) { + constructor: jQuery, + init: function( selector, context, rootjQuery ) { var match, elem, ret, doc; // Handle $(""), $(null), or $(undefined) @@ -111,12 +104,12 @@ jQuery.fn = jQuery.prototype = { this.length = 1; return this; } - + // The body element only exists once, optimize finding it if ( selector === "body" && !context && document.body ) { this.context = document; this[0] = document.body; - this.selector = "body"; + this.selector = selector; this.length = 1; return this; } @@ -124,13 +117,20 @@ jQuery.fn = jQuery.prototype = { // Handle HTML strings if ( typeof selector === "string" ) { // Are we dealing with HTML string or an ID? - match = quickExpr.exec( selector ); + if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) { + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [ null, selector, null ]; + + } else { + match = quickExpr.exec( selector ); + } // Verify a match, and that no context was specified for #id if ( match && (match[1] || !context) ) { // HANDLE: $(html) -> $(array) if ( match[1] ) { + context = context instanceof jQuery ? context[0] : context; doc = (context ? context.ownerDocument || context : document); // If a single string is passed in and it's a single tag @@ -148,11 +148,11 @@ jQuery.fn = jQuery.prototype = { } else { ret = jQuery.buildFragment( [ match[1] ], [ doc ] ); - selector = (ret.cacheable ? ret.fragment.cloneNode(true) : ret.fragment).childNodes; + selector = (ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment).childNodes; } - + return jQuery.merge( this, selector ); - + // HANDLE: $("#id") } else { elem = document.getElementById( match[2] ); @@ -176,13 +176,6 @@ jQuery.fn = jQuery.prototype = { return this; } - // HANDLE: $("TAG") - } else if ( !context && !rnonword.test( selector ) ) { - this.selector = selector; - this.context = document; - selector = document.getElementsByTagName( selector ); - return jQuery.merge( this, selector ); - // HANDLE: $(expr, $(...)) } else if ( !context || context.jquery ) { return (context || rootjQuery).find( selector ); @@ -190,7 +183,7 @@ jQuery.fn = jQuery.prototype = { // HANDLE: $(expr, context) // (which is just equivalent to: $(context).find(expr) } else { - return jQuery( context ).find( selector ); + return this.constructor( context ).find( selector ); } // HANDLE: $(function) @@ -211,7 +204,7 @@ jQuery.fn = jQuery.prototype = { selector: "", // The current version of jQuery being used - jquery: "1.4.4", + jquery: "1.6.1", // The default length of a jQuery object is 0 length: 0, @@ -234,18 +227,18 @@ jQuery.fn = jQuery.prototype = { this.toArray() : // Return just the object - ( num < 0 ? this.slice(num)[ 0 ] : this[ num ] ); + ( num < 0 ? this[ this.length + num ] : this[ num ] ); }, // Take an array of elements and push it onto the stack // (returning the new matched element set) pushStack: function( elems, name, selector ) { // Build a new jQuery matched element set - var ret = jQuery(); + var ret = this.constructor(); if ( jQuery.isArray( elems ) ) { push.apply( ret, elems ); - + } else { jQuery.merge( ret, elems ); } @@ -271,25 +264,17 @@ jQuery.fn = jQuery.prototype = { each: function( callback, args ) { return jQuery.each( this, callback, args ); }, - + ready: function( fn ) { // Attach the listeners jQuery.bindReady(); - // If the DOM is already ready - if ( jQuery.isReady ) { - // Execute the function immediately - fn.call( document, jQuery ); - - // Otherwise, remember the function for later - } else if ( readyList ) { - // Add the function to the wait list - readyList.push( fn ); - } + // Add the callback + readyList.done( fn ); return this; }, - + eq: function( i ) { return i === -1 ? this.slice( i ) : @@ -314,9 +299,9 @@ jQuery.fn = jQuery.prototype = { return callback.call( elem, i, elem ); })); }, - + end: function() { - return this.prevObject || jQuery(null); + return this.prevObject || this.constructor(null); }, // For internal use only. @@ -330,7 +315,7 @@ jQuery.fn = jQuery.prototype = { jQuery.fn.init.prototype = jQuery.fn; jQuery.extend = jQuery.fn.extend = function() { - var options, name, src, copy, copyIsArray, clone, + var options, name, src, copy, copyIsArray, clone, target = arguments[0] || {}, i = 1, length = arguments.length, @@ -395,31 +380,37 @@ jQuery.extend = jQuery.fn.extend = function() { jQuery.extend({ noConflict: function( deep ) { - window.$ = _$; + if ( window.$ === jQuery ) { + window.$ = _$; + } - if ( deep ) { + if ( deep && window.jQuery === jQuery ) { window.jQuery = _jQuery; } return jQuery; }, - + // Is the DOM ready to be used? Set to true once it occurs. isReady: false, // A counter to track how many items to wait for before // the ready event fires. See #6781 readyWait: 1, - - // Handle when the DOM is ready - ready: function( wait ) { - // A third-party is pushing the ready event forwards - if ( wait === true ) { - jQuery.readyWait--; + + // Hold (or release) the ready event + holdReady: function( hold ) { + if ( hold ) { + jQuery.readyWait++; + } else { + jQuery.ready( true ); } + }, - // Make sure that the DOM is not already loaded - if ( !jQuery.readyWait || (wait !== true && !jQuery.isReady) ) { + // Handle when the DOM is ready + ready: function( wait ) { + // Either a released hold or an DOMready/load event and not yet ready + if ( (wait === true && !--jQuery.readyWait) || (wait !== true && !jQuery.isReady) ) { // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). if ( !document.body ) { return setTimeout( jQuery.ready, 1 ); @@ -434,33 +425,21 @@ jQuery.extend({ } // If there are functions bound, to execute - if ( readyList ) { - // Execute all of them - var fn, - i = 0, - ready = readyList; + readyList.resolveWith( document, [ jQuery ] ); - // Reset the list of functions - readyList = null; - - while ( (fn = ready[ i++ ]) ) { - fn.call( document, jQuery ); - } - - // Trigger any bound ready events - if ( jQuery.fn.trigger ) { - jQuery( document ).trigger( "ready" ).unbind( "ready" ); - } + // Trigger any bound ready events + if ( jQuery.fn.trigger ) { + jQuery( document ).trigger( "ready" ).unbind( "ready" ); } } }, - + bindReady: function() { - if ( readyBound ) { + if ( readyList ) { return; } - readyBound = true; + readyList = jQuery._Deferred(); // Catch cases where $(document).ready() is called after the // browser event has already occurred. @@ -473,7 +452,7 @@ jQuery.extend({ if ( document.addEventListener ) { // Use the handy event callback document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false ); - + // A fallback to window.onload, that will always work window.addEventListener( "load", jQuery.ready, false ); @@ -481,8 +460,8 @@ jQuery.extend({ } else if ( document.attachEvent ) { // ensure firing before onload, // maybe late but safe also for iframes - document.attachEvent("onreadystatechange", DOMContentLoaded); - + document.attachEvent( "onreadystatechange", DOMContentLoaded ); + // A fallback to window.onload, that will always work window.attachEvent( "onload", jQuery.ready ); @@ -533,20 +512,20 @@ jQuery.extend({ if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) { return false; } - + // Not own constructor property must be Object if ( obj.constructor && !hasOwn.call(obj, "constructor") && !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) { return false; } - + // Own properties are enumerated firstly, so to speed up, // if last one is own, then all properties are own. - + var key; for ( key in obj ) {} - + return key === undefined || hasOwn.call( obj, key ); }, @@ -556,11 +535,11 @@ jQuery.extend({ } return true; }, - + error: function( msg ) { throw msg; }, - + parseJSON: function( data ) { if ( typeof data !== "string" || !data ) { return null; @@ -568,45 +547,59 @@ jQuery.extend({ // Make sure leading/trailing whitespace is removed (IE can't handle it) data = jQuery.trim( data ); - + + // Attempt to parse using the native JSON parser first + if ( window.JSON && window.JSON.parse ) { + return window.JSON.parse( data ); + } + // Make sure the incoming data is actual JSON // Logic borrowed from http://json.org/json2.js - if ( rvalidchars.test(data.replace(rvalidescape, "@") - .replace(rvalidtokens, "]") - .replace(rvalidbraces, "")) ) { + if ( rvalidchars.test( data.replace( rvalidescape, "@" ) + .replace( rvalidtokens, "]" ) + .replace( rvalidbraces, "")) ) { - // Try to use the native JSON parser first - return window.JSON && window.JSON.parse ? - window.JSON.parse( data ) : - (new Function("return " + data))(); + return (new Function( "return " + data ))(); - } else { - jQuery.error( "Invalid JSON: " + data ); } + jQuery.error( "Invalid JSON: " + data ); }, - noop: function() {}, + // Cross-browser xml parsing + // (xml & tmp used internally) + parseXML: function( data , xml , tmp ) { - // Evalulates a script in a global context - globalEval: function( data ) { - if ( data && rnotwhite.test(data) ) { - // Inspired by code by Andrea Giammarchi - // http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html - var head = document.getElementsByTagName("head")[0] || document.documentElement, - script = document.createElement("script"); + if ( window.DOMParser ) { // Standard + tmp = new DOMParser(); + xml = tmp.parseFromString( data , "text/xml" ); + } else { // IE + xml = new ActiveXObject( "Microsoft.XMLDOM" ); + xml.async = "false"; + xml.loadXML( data ); + } - script.type = "text/javascript"; + tmp = xml.documentElement; - if ( jQuery.support.scriptEval ) { - script.appendChild( document.createTextNode( data ) ); - } else { - script.text = data; - } + if ( ! tmp || ! tmp.nodeName || tmp.nodeName === "parsererror" ) { + jQuery.error( "Invalid XML: " + data ); + } + + return xml; + }, + + noop: function() {}, - // Use insertBefore instead of appendChild to circumvent an IE6 bug. - // This arises when a base node is used (#2709). - head.insertBefore( script, head.firstChild ); - head.removeChild( script ); + // Evaluates a script in a global context + // Workarounds based on findings by Jim Driscoll + // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context + globalEval: function( data ) { + if ( data && rnotwhite.test( data ) ) { + // We use execScript on Internet Explorer + // We use an anonymous function so that context is window + // rather than jQuery in Firefox + ( window.execScript || function( data ) { + window[ "eval" ].call( window, data ); + } )( data ); } }, @@ -618,7 +611,7 @@ jQuery.extend({ each: function( object, callback, args ) { var name, i = 0, length = object.length, - isObj = length === undefined || jQuery.isFunction(object); + isObj = length === undefined || jQuery.isFunction( object ); if ( args ) { if ( isObj ) { @@ -644,8 +637,11 @@ jQuery.extend({ } } } else { - for ( var value = object[0]; - i < length && callback.call( value, i, value ) !== false; value = object[++i] ) {} + for ( ; i < length; ) { + if ( callback.call( object[ i ], i, object[ i++ ] ) === false ) { + break; + } + } } } @@ -676,7 +672,7 @@ jQuery.extend({ // The extra typeof function check is to prevent crashes // in Safari 2 (See: #3039) // Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930 - var type = jQuery.type(array); + var type = jQuery.type( array ); if ( array.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( array ) ) { push.call( ret, array ); @@ -689,8 +685,9 @@ jQuery.extend({ }, inArray: function( elem, array ) { - if ( array.indexOf ) { - return array.indexOf( elem ); + + if ( indexOf ) { + return indexOf.call( array, elem ); } for ( var i = 0, length = array.length; i < length; i++ ) { @@ -710,7 +707,7 @@ jQuery.extend({ for ( var l = second.length; j < l; j++ ) { first[ i++ ] = second[ j ]; } - + } else { while ( second[j] !== undefined ) { first[ i++ ] = second[ j++ ]; @@ -740,49 +737,64 @@ jQuery.extend({ // arg is for internal usage only map: function( elems, callback, arg ) { - var ret = [], value; + var value, key, ret = [], + i = 0, + length = elems.length, + // jquery objects are treated as arrays + isArray = elems instanceof jQuery || length !== undefined && typeof length === "number" && ( ( length > 0 && elems[ 0 ] && elems[ length -1 ] ) || length === 0 || jQuery.isArray( elems ) ) ; // Go through the array, translating each of the items to their - // new value (or values). - for ( var i = 0, length = elems.length; i < length; i++ ) { - value = callback( elems[ i ], i, arg ); + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret[ ret.length ] = value; + } + } + + // Go through every key on the object, + } else { + for ( key in elems ) { + value = callback( elems[ key ], key, arg ); - if ( value != null ) { - ret[ ret.length ] = value; + if ( value != null ) { + ret[ ret.length ] = value; + } } } + // Flatten any nested arrays return ret.concat.apply( [], ret ); }, // A global GUID counter for objects guid: 1, - proxy: function( fn, proxy, thisObject ) { - if ( arguments.length === 2 ) { - if ( typeof proxy === "string" ) { - thisObject = fn; - fn = thisObject[ proxy ]; - proxy = undefined; + // Bind a function to a context, optionally partially applying any + // arguments. + proxy: function( fn, context ) { + if ( typeof context === "string" ) { + var tmp = fn[ context ]; + context = fn; + fn = tmp; + } - } else if ( proxy && !jQuery.isFunction( proxy ) ) { - thisObject = proxy; - proxy = undefined; - } + // Quick check to determine if target is callable, in the spec + // this throws a TypeError, but we will just return undefined. + if ( !jQuery.isFunction( fn ) ) { + return undefined; } - if ( !proxy && fn ) { + // Simulated bind + var args = slice.call( arguments, 2 ), proxy = function() { - return fn.apply( thisObject || this, arguments ); + return fn.apply( context, args.concat( slice.call( arguments ) ) ); }; - } // Set the guid of unique handler to the same of original handler, so it can be removed - if ( fn ) { - proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++; - } + proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++; - // So proxy can be declared as an argument return proxy; }, @@ -790,7 +802,7 @@ jQuery.extend({ // The value/s can be optionally by executed if its a function access: function( elems, key, value, exec, fn, pass ) { var length = elems.length; - + // Setting many attributes if ( typeof key === "object" ) { for ( var k in key ) { @@ -798,19 +810,19 @@ jQuery.extend({ } return elems; } - + // Setting one attribute if ( value !== undefined ) { // Optionally, function values get executed if exec is true exec = !pass && exec && jQuery.isFunction(value); - + for ( var i = 0; i < length; i++ ) { fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass ); } - + return elems; } - + // Getting an attribute return length ? fn( elems[0], key ) : undefined; }, @@ -833,6 +845,27 @@ jQuery.extend({ return { browser: match[1] || "", version: match[2] || "0" }; }, + sub: function() { + function jQuerySub( selector, context ) { + return new jQuerySub.fn.init( selector, context ); + } + jQuery.extend( true, jQuerySub, this ); + jQuerySub.superclass = this; + jQuerySub.fn = jQuerySub.prototype = this(); + jQuerySub.fn.constructor = jQuerySub; + jQuerySub.sub = this.sub; + jQuerySub.fn.init = function init( selector, context ) { + if ( context && context instanceof jQuery && !(context instanceof jQuerySub) ) { + context = jQuerySub( context ); + } + + return jQuery.fn.init.call( this, selector, context, rootjQuerySub ); + }; + jQuerySub.fn.init.prototype = jQuerySub.fn; + var rootjQuerySub = jQuerySub(document); + return jQuerySub; + }, + browser: {} }); @@ -852,15 +885,8 @@ if ( jQuery.browser.webkit ) { jQuery.browser.safari = true; } -if ( indexOf ) { - jQuery.inArray = function( elem, array ) { - return indexOf.call( array, elem ); - }; -} - -// Verify that \s matches non-breaking spaces -// (IE fails on this test) -if ( !rwhite.test( "\xA0" ) ) { +// IE doesn't match non-breaking spaces with \s +if ( rnotwhite.test( "\xA0" ) ) { trimLeft = /^[\s\xA0]+/; trimRight = /[\s\xA0]+$/; } @@ -905,52 +931,265 @@ function doScrollCheck() { } // Expose jQuery to the global object -return (window.jQuery = window.$ = jQuery); +return jQuery; })(); -(function() { +var // Promise methods + promiseMethods = "done fail isResolved isRejected promise then always pipe".split( " " ), + // Static reference to slice + sliceDeferred = [].slice; + +jQuery.extend({ + // Create a simple deferred (one callbacks list) + _Deferred: function() { + var // callbacks list + callbacks = [], + // stored [ context , args ] + fired, + // to avoid firing when already doing so + firing, + // flag to know if the deferred has been cancelled + cancelled, + // the deferred itself + deferred = { + + // done( f1, f2, ...) + done: function() { + if ( !cancelled ) { + var args = arguments, + i, + length, + elem, + type, + _fired; + if ( fired ) { + _fired = fired; + fired = 0; + } + for ( i = 0, length = args.length; i < length; i++ ) { + elem = args[ i ]; + type = jQuery.type( elem ); + if ( type === "array" ) { + deferred.done.apply( deferred, elem ); + } else if ( type === "function" ) { + callbacks.push( elem ); + } + } + if ( _fired ) { + deferred.resolveWith( _fired[ 0 ], _fired[ 1 ] ); + } + } + return this; + }, + + // resolve with given context and args + resolveWith: function( context, args ) { + if ( !cancelled && !fired && !firing ) { + // make sure args are available (#8421) + args = args || []; + firing = 1; + try { + while( callbacks[ 0 ] ) { + callbacks.shift().apply( context, args ); + } + } + finally { + fired = [ context, args ]; + firing = 0; + } + } + return this; + }, + + // resolve with this as context and given arguments + resolve: function() { + deferred.resolveWith( this, arguments ); + return this; + }, + + // Has this deferred been resolved? + isResolved: function() { + return !!( firing || fired ); + }, + + // Cancel + cancel: function() { + cancelled = 1; + callbacks = []; + return this; + } + }; + + return deferred; + }, + + // Full fledged deferred (two callbacks list) + Deferred: function( func ) { + var deferred = jQuery._Deferred(), + failDeferred = jQuery._Deferred(), + promise; + // Add errorDeferred methods, then and promise + jQuery.extend( deferred, { + then: function( doneCallbacks, failCallbacks ) { + deferred.done( doneCallbacks ).fail( failCallbacks ); + return this; + }, + always: function() { + return deferred.done.apply( deferred, arguments ).fail.apply( this, arguments ); + }, + fail: failDeferred.done, + rejectWith: failDeferred.resolveWith, + reject: failDeferred.resolve, + isRejected: failDeferred.isResolved, + pipe: function( fnDone, fnFail ) { + return jQuery.Deferred(function( newDefer ) { + jQuery.each( { + done: [ fnDone, "resolve" ], + fail: [ fnFail, "reject" ] + }, function( handler, data ) { + var fn = data[ 0 ], + action = data[ 1 ], + returned; + if ( jQuery.isFunction( fn ) ) { + deferred[ handler ](function() { + returned = fn.apply( this, arguments ); + if ( returned && jQuery.isFunction( returned.promise ) ) { + returned.promise().then( newDefer.resolve, newDefer.reject ); + } else { + newDefer[ action ]( returned ); + } + }); + } else { + deferred[ handler ]( newDefer[ action ] ); + } + }); + }).promise(); + }, + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + if ( obj == null ) { + if ( promise ) { + return promise; + } + promise = obj = {}; + } + var i = promiseMethods.length; + while( i-- ) { + obj[ promiseMethods[i] ] = deferred[ promiseMethods[i] ]; + } + return obj; + } + }); + // Make sure only one callback list will be used + deferred.done( failDeferred.cancel ).fail( deferred.cancel ); + // Unexpose cancel + delete deferred.cancel; + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + return deferred; + }, + + // Deferred helper + when: function( firstParam ) { + var args = arguments, + i = 0, + length = args.length, + count = length, + deferred = length <= 1 && firstParam && jQuery.isFunction( firstParam.promise ) ? + firstParam : + jQuery.Deferred(); + function resolveFunc( i ) { + return function( value ) { + args[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value; + if ( !( --count ) ) { + // Strange bug in FF4: + // Values changed onto the arguments object sometimes end up as undefined values + // outside the $.when method. Cloning the object into a fresh array solves the issue + deferred.resolveWith( deferred, sliceDeferred.call( args, 0 ) ); + } + }; + } + if ( length > 1 ) { + for( ; i < length; i++ ) { + if ( args[ i ] && jQuery.isFunction( args[ i ].promise ) ) { + args[ i ].promise().then( resolveFunc(i), deferred.reject ); + } else { + --count; + } + } + if ( !count ) { + deferred.resolveWith( deferred, args ); + } + } else if ( deferred !== firstParam ) { + deferred.resolveWith( deferred, length ? [ firstParam ] : [] ); + } + return deferred.promise(); + } +}); + + - jQuery.support = {}; +jQuery.support = (function() { - var root = document.documentElement, - script = document.createElement("script"), - div = document.createElement("div"), - id = "script" + jQuery.now(); + var div = document.createElement( "div" ), + documentElement = document.documentElement, + all, + a, + select, + opt, + input, + marginDiv, + support, + fragment, + body, + bodyStyle, + tds, + events, + eventName, + i, + isSupported; - div.style.display = "none"; - div.innerHTML = " <link/><table></table><a href='/a' style='color:red;float:left;opacity:.55;'>a</a><input type='checkbox'/>"; + // Preliminary tests + div.setAttribute("className", "t"); + div.innerHTML = " <link/><table></table><a href='/a' style='top:1px;float:left;opacity:.55;'>a</a><input type='checkbox'/>"; - var all = div.getElementsByTagName("*"), - a = div.getElementsByTagName("a")[0], - select = document.createElement("select"), - opt = select.appendChild( document.createElement("option") ); + all = div.getElementsByTagName( "*" ); + a = div.getElementsByTagName( "a" )[ 0 ]; // Can't get basic test support if ( !all || !all.length || !a ) { - return; + return {}; } - jQuery.support = { + // First batch of supports tests + select = document.createElement( "select" ); + opt = select.appendChild( document.createElement("option") ); + input = div.getElementsByTagName( "input" )[ 0 ]; + + support = { // IE strips leading whitespace when .innerHTML is used - leadingWhitespace: div.firstChild.nodeType === 3, + leadingWhitespace: ( div.firstChild.nodeType === 3 ), // Make sure that tbody elements aren't automatically inserted // IE will insert them into empty tables - tbody: !div.getElementsByTagName("tbody").length, + tbody: !div.getElementsByTagName( "tbody" ).length, // Make sure that link elements get serialized correctly by innerHTML // This requires a wrapper element in IE - htmlSerialize: !!div.getElementsByTagName("link").length, + htmlSerialize: !!div.getElementsByTagName( "link" ).length, // Get the style information from getAttribute - // (IE uses .cssText insted) - style: /red/.test( a.getAttribute("style") ), + // (IE uses .cssText instead) + style: /top/.test( a.getAttribute("style") ), // Make sure that URLs aren't manipulated // (IE normalizes it by default) - hrefNormalized: a.getAttribute("href") === "/a", + hrefNormalized: ( a.getAttribute( "href" ) === "/a" ), // Make sure that element opacity exists // (IE uses filter instead) @@ -964,150 +1203,183 @@ return (window.jQuery = window.$ = jQuery); // Make sure that if no value is specified for a checkbox // that it defaults to "on". // (WebKit defaults to "" instead) - checkOn: div.getElementsByTagName("input")[0].value === "on", + checkOn: ( input.value === "on" ), // Make sure that a selected-by-default option has a working selected property. // (WebKit defaults to false instead of true, IE too, if it's in an optgroup) optSelected: opt.selected, + // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7) + getSetAttribute: div.className !== "t", + // Will be defined later + submitBubbles: true, + changeBubbles: true, + focusinBubbles: false, deleteExpando: true, - optDisabled: false, - checkClone: false, - scriptEval: false, noCloneEvent: true, - boxModel: null, inlineBlockNeedsLayout: false, shrinkWrapBlocks: false, - reliableHiddenOffsets: true + reliableMarginRight: true }; + // Make sure checked status is properly cloned + input.checked = true; + support.noCloneChecked = input.cloneNode( true ).checked; + // Make sure that the options inside disabled selects aren't marked as disabled - // (WebKit marks them as diabled) + // (WebKit marks them as disabled) select.disabled = true; - jQuery.support.optDisabled = !opt.disabled; - - script.type = "text/javascript"; - try { - script.appendChild( document.createTextNode( "window." + id + "=1;" ) ); - } catch(e) {} - - root.insertBefore( script, root.firstChild ); - - // Make sure that the execution of code works by injecting a script - // tag with appendChild/createTextNode - // (IE doesn't support this, fails, and uses .text instead) - if ( window[ id ] ) { - jQuery.support.scriptEval = true; - delete window[ id ]; - } + support.optDisabled = !opt.disabled; // Test to see if it's possible to delete an expando from an element // Fails in Internet Explorer try { - delete script.test; - - } catch(e) { - jQuery.support.deleteExpando = false; + delete div.test; + } catch( e ) { + support.deleteExpando = false; } - root.removeChild( script ); - - if ( div.attachEvent && div.fireEvent ) { - div.attachEvent("onclick", function click() { + if ( !div.addEventListener && div.attachEvent && div.fireEvent ) { + div.attachEvent( "onclick", function click() { // Cloning a node shouldn't copy over any // bound event handlers (IE does this) - jQuery.support.noCloneEvent = false; - div.detachEvent("onclick", click); + support.noCloneEvent = false; + div.detachEvent( "onclick", click ); }); - div.cloneNode(true).fireEvent("onclick"); + div.cloneNode( true ).fireEvent( "onclick" ); } - div = document.createElement("div"); - div.innerHTML = "<input type='radio' name='radiotest' checked='checked'/>"; + // Check if a radio maintains it's value + // after being appended to the DOM + input = document.createElement("input"); + input.value = "t"; + input.setAttribute("type", "radio"); + support.radioValue = input.value === "t"; - var fragment = document.createDocumentFragment(); + input.setAttribute("checked", "checked"); + div.appendChild( input ); + fragment = document.createDocumentFragment(); fragment.appendChild( div.firstChild ); // WebKit doesn't clone checked state correctly in fragments - jQuery.support.checkClone = fragment.cloneNode(true).cloneNode(true).lastChild.checked; + support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked; + + div.innerHTML = ""; // Figure out if the W3C box model works as expected - // document.body must exist before we can do this - jQuery(function() { - var div = document.createElement("div"); - div.style.width = div.style.paddingLeft = "1px"; - - document.body.appendChild( div ); - jQuery.boxModel = jQuery.support.boxModel = div.offsetWidth === 2; - - if ( "zoom" in div.style ) { - // Check if natively block-level elements act like inline-block - // elements when setting their display to 'inline' and giving - // them layout - // (IE < 8 does this) - div.style.display = "inline"; - div.style.zoom = 1; - jQuery.support.inlineBlockNeedsLayout = div.offsetWidth === 2; - - // Check if elements with layout shrink-wrap their children - // (IE 6 does this) - div.style.display = ""; - div.innerHTML = "<div style='width:4px;'></div>"; - jQuery.support.shrinkWrapBlocks = div.offsetWidth !== 2; - } - - div.innerHTML = "<table><tr><td style='padding:0;display:none'></td><td>t</td></tr></table>"; - var tds = div.getElementsByTagName("td"); - - // Check if table cells still have offsetWidth/Height when they are set - // to display:none and there are still other visible table cells in a - // table row; if so, offsetWidth/Height are not reliable for use when - // determining if an element has been hidden directly using - // display:none (it is still safe to use offsets if a parent element is - // hidden; don safety goggles and see bug #4512 for more information). - // (only IE 8 fails this test) - jQuery.support.reliableHiddenOffsets = tds[0].offsetHeight === 0; - - tds[0].style.display = ""; - tds[1].style.display = "none"; - - // Check if empty table cells still have offsetWidth/Height - // (IE < 8 fail this test) - jQuery.support.reliableHiddenOffsets = jQuery.support.reliableHiddenOffsets && tds[0].offsetHeight === 0; - div.innerHTML = ""; - - document.body.removeChild( div ).style.display = "none"; - div = tds = null; - }); + div.style.width = div.style.paddingLeft = "1px"; + + // We use our own, invisible, body + body = document.createElement( "body" ); + bodyStyle = { + visibility: "hidden", + width: 0, + height: 0, + border: 0, + margin: 0, + // Set background to avoid IE crashes when removing (#9028) + background: "none" + }; + for ( i in bodyStyle ) { + body.style[ i ] = bodyStyle[ i ]; + } + body.appendChild( div ); + documentElement.insertBefore( body, documentElement.firstChild ); + + // Check if a disconnected checkbox will retain its checked + // value of true after appended to the DOM (IE6/7) + support.appendChecked = input.checked; + + support.boxModel = div.offsetWidth === 2; + + if ( "zoom" in div.style ) { + // Check if natively block-level elements act like inline-block + // elements when setting their display to 'inline' and giving + // them layout + // (IE < 8 does this) + div.style.display = "inline"; + div.style.zoom = 1; + support.inlineBlockNeedsLayout = ( div.offsetWidth === 2 ); + + // Check if elements with layout shrink-wrap their children + // (IE 6 does this) + div.style.display = ""; + div.innerHTML = "<div style='width:4px;'></div>"; + support.shrinkWrapBlocks = ( div.offsetWidth !== 2 ); + } + + div.innerHTML = "<table><tr><td style='padding:0;border:0;display:none'></td><td>t</td></tr></table>"; + tds = div.getElementsByTagName( "td" ); + + // Check if table cells still have offsetWidth/Height when they are set + // to display:none and there are still other visible table cells in a + // table row; if so, offsetWidth/Height are not reliable for use when + // determining if an element has been hidden directly using + // display:none (it is still safe to use offsets if a parent element is + // hidden; don safety goggles and see bug #4512 for more information). + // (only IE 8 fails this test) + isSupported = ( tds[ 0 ].offsetHeight === 0 ); + + tds[ 0 ].style.display = ""; + tds[ 1 ].style.display = "none"; + + // Check if empty table cells still have offsetWidth/Height + // (IE < 8 fail this test) + support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 ); + div.innerHTML = ""; + + // Check if div with explicit width and no margin-right incorrectly + // gets computed margin-right based on width of container. For more + // info see bug #3333 + // Fails in WebKit before Feb 2011 nightlies + // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right + if ( document.defaultView && document.defaultView.getComputedStyle ) { + marginDiv = document.createElement( "div" ); + marginDiv.style.width = "0"; + marginDiv.style.marginRight = "0"; + div.appendChild( marginDiv ); + support.reliableMarginRight = + ( parseInt( ( document.defaultView.getComputedStyle( marginDiv, null ) || { marginRight: 0 } ).marginRight, 10 ) || 0 ) === 0; + } + + // Remove the body element we added + body.innerHTML = ""; + documentElement.removeChild( body ); // Technique from Juriy Zaytsev // http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/ - var eventSupported = function( eventName ) { - var el = document.createElement("div"); - eventName = "on" + eventName; - - var isSupported = (eventName in el); - if ( !isSupported ) { - el.setAttribute(eventName, "return;"); - isSupported = typeof el[eventName] === "function"; + // We only care about the case where non-standard event systems + // are used, namely in IE. Short-circuiting here helps us to + // avoid an eval call (in setAttribute) which can cause CSP + // to go haywire. See: https://developer.mozilla.org/en/Security/CSP + if ( div.attachEvent ) { + for( i in { + submit: 1, + change: 1, + focusin: 1 + } ) { + eventName = "on" + i; + isSupported = ( eventName in div ); + if ( !isSupported ) { + div.setAttribute( eventName, "return;" ); + isSupported = ( typeof div[ eventName ] === "function" ); + } + support[ i + "Bubbles" ] = isSupported; } - el = null; + } - return isSupported; - }; + return support; +})(); - jQuery.support.submitBubbles = eventSupported("submit"); - jQuery.support.changeBubbles = eventSupported("change"); +// Keep track of boxModel +jQuery.boxModel = jQuery.support.boxModel; - // release memory in IE - root = script = div = all = a = null; -})(); -var windowData = {}, - rbrace = /^(?:\{.*\}|\[.*\])$/; +var rbrace = /^(?:\{.*\}|\[.*\])$/, + rmultiDash = /([a-z])([A-Z])/g; jQuery.extend({ cache: {}, @@ -1115,8 +1387,9 @@ jQuery.extend({ // Please use with caution uuid: 0, - // Unique for each copy of jQuery on the page - expando: "jQuery" + jQuery.now(), + // Unique for each copy of jQuery on the page + // Non-digits removed to match rinlinejQuery + expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ), // The following elements throw uncatchable exceptions if you // attempt to add expando properties to them. @@ -1127,103 +1400,185 @@ jQuery.extend({ "applet": true }, - data: function( elem, name, data ) { + hasData: function( elem ) { + elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ]; + + return !!elem && !isEmptyDataObject( elem ); + }, + + data: function( elem, name, data, pvt /* Internal Use Only */ ) { if ( !jQuery.acceptData( elem ) ) { return; } - elem = elem == window ? - windowData : - elem; + var internalKey = jQuery.expando, getByName = typeof name === "string", thisCache, - var isNode = elem.nodeType, - id = isNode ? elem[ jQuery.expando ] : null, - cache = jQuery.cache, thisCache; + // We have to handle DOM nodes and JS objects differently because IE6-7 + // can't GC object references properly across the DOM-JS boundary + isNode = elem.nodeType, - if ( isNode && !id && typeof name === "string" && data === undefined ) { - return; - } + // Only DOM nodes need the global jQuery cache; JS object data is + // attached directly to the object so GC can occur automatically + cache = isNode ? jQuery.cache : elem, - // Get the data from the object directly - if ( !isNode ) { - cache = elem; + // Only defining an ID for JS objects if its cache already exists allows + // the code to shortcut on the same path as a DOM node with no cache + id = isNode ? elem[ jQuery.expando ] : elem[ jQuery.expando ] && jQuery.expando; - // Compute a unique ID for the element - } else if ( !id ) { - elem[ jQuery.expando ] = id = ++jQuery.uuid; + // Avoid doing any more work than we need to when trying to get data on an + // object that has no data at all + if ( (!id || (pvt && id && !cache[ id ][ internalKey ])) && getByName && data === undefined ) { + return; } - // Avoid generating a new cache unless none exists and we - // want to manipulate it. - if ( typeof name === "object" ) { + if ( !id ) { + // Only DOM nodes need a new unique ID for each element since their data + // ends up in the global cache if ( isNode ) { - cache[ id ] = jQuery.extend(cache[ id ], name); - + elem[ jQuery.expando ] = id = ++jQuery.uuid; } else { - jQuery.extend( cache, name ); + id = jQuery.expando; } + } - } else if ( isNode && !cache[ id ] ) { + if ( !cache[ id ] ) { cache[ id ] = {}; + + // TODO: This is a hack for 1.5 ONLY. Avoids exposing jQuery + // metadata on plain JS objects when the object is serialized using + // JSON.stringify + if ( !isNode ) { + cache[ id ].toJSON = jQuery.noop; + } + } + + // An object can be passed to jQuery.data instead of a key/value pair; this gets + // shallow copied over onto the existing cache + if ( typeof name === "object" || typeof name === "function" ) { + if ( pvt ) { + cache[ id ][ internalKey ] = jQuery.extend(cache[ id ][ internalKey ], name); + } else { + cache[ id ] = jQuery.extend(cache[ id ], name); + } } - thisCache = isNode ? cache[ id ] : cache; + thisCache = cache[ id ]; + + // Internal jQuery data is stored in a separate object inside the object's data + // cache in order to avoid key collisions between internal data and user-defined + // data + if ( pvt ) { + if ( !thisCache[ internalKey ] ) { + thisCache[ internalKey ] = {}; + } + + thisCache = thisCache[ internalKey ]; + } - // Prevent overriding the named cache with undefined values if ( data !== undefined ) { - thisCache[ name ] = data; + thisCache[ jQuery.camelCase( name ) ] = data; + } + + // TODO: This is a hack for 1.5 ONLY. It will be removed in 1.6. Users should + // not attempt to inspect the internal events object using jQuery.data, as this + // internal data object is undocumented and subject to change. + if ( name === "events" && !thisCache[name] ) { + return thisCache[ internalKey ] && thisCache[ internalKey ].events; } - return typeof name === "string" ? thisCache[ name ] : thisCache; + return getByName ? thisCache[ jQuery.camelCase( name ) ] : thisCache; }, - removeData: function( elem, name ) { + removeData: function( elem, name, pvt /* Internal Use Only */ ) { if ( !jQuery.acceptData( elem ) ) { return; } - elem = elem == window ? - windowData : - elem; + var internalKey = jQuery.expando, isNode = elem.nodeType, + + // See jQuery.data for more information + cache = isNode ? jQuery.cache : elem, - var isNode = elem.nodeType, - id = isNode ? elem[ jQuery.expando ] : elem, - cache = jQuery.cache, - thisCache = isNode ? cache[ id ] : id; + // See jQuery.data for more information + id = isNode ? elem[ jQuery.expando ] : jQuery.expando; + + // If there is already no cache entry for this object, there is no + // purpose in continuing + if ( !cache[ id ] ) { + return; + } - // If we want to remove a specific section of the element's data if ( name ) { + var thisCache = pvt ? cache[ id ][ internalKey ] : cache[ id ]; + if ( thisCache ) { - // Remove the section of cache data delete thisCache[ name ]; - // If we've removed all the data, remove the element's cache - if ( isNode && jQuery.isEmptyObject(thisCache) ) { - jQuery.removeData( elem ); + // If there is no data left in the cache, we want to continue + // and let the cache object itself get destroyed + if ( !isEmptyDataObject(thisCache) ) { + return; } } + } + + // See jQuery.data for more information + if ( pvt ) { + delete cache[ id ][ internalKey ]; + + // Don't destroy the parent cache unless the internal data object + // had been the only thing left in it + if ( !isEmptyDataObject(cache[ id ]) ) { + return; + } + } - // Otherwise, we want to remove all of the element's data + var internalCache = cache[ id ][ internalKey ]; + + // Browsers that fail expando deletion also refuse to delete expandos on + // the window, but it will allow it on all other JS objects; other browsers + // don't care + if ( jQuery.support.deleteExpando || cache != window ) { + delete cache[ id ]; } else { - if ( isNode && jQuery.support.deleteExpando ) { - delete elem[ jQuery.expando ]; + cache[ id ] = null; + } + // We destroyed the entire user cache at once because it's faster than + // iterating through each key, but we need to continue to persist internal + // data if it existed + if ( internalCache ) { + cache[ id ] = {}; + // TODO: This is a hack for 1.5 ONLY. Avoids exposing jQuery + // metadata on plain JS objects when the object is serialized using + // JSON.stringify + if ( !isNode ) { + cache[ id ].toJSON = jQuery.noop; + } + + cache[ id ][ internalKey ] = internalCache; + + // Otherwise, we need to eliminate the expando on the node to avoid + // false lookups in the cache for entries that no longer exist + } else if ( isNode ) { + // IE does not allow us to delete expando properties from nodes, + // nor does it have a removeAttribute function on Document nodes; + // we must handle all of these cases + if ( jQuery.support.deleteExpando ) { + delete elem[ jQuery.expando ]; } else if ( elem.removeAttribute ) { elem.removeAttribute( jQuery.expando ); - - // Completely remove the data cache - } else if ( isNode ) { - delete cache[ id ]; - - // Remove all fields from the object } else { - for ( var n in elem ) { - delete elem[ n ]; - } + elem[ jQuery.expando ] = null; } } }, + // For internal use only. + _data: function( elem, name, data ) { + return jQuery.data( elem, name, data, true ); + }, + // A method for determining if a DOM node can handle the data expando acceptData: function( elem ) { if ( elem.nodeName ) { @@ -1244,15 +1599,18 @@ jQuery.fn.extend({ if ( typeof key === "undefined" ) { if ( this.length ) { - var attr = this[0].attributes, name; data = jQuery.data( this[0] ); - for ( var i = 0, l = attr.length; i < l; i++ ) { - name = attr[i].name; + if ( this[0].nodeType === 1 ) { + var attr = this[0].attributes, name; + for ( var i = 0, l = attr.length; i < l; i++ ) { + name = attr[i].name; - if ( name.indexOf( "data-" ) === 0 ) { - name = name.substr( 5 ); - dataAttr( this[0], name, data[ name ] ); + if ( name.indexOf( "data-" ) === 0 ) { + name = jQuery.camelCase( name.substring(5) ); + + dataAttr( this[0], name, data[ name ] ); + } } } } @@ -1304,7 +1662,9 @@ function dataAttr( elem, key, data ) { // If nothing was found internally, try to fetch any // data from the HTML5 data-* attribute if ( data === undefined && elem.nodeType === 1 ) { - data = elem.getAttribute( "data-" + key ); + var name = "data-" + key.replace( rmultiDash, "$1-$2" ).toLowerCase(); + + data = elem.getAttribute( name ); if ( typeof data === "string" ) { try { @@ -1327,38 +1687,92 @@ function dataAttr( elem, key, data ) { return data; } - - - -jQuery.extend({ - queue: function( elem, type, data ) { - if ( !elem ) { - return; +// TODO: This is a hack for 1.5 ONLY to allow objects with a single toJSON +// property to be considered empty objects; this property always exists in +// order to make sure JSON.stringify does not expose internal metadata +function isEmptyDataObject( obj ) { + for ( var name in obj ) { + if ( name !== "toJSON" ) { + return false; } + } - type = (type || "fx") + "queue"; - var q = jQuery.data( elem, type ); + return true; +} - // Speed up dequeue by getting out quickly if this is just a lookup - if ( !data ) { - return q || []; - } - if ( !q || jQuery.isArray(data) ) { - q = jQuery.data( elem, type, jQuery.makeArray(data) ); - } else { - q.push( data ); + +function handleQueueMarkDefer( elem, type, src ) { + var deferDataKey = type + "defer", + queueDataKey = type + "queue", + markDataKey = type + "mark", + defer = jQuery.data( elem, deferDataKey, undefined, true ); + if ( defer && + ( src === "queue" || !jQuery.data( elem, queueDataKey, undefined, true ) ) && + ( src === "mark" || !jQuery.data( elem, markDataKey, undefined, true ) ) ) { + // Give room for hard-coded callbacks to fire first + // and eventually mark/queue something else on the element + setTimeout( function() { + if ( !jQuery.data( elem, queueDataKey, undefined, true ) && + !jQuery.data( elem, markDataKey, undefined, true ) ) { + jQuery.removeData( elem, deferDataKey, true ); + defer.resolve(); + } + }, 0 ); + } +} + +jQuery.extend({ + + _mark: function( elem, type ) { + if ( elem ) { + type = (type || "fx") + "mark"; + jQuery.data( elem, type, (jQuery.data(elem,type,undefined,true) || 0) + 1, true ); } + }, - return q; + _unmark: function( force, elem, type ) { + if ( force !== true ) { + type = elem; + elem = force; + force = false; + } + if ( elem ) { + type = type || "fx"; + var key = type + "mark", + count = force ? 0 : ( (jQuery.data( elem, key, undefined, true) || 1 ) - 1 ); + if ( count ) { + jQuery.data( elem, key, count, true ); + } else { + jQuery.removeData( elem, key, true ); + handleQueueMarkDefer( elem, type, "mark" ); + } + } + }, + + queue: function( elem, type, data ) { + if ( elem ) { + type = (type || "fx") + "queue"; + var q = jQuery.data( elem, type, undefined, true ); + // Speed up dequeue by getting out quickly if this is just a lookup + if ( data ) { + if ( !q || jQuery.isArray(data) ) { + q = jQuery.data( elem, type, jQuery.makeArray(data), true ); + } else { + q.push( data ); + } + } + return q || []; + } }, dequeue: function( elem, type ) { type = type || "fx"; var queue = jQuery.queue( elem, type ), - fn = queue.shift(); + fn = queue.shift(), + defer; // If the fx queue is dequeued, always remove the progress sentinel if ( fn === "inprogress" ) { @@ -1376,6 +1790,11 @@ jQuery.extend({ jQuery.dequeue(elem, type); }); } + + if ( !queue.length ) { + jQuery.removeData( elem, type + "queue", true ); + handleQueueMarkDefer( elem, type, "queue" ); + } } }); @@ -1389,7 +1808,7 @@ jQuery.fn.extend({ if ( data === undefined ) { return jQuery.queue( this[0], type ); } - return this.each(function( i ) { + return this.each(function() { var queue = jQuery.queue( this, type, data ); if ( type === "fx" && queue[0] !== "inprogress" ) { @@ -1402,7 +1821,6 @@ jQuery.fn.extend({ jQuery.dequeue( this, type ); }); }, - // Based off of the plugin by Clint Helfers, with permission. // http://blindsignals.com/index.php/2009/07/jquery-delay/ delay: function( time, type ) { @@ -1416,61 +1834,93 @@ jQuery.fn.extend({ }, time ); }); }, - clearQueue: function( type ) { return this.queue( type || "fx", [] ); + }, + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function( type, object ) { + if ( typeof type !== "string" ) { + object = type; + type = undefined; + } + type = type || "fx"; + var defer = jQuery.Deferred(), + elements = this, + i = elements.length, + count = 1, + deferDataKey = type + "defer", + queueDataKey = type + "queue", + markDataKey = type + "mark", + tmp; + function resolve() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + } + while( i-- ) { + if (( tmp = jQuery.data( elements[ i ], deferDataKey, undefined, true ) || + ( jQuery.data( elements[ i ], queueDataKey, undefined, true ) || + jQuery.data( elements[ i ], markDataKey, undefined, true ) ) && + jQuery.data( elements[ i ], deferDataKey, jQuery._Deferred(), true ) )) { + count++; + tmp.done( resolve ); + } + } + resolve(); + return defer.promise(); } }); -var rclass = /[\n\t]/g, - rspaces = /\s+/, +var rclass = /[\n\t\r]/g, + rspace = /\s+/, rreturn = /\r/g, - rspecialurl = /^(?:href|src|style)$/, rtype = /^(?:button|input)$/i, rfocusable = /^(?:button|input|object|select|textarea)$/i, rclickable = /^a(?:rea)?$/i, - rradiocheck = /^(?:radio|checkbox)$/i; - -jQuery.props = { - "for": "htmlFor", - "class": "className", - readonly: "readOnly", - maxlength: "maxLength", - cellspacing: "cellSpacing", - rowspan: "rowSpan", - colspan: "colSpan", - tabindex: "tabIndex", - usemap: "useMap", - frameborder: "frameBorder" -}; + rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i, + rinvalidChar = /\:/, + formHook, boolHook; jQuery.fn.extend({ attr: function( name, value ) { return jQuery.access( this, name, value, true, jQuery.attr ); }, - removeAttr: function( name, fn ) { - return this.each(function(){ - jQuery.attr( this, name, "" ); - if ( this.nodeType === 1 ) { - this.removeAttribute( name ); - } + removeAttr: function( name ) { + return this.each(function() { + jQuery.removeAttr( this, name ); + }); + }, + + prop: function( name, value ) { + return jQuery.access( this, name, value, true, jQuery.prop ); + }, + + removeProp: function( name ) { + name = jQuery.propFix[ name ] || name; + return this.each(function() { + // try/catch handles cases where IE balks (such as removing a property on window) + try { + this[ name ] = undefined; + delete this[ name ]; + } catch( e ) {} }); }, addClass: function( value ) { - if ( jQuery.isFunction(value) ) { + if ( jQuery.isFunction( value ) ) { return this.each(function(i) { var self = jQuery(this); - self.addClass( value.call(this, i, self.attr("class")) ); + self.addClass( value.call(this, i, self.attr("class") || "") ); }); } if ( value && typeof value === "string" ) { - var classNames = (value || "").split( rspaces ); + var classNames = (value || "").split( rspace ); for ( var i = 0, l = this.length; i < l; i++ ) { var elem = this[i]; @@ -1506,7 +1956,7 @@ jQuery.fn.extend({ } if ( (value && typeof value === "string") || value === undefined ) { - var classNames = (value || "").split( rspaces ); + var classNames = (value || "").split( rspace ); for ( var i = 0, l = this.length; i < l; i++ ) { var elem = this[i]; @@ -1547,7 +1997,7 @@ jQuery.fn.extend({ i = 0, self = jQuery( this ), state = stateVal, - classNames = value.split( rspaces ); + classNames = value.split( rspace ); while ( (className = classNames[ i++ ]) ) { // check each className given, space seperated list @@ -1558,11 +2008,11 @@ jQuery.fn.extend({ } else if ( type === "undefined" || type === "boolean" ) { if ( this.className ) { // store className if set - jQuery.data( this, "__className__", this.className ); + jQuery._data( this, "__className__", this.className ); } // toggle whole className - this.className = this.className || value === false ? "" : jQuery.data( this, "__className__" ) || ""; + this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || ""; } }); }, @@ -1579,78 +2029,36 @@ jQuery.fn.extend({ }, val: function( value ) { + var hooks, ret, + elem = this[0]; + if ( !arguments.length ) { - var elem = this[0]; - if ( elem ) { - if ( jQuery.nodeName( elem, "option" ) ) { - // attributes.value is undefined in Blackberry 4.7 but - // uses .value. See #6932 - var val = elem.attributes.value; - return !val || val.specified ? elem.value : elem.text; - } - - // We need to handle select boxes special - if ( jQuery.nodeName( elem, "select" ) ) { - var index = elem.selectedIndex, - values = [], - options = elem.options, - one = elem.type === "select-one"; - - // Nothing was selected - if ( index < 0 ) { - return null; - } - - // Loop through all the selected options - for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) { - var option = options[ i ]; - - // Don't return options that are disabled or in a disabled optgroup - if ( option.selected && (jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) && - (!option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" )) ) { - - // Get the specific value for the option - value = jQuery(option).val(); + hooks = jQuery.valHooks[ elem.nodeName.toLowerCase() ] || jQuery.valHooks[ elem.type ]; - // We don't need an array for one selects - if ( one ) { - return value; - } - - // Multi-Selects return an array - values.push( value ); - } - } - - return values; + if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) { + return ret; } - // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified - if ( rradiocheck.test( elem.type ) && !jQuery.support.checkOn ) { - return elem.getAttribute("value") === null ? "on" : elem.value; - } - - - // Everything else, we just grab the value return (elem.value || "").replace(rreturn, ""); - } return undefined; } - var isFunction = jQuery.isFunction(value); + var isFunction = jQuery.isFunction( value ); - return this.each(function(i) { - var self = jQuery(this), val = value; + return this.each(function( i ) { + var self = jQuery(this), val; if ( this.nodeType !== 1 ) { return; } if ( isFunction ) { - val = value.call(this, i, self.val()); + val = value.call( this, i, self.val() ); + } else { + val = value; } // Treat null/undefined as ""; convert numbers to string @@ -1658,34 +2066,89 @@ jQuery.fn.extend({ val = ""; } else if ( typeof val === "number" ) { val += ""; - } else if ( jQuery.isArray(val) ) { - val = jQuery.map(val, function (value) { + } else if ( jQuery.isArray( val ) ) { + val = jQuery.map(val, function ( value ) { return value == null ? "" : value + ""; }); } - if ( jQuery.isArray(val) && rradiocheck.test( this.type ) ) { - this.checked = jQuery.inArray( self.val(), val ) >= 0; + hooks = jQuery.valHooks[ this.nodeName.toLowerCase() ] || jQuery.valHooks[ this.type ]; + + // If set returns undefined, fall back to normal setting + if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) { + this.value = val; + } + }); + } +}); + +jQuery.extend({ + valHooks: { + option: { + get: function( elem ) { + // attributes.value is undefined in Blackberry 4.7 but + // uses .value. See #6932 + var val = elem.attributes.value; + return !val || val.specified ? elem.value : elem.text; + } + }, + select: { + get: function( elem ) { + var value, + index = elem.selectedIndex, + values = [], + options = elem.options, + one = elem.type === "select-one"; + + // Nothing was selected + if ( index < 0 ) { + return null; + } - } else if ( jQuery.nodeName( this, "select" ) ) { - var values = jQuery.makeArray(val); + // Loop through all the selected options + for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) { + var option = options[ i ]; - jQuery( "option", this ).each(function() { + // Don't return options that are disabled or in a disabled optgroup + if ( option.selected && (jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) && + (!option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" )) ) { + + // Get the specific value for the option + value = jQuery( option ).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } + } + + // Fixes Bug #2551 -- select.val() broken in IE after form.reset() + if ( one && !values.length && options.length ) { + return jQuery( options[ index ] ).val(); + } + + return values; + }, + + set: function( elem, value ) { + var values = jQuery.makeArray( value ); + + jQuery(elem).find("option").each(function() { this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0; }); if ( !values.length ) { - this.selectedIndex = -1; + elem.selectedIndex = -1; } - - } else { - this.value = val; + return values; } - }); - } -}); + } + }, -jQuery.extend({ attrFn: { val: true, css: true, @@ -1696,121 +2159,349 @@ jQuery.extend({ height: true, offset: true }, - + + attrFix: { + // Always normalize to ensure hook usage + tabindex: "tabIndex" + }, + attr: function( elem, name, value, pass ) { - // don't set attributes on text and comment nodes - if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 ) { + var nType = elem.nodeType; + + // don't get/set attributes on text, comment and attribute nodes + if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { return undefined; } if ( pass && name in jQuery.attrFn ) { - return jQuery(elem)[name](value); + return jQuery( elem )[ name ]( value ); } - var notxml = elem.nodeType !== 1 || !jQuery.isXMLDoc( elem ), - // Whether we are setting (or getting) - set = value !== undefined; + // Fallback to prop when attributes are not supported + if ( !("getAttribute" in elem) ) { + return jQuery.prop( elem, name, value ); + } - // Try to normalize/fix the name - name = notxml && jQuery.props[ name ] || name; + var ret, hooks, + notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); - // These attributes require special treatment - var special = rspecialurl.test( name ); + // Normalize the name if needed + name = notxml && jQuery.attrFix[ name ] || name; - // Safari mis-reports the default selected property of an option - // Accessing the parent's selectedIndex property fixes it - if ( name === "selected" && !jQuery.support.optSelected ) { - var parent = elem.parentNode; - if ( parent ) { - parent.selectedIndex; + hooks = jQuery.attrHooks[ name ]; - // Make sure that it also works with optgroups, see #5701 - if ( parent.parentNode ) { - parent.parentNode.selectedIndex; - } + if ( !hooks ) { + // Use boolHook for boolean attributes + if ( rboolean.test( name ) && + (typeof value === "boolean" || value === undefined || value.toLowerCase() === name.toLowerCase()) ) { + + hooks = boolHook; + + // Use formHook for forms and if the name contains certain characters + } else if ( formHook && (jQuery.nodeName( elem, "form" ) || rinvalidChar.test( name )) ) { + hooks = formHook; } } - // If applicable, access the attribute via the DOM 0 way - // 'in' checks fail in Blackberry 4.7 #6931 - if ( (name in elem || elem[ name ] !== undefined) && notxml && !special ) { - if ( set ) { - // We can't allow the type property to be changed (since it causes problems in IE) - if ( name === "type" && rtype.test( elem.nodeName ) && elem.parentNode ) { - jQuery.error( "type property can't be changed" ); - } + if ( value !== undefined ) { - if ( value === null ) { - if ( elem.nodeType === 1 ) { - elem.removeAttribute( name ); - } + if ( value === null ) { + jQuery.removeAttr( elem, name ); + return undefined; - } else { - elem[ name ] = value; - } + } else if ( hooks && "set" in hooks && notxml && (ret = hooks.set( elem, value, name )) !== undefined ) { + return ret; + + } else { + elem.setAttribute( name, "" + value ); + return value; } - // browsers index elements by id/name on forms, give priority to attributes. - if ( jQuery.nodeName( elem, "form" ) && elem.getAttributeNode(name) ) { - return elem.getAttributeNode( name ).nodeValue; + } else if ( hooks && "get" in hooks && notxml ) { + return hooks.get( elem, name ); + + } else { + + ret = elem.getAttribute( name ); + + // Non-existent attributes return null, we normalize to undefined + return ret === null ? + undefined : + ret; + } + }, + + removeAttr: function( elem, name ) { + var propName; + if ( elem.nodeType === 1 ) { + name = jQuery.attrFix[ name ] || name; + + if ( jQuery.support.getSetAttribute ) { + // Use removeAttribute in browsers that support it + elem.removeAttribute( name ); + } else { + jQuery.attr( elem, name, "" ); + elem.removeAttributeNode( elem.getAttributeNode( name ) ); } - // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set - // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ - if ( name === "tabIndex" ) { - var attributeNode = elem.getAttributeNode( "tabIndex" ); + // Set corresponding property to false for boolean attributes + if ( rboolean.test( name ) && (propName = jQuery.propFix[ name ] || name) in elem ) { + elem[ propName ] = false; + } + } + }, + + attrHooks: { + type: { + set: function( elem, value ) { + // We can't allow the type property to be changed (since it causes problems in IE) + if ( rtype.test( elem.nodeName ) && elem.parentNode ) { + jQuery.error( "type property can't be changed" ); + } else if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) { + // Setting the type on a radio button after the value resets the value in IE6-9 + // Reset value to it's default in case type is set after value + // This is for element creation + var val = elem.value; + elem.setAttribute( "type", value ); + if ( val ) { + elem.value = val; + } + return value; + } + } + }, + tabIndex: { + get: function( elem ) { + // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set + // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + var attributeNode = elem.getAttributeNode("tabIndex"); return attributeNode && attributeNode.specified ? - attributeNode.value : + parseInt( attributeNode.value, 10 ) : rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? 0 : undefined; } + } + }, - return elem[ name ]; + propFix: { + tabindex: "tabIndex", + readonly: "readOnly", + "for": "htmlFor", + "class": "className", + maxlength: "maxLength", + cellspacing: "cellSpacing", + cellpadding: "cellPadding", + rowspan: "rowSpan", + colspan: "colSpan", + usemap: "useMap", + frameborder: "frameBorder", + contenteditable: "contentEditable" + }, + + prop: function( elem, name, value ) { + var nType = elem.nodeType; + + // don't get/set properties on text, comment and attribute nodes + if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { + return undefined; } - if ( !jQuery.support.style && notxml && name === "style" ) { - if ( set ) { - elem.style.cssText = "" + value; + var ret, hooks, + notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); + + // Try to normalize/fix the name + name = notxml && jQuery.propFix[ name ] || name; + + hooks = jQuery.propHooks[ name ]; + + if ( value !== undefined ) { + if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) { + return ret; + + } else { + return (elem[ name ] = value); } - return elem.style.cssText; + } else { + if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== undefined ) { + return ret; + + } else { + return elem[ name ]; + } } + }, + + propHooks: {} +}); - if ( set ) { - // convert the value to a string (all browsers do this but IE) see #1070 - elem.setAttribute( name, "" + value ); +// Hook for boolean attributes +boolHook = { + get: function( elem, name ) { + // Align boolean attributes with corresponding properties + return elem[ jQuery.propFix[ name ] || name ] ? + name.toLowerCase() : + undefined; + }, + set: function( elem, value, name ) { + var propName; + if ( value === false ) { + // Remove boolean attributes when set to false + jQuery.removeAttr( elem, name ); + } else { + // value is true since we know at this point it's type boolean and not false + // Set boolean attributes to the same name and set the DOM property + propName = jQuery.propFix[ name ] || name; + if ( propName in elem ) { + // Only set the IDL specifically if it already exists on the element + elem[ propName ] = value; + } + + elem.setAttribute( name, name.toLowerCase() ); } + return name; + } +}; - // Ensure that missing attributes return undefined - // Blackberry 4.7 returns "" from getAttribute #6938 - if ( !elem.attributes[ name ] && (elem.hasAttribute && !elem.hasAttribute( name )) ) { - return undefined; +// Use the value property for back compat +// Use the formHook for button elements in IE6/7 (#1954) +jQuery.attrHooks.value = { + get: function( elem, name ) { + if ( formHook && jQuery.nodeName( elem, "button" ) ) { + return formHook.get( elem, name ); + } + return elem.value; + }, + set: function( elem, value, name ) { + if ( formHook && jQuery.nodeName( elem, "button" ) ) { + return formHook.set( elem, value, name ); } + // Does not return so that setAttribute is also used + elem.value = value; + } +}; - var attr = !jQuery.support.hrefNormalized && notxml && special ? - // Some attributes require a special call on IE - elem.getAttribute( name, 2 ) : - elem.getAttribute( name ); +// IE6/7 do not support getting/setting some attributes with get/setAttribute +if ( !jQuery.support.getSetAttribute ) { - // Non-existent attributes return null, we normalize to undefined - return attr === null ? undefined : attr; - } + // propFix is more comprehensive and contains all fixes + jQuery.attrFix = jQuery.propFix; + + // Use this for any attribute on a form in IE6/7 + formHook = jQuery.attrHooks.name = jQuery.valHooks.button = { + get: function( elem, name ) { + var ret; + ret = elem.getAttributeNode( name ); + // Return undefined if nodeValue is empty string + return ret && ret.nodeValue !== "" ? + ret.nodeValue : + undefined; + }, + set: function( elem, value, name ) { + // Check form objects in IE (multiple bugs related) + // Only use nodeValue if the attribute node exists on the form + var ret = elem.getAttributeNode( name ); + if ( ret ) { + ret.nodeValue = value; + return value; + } + } + }; + + // Set width and height to auto instead of 0 on empty string( Bug #8150 ) + // This is for removals + jQuery.each([ "width", "height" ], function( i, name ) { + jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], { + set: function( elem, value ) { + if ( value === "" ) { + elem.setAttribute( name, "auto" ); + return value; + } + } + }); + }); +} + + +// Some attributes require a special call on IE +if ( !jQuery.support.hrefNormalized ) { + jQuery.each([ "href", "src", "width", "height" ], function( i, name ) { + jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], { + get: function( elem ) { + var ret = elem.getAttribute( name, 2 ); + return ret === null ? undefined : ret; + } + }); + }); +} + +if ( !jQuery.support.style ) { + jQuery.attrHooks.style = { + get: function( elem ) { + // Return undefined in the case of empty string + // Normalize to lowercase since IE uppercases css property names + return elem.style.cssText.toLowerCase() || undefined; + }, + set: function( elem, value ) { + return (elem.style.cssText = "" + value); + } + }; +} + +// Safari mis-reports the default selected property of an option +// Accessing the parent's selectedIndex property fixes it +if ( !jQuery.support.optSelected ) { + jQuery.propHooks.selected = jQuery.extend( jQuery.propHooks.selected, { + get: function( elem ) { + var parent = elem.parentNode; + + if ( parent ) { + parent.selectedIndex; + + // Make sure that it also works with optgroups, see #5701 + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + } + }); +} + +// Radios and checkboxes getter/setter +if ( !jQuery.support.checkOn ) { + jQuery.each([ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = { + get: function( elem ) { + // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified + return elem.getAttribute("value") === null ? "on" : elem.value; + } + }; + }); +} +jQuery.each([ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = jQuery.extend( jQuery.valHooks[ this ], { + set: function( elem, value ) { + if ( jQuery.isArray( value ) ) { + return (elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0); + } + } + }); }); -var rnamespaces = /\.(.*)$/, +var hasOwn = Object.prototype.hasOwnProperty, + rnamespaces = /\.(.*)$/, rformElems = /^(?:textarea|input|select)$/i, rperiod = /\./g, - rspace = / /g, + rspaces = / /g, rescape = /[^\w\s.|`]/g, fcleanup = function( nm ) { return nm.replace(rescape, "\\$&"); - }, - focusCounts = { focusin: 0, focusout: 0 }; + }; /* * A number of helper functions used for managing events. @@ -1826,17 +2517,11 @@ jQuery.event = { return; } - // For whatever reason, IE has trouble passing the window object - // around, causing it to be cloned in the process - if ( jQuery.isWindow( elem ) && ( elem !== window && !elem.frameElement ) ) { - elem = window; - } - if ( handler === false ) { handler = returnFalse; } else if ( !handler ) { // Fixes bug #7229. Fix recommended by jdalton - return; + return; } var handleObjIn, handleObj; @@ -1852,7 +2537,7 @@ jQuery.event = { } // Init the element's event structure - var elemData = jQuery.data( elem ); + var elemData = jQuery._data( elem ); // If no elemData is found then we must be trying to bind to one of the // banned noData elements @@ -1860,34 +2545,18 @@ jQuery.event = { return; } - // Use a key less likely to result in collisions for plain JS objects. - // Fixes bug #7150. - var eventKey = elem.nodeType ? "events" : "__events__", - events = elemData[ eventKey ], + var events = elemData.events, eventHandle = elemData.handle; - - if ( typeof events === "function" ) { - // On plain objects events is a fn that holds the the data - // which prevents this data from being JSON serialized - // the function does not need to be called, it just contains the data - eventHandle = events.handle; - events = events.events; - - } else if ( !events ) { - if ( !elem.nodeType ) { - // On plain objects, create a fn that acts as the holder - // of the values to avoid JSON serialization of event data - elemData[ eventKey ] = elemData = function(){}; - } + if ( !events ) { elemData.events = events = {}; } if ( !eventHandle ) { - elemData.handle = eventHandle = function() { - // Handle the second event of a trigger and when - // an event is called after a page has unloaded - return typeof jQuery !== "undefined" && !jQuery.event.triggered ? + elemData.handle = eventHandle = function( e ) { + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ? jQuery.event.handle.apply( eventHandle.elem, arguments ) : undefined; }; @@ -1945,9 +2614,9 @@ jQuery.event = { } } } - - if ( special.add ) { - special.add.call( elem, handleObj ); + + if ( special.add ) { + special.add.call( elem, handleObj ); if ( !handleObj.handler.guid ) { handleObj.handler.guid = handler.guid; @@ -1957,7 +2626,7 @@ jQuery.event = { // Add the function to the element's handler list handlers.push( handleObj ); - // Keep track of which events have been used, for global triggering + // Keep track of which events have been used, for event optimization jQuery.event.global[ type ] = true; } @@ -1979,18 +2648,12 @@ jQuery.event = { } var ret, type, fn, j, i = 0, all, namespaces, namespace, special, eventType, handleObj, origType, - eventKey = elem.nodeType ? "events" : "__events__", - elemData = jQuery.data( elem ), - events = elemData && elemData[ eventKey ]; + elemData = jQuery.hasData( elem ) && jQuery._data( elem ), + events = elemData && elemData.events; if ( !elemData || !events ) { return; } - - if ( typeof events === "function" ) { - elemData = events; - events = events.events; - } // types is actually an event object here if ( types && types.type ) { @@ -2024,7 +2687,7 @@ jQuery.event = { namespaces = type.split("."); type = namespaces.shift(); - namespace = new RegExp("(^|\\.)" + + namespace = new RegExp("(^|\\.)" + jQuery.map( namespaces.slice(0).sort(), fcleanup ).join("\\.(?:.*\\.)?") + "(\\.|$)"); } @@ -2091,191 +2754,190 @@ jQuery.event = { delete elemData.events; delete elemData.handle; - if ( typeof elemData === "function" ) { - jQuery.removeData( elem, eventKey ); - - } else if ( jQuery.isEmptyObject( elemData ) ) { - jQuery.removeData( elem ); + if ( jQuery.isEmptyObject( elemData ) ) { + jQuery.removeData( elem, undefined, true ); } } }, + + // Events that are safe to short-circuit if no handlers are attached. + // Native DOM events should not be added, they may have inline handlers. + customEvent: { + "getData": true, + "setData": true, + "changeData": true + }, - // bubbling is internal - trigger: function( event, data, elem /*, bubbling */ ) { + trigger: function( event, data, elem, onlyHandlers ) { // Event object or event type var type = event.type || event, - bubbling = arguments[3]; + namespaces = [], + exclusive; - if ( !bubbling ) { - event = typeof event === "object" ? - // jQuery.Event object - event[ jQuery.expando ] ? event : - // Object literal - jQuery.extend( jQuery.Event(type), event ) : - // Just the event type (string) - jQuery.Event(type); + if ( type.indexOf("!") >= 0 ) { + // Exclusive events trigger only for the exact event (no namespaces) + type = type.slice(0, -1); + exclusive = true; + } - if ( type.indexOf("!") >= 0 ) { - event.type = type = type.slice(0, -1); - event.exclusive = true; - } + if ( type.indexOf(".") >= 0 ) { + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split("."); + type = namespaces.shift(); + namespaces.sort(); + } - // Handle a global trigger - if ( !elem ) { - // Don't bubble custom events when global (to avoid too much overhead) - event.stopPropagation(); - - // Only trigger if we've ever bound an event for it - if ( jQuery.event.global[ type ] ) { - jQuery.each( jQuery.cache, function() { - if ( this.events && this.events[type] ) { - jQuery.event.trigger( event, data, this.handle.elem ); - } - }); - } - } + if ( (!elem || jQuery.event.customEvent[ type ]) && !jQuery.event.global[ type ] ) { + // No jQuery handlers for this event type, and it can't have inline handlers + return; + } - // Handle triggering a single element + // Caller can pass in an Event, Object, or just an event type string + event = typeof event === "object" ? + // jQuery.Event object + event[ jQuery.expando ] ? event : + // Object literal + new jQuery.Event( type, event ) : + // Just the event type (string) + new jQuery.Event( type ); - // don't do events on text and comment nodes - if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 ) { - return undefined; - } + event.type = type; + event.exclusive = exclusive; + event.namespace = namespaces.join("."); + event.namespace_re = new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.)?") + "(\\.|$)"); + + // triggerHandler() and global events don't bubble or run the default action + if ( onlyHandlers || !elem ) { + event.preventDefault(); + event.stopPropagation(); + } - // Clean up in case it is reused - event.result = undefined; - event.target = elem; + // Handle a global trigger + if ( !elem ) { + // TODO: Stop taunting the data cache; remove global events and always attach to document + jQuery.each( jQuery.cache, function() { + // internalKey variable is just used to make it easier to find + // and potentially change this stuff later; currently it just + // points to jQuery.expando + var internalKey = jQuery.expando, + internalCache = this[ internalKey ]; + if ( internalCache && internalCache.events && internalCache.events[ type ] ) { + jQuery.event.trigger( event, data, internalCache.handle.elem ); + } + }); + return; + } - // Clone the incoming data, if any - data = jQuery.makeArray( data ); - data.unshift( event ); + // Don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; } - event.currentTarget = elem; + // Clean up the event in case it is being reused + event.result = undefined; + event.target = elem; - // Trigger the event, it is assumed that "handle" is a function - var handle = elem.nodeType ? - jQuery.data( elem, "handle" ) : - (jQuery.data( elem, "__events__" ) || {}).handle; + // Clone any incoming data and prepend the event, creating the handler arg list + data = data ? jQuery.makeArray( data ) : []; + data.unshift( event ); - if ( handle ) { - handle.apply( elem, data ); - } + var cur = elem, + // IE doesn't like method names with a colon (#3533, #8272) + ontype = type.indexOf(":") < 0 ? "on" + type : ""; - var parent = elem.parentNode || elem.ownerDocument; + // Fire event on the current element, then bubble up the DOM tree + do { + var handle = jQuery._data( cur, "handle" ); - // Trigger an inline bound script - try { - if ( !(elem && elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()]) ) { - if ( elem[ "on" + type ] && elem[ "on" + type ].apply( elem, data ) === false ) { - event.result = false; - event.preventDefault(); - } + event.currentTarget = cur; + if ( handle ) { + handle.apply( cur, data ); } - // prevent IE from throwing an error for some elements with some event types, see #3533 - } catch (inlineError) {} + // Trigger an inline bound script + if ( ontype && jQuery.acceptData( cur ) && cur[ ontype ] && cur[ ontype ].apply( cur, data ) === false ) { + event.result = false; + event.preventDefault(); + } - if ( !event.isPropagationStopped() && parent ) { - jQuery.event.trigger( event, data, parent, true ); + // Bubble up to document, then to window + cur = cur.parentNode || cur.ownerDocument || cur === event.target.ownerDocument && window; + } while ( cur && !event.isPropagationStopped() ); - } else if ( !event.isDefaultPrevented() ) { + // If nobody prevented the default action, do it now + if ( !event.isDefaultPrevented() ) { var old, - target = event.target, - targetType = type.replace( rnamespaces, "" ), - isClick = jQuery.nodeName( target, "a" ) && targetType === "click", - special = jQuery.event.special[ targetType ] || {}; + special = jQuery.event.special[ type ] || {}; - if ( (!special._default || special._default.call( elem, event ) === false) && - !isClick && !(target && target.nodeName && jQuery.noData[target.nodeName.toLowerCase()]) ) { + if ( (!special._default || special._default.call( elem.ownerDocument, event ) === false) && + !(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) { + // Call a native DOM method on the target with the same name name as the event. + // Can't use an .isFunction)() check here because IE6/7 fails that test. + // IE<9 dies on focus to hidden element (#1486), may want to revisit a try/catch. try { - if ( target[ targetType ] ) { - // Make sure that we don't accidentally re-trigger the onFOO events - old = target[ "on" + targetType ]; + if ( ontype && elem[ type ] ) { + // Don't re-trigger an onFOO event when we call its FOO() method + old = elem[ ontype ]; if ( old ) { - target[ "on" + targetType ] = null; + elem[ ontype ] = null; } - jQuery.event.triggered = true; - target[ targetType ](); + jQuery.event.triggered = type; + elem[ type ](); } - - // prevent IE from throwing an error for some elements with some event types, see #3533 - } catch (triggerError) {} + } catch ( ieError ) {} if ( old ) { - target[ "on" + targetType ] = old; + elem[ ontype ] = old; } - jQuery.event.triggered = false; + jQuery.event.triggered = undefined; } } + + return event.result; }, handle: function( event ) { - var all, handlers, namespaces, namespace_re, events, - namespace_sort = [], - args = jQuery.makeArray( arguments ); - - event = args[0] = jQuery.event.fix( event || window.event ); + event = jQuery.event.fix( event || window.event ); + // Snapshot the handlers list since a called handler may add/remove events. + var handlers = ((jQuery._data( this, "events" ) || {})[ event.type ] || []).slice(0), + run_all = !event.exclusive && !event.namespace, + args = Array.prototype.slice.call( arguments, 0 ); + + // Use the fix-ed Event rather than the (read-only) native event + args[0] = event; event.currentTarget = this; - // Namespaced event handlers - all = event.type.indexOf(".") < 0 && !event.exclusive; - - if ( !all ) { - namespaces = event.type.split("."); - event.type = namespaces.shift(); - namespace_sort = namespaces.slice(0).sort(); - namespace_re = new RegExp("(^|\\.)" + namespace_sort.join("\\.(?:.*\\.)?") + "(\\.|$)"); - } - - event.namespace = event.namespace || namespace_sort.join("."); - - events = jQuery.data(this, this.nodeType ? "events" : "__events__"); - - if ( typeof events === "function" ) { - events = events.events; - } - - handlers = (events || {})[ event.type ]; - - if ( events && handlers ) { - // Clone the handlers to prevent manipulation - handlers = handlers.slice(0); - - for ( var j = 0, l = handlers.length; j < l; j++ ) { - var handleObj = handlers[ j ]; - - // Filter the functions by class - if ( all || namespace_re.test( handleObj.namespace ) ) { - // Pass in a reference to the handler function itself - // So that we can later remove it - event.handler = handleObj.handler; - event.data = handleObj.data; - event.handleObj = handleObj; - - var oldHandle = event.handled, - ret = handleObj.handler.apply( this, args ); - event.handled = event.handled ===null || handleObj.handler === liveHandler ? oldHandle : true - - if ( ret !== undefined ) { - event.result = ret; - if ( ret === false ) { - event.preventDefault(); - event.stopPropagation(); - } + for ( var j = 0, l = handlers.length; j < l; j++ ) { + var handleObj = handlers[ j ]; + + // Triggered event must 1) be non-exclusive and have no namespace, or + // 2) have namespace(s) a subset or equal to those in the bound event. + if ( run_all || event.namespace_re.test( handleObj.namespace ) ) { + // Pass in a reference to the handler function itself + // So that we can later remove it + event.handler = handleObj.handler; + event.data = handleObj.data; + event.handleObj = handleObj; + + var ret = handleObj.handler.apply( this, args ); + + if ( ret !== undefined ) { + event.result = ret; + if ( ret === false ) { + event.preventDefault(); + event.stopPropagation(); } + } - if ( event.isImmediatePropagationStopped() ) { - break; - } + if ( event.isImmediatePropagationStopped() ) { + break; } } } - return event.result; }, @@ -2314,8 +2976,9 @@ jQuery.event = { // Calculate pageX/Y if missing and clientX/Y available if ( event.pageX == null && event.clientX != null ) { - var doc = document.documentElement, - body = document.body; + var eventDocument = event.target.ownerDocument || document, + doc = eventDocument.documentElement, + body = eventDocument.body; event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0); event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0); @@ -2357,7 +3020,7 @@ jQuery.event = { add: function( handleObj ) { jQuery.event.add( this, liveConvert( handleObj.origType, handleObj.selector ), - jQuery.extend({}, handleObj, {handler: liveHandler, guid: handleObj.handler.guid}) ); + jQuery.extend({}, handleObj, {handler: liveHandler, guid: handleObj.handler.guid}) ); }, remove: function( handleObj ) { @@ -2387,28 +3050,39 @@ jQuery.removeEvent = document.removeEventListener ? if ( elem.removeEventListener ) { elem.removeEventListener( type, handle, false ); } - } : + } : function( elem, type, handle ) { if ( elem.detachEvent ) { elem.detachEvent( "on" + type, handle ); } }; -jQuery.Event = function( src ) { +jQuery.Event = function( src, props ) { // Allow instantiation without the 'new' keyword if ( !this.preventDefault ) { - return new jQuery.Event( src ); + return new jQuery.Event( src, props ); } // Event object if ( src && src.type ) { this.originalEvent = src; this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = (src.defaultPrevented || src.returnValue === false || + src.getPreventDefault && src.getPreventDefault()) ? returnTrue : returnFalse; + // Event type } else { this.type = src; } + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + // timeStamp is buggy for some events on Firefox(#3843) // So we won't rely on the native value this.timeStamp = jQuery.now(); @@ -2434,7 +3108,7 @@ jQuery.Event.prototype = { if ( !e ) { return; } - + // if preventDefault exists run it on the original event if ( e.preventDefault ) { e.preventDefault(); @@ -2473,18 +3147,25 @@ var withinElement = function( event ) { // Check if mouse(over|out) are still within the same parent element var parent = event.relatedTarget; + // set the correct event type + event.type = event.data; + // Firefox sometimes assigns relatedTarget a XUL element // which we cannot access the parentNode property of try { + + // Chrome does something similar, the parentNode property + // can be accessed but is null. + if ( parent && parent !== document && !parent.parentNode ) { + return; + } + // Traverse up the tree while ( parent && parent !== this ) { parent = parent.parentNode; } if ( parent !== this ) { - // set the correct event type - event.type = event.data; - // handle event if we actually just moused on to a non sub-element jQuery.event.handle.apply( this, arguments ); } @@ -2520,24 +3201,22 @@ if ( !jQuery.support.submitBubbles ) { jQuery.event.special.submit = { setup: function( data, namespaces ) { - if ( this.nodeName.toLowerCase() !== "form" ) { + if ( !jQuery.nodeName( this, "form" ) ) { jQuery.event.add(this, "click.specialSubmit", function( e ) { var elem = e.target, type = elem.type; if ( (type === "submit" || type === "image") && jQuery( elem ).closest("form").length ) { - e.liveFired = undefined; - return trigger( "submit", this, arguments ); + trigger( "submit", this, arguments ); } }); - + jQuery.event.add(this, "keypress.specialSubmit", function( e ) { var elem = e.target, type = elem.type; if ( (type === "text" || type === "password") && jQuery( elem ).closest("form").length && e.keyCode === 13 ) { - e.liveFired = undefined; - return trigger( "submit", this, arguments ); + trigger( "submit", this, arguments ); } }); @@ -2571,7 +3250,7 @@ if ( !jQuery.support.changeBubbles ) { }).join("-") : ""; - } else if ( elem.nodeName.toLowerCase() === "select" ) { + } else if ( jQuery.nodeName( elem, "select" ) ) { val = elem.selectedIndex; } @@ -2585,14 +3264,14 @@ if ( !jQuery.support.changeBubbles ) { return; } - data = jQuery.data( elem, "_change_data" ); + data = jQuery._data( elem, "_change_data" ); val = getVal(elem); // the current data will be also retrieved by beforeactivate if ( e.type !== "focusout" || elem.type !== "radio" ) { - jQuery.data( elem, "_change_data", val ); + jQuery._data( elem, "_change_data", val ); } - + if ( data === undefined || val === data ) { return; } @@ -2600,33 +3279,33 @@ if ( !jQuery.support.changeBubbles ) { if ( data != null || val ) { e.type = "change"; e.liveFired = undefined; - return jQuery.event.trigger( e, arguments[1], elem ); + jQuery.event.trigger( e, arguments[1], elem ); } }; jQuery.event.special.change = { filters: { - focusout: testChange, + focusout: testChange, beforedeactivate: testChange, click: function( e ) { - var elem = e.target, type = elem.type; + var elem = e.target, type = jQuery.nodeName( elem, "input" ) ? elem.type : ""; - if ( type === "radio" || type === "checkbox" || elem.nodeName.toLowerCase() === "select" ) { - return testChange.call( this, e ); + if ( type === "radio" || type === "checkbox" || jQuery.nodeName( elem, "select" ) ) { + testChange.call( this, e ); } }, // Change has to be called before submit // Keydown will be called before keypress, which is used in submit-event delegation keydown: function( e ) { - var elem = e.target, type = elem.type; + var elem = e.target, type = jQuery.nodeName( elem, "input" ) ? elem.type : ""; - if ( (e.keyCode === 13 && elem.nodeName.toLowerCase() !== "textarea") || + if ( (e.keyCode === 13 && !jQuery.nodeName( elem, "textarea" ) ) || (e.keyCode === 32 && (type === "checkbox" || type === "radio")) || type === "select-multiple" ) { - return testChange.call( this, e ); + testChange.call( this, e ); } }, @@ -2635,7 +3314,7 @@ if ( !jQuery.support.changeBubbles ) { // information beforeactivate: function( e ) { var elem = e.target; - jQuery.data( elem, "_change_data", getVal(elem) ); + jQuery._data( elem, "_change_data", getVal(elem) ); } }, @@ -2665,36 +3344,58 @@ if ( !jQuery.support.changeBubbles ) { } function trigger( type, elem, args ) { - args[0].type = type; - return jQuery.event.handle.apply( elem, args ); + // Piggyback on a donor event to simulate a different one. + // Fake originalEvent to avoid donor's stopPropagation, but if the + // simulated event prevents default then we do the same on the donor. + // Don't pass args or remember liveFired; they apply to the donor event. + var event = jQuery.extend( {}, args[ 0 ] ); + event.type = type; + event.originalEvent = {}; + event.liveFired = undefined; + jQuery.event.handle.call( elem, event ); + if ( event.isDefaultPrevented() ) { + args[ 0 ].preventDefault(); + } } // Create "bubbling" focus and blur events -if ( document.addEventListener ) { +if ( !jQuery.support.focusinBubbles ) { jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler while someone wants focusin/focusout + var attaches = 0; + jQuery.event.special[ fix ] = { setup: function() { - if ( focusCounts[fix]++ === 0 ) { + if ( attaches++ === 0 ) { document.addEventListener( orig, handler, true ); } - }, - teardown: function() { - if ( --focusCounts[fix] === 0 ) { + }, + teardown: function() { + if ( --attaches === 0 ) { document.removeEventListener( orig, handler, true ); } } }; - function handler( e ) { - e = jQuery.event.fix( e ); + function handler( donor ) { + // Donor event is always a native one; fix it and switch its type. + // Let focusin/out handler cancel the donor focus/blur event. + var e = jQuery.event.fix( donor ); e.type = fix; - return jQuery.event.trigger( e, null, e.target ); + e.originalEvent = {}; + jQuery.event.trigger( e, null, e.target ); + if ( e.isDefaultPrevented() ) { + donor.preventDefault(); + } } }); } jQuery.each(["bind", "one"], function( i, name ) { jQuery.fn[ name ] = function( type, data, fn ) { + var handler; + // Handle object literals if ( typeof type === "object" ) { for ( var key in type ) { @@ -2702,16 +3403,21 @@ jQuery.each(["bind", "one"], function( i, name ) { } return this; } - - if ( jQuery.isFunction( data ) || data === false ) { + + if ( arguments.length === 2 || data === false ) { fn = data; data = undefined; } - var handler = name === "one" ? jQuery.proxy( fn, function( event ) { - jQuery( this ).unbind( event, handler ); - return fn.apply( this, arguments ); - }) : fn; + if ( name === "one" ) { + handler = function( event ) { + jQuery( this ).unbind( event, handler ); + return fn.apply( this, arguments ); + }; + handler.guid = fn.guid || jQuery.guid++; + } else { + handler = fn; + } if ( type === "unload" && name !== "one" ) { this.one( type, data, fn ); @@ -2742,20 +3448,20 @@ jQuery.fn.extend({ return this; }, - + delegate: function( selector, types, data, fn ) { return this.live( types, data, fn, selector ); }, - + undelegate: function( selector, types, fn ) { if ( arguments.length === 0 ) { - return this.unbind( "live" ); - + return this.unbind( "live" ); + } else { return this.die( types, null, fn, selector ); } }, - + trigger: function( type, data ) { return this.each(function() { jQuery.event.trigger( type, data, this ); @@ -2764,35 +3470,34 @@ jQuery.fn.extend({ triggerHandler: function( type, data ) { if ( this[0] ) { - var event = jQuery.Event( type ); - event.preventDefault(); - event.stopPropagation(); - jQuery.event.trigger( event, data, this[0] ); - return event.result; + return jQuery.event.trigger( type, data, this[0], true ); } }, toggle: function( fn ) { // Save reference to arguments for access in closure var args = arguments, - i = 1; + guid = fn.guid || jQuery.guid++, + i = 0, + toggler = function( event ) { + // Figure out which function to execute + var lastToggle = ( jQuery.data( this, "lastToggle" + fn.guid ) || 0 ) % i; + jQuery.data( this, "lastToggle" + fn.guid, lastToggle + 1 ); + + // Make sure that clicks stop + event.preventDefault(); + + // and execute the function + return args[ lastToggle ].apply( this, arguments ) || false; + }; // link all the functions, so any of them can unbind this click handler + toggler.guid = guid; while ( i < args.length ) { - jQuery.proxy( fn, args[ i++ ] ); + args[ i++ ].guid = guid; } - return this.click( jQuery.proxy( fn, function( event ) { - // Figure out which function to execute - var lastToggle = ( jQuery.data( this, "lastToggle" + fn.guid ) || 0 ) % i; - jQuery.data( this, "lastToggle" + fn.guid, lastToggle + 1 ); - - // Make sure that clicks stop - event.preventDefault(); - - // and execute the function - return args[ lastToggle ].apply( this, arguments ) || false; - })); + return this.click( toggler ); }, hover: function( fnOver, fnOut ) { @@ -2812,17 +3517,25 @@ jQuery.each(["live", "die"], function( i, name ) { var type, i = 0, match, namespaces, preType, selector = origSelector || this.selector, context = origSelector ? this : jQuery( this.context ); - + if ( typeof types === "object" && !types.preventDefault ) { for ( var key in types ) { context[ name ]( key, data, types[key], selector ); } - + return this; } - if ( jQuery.isFunction( data ) ) { - fn = data; + if ( name === "die" && !types && + origSelector && origSelector.charAt(0) === "." ) { + + context.unbind( origSelector ); + + return this; + } + + if ( data === false || jQuery.isFunction( data ) ) { + fn = data || returnFalse; data = undefined; } @@ -2844,7 +3557,7 @@ jQuery.each(["live", "die"], function( i, name ) { preType = type; - if ( type === "focus" || type === "blur" ) { + if ( liveMap[ type ] ) { types.push( liveMap[ type ] + namespaces ); type = type + namespaces; @@ -2864,7 +3577,7 @@ jQuery.each(["live", "die"], function( i, name ) { context.unbind( "live." + liveConvert( type, selector ), fn ); } } - + return this; }; }); @@ -2873,17 +3586,13 @@ function liveHandler( event ) { var stop, maxLevel, related, match, handleObj, elem, j, i, l, data, close, namespace, ret, elems = [], selectors = [], - events = jQuery.data( this, this.nodeType ? "events" : "__events__" ); + events = jQuery._data( this, "events" ); - if ( typeof events === "function" ) { - events = events.events; - } - - // Make sure we avoid non-left-click bubbling in Firefox (#3861) - if ( event.liveFired === this || !events || !events.live || event.button && event.type === "click" ) { + // Make sure we avoid non-left-click bubbling in Firefox (#3861) and disabled elements in IE (#6911) + if ( event.liveFired === this || !events || !events.live || event.target.disabled || event.button && event.type === "click" ) { return; } - + if ( event.namespace ) { namespace = new RegExp("(^|\\.)" + event.namespace.split(".").join("\\.(?:.*\\.)?") + "(\\.|$)"); } @@ -2911,7 +3620,7 @@ function liveHandler( event ) { for ( j = 0; j < live.length; j++ ) { handleObj = live[j]; - if ( close.selector === handleObj.selector && (!namespace || namespace.test( handleObj.namespace )) ) { + if ( close.selector === handleObj.selector && (!namespace || namespace.test( handleObj.namespace )) && !close.elem.disabled ) { elem = close.elem; related = null; @@ -2919,6 +3628,11 @@ function liveHandler( event ) { if ( handleObj.preType === "mouseenter" || handleObj.preType === "mouseleave" ) { event.type = handleObj.preType; related = jQuery( event.relatedTarget ).closest( handleObj.selector )[0]; + + // Make sure not to accidentally match a child element with the same selector + if ( related && jQuery.contains( elem, related ) ) { + related = elem; + } } if ( !related || related !== elem ) { @@ -2939,9 +3653,7 @@ function liveHandler( event ) { event.data = match.handleObj.data; event.handleObj = match.handleObj; - var oldHandle = event.handled; ret = match.handleObj.origHandler.apply( match.elem, arguments ); - event.handled = event.handled === null ? oldHandle : true; if ( ret === false || event.isPropagationStopped() ) { maxLevel = match.level; @@ -2959,7 +3671,7 @@ function liveHandler( event ) { } function liveConvert( type, selector ) { - return (type && type !== "*" ? type + "." : "") + selector.replace(rperiod, "`").replace(rspace, "&"); + return (type && type !== "*" ? type + "." : "") + selector.replace(rperiod, "`").replace(rspaces, "&"); } jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " + @@ -2983,27 +3695,11 @@ jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblcl } }); -// Prevent memory leaks in IE -// Window isn't included so as not to unbind existing unload events -// More info: -// - http://isaacschlueter.com/2006/10/msie-memory-leaks/ -if ( window.attachEvent && !window.addEventListener ) { - jQuery(window).bind("unload", function() { - for ( var id in jQuery.cache ) { - if ( jQuery.cache[ id ].handle ) { - // Try/Catch is to handle iframes being unloaded, see #4280 - try { - jQuery.event.remove( jQuery.cache[ id ].handle.elem ); - } catch(e) {} - } - } - }); -} /*! - * Sizzle CSS Selector Engine - v1.0 - * Copyright 2009, The Dojo Foundation + * Sizzle CSS Selector Engine + * Copyright 2011, The Dojo Foundation * Released under the MIT, BSD, and GPL Licenses. * More information: http://sizzlejs.com/ */ @@ -3013,7 +3709,9 @@ var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[ done = 0, toString = Object.prototype.toString, hasDuplicate = false, - baseHasDuplicate = true; + baseHasDuplicate = true, + rBackslash = /\\/g, + rNonWord = /\W/; // Here we check if the JavaScript engine is using some sort of // optimization where it does not always call our comparision @@ -3212,7 +3910,7 @@ Sizzle.find = function( expr, context, isXML ) { match.splice( 1, 1 ); if ( left.substr( left.length - 1 ) !== "\\" ) { - match[1] = (match[1] || "").replace(/\\/g, ""); + match[1] = (match[1] || "").replace( rBackslash, "" ); set = Expr.find[ type ]( match, context, isXML ); if ( set != null ) { @@ -3224,7 +3922,9 @@ Sizzle.find = function( expr, context, isXML ) { } if ( !set ) { - set = context.getElementsByTagName( "*" ); + set = typeof context.getElementsByTagName !== "undefined" ? + context.getElementsByTagName( "*" ) : + []; } return { set: set, expr: expr }; @@ -3332,9 +4032,9 @@ var Expr = Sizzle.selectors = { ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/, - ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/, + ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/, TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/, - CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+\-]*)\))?/, + CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/, POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/, PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/ }, @@ -3349,13 +4049,16 @@ var Expr = Sizzle.selectors = { attrHandle: { href: function( elem ) { return elem.getAttribute( "href" ); + }, + type: function( elem ) { + return elem.getAttribute( "type" ); } }, relative: { "+": function(checkSet, part){ var isPartStr = typeof part === "string", - isTag = isPartStr && !/\W/.test( part ), + isTag = isPartStr && !rNonWord.test( part ), isPartStrNotTag = isPartStr && !isTag; if ( isTag ) { @@ -3383,7 +4086,7 @@ var Expr = Sizzle.selectors = { i = 0, l = checkSet.length; - if ( isPartStr && !/\W/.test( part ) ) { + if ( isPartStr && !rNonWord.test( part ) ) { part = part.toLowerCase(); for ( ; i < l; i++ ) { @@ -3417,7 +4120,7 @@ var Expr = Sizzle.selectors = { doneName = done++, checkFn = dirCheck; - if ( typeof part === "string" && !/\W/.test(part) ) { + if ( typeof part === "string" && !rNonWord.test( part ) ) { part = part.toLowerCase(); nodeCheck = part; checkFn = dirNodeCheck; @@ -3431,7 +4134,7 @@ var Expr = Sizzle.selectors = { doneName = done++, checkFn = dirCheck; - if ( typeof part === "string" && !/\W/.test( part ) ) { + if ( typeof part === "string" && !rNonWord.test( part ) ) { part = part.toLowerCase(); nodeCheck = part; checkFn = dirNodeCheck; @@ -3467,12 +4170,14 @@ var Expr = Sizzle.selectors = { }, TAG: function( match, context ) { - return context.getElementsByTagName( match[1] ); + if ( typeof context.getElementsByTagName !== "undefined" ) { + return context.getElementsByTagName( match[1] ); + } } }, preFilter: { CLASS: function( match, curLoop, inplace, result, not, isXML ) { - match = " " + match[1].replace(/\\/g, "") + " "; + match = " " + match[1].replace( rBackslash, "" ) + " "; if ( isXML ) { return match; @@ -3480,7 +4185,7 @@ var Expr = Sizzle.selectors = { for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { if ( elem ) { - if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n]/g, " ").indexOf(match) >= 0) ) { + if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) { if ( !inplace ) { result.push( elem ); } @@ -3495,17 +4200,23 @@ var Expr = Sizzle.selectors = { }, ID: function( match ) { - return match[1].replace(/\\/g, ""); + return match[1].replace( rBackslash, "" ); }, TAG: function( match, curLoop ) { - return match[1].toLowerCase(); + return match[1].replace( rBackslash, "" ).toLowerCase(); }, CHILD: function( match ) { if ( match[1] === "nth" ) { + if ( !match[2] ) { + Sizzle.error( match[0] ); + } + + match[2] = match[2].replace(/^\+|\s*/g, ''); + // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' - var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec( + var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec( match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" || !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); @@ -3513,6 +4224,9 @@ var Expr = Sizzle.selectors = { match[2] = (test[1] + (test[2] || 1)) - 0; match[3] = test[3] - 0; } + else if ( match[2] ) { + Sizzle.error( match[0] ); + } // TODO: Move to normal caching system match[0] = done++; @@ -3521,12 +4235,15 @@ var Expr = Sizzle.selectors = { }, ATTR: function( match, curLoop, inplace, result, not, isXML ) { - var name = match[1].replace(/\\/g, ""); + var name = match[1] = match[1].replace( rBackslash, "" ); if ( !isXML && Expr.attrMap[name] ) { match[1] = Expr.attrMap[name]; } + // Handle if an un-quoted value was used + match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" ); + if ( match[2] === "~=" ) { match[4] = " " + match[4] + " "; } @@ -3580,7 +4297,9 @@ var Expr = Sizzle.selectors = { selected: function( elem ) { // Accessing this property makes selected-by-default // options in Safari work properly - elem.parentNode.selectedIndex; + if ( elem.parentNode ) { + elem.parentNode.selectedIndex; + } return elem.selected === true; }, @@ -3602,41 +4321,53 @@ var Expr = Sizzle.selectors = { }, text: function( elem ) { - return "text" === elem.type; + var attr = elem.getAttribute( "type" ), type = elem.type; + // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc) + // use getAttribute instead to test this case + return elem.nodeName.toLowerCase() === "input" && "text" === type && ( attr === type || attr === null ); }, + radio: function( elem ) { - return "radio" === elem.type; + return elem.nodeName.toLowerCase() === "input" && "radio" === elem.type; }, checkbox: function( elem ) { - return "checkbox" === elem.type; + return elem.nodeName.toLowerCase() === "input" && "checkbox" === elem.type; }, file: function( elem ) { - return "file" === elem.type; + return elem.nodeName.toLowerCase() === "input" && "file" === elem.type; }, + password: function( elem ) { - return "password" === elem.type; + return elem.nodeName.toLowerCase() === "input" && "password" === elem.type; }, submit: function( elem ) { - return "submit" === elem.type; + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && "submit" === elem.type; }, image: function( elem ) { - return "image" === elem.type; + return elem.nodeName.toLowerCase() === "input" && "image" === elem.type; }, reset: function( elem ) { - return "reset" === elem.type; + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && "reset" === elem.type; }, button: function( elem ) { - return "button" === elem.type || elem.nodeName.toLowerCase() === "button"; + var name = elem.nodeName.toLowerCase(); + return name === "input" && "button" === elem.type || name === "button"; }, input: function( elem ) { return (/input|select|textarea|button/i).test( elem.nodeName ); + }, + + focus: function( elem ) { + return elem === elem.ownerDocument.activeElement; } }, setFilters: { @@ -3695,7 +4426,7 @@ var Expr = Sizzle.selectors = { return true; } else { - Sizzle.error( "Syntax error, unrecognized expression: " + name ); + Sizzle.error( name ); } }, @@ -3889,6 +4620,16 @@ if ( document.documentElement.compareDocumentPosition ) { } else { sortOrder = function( a, b ) { + // The nodes are identical, we can exit early + if ( a === b ) { + hasDuplicate = true; + return 0; + + // Fallback to using sourceIndex (in IE) if it's available on both nodes + } else if ( a.sourceIndex && b.sourceIndex ) { + return a.sourceIndex - b.sourceIndex; + } + var al, bl, ap = [], bp = [], @@ -3896,13 +4637,8 @@ if ( document.documentElement.compareDocumentPosition ) { bup = b.parentNode, cur = aup; - // The nodes are identical, we can exit early - if ( a === b ) { - hasDuplicate = true; - return 0; - // If the nodes are siblings (or identical) we can do a quick check - } else if ( aup === bup ) { + if ( aup === bup ) { return siblingCheck( a, b ); // If no parents were found then the nodes are disconnected @@ -4085,13 +4821,47 @@ if ( document.querySelectorAll ) { Sizzle = function( query, context, extra, seed ) { context = context || document; - // Make sure that attribute selectors are quoted - query = query.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']"); - // Only use querySelectorAll on non-XML documents // (ID selectors don't work in non-HTML documents) if ( !seed && !Sizzle.isXML(context) ) { + // See if we find a selector to speed up + var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query ); + + if ( match && (context.nodeType === 1 || context.nodeType === 9) ) { + // Speed-up: Sizzle("TAG") + if ( match[1] ) { + return makeArray( context.getElementsByTagName( query ), extra ); + + // Speed-up: Sizzle(".CLASS") + } else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) { + return makeArray( context.getElementsByClassName( match[2] ), extra ); + } + } + if ( context.nodeType === 9 ) { + // Speed-up: Sizzle("body") + // The body element only exists once, optimize finding it + if ( query === "body" && context.body ) { + return makeArray( [ context.body ], extra ); + + // Speed-up: Sizzle("#ID") + } else if ( match && match[3] ) { + var elem = context.getElementById( match[3] ); + + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + if ( elem && elem.parentNode ) { + // Handle the case where IE and Opera return items + // by name instead of ID + if ( elem.id === match[3] ) { + return makeArray( [ elem ], extra ); + } + + } else { + return makeArray( [], extra ); + } + } + try { return makeArray( context.querySelectorAll(query), extra ); } catch(qsaError) {} @@ -4101,20 +4871,30 @@ if ( document.querySelectorAll ) { // and working up from there (Thanks to Andrew Dupont for the technique) // IE 8 doesn't work on object elements } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { - var old = context.getAttribute( "id" ), - nid = old || id; + var oldContext = context, + old = context.getAttribute( "id" ), + nid = old || id, + hasParent = context.parentNode, + relativeHierarchySelector = /^\s*[+~]/.test( query ); if ( !old ) { context.setAttribute( "id", nid ); + } else { + nid = nid.replace( /'/g, "\\$&" ); + } + if ( relativeHierarchySelector && hasParent ) { + context = context.parentNode; } try { - return makeArray( context.querySelectorAll( "#" + nid + " " + query ), extra ); + if ( !relativeHierarchySelector || hasParent ) { + return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra ); + } } catch(pseudoError) { } finally { if ( !old ) { - context.removeAttribute( "id" ); + oldContext.removeAttribute( "id" ); } } } @@ -4134,19 +4914,23 @@ if ( document.querySelectorAll ) { (function(){ var html = document.documentElement, - matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector, - pseudoWorks = false; + matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector; - try { - // This should fail with an exception - // Gecko does not error, returns false instead - matches.call( document.documentElement, "[test!='']:sizzle" ); + if ( matches ) { + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9 fails this) + var disconnectedMatch = !matches.call( document.createElement( "div" ), "div" ), + pseudoWorks = false; + + try { + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( document.documentElement, "[test!='']:sizzle" ); - } catch( pseudoError ) { - pseudoWorks = true; - } + } catch( pseudoError ) { + pseudoWorks = true; + } - if ( matches ) { Sizzle.matchesSelector = function( node, expr ) { // Make sure that attribute selectors are quoted expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']"); @@ -4154,7 +4938,15 @@ if ( document.querySelectorAll ) { if ( !Sizzle.isXML( node ) ) { try { if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) { - return matches.call( node, expr ); + var ret = matches.call( node, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || !disconnectedMatch || + // As well, disconnected nodes are said to be in a document + // fragment in IE 9, so check for that + node.document && node.document.nodeType !== 11 ) { + return ret; + } } } catch(e) {} } @@ -4332,21 +5124,41 @@ var runtil = /Until$/, rmultiselector = /,/, isSimple = /^.[^:#\[\.,]*$/, slice = Array.prototype.slice, - POS = jQuery.expr.match.POS; + POS = jQuery.expr.match.POS, + // methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; jQuery.fn.extend({ find: function( selector ) { + var self = this, + i, l; + + if ( typeof selector !== "string" ) { + return jQuery( selector ).filter(function() { + for ( i = 0, l = self.length; i < l; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + }); + } + var ret = this.pushStack( "", "find", selector ), - length = 0; + length, n, r; - for ( var i = 0, l = this.length; i < l; i++ ) { + for ( i = 0, l = this.length; i < l; i++ ) { length = ret.length; jQuery.find( selector, this[i], ret ); if ( i > 0 ) { // Make sure that the results are unique - for ( var n = length; n < ret.length; n++ ) { - for ( var r = 0; r < length; r++ ) { + for ( n = length; n < ret.length; n++ ) { + for ( r = 0; r < length; r++ ) { if ( ret[r] === ret[n] ) { ret.splice(n--, 1); break; @@ -4377,14 +5189,17 @@ jQuery.fn.extend({ filter: function( selector ) { return this.pushStack( winnow(this, selector, true), "filter", selector ); }, - + is: function( selector ) { - return !!selector && jQuery.filter( selector, this ).length > 0; + return !!selector && ( typeof selector === "string" ? + jQuery.filter( selector, this ).length > 0 : + this.filter( selector ).length > 0 ); }, closest: function( selectors, context ) { var ret = [], i, l, cur = this[0]; - + + // Array if ( jQuery.isArray( selectors ) ) { var match, selector, matches = {}, @@ -4394,8 +5209,8 @@ jQuery.fn.extend({ for ( i = 0, l = selectors.length; i < l; i++ ) { selector = selectors[i]; - if ( !matches[selector] ) { - matches[selector] = jQuery.expr.match.POS.test( selector ) ? + if ( !matches[ selector ] ) { + matches[ selector ] = POS.test( selector ) ? jQuery( selector, context || this.context ) : selector; } @@ -4403,9 +5218,9 @@ jQuery.fn.extend({ while ( cur && cur.ownerDocument && cur !== context ) { for ( selector in matches ) { - match = matches[selector]; + match = matches[ selector ]; - if ( match.jquery ? match.index(cur) > -1 : jQuery(cur).is(match) ) { + if ( match.jquery ? match.index( cur ) > -1 : jQuery( cur ).is( match ) ) { ret.push({ selector: selector, elem: cur, level: level }); } } @@ -4418,8 +5233,10 @@ jQuery.fn.extend({ return ret; } - var pos = POS.test( selectors ) ? - jQuery( selectors, context || this.context ) : null; + // String + var pos = POS.test( selectors ) || typeof selectors !== "string" ? + jQuery( selectors, context || this.context ) : + 0; for ( i = 0, l = this.length; i < l; i++ ) { cur = this[i]; @@ -4431,18 +5248,18 @@ jQuery.fn.extend({ } else { cur = cur.parentNode; - if ( !cur || !cur.ownerDocument || cur === context ) { + if ( !cur || !cur.ownerDocument || cur === context || cur.nodeType === 11 ) { break; } } } } - ret = ret.length > 1 ? jQuery.unique(ret) : ret; - + ret = ret.length > 1 ? jQuery.unique( ret ) : ret; + return this.pushStack( ret, "closest", selectors ); }, - + // Determine the position of an element within // the matched set of elements index: function( elem ) { @@ -4460,8 +5277,8 @@ jQuery.fn.extend({ add: function( selector, context ) { var set = typeof selector === "string" ? - jQuery( selector, context || this.context ) : - jQuery.makeArray( selector ), + jQuery( selector, context ) : + jQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ), all = jQuery.merge( this.get(), set ); return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ? @@ -4522,8 +5339,13 @@ jQuery.each({ } }, function( name, fn ) { jQuery.fn[ name ] = function( until, selector ) { - var ret = jQuery.map( this, fn, until ); - + var ret = jQuery.map( this, fn, until ), + // The variable 'args' was introduced in + // https://github.com/jquery/jquery/commit/52a0238 + // to work around a bug in Chrome 10 (Dev) and should be removed when the bug is fixed. + // http://code.google.com/p/v8/issues/detail?id=1050 + args = slice.call(arguments); + if ( !runtil.test( name ) ) { selector = until; } @@ -4532,13 +5354,13 @@ jQuery.each({ ret = jQuery.filter( selector, ret ); } - ret = this.length > 1 ? jQuery.unique( ret ) : ret; + ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret; if ( (this.length > 1 || rmultiselector.test( selector )) && rparentsprev.test( name ) ) { ret = ret.reverse(); } - return this.pushStack( ret, name, slice.call(arguments).join(",") ); + return this.pushStack( ret, name, args.join(",") ); }; }); @@ -4552,7 +5374,7 @@ jQuery.extend({ jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] : jQuery.find.matches(expr, elems); }, - + dir: function( elem, dir, until ) { var matched = [], cur = elem[ dir ]; @@ -4594,6 +5416,11 @@ jQuery.extend({ // Implement the identical functionality for filter and not function winnow( elements, qualifier, keep ) { + + // Can't pass null or undefined to indexOf in Firefox 4 + // Set to 0 to skip string check + qualifier = qualifier || 0; + if ( jQuery.isFunction( qualifier ) ) { return jQuery.grep(elements, function( elem, i ) { var retVal = !!qualifier.call( elem, i, elem ); @@ -4632,9 +5459,10 @@ var rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g, rtbody = /<tbody/i, rhtml = /<|&#?\w+;/, rnocache = /<(?:script|object|embed|option|style)/i, - // checked="checked" or checked (html5) + // checked="checked" or checked rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i, - raction = /\=([^="'>\s]+\/)>/g, + rscriptType = /\/(java|ecma)script/i, + rcleanScript = /^\s*<!(?:\[CDATA\[|\-\-)/, wrapMap = { option: [ 1, "<select multiple='multiple'>", "</select>" ], legend: [ 1, "<fieldset>", "</fieldset>" ], @@ -4695,7 +5523,7 @@ jQuery.fn.extend({ } return elem; - }).append(this); + }).append( this ); } return this; @@ -4774,7 +5602,7 @@ jQuery.fn.extend({ return set; } }, - + // keepData is for internal use only--do not document remove: function( selector, keepData ) { for ( var i = 0, elem; (elem = this[i]) != null; i++ ) { @@ -4785,11 +5613,11 @@ jQuery.fn.extend({ } if ( elem.parentNode ) { - elem.parentNode.removeChild( elem ); + elem.parentNode.removeChild( elem ); } } } - + return this; }, @@ -4805,48 +5633,17 @@ jQuery.fn.extend({ elem.removeChild( elem.firstChild ); } } - + return this; }, - clone: function( events ) { - // Do the clone - var ret = this.map(function() { - if ( !jQuery.support.noCloneEvent && !jQuery.isXMLDoc(this) ) { - // IE copies events bound via attachEvent when - // using cloneNode. Calling detachEvent on the - // clone will also remove the events from the orignal - // In order to get around this, we use innerHTML. - // Unfortunately, this means some modifications to - // attributes in IE that are actually only stored - // as properties will not be copied (such as the - // the name attribute on an input). - var html = this.outerHTML, - ownerDocument = this.ownerDocument; - - if ( !html ) { - var div = ownerDocument.createElement("div"); - div.appendChild( this.cloneNode(true) ); - html = div.innerHTML; - } - - return jQuery.clean([html.replace(rinlinejQuery, "") - // Handle the case in IE 8 where action=/test/> self-closes a tag - .replace(raction, '="$1">') - .replace(rleadingWhitespace, "")], ownerDocument)[0]; - } else { - return this.cloneNode(true); - } - }); + clone: function( dataAndEvents, deepDataAndEvents ) { + dataAndEvents = dataAndEvents == null ? false : dataAndEvents; + deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; - // Copy the events from the original to the clone - if ( events === true ) { - cloneCopyEvent( this, ret ); - cloneCopyEvent( this.find("*"), ret.find("*") ); - } - - // Return the cloned set - return ret; + return this.map( function () { + return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); + }); }, html: function( value ) { @@ -4918,7 +5715,9 @@ jQuery.fn.extend({ } }); } else { - return this.pushStack( jQuery(jQuery.isFunction(value) ? value() : value), "replaceWith", value ); + return this.length ? + this.pushStack( jQuery(jQuery.isFunction(value) ? value() : value), "replaceWith", value ) : + this; } }, @@ -4956,9 +5755,9 @@ jQuery.fn.extend({ } else { results = jQuery.buildFragment( args, this, scripts ); } - + fragment = results.fragment; - + if ( fragment.childNodes.length === 1 ) { first = fragment = fragment.firstChild; } else { @@ -4968,13 +5767,20 @@ jQuery.fn.extend({ if ( first ) { table = table && jQuery.nodeName( first, "tr" ); - for ( var i = 0, l = this.length; i < l; i++ ) { + for ( var i = 0, l = this.length, lastIndex = l - 1; i < l; i++ ) { callback.call( table ? root(this[i], first) : this[i], - i > 0 || results.cacheable || this.length > 1 ? - fragment.cloneNode(true) : + // Make sure that we do not leak memory by inadvertently discarding + // the original fragment (which might have attached data) instead of + // using it; in addition, use the original fragment object for the last + // item instead of first because it can end up being emptied incorrectly + // in certain situations (Bug #8070). + // Fragments from the fragment cache must always be cloned and never used + // in place. + results.cacheable || (l > 1 && i < lastIndex) ? + jQuery.clone( fragment, true, true ) : fragment ); } @@ -4996,48 +5802,109 @@ function root( elem, cur ) { elem; } -function cloneCopyEvent(orig, ret) { - var i = 0; +function cloneCopyEvent( src, dest ) { - ret.each(function() { - if ( this.nodeName !== (orig[i] && orig[i].nodeName) ) { - return; - } + if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) { + return; + } - var oldData = jQuery.data( orig[i++] ), - curData = jQuery.data( this, oldData ), - events = oldData && oldData.events; + var internalKey = jQuery.expando, + oldData = jQuery.data( src ), + curData = jQuery.data( dest, oldData ); + + // Switch to use the internal data object, if it exists, for the next + // stage of data copying + if ( (oldData = oldData[ internalKey ]) ) { + var events = oldData.events; + curData = curData[ internalKey ] = jQuery.extend({}, oldData); if ( events ) { delete curData.handle; curData.events = {}; for ( var type in events ) { - for ( var handler in events[ type ] ) { - jQuery.event.add( this, type, events[ type ][ handler ], events[ type ][ handler ].data ); + for ( var i = 0, l = events[ type ].length; i < l; i++ ) { + jQuery.event.add( dest, type + ( events[ type ][ i ].namespace ? "." : "" ) + events[ type ][ i ].namespace, events[ type ][ i ], events[ type ][ i ].data ); } } } - }); + } +} + +function cloneFixAttributes( src, dest ) { + var nodeName; + + // We do not need to do anything for non-Elements + if ( dest.nodeType !== 1 ) { + return; + } + + // clearAttributes removes the attributes, which we don't want, + // but also removes the attachEvent events, which we *do* want + if ( dest.clearAttributes ) { + dest.clearAttributes(); + } + + // mergeAttributes, in contrast, only merges back on the + // original attributes, not the events + if ( dest.mergeAttributes ) { + dest.mergeAttributes( src ); + } + + nodeName = dest.nodeName.toLowerCase(); + + // IE6-8 fail to clone children inside object elements that use + // the proprietary classid attribute value (rather than the type + // attribute) to identify the type of content to display + if ( nodeName === "object" ) { + dest.outerHTML = src.outerHTML; + + } else if ( nodeName === "input" && (src.type === "checkbox" || src.type === "radio") ) { + // IE6-8 fails to persist the checked state of a cloned checkbox + // or radio button. Worse, IE6-7 fail to give the cloned element + // a checked appearance if the defaultChecked value isn't also set + if ( src.checked ) { + dest.defaultChecked = dest.checked = src.checked; + } + + // IE6-7 get confused and end up setting the value of a cloned + // checkbox/radio button to an empty string instead of "on" + if ( dest.value !== src.value ) { + dest.value = src.value; + } + + // IE6-8 fails to return the selected option to the default selected + // state when cloning options + } else if ( nodeName === "option" ) { + dest.selected = src.defaultSelected; + + // IE6-8 fails to set the defaultValue to the correct value when + // cloning other types of input fields + } else if ( nodeName === "input" || nodeName === "textarea" ) { + dest.defaultValue = src.defaultValue; + } + + // Event data gets referenced instead of copied if the expando + // gets copied too + dest.removeAttribute( jQuery.expando ); } jQuery.buildFragment = function( args, nodes, scripts ) { var fragment, cacheable, cacheresults, doc = (nodes && nodes[0] ? nodes[0].ownerDocument || nodes[0] : document); - // Only cache "small" (1/2 KB) strings that are associated with the main document + // Only cache "small" (1/2 KB) HTML strings that are associated with the main document // Cloning options loses the selected state, so don't cache them // IE 6 doesn't like it when you put <object> or <embed> elements in a fragment // Also, WebKit does not clone 'checked' attributes on cloneNode, so don't cache if ( args.length === 1 && typeof args[0] === "string" && args[0].length < 512 && doc === document && - !rnocache.test( args[0] ) && (jQuery.support.checkClone || !rchecked.test( args[0] )) ) { + args[0].charAt(0) === "<" && !rnocache.test( args[0] ) && (jQuery.support.checkClone || !rchecked.test( args[0] )) ) { cacheable = true; + cacheresults = jQuery.fragments[ args[0] ]; - if ( cacheresults ) { - if ( cacheresults !== 1 ) { - fragment = cacheresults; - } + if ( cacheresults && cacheresults !== 1 ) { + fragment = cacheresults; } } @@ -5066,25 +5933,101 @@ jQuery.each({ var ret = [], insert = jQuery( selector ), parent = this.length === 1 && this[0].parentNode; - + if ( parent && parent.nodeType === 11 && parent.childNodes.length === 1 && insert.length === 1 ) { insert[ original ]( this[0] ); return this; - + } else { for ( var i = 0, l = insert.length; i < l; i++ ) { var elems = (i > 0 ? this.clone(true) : this).get(); jQuery( insert[i] )[ original ]( elems ); ret = ret.concat( elems ); } - + return this.pushStack( ret, name, insert.selector ); } }; }); +function getAll( elem ) { + if ( "getElementsByTagName" in elem ) { + return elem.getElementsByTagName( "*" ); + + } else if ( "querySelectorAll" in elem ) { + return elem.querySelectorAll( "*" ); + + } else { + return []; + } +} + +// Used in clean, fixes the defaultChecked property +function fixDefaultChecked( elem ) { + if ( elem.type === "checkbox" || elem.type === "radio" ) { + elem.defaultChecked = elem.checked; + } +} +// Finds all inputs and passes them to fixDefaultChecked +function findInputs( elem ) { + if ( jQuery.nodeName( elem, "input" ) ) { + fixDefaultChecked( elem ); + } else if ( elem.getElementsByTagName ) { + jQuery.grep( elem.getElementsByTagName("input"), fixDefaultChecked ); + } +} + jQuery.extend({ + clone: function( elem, dataAndEvents, deepDataAndEvents ) { + var clone = elem.cloneNode(true), + srcElements, + destElements, + i; + + if ( (!jQuery.support.noCloneEvent || !jQuery.support.noCloneChecked) && + (elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) { + // IE copies events bound via attachEvent when using cloneNode. + // Calling detachEvent on the clone will also remove the events + // from the original. In order to get around this, we use some + // proprietary methods to clear the events. Thanks to MooTools + // guys for this hotness. + + cloneFixAttributes( elem, clone ); + + // Using Sizzle here is crazy slow, so we use getElementsByTagName + // instead + srcElements = getAll( elem ); + destElements = getAll( clone ); + + // Weird iteration because IE will replace the length property + // with an element if you are cloning the body and one of the + // elements on the page has a name or id of "length" + for ( i = 0; srcElements[i]; ++i ) { + cloneFixAttributes( srcElements[i], destElements[i] ); + } + } + + // Copy the events from the original to the clone + if ( dataAndEvents ) { + cloneCopyEvent( elem, clone ); + + if ( deepDataAndEvents ) { + srcElements = getAll( elem ); + destElements = getAll( clone ); + + for ( i = 0; srcElements[i]; ++i ) { + cloneCopyEvent( srcElements[i], destElements[i] ); + } + } + } + + // Return the cloned set + return clone; + }, + clean: function( elems, context, fragment, scripts ) { + var checkScriptType; + context = context || document; // !context.createElement fails in IE with an error but returns typeof 'object' @@ -5092,7 +6035,7 @@ jQuery.extend({ context = context.ownerDocument || context[0] && context[0].ownerDocument || document; } - var ret = []; + var ret = [], j; for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { if ( typeof elem === "number" ) { @@ -5104,54 +6047,67 @@ jQuery.extend({ } // Convert html string into DOM nodes - if ( typeof elem === "string" && !rhtml.test( elem ) ) { - elem = context.createTextNode( elem ); - - } else if ( typeof elem === "string" ) { - // Fix "XHTML"-style tags in all browsers - elem = elem.replace(rxhtmlTag, "<$1></$2>"); + if ( typeof elem === "string" ) { + if ( !rhtml.test( elem ) ) { + elem = context.createTextNode( elem ); + } else { + // Fix "XHTML"-style tags in all browsers + elem = elem.replace(rxhtmlTag, "<$1></$2>"); - // Trim whitespace, otherwise indexOf won't work as expected - var tag = (rtagName.exec( elem ) || ["", ""])[1].toLowerCase(), - wrap = wrapMap[ tag ] || wrapMap._default, - depth = wrap[0], - div = context.createElement("div"); + // Trim whitespace, otherwise indexOf won't work as expected + var tag = (rtagName.exec( elem ) || ["", ""])[1].toLowerCase(), + wrap = wrapMap[ tag ] || wrapMap._default, + depth = wrap[0], + div = context.createElement("div"); - // Go to html and back, then peel off extra wrappers - div.innerHTML = wrap[1] + elem + wrap[2]; + // Go to html and back, then peel off extra wrappers + div.innerHTML = wrap[1] + elem + wrap[2]; - // Move to the right depth - while ( depth-- ) { - div = div.lastChild; - } + // Move to the right depth + while ( depth-- ) { + div = div.lastChild; + } - // Remove IE's autoinserted <tbody> from table fragments - if ( !jQuery.support.tbody ) { + // Remove IE's autoinserted <tbody> from table fragments + if ( !jQuery.support.tbody ) { - // String was a <table>, *may* have spurious <tbody> - var hasBody = rtbody.test(elem), - tbody = tag === "table" && !hasBody ? - div.firstChild && div.firstChild.childNodes : + // String was a <table>, *may* have spurious <tbody> + var hasBody = rtbody.test(elem), + tbody = tag === "table" && !hasBody ? + div.firstChild && div.firstChild.childNodes : - // String was a bare <thead> or <tfoot> - wrap[1] === "<table>" && !hasBody ? - div.childNodes : - []; + // String was a bare <thead> or <tfoot> + wrap[1] === "<table>" && !hasBody ? + div.childNodes : + []; - for ( var j = tbody.length - 1; j >= 0 ; --j ) { - if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length ) { - tbody[ j ].parentNode.removeChild( tbody[ j ] ); + for ( j = tbody.length - 1; j >= 0 ; --j ) { + if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length ) { + tbody[ j ].parentNode.removeChild( tbody[ j ] ); + } } } - } + // IE completely kills leading whitespace when innerHTML is used + if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) { + div.insertBefore( context.createTextNode( rleadingWhitespace.exec(elem)[0] ), div.firstChild ); + } - // IE completely kills leading whitespace when innerHTML is used - if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) { - div.insertBefore( context.createTextNode( rleadingWhitespace.exec(elem)[0] ), div.firstChild ); + elem = div.childNodes; } + } - elem = div.childNodes; + // Resets defaultChecked for any radios and checkboxes + // about to be appended to the DOM in IE 6/7 (#8060) + var len; + if ( !jQuery.support.appendChecked ) { + if ( elem[0] && typeof (len = elem.length) === "number" ) { + for ( j = 0; j < len; j++ ) { + findInputs( elem[j] ); + } + } else { + findInputs( elem ); + } } if ( elem.nodeType ) { @@ -5162,13 +6118,18 @@ jQuery.extend({ } if ( fragment ) { + checkScriptType = function( elem ) { + return !elem.type || rscriptType.test( elem.type ); + }; for ( i = 0; ret[i]; i++ ) { if ( scripts && jQuery.nodeName( ret[i], "script" ) && (!ret[i].type || ret[i].type.toLowerCase() === "text/javascript") ) { scripts.push( ret[i].parentNode ? ret[i].parentNode.removeChild( ret[i] ) : ret[i] ); - + } else { if ( ret[i].nodeType === 1 ) { - ret.splice.apply( ret, [i + 1, 0].concat(jQuery.makeArray(ret[i].getElementsByTagName("script"))) ); + var jsTags = jQuery.grep( ret[i].getElementsByTagName( "script" ), checkScriptType ); + + ret.splice.apply( ret, [i + 1, 0].concat( jsTags ) ); } fragment.appendChild( ret[i] ); } @@ -5177,40 +6138,45 @@ jQuery.extend({ return ret; }, - + cleanData: function( elems ) { - var data, id, cache = jQuery.cache, - special = jQuery.event.special, + var data, id, cache = jQuery.cache, internalKey = jQuery.expando, special = jQuery.event.special, deleteExpando = jQuery.support.deleteExpando; - + for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { if ( elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()] ) { continue; } id = elem[ jQuery.expando ]; - + if ( id ) { - data = cache[ id ]; - + data = cache[ id ] && cache[ id ][ internalKey ]; + if ( data && data.events ) { for ( var type in data.events ) { if ( special[ type ] ) { jQuery.event.remove( elem, type ); + // This is a shortcut to avoid jQuery.event.remove's overhead } else { jQuery.removeEvent( elem, type, data.handle ); } } + + // Null the DOM reference to avoid IE6/7/8 leak (#7054) + if ( data.handle ) { + data.handle.elem = null; + } } - + if ( deleteExpando ) { delete elem[ jQuery.expando ]; } else if ( elem.removeAttribute ) { elem.removeAttribute( jQuery.expando ); } - + delete cache[ id ]; } } @@ -5225,7 +6191,7 @@ function evalScript( i, elem ) { dataType: "script" }); } else { - jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" ); + jQuery.globalEval( ( elem.text || elem.textContent || elem.innerHTML || "" ).replace( rcleanScript, "/*$0*/" ) ); } if ( elem.parentNode ) { @@ -5239,9 +6205,12 @@ function evalScript( i, elem ) { var ralpha = /alpha\([^)]*\)/i, ropacity = /opacity=([^)]*)/, rdashAlpha = /-([a-z])/ig, - rupper = /([A-Z])/g, + // fixed for IE9, see #8346 + rupper = /([A-Z]|^ms)/g, rnumpx = /^-?\d+(?:px)?$/i, rnum = /^-?\d/, + rrelNum = /^[+\-]=/, + rrelNumFilter = /[^+\-\.\de]+/g, cssShow = { position: "absolute", visibility: "hidden", display: "block" }, cssWidth = [ "Left", "Right" ], @@ -5292,7 +6261,9 @@ jQuery.extend({ "fontWeight": true, "opacity": true, "zoom": true, - "lineHeight": true + "lineHeight": true, + "widows": true, + "orphans": true }, // Add in properties whose names you wish to fix before @@ -5310,20 +6281,27 @@ jQuery.extend({ } // Make sure that we're working with the right name - var ret, origName = jQuery.camelCase( name ), + var ret, type, origName = jQuery.camelCase( name ), style = elem.style, hooks = jQuery.cssHooks[ origName ]; name = jQuery.cssProps[ origName ] || origName; // Check if we're setting a value if ( value !== undefined ) { + type = typeof value; + // Make sure that NaN and null values aren't set. See: #7116 - if ( typeof value === "number" && isNaN( value ) || value == null ) { + if ( type === "number" && isNaN( value ) || value == null ) { return; } + // convert relative number strings (+= or -=) to relative numbers. #7345 + if ( type === "string" && rrelNum.test( value ) ) { + value = +value.replace( rrelNumFilter, "" ) + parseFloat( jQuery.css( elem, name ) ); + } + // If a number was passed in, add 'px' to the (except for certain CSS properties) - if ( typeof value === "number" && !jQuery.cssNumber[ origName ] ) { + if ( type === "number" && !jQuery.cssNumber[ origName ] ) { value += "px"; } @@ -5348,11 +6326,17 @@ jQuery.extend({ }, css: function( elem, name, extra ) { + var ret, hooks; + // Make sure that we're working with the right name - var ret, origName = jQuery.camelCase( name ), - hooks = jQuery.cssHooks[ origName ]; + name = jQuery.camelCase( name ); + hooks = jQuery.cssHooks[ name ]; + name = jQuery.cssProps[ name ] || name; - name = jQuery.cssProps[ origName ] || origName; + // cssFloat needs a special treatment + if ( name === "cssFloat" ) { + name = "float"; + } // If a hook was provided get the computed value from there if ( hooks && "get" in hooks && (ret = hooks.get( elem, true, extra )) !== undefined ) { @@ -5360,7 +6344,7 @@ jQuery.extend({ // Otherwise, if a way to get the computed value exists, use that } else if ( curCSS ) { - return curCSS( elem, name, origName ); + return curCSS( elem, name ); } }, @@ -5451,33 +6435,56 @@ if ( !jQuery.support.opacity ) { jQuery.cssHooks.opacity = { get: function( elem, computed ) { // IE uses filters for opacity - return ropacity.test((computed && elem.currentStyle ? elem.currentStyle.filter : elem.style.filter) || "") ? - (parseFloat(RegExp.$1) / 100) + "" : + return ropacity.test( (computed && elem.currentStyle ? elem.currentStyle.filter : elem.style.filter) || "" ) ? + ( parseFloat( RegExp.$1 ) / 100 ) + "" : computed ? "1" : ""; }, set: function( elem, value ) { - var style = elem.style; + var style = elem.style, + currentStyle = elem.currentStyle; // IE has trouble with opacity if it does not have layout // Force it by setting the zoom level style.zoom = 1; // Set the alpha filter to set the opacity - var opacity = jQuery.isNaN(value) ? + var opacity = jQuery.isNaN( value ) ? "" : "alpha(opacity=" + value * 100 + ")", - filter = style.filter || ""; + filter = currentStyle && currentStyle.filter || style.filter || ""; - style.filter = ralpha.test(filter) ? - filter.replace(ralpha, opacity) : - style.filter + ' ' + opacity; + style.filter = ralpha.test( filter ) ? + filter.replace( ralpha, opacity ) : + filter + " " + opacity; } }; } +jQuery(function() { + // This hook cannot be added until DOM ready because the support test + // for it is not run until after DOM ready + if ( !jQuery.support.reliableMarginRight ) { + jQuery.cssHooks.marginRight = { + get: function( elem, computed ) { + // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right + // Work around by temporarily setting element display to inline-block + var ret; + jQuery.swap( elem, { "display": "inline-block" }, function() { + if ( computed ) { + ret = curCSS( elem, "margin-right", "marginRight" ); + } else { + ret = elem.style.marginRight; + } + }); + return ret; + } + }; + } +}); + if ( document.defaultView && document.defaultView.getComputedStyle ) { - getComputedStyle = function( elem, newName, name ) { + getComputedStyle = function( elem, name ) { var ret, defaultView, computedStyle; name = name.replace( rupper, "-$1" ).toLowerCase(); @@ -5499,8 +6506,9 @@ if ( document.defaultView && document.defaultView.getComputedStyle ) { if ( document.documentElement.currentStyle ) { currentStyle = function( elem, name ) { - var left, rsLeft, + var left, ret = elem.currentStyle && elem.currentStyle[ name ], + rsLeft = elem.runtimeStyle && elem.runtimeStyle[ name ], style = elem.style; // From the awesome hack by Dean Edwards @@ -5511,16 +6519,19 @@ if ( document.documentElement.currentStyle ) { if ( !rnumpx.test( ret ) && rnum.test( ret ) ) { // Remember the original values left = style.left; - rsLeft = elem.runtimeStyle.left; // Put in the new values to get a computed value out - elem.runtimeStyle.left = elem.currentStyle.left; + if ( rsLeft ) { + elem.runtimeStyle.left = elem.currentStyle.left; + } style.left = name === "fontSize" ? "1em" : (ret || 0); ret = style.pixelLeft + "px"; // Revert the changed values style.left = left; - elem.runtimeStyle.left = rsLeft; + if ( rsLeft ) { + elem.runtimeStyle.left = rsLeft; + } } return ret === "" ? "auto" : ret; @@ -5569,21 +6580,140 @@ if ( jQuery.expr && jQuery.expr.filters ) { -var jsc = jQuery.now(), - rscript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, - rselectTextarea = /^(?:select|textarea)/i, +var r20 = /%20/g, + rbracket = /\[\]$/, + rCRLF = /\r?\n/g, + rhash = /#.*$/, + rheaders = /^(.*?):[ \t]*([^\r\n]*)\r?$/mg, // IE leaves an \r character at EOL rinput = /^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i, + // #7653, #8125, #8152: local protocol detection + rlocalProtocol = /^(?:about|app|app\-storage|.+\-extension|file|widget):$/, rnoContent = /^(?:GET|HEAD)$/, - rbracket = /\[\]$/, - jsre = /\=\?(&|$)/, + rprotocol = /^\/\//, rquery = /\?/, + rscript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, + rselectTextarea = /^(?:select|textarea)/i, + rspacesAjax = /\s+/, rts = /([?&])_=[^&]*/, - rurl = /^(\w+:)?\/\/([^\/?#]+)/, - r20 = /%20/g, - rhash = /#.*$/, + rurl = /^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/, // Keep a copy of the old load method - _load = jQuery.fn.load; + _load = jQuery.fn.load, + + /* Prefilters + * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) + * 2) These are called: + * - BEFORE asking for a transport + * - AFTER param serialization (s.data is a string if s.processData is true) + * 3) key is the dataType + * 4) the catchall symbol "*" can be used + * 5) execution will start with transport dataType and THEN continue down to "*" if needed + */ + prefilters = {}, + + /* Transports bindings + * 1) key is the dataType + * 2) the catchall symbol "*" can be used + * 3) selection will start with transport dataType and THEN go to "*" if needed + */ + transports = {}, + + // Document location + ajaxLocation, + + // Document location segments + ajaxLocParts; + +// #8138, IE may throw an exception when accessing +// a field from window.location if document.domain has been set +try { + ajaxLocation = location.href; +} catch( e ) { + // Use the href attribute of an A element + // since IE will modify it given document.location + ajaxLocation = document.createElement( "a" ); + ajaxLocation.href = ""; + ajaxLocation = ajaxLocation.href; +} + +// Segment location into parts +ajaxLocParts = rurl.exec( ajaxLocation.toLowerCase() ) || []; + +// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport +function addToPrefiltersOrTransports( structure ) { + + // dataTypeExpression is optional and defaults to "*" + return function( dataTypeExpression, func ) { + + if ( typeof dataTypeExpression !== "string" ) { + func = dataTypeExpression; + dataTypeExpression = "*"; + } + + if ( jQuery.isFunction( func ) ) { + var dataTypes = dataTypeExpression.toLowerCase().split( rspacesAjax ), + i = 0, + length = dataTypes.length, + dataType, + list, + placeBefore; + + // For each dataType in the dataTypeExpression + for(; i < length; i++ ) { + dataType = dataTypes[ i ]; + // We control if we're asked to add before + // any existing element + placeBefore = /^\+/.test( dataType ); + if ( placeBefore ) { + dataType = dataType.substr( 1 ) || "*"; + } + list = structure[ dataType ] = structure[ dataType ] || []; + // then we add to the structure accordingly + list[ placeBefore ? "unshift" : "push" ]( func ); + } + } + }; +} + +// Base inspection function for prefilters and transports +function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR, + dataType /* internal */, inspected /* internal */ ) { + + dataType = dataType || options.dataTypes[ 0 ]; + inspected = inspected || {}; + + inspected[ dataType ] = true; + + var list = structure[ dataType ], + i = 0, + length = list ? list.length : 0, + executeOnly = ( structure === prefilters ), + selection; + + for(; i < length && ( executeOnly || !selection ); i++ ) { + selection = list[ i ]( options, originalOptions, jqXHR ); + // If we got redirected to another dataType + // we try there if executing only and not done already + if ( typeof selection === "string" ) { + if ( !executeOnly || inspected[ selection ] ) { + selection = undefined; + } else { + options.dataTypes.unshift( selection ); + selection = inspectPrefiltersOrTransports( + structure, options, originalOptions, jqXHR, selection, inspected ); + } + } + } + // If we're only executing or nothing was selected + // we try the catchall dataType if not done already + if ( ( executeOnly || !selection ) && !inspected[ "*" ] ) { + selection = inspectPrefiltersOrTransports( + structure, options, originalOptions, jqXHR, "*", inspected ); + } + // unnecessary when only executing (prefilters) + // but it'll be ignored by the caller in that case + return selection; +} jQuery.fn.extend({ load: function( url, params, callback ) { @@ -5595,10 +6725,10 @@ jQuery.fn.extend({ return this; } - var off = url.indexOf(" "); + var off = url.indexOf( " " ); if ( off >= 0 ) { - var selector = url.slice(off, url.length); - url = url.slice(0, off); + var selector = url.slice( off, url.length ); + url = url.slice( 0, off ); } // Default to a GET request @@ -5610,7 +6740,7 @@ jQuery.fn.extend({ if ( jQuery.isFunction( params ) ) { // We assume that it's the callback callback = params; - params = null; + params = undefined; // Otherwise, build a param string } else if ( typeof params === "object" ) { @@ -5627,26 +6757,34 @@ jQuery.fn.extend({ type: type, dataType: "html", data: params, - complete: function( res, status ) { + // Complete callback (responseText is used internally) + complete: function( jqXHR, status, responseText ) { + // Store the response as specified by the jqXHR object + responseText = jqXHR.responseText; // If successful, inject the HTML into all the matched elements - if ( status === "success" || status === "notmodified" ) { + if ( jqXHR.isResolved() ) { + // #4825: Get the actual response in case + // a dataFilter is present in ajaxSettings + jqXHR.done(function( r ) { + responseText = r; + }); // See if a selector was specified self.html( selector ? // Create a dummy div to hold the results jQuery("<div>") // inject the contents of the document in, removing the scripts // to avoid any 'Permission Denied' errors in IE - .append(res.responseText.replace(rscript, "")) + .append(responseText.replace(rscript, "")) // Locate the specified elements .find(selector) : // If not, just inject the full result - res.responseText ); + responseText ); } if ( callback ) { - self.each( callback, [res.responseText, status, res] ); + self.each( callback, [ responseText, status, jqXHR ] ); } } }); @@ -5655,88 +6793,94 @@ jQuery.fn.extend({ }, serialize: function() { - return jQuery.param(this.serializeArray()); + return jQuery.param( this.serializeArray() ); }, serializeArray: function() { - return this.map(function() { - return this.elements ? jQuery.makeArray(this.elements) : this; + return this.map(function(){ + return this.elements ? jQuery.makeArray( this.elements ) : this; }) - .filter(function() { + .filter(function(){ return this.name && !this.disabled && - (this.checked || rselectTextarea.test(this.nodeName) || - rinput.test(this.type)); + ( this.checked || rselectTextarea.test( this.nodeName ) || + rinput.test( this.type ) ); }) - .map(function( i, elem ) { - var val = jQuery(this).val(); + .map(function( i, elem ){ + var val = jQuery( this ).val(); return val == null ? null : - jQuery.isArray(val) ? - jQuery.map( val, function( val, i ) { - return { name: elem.name, value: val }; + jQuery.isArray( val ) ? + jQuery.map( val, function( val, i ){ + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; }) : - { name: elem.name, value: val }; + { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; }).get(); } }); // Attach a bunch of functions for handling common AJAX events -jQuery.each( "ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "), function( i, o ) { - jQuery.fn[o] = function( f ) { - return this.bind(o, f); +jQuery.each( "ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split( " " ), function( i, o ){ + jQuery.fn[ o ] = function( f ){ + return this.bind( o, f ); }; }); -jQuery.extend({ - get: function( url, data, callback, type ) { - // shift arguments if data argument was omited +jQuery.each( [ "get", "post" ], function( i, method ) { + jQuery[ method ] = function( url, data, callback, type ) { + // shift arguments if data argument was omitted if ( jQuery.isFunction( data ) ) { type = type || callback; callback = data; - data = null; + data = undefined; } return jQuery.ajax({ - type: "GET", + type: method, url: url, data: data, success: callback, dataType: type }); - }, + }; +}); + +jQuery.extend({ getScript: function( url, callback ) { - return jQuery.get(url, null, callback, "script"); + return jQuery.get( url, undefined, callback, "script" ); }, getJSON: function( url, data, callback ) { - return jQuery.get(url, data, callback, "json"); + return jQuery.get( url, data, callback, "json" ); }, - post: function( url, data, callback, type ) { - // shift arguments if data argument was omited - if ( jQuery.isFunction( data ) ) { - type = type || callback; - callback = data; - data = {}; + // Creates a full fledged settings object into target + // with both ajaxSettings and settings fields. + // If target is omitted, writes into ajaxSettings. + ajaxSetup: function ( target, settings ) { + if ( !settings ) { + // Only one parameter, we extend ajaxSettings + settings = target; + target = jQuery.extend( true, jQuery.ajaxSettings, settings ); + } else { + // target was provided, we extend into it + jQuery.extend( true, target, jQuery.ajaxSettings, settings ); } - - return jQuery.ajax({ - type: "POST", - url: url, - data: data, - success: callback, - dataType: type - }); - }, - - ajaxSetup: function( settings ) { - jQuery.extend( jQuery.ajaxSettings, settings ); + // Flatten fields we don't want deep extended + for( var field in { context: 1, url: 1 } ) { + if ( field in settings ) { + target[ field ] = settings[ field ]; + } else if( field in jQuery.ajaxSettings ) { + target[ field ] = jQuery.ajaxSettings[ field ]; + } + } + return target; }, ajaxSettings: { - url: location.href, + url: ajaxLocation, + isLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ), global: true, type: "GET", contentType: "application/x-www-form-urlencoded", @@ -5745,332 +6889,434 @@ jQuery.extend({ /* timeout: 0, data: null, + dataType: null, username: null, password: null, + cache: null, traditional: false, + headers: {}, */ - // This function can be overriden by calling jQuery.ajaxSetup - xhr: function() { - return new window.XMLHttpRequest(); - }, + accepts: { xml: "application/xml, text/xml", html: "text/html", - script: "text/javascript, application/javascript", - json: "application/json, text/javascript", text: "text/plain", - _default: "*/*" - } - }, + json: "application/json, text/javascript", + "*": "*/*" + }, - ajax: function( origSettings ) { - var s = jQuery.extend(true, {}, jQuery.ajaxSettings, origSettings), - jsonp, status, data, type = s.type.toUpperCase(), noContent = rnoContent.test(type); + contents: { + xml: /xml/, + html: /html/, + json: /json/ + }, - s.url = s.url.replace( rhash, "" ); + responseFields: { + xml: "responseXML", + text: "responseText" + }, - // Use original (not extended) context object if it was provided - s.context = origSettings && origSettings.context != null ? origSettings.context : s; + // List of data converters + // 1) key format is "source_type destination_type" (a single space in-between) + // 2) the catchall symbol "*" can be used for source_type + converters: { + + // Convert anything to text + "* text": window.String, + + // Text to html (true = no transformation) + "text html": true, + + // Evaluate text as a json expression + "text json": jQuery.parseJSON, + + // Parse text as xml + "text xml": jQuery.parseXML + } + }, + + ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), + ajaxTransport: addToPrefiltersOrTransports( transports ), + + // Main method + ajax: function( url, options ) { + + // If url is an object, simulate pre-1.5 signature + if ( typeof url === "object" ) { + options = url; + url = undefined; + } + + // Force options to be an object + options = options || {}; + + var // Create the final options object + s = jQuery.ajaxSetup( {}, options ), + // Callbacks context + callbackContext = s.context || s, + // Context for global events + // It's the callbackContext if one was provided in the options + // and if it's a DOM node or a jQuery collection + globalEventContext = callbackContext !== s && + ( callbackContext.nodeType || callbackContext instanceof jQuery ) ? + jQuery( callbackContext ) : jQuery.event, + // Deferreds + deferred = jQuery.Deferred(), + completeDeferred = jQuery._Deferred(), + // Status-dependent callbacks + statusCode = s.statusCode || {}, + // ifModified key + ifModifiedKey, + // Headers (they are sent all at once) + requestHeaders = {}, + requestHeadersNames = {}, + // Response headers + responseHeadersString, + responseHeaders, + // transport + transport, + // timeout handle + timeoutTimer, + // Cross-domain detection vars + parts, + // The jqXHR state + state = 0, + // To know if global events are to be dispatched + fireGlobals, + // Loop variable + i, + // Fake xhr + jqXHR = { + + readyState: 0, + + // Caches the header + setRequestHeader: function( name, value ) { + if ( !state ) { + var lname = name.toLowerCase(); + name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name; + requestHeaders[ name ] = value; + } + return this; + }, + + // Raw string + getAllResponseHeaders: function() { + return state === 2 ? responseHeadersString : null; + }, + + // Builds headers hashtable if needed + getResponseHeader: function( key ) { + var match; + if ( state === 2 ) { + if ( !responseHeaders ) { + responseHeaders = {}; + while( ( match = rheaders.exec( responseHeadersString ) ) ) { + responseHeaders[ match[1].toLowerCase() ] = match[ 2 ]; + } + } + match = responseHeaders[ key.toLowerCase() ]; + } + return match === undefined ? null : match; + }, - // convert data if not already a string - if ( s.data && s.processData && typeof s.data !== "string" ) { - s.data = jQuery.param( s.data, s.traditional ); - } + // Overrides response content-type header + overrideMimeType: function( type ) { + if ( !state ) { + s.mimeType = type; + } + return this; + }, - // Handle JSONP Parameter Callbacks - if ( s.dataType === "jsonp" ) { - if ( type === "GET" ) { - if ( !jsre.test( s.url ) ) { - s.url += (rquery.test( s.url ) ? "&" : "?") + (s.jsonp || "callback") + "=?"; + // Cancel the request + abort: function( statusText ) { + statusText = statusText || "abort"; + if ( transport ) { + transport.abort( statusText ); + } + done( 0, statusText ); + return this; } - } else if ( !s.data || !jsre.test(s.data) ) { - s.data = (s.data ? s.data + "&" : "") + (s.jsonp || "callback") + "=?"; - } - s.dataType = "json"; - } + }; - // Build temporary JSONP function - if ( s.dataType === "json" && (s.data && jsre.test(s.data) || jsre.test(s.url)) ) { - jsonp = s.jsonpCallback || ("jsonp" + jsc++); + // Callback for when everything is done + // It is defined here because jslint complains if it is declared + // at the end of the function (which would be more logical and readable) + function done( status, statusText, responses, headers ) { - // Replace the =? sequence both in the query string and the data - if ( s.data ) { - s.data = (s.data + "").replace(jsre, "=" + jsonp + "$1"); + // Called once + if ( state === 2 ) { + return; } - s.url = s.url.replace(jsre, "=" + jsonp + "$1"); - - // We need to make sure - // that a JSONP style response is executed properly - s.dataType = "script"; - - // Handle JSONP-style loading - var customJsonp = window[ jsonp ]; - - window[ jsonp ] = function( tmp ) { - if ( jQuery.isFunction( customJsonp ) ) { - customJsonp( tmp ); - - } else { - // Garbage collect - window[ jsonp ] = undefined; - - try { - delete window[ jsonp ]; - } catch( jsonpError ) {} - } + // State is "done" now + state = 2; - data = tmp; - jQuery.handleSuccess( s, xhr, status, data ); - jQuery.handleComplete( s, xhr, status, data ); - - if ( head ) { - head.removeChild( script ); - } - }; - } + // Clear timeout if it exists + if ( timeoutTimer ) { + clearTimeout( timeoutTimer ); + } - if ( s.dataType === "script" && s.cache === null ) { - s.cache = false; - } + // Dereference transport for early garbage collection + // (no matter how long the jqXHR object will be used) + transport = undefined; - if ( s.cache === false && noContent ) { - var ts = jQuery.now(); + // Cache response headers + responseHeadersString = headers || ""; - // try replacing _= if it is there - var ret = s.url.replace(rts, "$1_=" + ts); + // Set readyState + jqXHR.readyState = status ? 4 : 0; - // if nothing was replaced, add timestamp to the end - s.url = ret + ((ret === s.url) ? (rquery.test(s.url) ? "&" : "?") + "_=" + ts : ""); - } + var isSuccess, + success, + error, + response = responses ? ajaxHandleResponses( s, jqXHR, responses ) : undefined, + lastModified, + etag; - // If data is available, append data to url for GET/HEAD requests - if ( s.data && noContent ) { - s.url += (rquery.test(s.url) ? "&" : "?") + s.data; - } + // If successful, handle type chaining + if ( status >= 200 && status < 300 || status === 304 ) { - // Watch for a new set of requests - if ( s.global && jQuery.active++ === 0 ) { - jQuery.event.trigger( "ajaxStart" ); - } + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { - // Matches an absolute URL, and saves the domain - var parts = rurl.exec( s.url ), - remote = parts && (parts[1] && parts[1].toLowerCase() !== location.protocol || parts[2].toLowerCase() !== location.host); + if ( ( lastModified = jqXHR.getResponseHeader( "Last-Modified" ) ) ) { + jQuery.lastModified[ ifModifiedKey ] = lastModified; + } + if ( ( etag = jqXHR.getResponseHeader( "Etag" ) ) ) { + jQuery.etag[ ifModifiedKey ] = etag; + } + } - // If we're requesting a remote document - // and trying to load JSON or Script with a GET - if ( s.dataType === "script" && type === "GET" && remote ) { - var head = document.getElementsByTagName("head")[0] || document.documentElement; - var script = document.createElement("script"); - if ( s.scriptCharset ) { - script.charset = s.scriptCharset; - } - script.src = s.url; + // If not modified + if ( status === 304 ) { - // Handle Script loading - if ( !jsonp ) { - var done = false; + statusText = "notmodified"; + isSuccess = true; - // Attach handlers for all browsers - script.onload = script.onreadystatechange = function() { - if ( !done && (!this.readyState || - this.readyState === "loaded" || this.readyState === "complete") ) { - done = true; - jQuery.handleSuccess( s, xhr, status, data ); - jQuery.handleComplete( s, xhr, status, data ); + // If we have data + } else { - // Handle memory leak in IE - script.onload = script.onreadystatechange = null; - if ( head && script.parentNode ) { - head.removeChild( script ); - } + try { + success = ajaxConvert( s, response ); + statusText = "success"; + isSuccess = true; + } catch(e) { + // We have a parsererror + statusText = "parsererror"; + error = e; } - }; + } + } else { + // We extract error from statusText + // then normalize statusText and status for non-aborts + error = statusText; + if( !statusText || status ) { + statusText = "error"; + if ( status < 0 ) { + status = 0; + } + } } - // Use insertBefore instead of appendChild to circumvent an IE6 bug. - // This arises when a base node is used (#2709 and #4378). - head.insertBefore( script, head.firstChild ); - - // We handle everything using the script element injection - return undefined; - } + // Set data for the fake xhr object + jqXHR.status = status; + jqXHR.statusText = statusText; - var requestDone = false; + // Success/Error + if ( isSuccess ) { + deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); + } else { + deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); + } - // Create the request object - var xhr = s.xhr(); + // Status-dependent callbacks + jqXHR.statusCode( statusCode ); + statusCode = undefined; - if ( !xhr ) { - return; - } + if ( fireGlobals ) { + globalEventContext.trigger( "ajax" + ( isSuccess ? "Success" : "Error" ), + [ jqXHR, s, isSuccess ? success : error ] ); + } - // Open the socket - // Passing null username, generates a login popup on Opera (#2865) - if ( s.username ) { - xhr.open(type, s.url, s.async, s.username, s.password); - } else { - xhr.open(type, s.url, s.async); - } + // Complete + completeDeferred.resolveWith( callbackContext, [ jqXHR, statusText ] ); - // Need an extra try/catch for cross domain requests in Firefox 3 - try { - // Set content-type if data specified and content-body is valid for this type - if ( (s.data != null && !noContent) || (origSettings && origSettings.contentType) ) { - xhr.setRequestHeader("Content-Type", s.contentType); + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxComplete", [ jqXHR, s] ); + // Handle the global AJAX counter + if ( !( --jQuery.active ) ) { + jQuery.event.trigger( "ajaxStop" ); + } } + } - // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. - if ( s.ifModified ) { - if ( jQuery.lastModified[s.url] ) { - xhr.setRequestHeader("If-Modified-Since", jQuery.lastModified[s.url]); - } + // Attach deferreds + deferred.promise( jqXHR ); + jqXHR.success = jqXHR.done; + jqXHR.error = jqXHR.fail; + jqXHR.complete = completeDeferred.done; - if ( jQuery.etag[s.url] ) { - xhr.setRequestHeader("If-None-Match", jQuery.etag[s.url]); + // Status-dependent callbacks + jqXHR.statusCode = function( map ) { + if ( map ) { + var tmp; + if ( state < 2 ) { + for( tmp in map ) { + statusCode[ tmp ] = [ statusCode[tmp], map[tmp] ]; + } + } else { + tmp = map[ jqXHR.status ]; + jqXHR.then( tmp, tmp ); } } + return this; + }; - // Set header so the called script knows that it's an XMLHttpRequest - // Only send the header if it's not a remote XHR - if ( !remote ) { - xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest"); - } + // Remove hash character (#7531: and string promotion) + // Add protocol if not provided (#5866: IE7 issue with protocol-less urls) + // We also use the url parameter if available + s.url = ( ( url || s.url ) + "" ).replace( rhash, "" ).replace( rprotocol, ajaxLocParts[ 1 ] + "//" ); + + // Extract dataTypes list + s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().split( rspacesAjax ); + + // Determine if a cross-domain request is in order + if ( s.crossDomain == null ) { + parts = rurl.exec( s.url.toLowerCase() ); + s.crossDomain = !!( parts && + ( parts[ 1 ] != ajaxLocParts[ 1 ] || parts[ 2 ] != ajaxLocParts[ 2 ] || + ( parts[ 3 ] || ( parts[ 1 ] === "http:" ? 80 : 443 ) ) != + ( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === "http:" ? 80 : 443 ) ) ) + ); + } - // Set the Accepts header for the server, depending on the dataType - xhr.setRequestHeader("Accept", s.dataType && s.accepts[ s.dataType ] ? - s.accepts[ s.dataType ] + ", */*; q=0.01" : - s.accepts._default ); - } catch( headerError ) {} + // Convert data if not already a string + if ( s.data && s.processData && typeof s.data !== "string" ) { + s.data = jQuery.param( s.data, s.traditional ); + } - // Allow custom headers/mimetypes and early abort - if ( s.beforeSend && s.beforeSend.call(s.context, xhr, s) === false ) { - // Handle the global AJAX counter - if ( s.global && jQuery.active-- === 1 ) { - jQuery.event.trigger( "ajaxStop" ); - } + // Apply prefilters + inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); - // close opended socket - xhr.abort(); + // If request was aborted inside a prefiler, stop there + if ( state === 2 ) { return false; } - if ( s.global ) { - jQuery.triggerGlobal( s, "ajaxSend", [xhr, s] ); - } + // We can fire global events as of now if asked to + fireGlobals = s.global; - // Wait for a response to come back - var onreadystatechange = xhr.onreadystatechange = function( isTimeout ) { - // The request was aborted - if ( !xhr || xhr.readyState === 0 || isTimeout === "abort" ) { - // Opera doesn't call onreadystatechange before this point - // so we simulate the call - if ( !requestDone ) { - jQuery.handleComplete( s, xhr, status, data ); - } + // Uppercase the type + s.type = s.type.toUpperCase(); - requestDone = true; - if ( xhr ) { - xhr.onreadystatechange = jQuery.noop; - } + // Determine if request has content + s.hasContent = !rnoContent.test( s.type ); - // The transfer is complete and the data is available, or the request timed out - } else if ( !requestDone && xhr && (xhr.readyState === 4 || isTimeout === "timeout") ) { - requestDone = true; - xhr.onreadystatechange = jQuery.noop; + // Watch for a new set of requests + if ( fireGlobals && jQuery.active++ === 0 ) { + jQuery.event.trigger( "ajaxStart" ); + } - status = isTimeout === "timeout" ? - "timeout" : - !jQuery.httpSuccess( xhr ) ? - "error" : - s.ifModified && jQuery.httpNotModified( xhr, s.url ) ? - "notmodified" : - "success"; + // More options handling for requests with no content + if ( !s.hasContent ) { - var errMsg; + // If data is available, append data to url + if ( s.data ) { + s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.data; + } - if ( status === "success" ) { - // Watch for, and catch, XML document parse errors - try { - // process the data (runs the xml through httpData regardless of callback) - data = jQuery.httpData( xhr, s.dataType, s ); - } catch( parserError ) { - status = "parsererror"; - errMsg = parserError; - } - } + // Get ifModifiedKey before adding the anti-cache parameter + ifModifiedKey = s.url; - // Make sure that the request was successful or notmodified - if ( status === "success" || status === "notmodified" ) { - // JSONP handles its own success callback - if ( !jsonp ) { - jQuery.handleSuccess( s, xhr, status, data ); - } - } else { - jQuery.handleError( s, xhr, status, errMsg ); - } + // Add anti-cache in url if needed + if ( s.cache === false ) { - // Fire the complete handlers - if ( !jsonp ) { - jQuery.handleComplete( s, xhr, status, data ); - } + var ts = jQuery.now(), + // try replacing _= if it is there + ret = s.url.replace( rts, "$1_=" + ts ); - if ( isTimeout === "timeout" ) { - xhr.abort(); - } + // if nothing was replaced, add timestamp to the end + s.url = ret + ( (ret === s.url ) ? ( rquery.test( s.url ) ? "&" : "?" ) + "_=" + ts : "" ); + } + } - // Stop memory leaks - if ( s.async ) { - xhr = null; - } + // Set the correct header, if data is being sent + if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { + jqXHR.setRequestHeader( "Content-Type", s.contentType ); + } + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + ifModifiedKey = ifModifiedKey || s.url; + if ( jQuery.lastModified[ ifModifiedKey ] ) { + jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ ifModifiedKey ] ); } - }; + if ( jQuery.etag[ ifModifiedKey ] ) { + jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ ifModifiedKey ] ); + } + } - // Override the abort handler, if we can (IE 6 doesn't allow it, but that's OK) - // Opera doesn't fire onreadystatechange at all on abort - try { - var oldAbort = xhr.abort; - xhr.abort = function() { - if ( xhr ) { - // oldAbort has no call property in IE7 so - // just do it this way, which works in all - // browsers - Function.prototype.call.call( oldAbort, xhr ); - } + // Set the Accepts header for the server, depending on the dataType + jqXHR.setRequestHeader( + "Accept", + s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ? + s.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== "*" ? ", */*; q=0.01" : "" ) : + s.accepts[ "*" ] + ); - onreadystatechange( "abort" ); - }; - } catch( abortError ) {} + // Check for headers option + for ( i in s.headers ) { + jqXHR.setRequestHeader( i, s.headers[ i ] ); + } + + // Allow custom headers/mimetypes and early abort + if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) { + // Abort if not done already + jqXHR.abort(); + return false; - // Timeout checker - if ( s.async && s.timeout > 0 ) { - setTimeout(function() { - // Check to see if the request is still happening - if ( xhr && !requestDone ) { - onreadystatechange( "timeout" ); - } - }, s.timeout); } - // Send the data - try { - xhr.send( noContent || s.data == null ? null : s.data ); + // Install callbacks on deferreds + for ( i in { success: 1, error: 1, complete: 1 } ) { + jqXHR[ i ]( s[ i ] ); + } - } catch( sendError ) { - jQuery.handleError( s, xhr, null, sendError ); + // Get transport + transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); - // Fire the complete handlers - jQuery.handleComplete( s, xhr, status, data ); - } + // If no transport, we auto-abort + if ( !transport ) { + done( -1, "No Transport" ); + } else { + jqXHR.readyState = 1; + // Send global event + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); + } + // Timeout + if ( s.async && s.timeout > 0 ) { + timeoutTimer = setTimeout( function(){ + jqXHR.abort( "timeout" ); + }, s.timeout ); + } - // firefox 1.5 doesn't fire statechange for sync requests - if ( !s.async ) { - onreadystatechange(); + try { + state = 1; + transport.send( requestHeaders, done ); + } catch (e) { + // Propagate exception as error if not done + if ( status < 2 ) { + done( -1, e ); + // Simply rethrow otherwise + } else { + jQuery.error( e ); + } + } } - // return XMLHttpRequest to allow aborting the request etc. - return xhr; + return jqXHR; }, // Serialize an array of form elements or a set of @@ -6079,37 +7325,37 @@ jQuery.extend({ var s = [], add = function( key, value ) { // If value is a function, invoke it and return its value - value = jQuery.isFunction(value) ? value() : value; - s[ s.length ] = encodeURIComponent(key) + "=" + encodeURIComponent(value); + value = jQuery.isFunction( value ) ? value() : value; + s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value ); }; - + // Set traditional to true for jQuery <= 1.3.2 behavior. if ( traditional === undefined ) { traditional = jQuery.ajaxSettings.traditional; } - + // If an array was passed in, assume that it is an array of form elements. - if ( jQuery.isArray(a) || a.jquery ) { + if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { // Serialize the form elements jQuery.each( a, function() { add( this.name, this.value ); }); - + } else { // If traditional, encode the "old" way (the way 1.3.2 or older // did it), otherwise encode params recursively. for ( var prefix in a ) { - buildParams( prefix, a[prefix], traditional, add ); + buildParams( prefix, a[ prefix ], traditional, add ); } } // Return the resulting serialization - return s.join("&").replace(r20, "+"); + return s.join( "&" ).replace( r20, "+" ); } }); function buildParams( prefix, obj, traditional, add ) { - if ( jQuery.isArray(obj) && obj.length ) { + if ( jQuery.isArray( obj ) ) { // Serialize array item. jQuery.each( obj, function( i, v ) { if ( traditional || rbracket.test( prefix ) ) { @@ -6127,18 +7373,13 @@ function buildParams( prefix, obj, traditional, add ) { buildParams( prefix + "[" + ( typeof v === "object" || jQuery.isArray(v) ? i : "" ) + "]", v, traditional, add ); } }); - - } else if ( !traditional && obj != null && typeof obj === "object" ) { - if ( jQuery.isEmptyObject( obj ) ) { - add( prefix, "" ); + } else if ( !traditional && obj != null && typeof obj === "object" ) { // Serialize object item. - } else { - jQuery.each( obj, function( k, v ) { - buildParams( prefix + "[" + k + "]", v, traditional, add ); - }); + for ( var name in obj ) { + buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); } - + } else { // Serialize scalar item. add( prefix, obj ); @@ -6154,143 +7395,557 @@ jQuery.extend({ // Last-Modified header cache for next request lastModified: {}, - etag: {}, + etag: {} - handleError: function( s, xhr, status, e ) { - // If a local callback was specified, fire it - if ( s.error ) { - s.error.call( s.context, xhr, status, e ); - } +}); - // Fire the global callback - if ( s.global ) { - jQuery.triggerGlobal( s, "ajaxError", [xhr, s, e] ); - } - }, +/* Handles responses to an ajax request: + * - sets all responseXXX fields accordingly + * - finds the right dataType (mediates between content-type and expected dataType) + * - returns the corresponding response + */ +function ajaxHandleResponses( s, jqXHR, responses ) { - handleSuccess: function( s, xhr, status, data ) { - // If a local callback was specified, fire it and pass it the data - if ( s.success ) { - s.success.call( s.context, data, status, xhr ); - } + var contents = s.contents, + dataTypes = s.dataTypes, + responseFields = s.responseFields, + ct, + type, + finalDataType, + firstDataType; - // Fire the global callback - if ( s.global ) { - jQuery.triggerGlobal( s, "ajaxSuccess", [xhr, s] ); + // Fill responseXXX fields + for( type in responseFields ) { + if ( type in responses ) { + jqXHR[ responseFields[type] ] = responses[ type ]; } - }, + } - handleComplete: function( s, xhr, status ) { - // Process result - if ( s.complete ) { - s.complete.call( s.context, xhr, status ); + // Remove auto dataType and get content-type in the process + while( dataTypes[ 0 ] === "*" ) { + dataTypes.shift(); + if ( ct === undefined ) { + ct = s.mimeType || jqXHR.getResponseHeader( "content-type" ); } + } - // The request was completed - if ( s.global ) { - jQuery.triggerGlobal( s, "ajaxComplete", [xhr, s] ); + // Check if we're dealing with a known content-type + if ( ct ) { + for ( type in contents ) { + if ( contents[ type ] && contents[ type ].test( ct ) ) { + dataTypes.unshift( type ); + break; + } } + } - // Handle the global AJAX counter - if ( s.global && jQuery.active-- === 1 ) { - jQuery.event.trigger( "ajaxStop" ); + // Check to see if we have a response for the expected dataType + if ( dataTypes[ 0 ] in responses ) { + finalDataType = dataTypes[ 0 ]; + } else { + // Try convertible dataTypes + for ( type in responses ) { + if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[0] ] ) { + finalDataType = type; + break; + } + if ( !firstDataType ) { + firstDataType = type; + } } - }, - - triggerGlobal: function( s, type, args ) { - (s.context && s.context.url == null ? jQuery(s.context) : jQuery.event).trigger(type, args); - }, + // Or just use first one + finalDataType = finalDataType || firstDataType; + } - // Determines if an XMLHttpRequest was successful or not - httpSuccess: function( xhr ) { - try { - // IE error sometimes returns 1223 when it should be 204 so treat it as success, see #1450 - return !xhr.status && location.protocol === "file:" || - xhr.status >= 200 && xhr.status < 300 || - xhr.status === 304 || xhr.status === 1223; - } catch(e) {} + // If we found a dataType + // We add the dataType to the list if needed + // and return the corresponding response + if ( finalDataType ) { + if ( finalDataType !== dataTypes[ 0 ] ) { + dataTypes.unshift( finalDataType ); + } + return responses[ finalDataType ]; + } +} - return false; - }, +// Chain conversions given the request and the original response +function ajaxConvert( s, response ) { - // Determines if an XMLHttpRequest returns NotModified - httpNotModified: function( xhr, url ) { - var lastModified = xhr.getResponseHeader("Last-Modified"), - etag = xhr.getResponseHeader("Etag"); + // Apply the dataFilter if provided + if ( s.dataFilter ) { + response = s.dataFilter( response, s.dataType ); + } - if ( lastModified ) { - jQuery.lastModified[url] = lastModified; + var dataTypes = s.dataTypes, + converters = {}, + i, + key, + length = dataTypes.length, + tmp, + // Current and previous dataTypes + current = dataTypes[ 0 ], + prev, + // Conversion expression + conversion, + // Conversion function + conv, + // Conversion functions (transitive conversion) + conv1, + conv2; + + // For each dataType in the chain + for( i = 1; i < length; i++ ) { + + // Create converters map + // with lowercased keys + if ( i === 1 ) { + for( key in s.converters ) { + if( typeof key === "string" ) { + converters[ key.toLowerCase() ] = s.converters[ key ]; + } + } } - if ( etag ) { - jQuery.etag[url] = etag; + // Get the dataTypes + prev = current; + current = dataTypes[ i ]; + + // If current is auto dataType, update it to prev + if( current === "*" ) { + current = prev; + // If no auto and dataTypes are actually different + } else if ( prev !== "*" && prev !== current ) { + + // Get the converter + conversion = prev + " " + current; + conv = converters[ conversion ] || converters[ "* " + current ]; + + // If there is no direct converter, search transitively + if ( !conv ) { + conv2 = undefined; + for( conv1 in converters ) { + tmp = conv1.split( " " ); + if ( tmp[ 0 ] === prev || tmp[ 0 ] === "*" ) { + conv2 = converters[ tmp[1] + " " + current ]; + if ( conv2 ) { + conv1 = converters[ conv1 ]; + if ( conv1 === true ) { + conv = conv2; + } else if ( conv2 === true ) { + conv = conv1; + } + break; + } + } + } + } + // If we found no converter, dispatch an error + if ( !( conv || conv2 ) ) { + jQuery.error( "No conversion from " + conversion.replace(" "," to ") ); + } + // If found converter is not an equivalence + if ( conv !== true ) { + // Convert with 1 or 2 converters accordingly + response = conv ? conv( response ) : conv2( conv1(response) ); + } } + } + return response; +} - return xhr.status === 304; - }, - httpData: function( xhr, type, s ) { - var ct = xhr.getResponseHeader("content-type") || "", - xml = type === "xml" || !type && ct.indexOf("xml") >= 0, - data = xml ? xhr.responseXML : xhr.responseText; - if ( xml && data.documentElement.nodeName === "parsererror" ) { - jQuery.error( "parsererror" ); - } - // Allow a pre-filtering function to sanitize the response - // s is checked to keep backwards compatibility - if ( s && s.dataFilter ) { - data = s.dataFilter( data, type ); +var jsc = jQuery.now(), + jsre = /(\=)\?(&|$)|\?\?/i; + +// Default jsonp settings +jQuery.ajaxSetup({ + jsonp: "callback", + jsonpCallback: function() { + return jQuery.expando + "_" + ( jsc++ ); + } +}); + +// Detect, normalize options and install callbacks for jsonp requests +jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) { + + var inspectData = s.contentType === "application/x-www-form-urlencoded" && + ( typeof s.data === "string" ); + + if ( s.dataTypes[ 0 ] === "jsonp" || + s.jsonp !== false && ( jsre.test( s.url ) || + inspectData && jsre.test( s.data ) ) ) { + + var responseContainer, + jsonpCallback = s.jsonpCallback = + jQuery.isFunction( s.jsonpCallback ) ? s.jsonpCallback() : s.jsonpCallback, + previous = window[ jsonpCallback ], + url = s.url, + data = s.data, + replace = "$1" + jsonpCallback + "$2"; + + if ( s.jsonp !== false ) { + url = url.replace( jsre, replace ); + if ( s.url === url ) { + if ( inspectData ) { + data = data.replace( jsre, replace ); + } + if ( s.data === data ) { + // Add callback manually + url += (/\?/.test( url ) ? "&" : "?") + s.jsonp + "=" + jsonpCallback; + } + } } - // The filter can actually parse the response - if ( typeof data === "string" ) { - // Get the JavaScript object, if JSON is used. - if ( type === "json" || !type && ct.indexOf("json") >= 0 ) { - data = jQuery.parseJSON( data ); + s.url = url; + s.data = data; + + // Install callback + window[ jsonpCallback ] = function( response ) { + responseContainer = [ response ]; + }; + + // Clean-up function + jqXHR.always(function() { + // Set callback back to previous value + window[ jsonpCallback ] = previous; + // Call if it was a function and we have a response + if ( responseContainer && jQuery.isFunction( previous ) ) { + window[ jsonpCallback ]( responseContainer[ 0 ] ); + } + }); - // If the type is "script", eval it in global context - } else if ( type === "script" || !type && ct.indexOf("javascript") >= 0 ) { - jQuery.globalEval( data ); + // Use data converter to retrieve json after script execution + s.converters["script json"] = function() { + if ( !responseContainer ) { + jQuery.error( jsonpCallback + " was not called" ); } + return responseContainer[ 0 ]; + }; + + // force json dataType + s.dataTypes[ 0 ] = "json"; + + // Delegate to script + return "script"; + } +}); + + + + +// Install script dataType +jQuery.ajaxSetup({ + accepts: { + script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript" + }, + contents: { + script: /javascript|ecmascript/ + }, + converters: { + "text script": function( text ) { + jQuery.globalEval( text ); + return text; } + } +}); - return data; +// Handle cache's special case and global +jQuery.ajaxPrefilter( "script", function( s ) { + if ( s.cache === undefined ) { + s.cache = false; + } + if ( s.crossDomain ) { + s.type = "GET"; + s.global = false; } +}); + +// Bind script tag hack transport +jQuery.ajaxTransport( "script", function(s) { + + // This transport only deals with cross domain requests + if ( s.crossDomain ) { + + var script, + head = document.head || document.getElementsByTagName( "head" )[0] || document.documentElement; + + return { + + send: function( _, callback ) { + + script = document.createElement( "script" ); + + script.async = "async"; + + if ( s.scriptCharset ) { + script.charset = s.scriptCharset; + } + + script.src = s.url; + + // Attach handlers for all browsers + script.onload = script.onreadystatechange = function( _, isAbort ) { + + if ( isAbort || !script.readyState || /loaded|complete/.test( script.readyState ) ) { + + // Handle memory leak in IE + script.onload = script.onreadystatechange = null; + + // Remove the script + if ( head && script.parentNode ) { + head.removeChild( script ); + } + + // Dereference the script + script = undefined; + + // Callback if not abort + if ( !isAbort ) { + callback( 200, "success" ); + } + } + }; + // Use insertBefore instead of appendChild to circumvent an IE6 bug. + // This arises when a base node is used (#2709 and #4378). + head.insertBefore( script, head.firstChild ); + }, + abort: function() { + if ( script ) { + script.onload( 0, 1 ); + } + } + }; + } }); -/* - * Create the request object; Microsoft failed to properly - * implement the XMLHttpRequest in IE7 (can't request local files), - * so we use the ActiveXObject when it is available - * Additionally XMLHttpRequest can be disabled in IE7/IE8 so - * we need a fallback. - */ -if ( window.ActiveXObject ) { - jQuery.ajaxSettings.xhr = function() { - if ( window.location.protocol !== "file:" ) { - try { - return new window.XMLHttpRequest(); - } catch(xhrError) {} + + + +var // #5280: Internet Explorer will keep connections alive if we don't abort on unload + xhrOnUnloadAbort = window.ActiveXObject ? function() { + // Abort all pending requests + for ( var key in xhrCallbacks ) { + xhrCallbacks[ key ]( 0, 1 ); } + } : false, + xhrId = 0, + xhrCallbacks; - try { - return new window.ActiveXObject("Microsoft.XMLHTTP"); - } catch(activeError) {} - }; +// Functions to create xhrs +function createStandardXHR() { + try { + return new window.XMLHttpRequest(); + } catch( e ) {} +} + +function createActiveXHR() { + try { + return new window.ActiveXObject( "Microsoft.XMLHTTP" ); + } catch( e ) {} } -// Does this browser support XHR requests? -jQuery.support.ajax = !!jQuery.ajaxSettings.xhr(); +// Create the request object +// (This is still attached to ajaxSettings for backward compatibility) +jQuery.ajaxSettings.xhr = window.ActiveXObject ? + /* Microsoft failed to properly + * implement the XMLHttpRequest in IE7 (can't request local files), + * so we use the ActiveXObject when it is available + * Additionally XMLHttpRequest can be disabled in IE7/IE8 so + * we need a fallback. + */ + function() { + return !this.isLocal && createStandardXHR() || createActiveXHR(); + } : + // For all other browsers, use the standard XMLHttpRequest object + createStandardXHR; + +// Determine support properties +(function( xhr ) { + jQuery.extend( jQuery.support, { + ajax: !!xhr, + cors: !!xhr && ( "withCredentials" in xhr ) + }); +})( jQuery.ajaxSettings.xhr() ); + +// Create transport if the browser can provide an xhr +if ( jQuery.support.ajax ) { + + jQuery.ajaxTransport(function( s ) { + // Cross domain only allowed if supported through XMLHttpRequest + if ( !s.crossDomain || jQuery.support.cors ) { + + var callback; + + return { + send: function( headers, complete ) { + + // Get a new xhr + var xhr = s.xhr(), + handle, + i; + + // Open the socket + // Passing null username, generates a login popup on Opera (#2865) + if ( s.username ) { + xhr.open( s.type, s.url, s.async, s.username, s.password ); + } else { + xhr.open( s.type, s.url, s.async ); + } + + // Apply custom fields if provided + if ( s.xhrFields ) { + for ( i in s.xhrFields ) { + xhr[ i ] = s.xhrFields[ i ]; + } + } + + // Override mime type if needed + if ( s.mimeType && xhr.overrideMimeType ) { + xhr.overrideMimeType( s.mimeType ); + } + + // X-Requested-With header + // For cross-domain requests, seeing as conditions for a preflight are + // akin to a jigsaw puzzle, we simply never set it to be sure. + // (it can always be set on a per-request basis or even using ajaxSetup) + // For same-domain requests, won't change header if already provided. + if ( !s.crossDomain && !headers["X-Requested-With"] ) { + headers[ "X-Requested-With" ] = "XMLHttpRequest"; + } + + // Need an extra try/catch for cross domain requests in Firefox 3 + try { + for ( i in headers ) { + xhr.setRequestHeader( i, headers[ i ] ); + } + } catch( _ ) {} + + // Do send the request + // This may raise an exception which is actually + // handled in jQuery.ajax (so no try/catch here) + xhr.send( ( s.hasContent && s.data ) || null ); + + // Listener + callback = function( _, isAbort ) { + + var status, + statusText, + responseHeaders, + responses, + xml; + + // Firefox throws exceptions when accessing properties + // of an xhr when a network error occured + // http://helpful.knobs-dials.com/index.php/Component_returned_failure_code:_0x80040111_(NS_ERROR_NOT_AVAILABLE) + try { + + // Was never called and is aborted or complete + if ( callback && ( isAbort || xhr.readyState === 4 ) ) { + + // Only called once + callback = undefined; + + // Do not keep as active anymore + if ( handle ) { + xhr.onreadystatechange = jQuery.noop; + if ( xhrOnUnloadAbort ) { + delete xhrCallbacks[ handle ]; + } + } + + // If it's an abort + if ( isAbort ) { + // Abort it manually if needed + if ( xhr.readyState !== 4 ) { + xhr.abort(); + } + } else { + status = xhr.status; + responseHeaders = xhr.getAllResponseHeaders(); + responses = {}; + xml = xhr.responseXML; + + // Construct response list + if ( xml && xml.documentElement /* #4958 */ ) { + responses.xml = xml; + } + responses.text = xhr.responseText; + + // Firefox throws an exception when accessing + // statusText for faulty cross-domain requests + try { + statusText = xhr.statusText; + } catch( e ) { + // We normalize with Webkit giving an empty statusText + statusText = ""; + } + + // Filter status for non standard behaviors + + // If the request is local and we have data: assume a success + // (success with no data won't get notified, that's the best we + // can do given current implementations) + if ( !status && s.isLocal && !s.crossDomain ) { + status = responses.text ? 200 : 404; + // IE - #1450: sometimes returns 1223 when it should be 204 + } else if ( status === 1223 ) { + status = 204; + } + } + } + } catch( firefoxAccessException ) { + if ( !isAbort ) { + complete( -1, firefoxAccessException ); + } + } + + // Call complete if needed + if ( responses ) { + complete( status, statusText, responses, responseHeaders ); + } + }; + + // if we're in sync mode or it's in cache + // and has been retrieved directly (IE6 & IE7) + // we need to manually fire the callback + if ( !s.async || xhr.readyState === 4 ) { + callback(); + } else { + handle = ++xhrId; + if ( xhrOnUnloadAbort ) { + // Create the active xhrs callbacks list if needed + // and attach the unload handler + if ( !xhrCallbacks ) { + xhrCallbacks = {}; + jQuery( window ).unload( xhrOnUnloadAbort ); + } + // Add to list of active xhrs callbacks + xhrCallbacks[ handle ] = callback; + } + xhr.onreadystatechange = callback; + } + }, + + abort: function() { + if ( callback ) { + callback(0,1); + } + } + }; + } + }); +} var elemdisplay = {}, + iframe, iframeDoc, rfxtypes = /^(?:toggle|show|hide)$/, - rfxnum = /^([+\-]=)?([\d+.\-]+)(.*)$/, + rfxnum = /^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i, timerId, fxAttrs = [ // height animations @@ -6299,7 +7954,11 @@ var elemdisplay = {}, [ "width", "marginLeft", "marginRight", "paddingLeft", "paddingRight" ], // opacity animations [ "opacity" ] - ]; + ], + fxNow, + requestAnimationFrame = window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + window.oRequestAnimationFrame; jQuery.fn.extend({ show: function( speed, easing, callback ) { @@ -6311,19 +7970,22 @@ jQuery.fn.extend({ } else { for ( var i = 0, j = this.length; i < j; i++ ) { elem = this[i]; - display = elem.style.display; - // Reset the inline display of this element to learn if it is - // being hidden by cascaded rules or not - if ( !jQuery.data(elem, "olddisplay") && display === "none" ) { - display = elem.style.display = ""; - } + if ( elem.style ) { + display = elem.style.display; + + // Reset the inline display of this element to learn if it is + // being hidden by cascaded rules or not + if ( !jQuery._data(elem, "olddisplay") && display === "none" ) { + display = elem.style.display = ""; + } - // Set elements which have been overridden with display: none - // in a stylesheet to whatever the default browser style is - // for such an element - if ( display === "" && jQuery.css( elem, "display" ) === "none" ) { - jQuery.data(elem, "olddisplay", defaultDisplay(elem.nodeName)); + // Set elements which have been overridden with display: none + // in a stylesheet to whatever the default browser style is + // for such an element + if ( display === "" && jQuery.css( elem, "display" ) === "none" ) { + jQuery._data(elem, "olddisplay", defaultDisplay(elem.nodeName)); + } } } @@ -6331,10 +7993,13 @@ jQuery.fn.extend({ // to avoid the constant reflow for ( i = 0; i < j; i++ ) { elem = this[i]; - display = elem.style.display; - if ( display === "" || display === "none" ) { - elem.style.display = jQuery.data(elem, "olddisplay") || ""; + if ( elem.style ) { + display = elem.style.display; + + if ( display === "" || display === "none" ) { + elem.style.display = jQuery._data(elem, "olddisplay") || ""; + } } } @@ -6348,17 +8013,21 @@ jQuery.fn.extend({ } else { for ( var i = 0, j = this.length; i < j; i++ ) { - var display = jQuery.css( this[i], "display" ); + if ( this[i].style ) { + var display = jQuery.css( this[i], "display" ); - if ( display !== "none" ) { - jQuery.data( this[i], "olddisplay", display ); + if ( display !== "none" && !jQuery._data( this[i], "olddisplay" ) ) { + jQuery._data( this[i], "olddisplay", display ); + } } } // Set the display of the elements in a second loop // to avoid the constant reflow for ( i = 0; i < j; i++ ) { - this[i].style.display = "none"; + if ( this[i].style ) { + this[i].style.display = "none"; + } } return this; @@ -6396,32 +8065,54 @@ jQuery.fn.extend({ var optall = jQuery.speed(speed, easing, callback); if ( jQuery.isEmptyObject( prop ) ) { - return this.each( optall.complete ); + return this.each( optall.complete, [ false ] ); } + // Do not change referenced properties as per-property easing will be lost + prop = jQuery.extend( {}, prop ); + return this[ optall.queue === false ? "each" : "queue" ](function() { // XXX 'this' does not always have a nodeName when running the // test suite - var opt = jQuery.extend({}, optall), p, + if ( optall.queue === false ) { + jQuery._mark( this ); + } + + var opt = jQuery.extend( {}, optall ), isElement = this.nodeType === 1, hidden = isElement && jQuery(this).is(":hidden"), - self = this; + name, val, p, + display, e, + parts, start, end, unit; + + // will store per property easing and be used to determine when an animation is complete + opt.animatedProperties = {}; for ( p in prop ) { - var name = jQuery.camelCase( p ); + // property name normalization + name = jQuery.camelCase( p ); if ( p !== name ) { prop[ name ] = prop[ p ]; delete prop[ p ]; - p = name; } - if ( prop[p] === "hide" && hidden || prop[p] === "show" && !hidden ) { - return opt.complete.call(this); + val = prop[ name ]; + + // easing resolution: per property > opt.specialEasing > opt.easing > 'swing' (default) + if ( jQuery.isArray( val ) ) { + opt.animatedProperties[ name ] = val[ 1 ]; + val = prop[ name ] = val[ 0 ]; + } else { + opt.animatedProperties[ name ] = opt.specialEasing && opt.specialEasing[ name ] || opt.easing || 'swing'; + } + + if ( val === "hide" && hidden || val === "show" && !hidden ) { + return opt.complete.call( this ); } - if ( isElement && ( p === "height" || p === "width" ) ) { + if ( isElement && ( name === "height" || name === "width" ) ) { // Make sure that nothing sneaks out // Record all 3 overflow attributes because IE does not // change the overflow attribute when overflowX and @@ -6437,7 +8128,7 @@ jQuery.fn.extend({ this.style.display = "inline-block"; } else { - var display = defaultDisplay(this.nodeName); + display = defaultDisplay( this.nodeName ); // inline-level elements accept inline-block; // block-level elements need to be inline with layout @@ -6451,44 +8142,37 @@ jQuery.fn.extend({ } } } - - if ( jQuery.isArray( prop[p] ) ) { - // Create (if needed) and add to specialEasing - (opt.specialEasing = opt.specialEasing || {})[p] = prop[p][1]; - prop[p] = prop[p][0]; - } } if ( opt.overflow != null ) { this.style.overflow = "hidden"; } - opt.curAnim = jQuery.extend({}, prop); - - jQuery.each( prop, function( name, val ) { - var e = new jQuery.fx( self, opt, name ); + for ( p in prop ) { + e = new jQuery.fx( this, opt, p ); + val = prop[ p ]; if ( rfxtypes.test(val) ) { - e[ val === "toggle" ? hidden ? "show" : "hide" : val ]( prop ); + e[ val === "toggle" ? hidden ? "show" : "hide" : val ](); } else { - var parts = rfxnum.exec(val), - start = e.cur() || 0; + parts = rfxnum.exec( val ); + start = e.cur(); if ( parts ) { - var end = parseFloat( parts[2] ), - unit = parts[3] || "px"; + end = parseFloat( parts[2] ); + unit = parts[3] || ( jQuery.cssNumber[ p ] ? "" : "px" ); // We need to compute starting value if ( unit !== "px" ) { - jQuery.style( self, name, (end || 1) + unit); + jQuery.style( this, p, (end || 1) + unit); start = ((end || 1) / e.cur()) * start; - jQuery.style( self, name, start + unit); + jQuery.style( this, p, start + unit); } // If a +=/-= token was provided, we're doing a relative animation if ( parts[1] ) { - end = ((parts[1] === "-=" ? -1 : 1) * end) + start; + end = ( (parts[ 1 ] === "-=" ? -1 : 1) * end ) + start; } e.custom( start, end, unit ); @@ -6497,7 +8181,7 @@ jQuery.fn.extend({ e.custom( start, val, "" ); } } - }); + } // For JS strict compliance return true; @@ -6505,15 +8189,18 @@ jQuery.fn.extend({ }, stop: function( clearQueue, gotoEnd ) { - var timers = jQuery.timers; - if ( clearQueue ) { this.queue([]); } this.each(function() { - // go in reverse order so anything added to the queue during the loop is ignored - for ( var i = timers.length - 1; i >= 0; i-- ) { + var timers = jQuery.timers, + i = timers.length; + // clear marker counters if we know they won't be + if ( !gotoEnd ) { + jQuery._unmark( true, this ); + } + while ( i-- ) { if ( timers[i].elem === this ) { if (gotoEnd) { // force the next step to be the last @@ -6535,6 +8222,17 @@ jQuery.fn.extend({ }); +// Animations created synchronously will run synchronously +function createFxNow() { + setTimeout( clearFxNow, 0 ); + return ( fxNow = jQuery.now() ); +} + +function clearFxNow() { + fxNow = undefined; +} + +// Generate parameters to create a standard animation function genFx( type, num ) { var obj = {}; @@ -6573,10 +8271,13 @@ jQuery.extend({ // Queueing opt.old = opt.complete; - opt.complete = function() { + opt.complete = function( noUnmark ) { if ( opt.queue !== false ) { - jQuery(this).dequeue(); + jQuery.dequeue( this ); + } else if ( noUnmark !== false ) { + jQuery._unmark( this ); } + if ( jQuery.isFunction( opt.old ) ) { opt.old.call( this ); } @@ -6601,9 +8302,7 @@ jQuery.extend({ this.elem = elem; this.prop = prop; - if ( !options.orig ) { - options.orig = {}; - } + options.orig = options.orig || {}; } }); @@ -6624,19 +8323,24 @@ jQuery.fx.prototype = { return this.elem[ this.prop ]; } - var r = parseFloat( jQuery.css( this.elem, this.prop ) ); - return r && r > -10000 ? r : 0; + var parsed, + r = jQuery.css( this.elem, this.prop ); + // Empty strings, null, undefined and "auto" are converted to 0, + // complex values such as "rotate(1rad)" are returned as is, + // simple values such as "10px" are parsed to Float. + return isNaN( parsed = parseFloat( r ) ) ? !r || r === "auto" ? 0 : r : parsed; }, // Start an animation from one number to another custom: function( from, to, unit ) { var self = this, - fx = jQuery.fx; + fx = jQuery.fx, + raf; - this.startTime = jQuery.now(); + this.startTime = fxNow || createFxNow(); this.start = from; this.end = to; - this.unit = unit || this.unit || "px"; + this.unit = unit || this.unit || ( jQuery.cssNumber[ this.prop ] ? "" : "px" ); this.now = this.start; this.pos = this.state = 0; @@ -6647,7 +8351,20 @@ jQuery.fx.prototype = { t.elem = this.elem; if ( t() && jQuery.timers.push(t) && !timerId ) { - timerId = setInterval(fx.tick, fx.interval); + // Use requestAnimationFrame instead of setInterval if available + if ( requestAnimationFrame ) { + timerId = 1; + raf = function() { + // When timerId gets set to null at any point, this stops + if ( timerId ) { + requestAnimationFrame( raf ); + fx.tick(); + } + }; + requestAnimationFrame( raf ); + } else { + timerId = setInterval( fx.tick, fx.interval ); + } } }, @@ -6678,60 +8395,64 @@ jQuery.fx.prototype = { // Each step of an animation step: function( gotoEnd ) { - var t = jQuery.now(), done = true; + var t = fxNow || createFxNow(), + done = true, + elem = this.elem, + options = this.options, + i, n; - if ( gotoEnd || t >= this.options.duration + this.startTime ) { + if ( gotoEnd || t >= options.duration + this.startTime ) { this.now = this.end; this.pos = this.state = 1; this.update(); - this.options.curAnim[ this.prop ] = true; + options.animatedProperties[ this.prop ] = true; - for ( var i in this.options.curAnim ) { - if ( this.options.curAnim[i] !== true ) { + for ( i in options.animatedProperties ) { + if ( options.animatedProperties[i] !== true ) { done = false; } } if ( done ) { // Reset the overflow - if ( this.options.overflow != null && !jQuery.support.shrinkWrapBlocks ) { - var elem = this.elem, - options = this.options; + if ( options.overflow != null && !jQuery.support.shrinkWrapBlocks ) { jQuery.each( [ "", "X", "Y" ], function (index, value) { elem.style[ "overflow" + value ] = options.overflow[index]; - } ); + }); } // Hide the element if the "hide" operation was done - if ( this.options.hide ) { - jQuery(this.elem).hide(); + if ( options.hide ) { + jQuery(elem).hide(); } // Reset the properties, if the item has been hidden or shown - if ( this.options.hide || this.options.show ) { - for ( var p in this.options.curAnim ) { - jQuery.style( this.elem, p, this.options.orig[p] ); + if ( options.hide || options.show ) { + for ( var p in options.animatedProperties ) { + jQuery.style( elem, p, options.orig[p] ); } } // Execute the complete function - this.options.complete.call( this.elem ); + options.complete.call( elem ); } return false; } else { - var n = t - this.startTime; - this.state = n / this.options.duration; - - // Perform the easing function, defaults to swing - var specialEasing = this.options.specialEasing && this.options.specialEasing[this.prop]; - var defaultEasing = this.options.easing || (jQuery.easing.swing ? "swing" : "linear"); - this.pos = jQuery.easing[specialEasing || defaultEasing](this.state, n, 0, 1, this.options.duration); - this.now = this.start + ((this.end - this.start) * this.pos); + // classical easing cannot be used with an Infinity duration + if ( options.duration == Infinity ) { + this.now = t; + } else { + n = t - this.startTime; + this.state = n / options.duration; + // Perform the easing function, defaults to swing + this.pos = jQuery.easing[ options.animatedProperties[ this.prop ] ]( this.state, n, 0, 1, options.duration ); + this.now = this.start + ((this.end - this.start) * this.pos); + } // Perform the next step of the animation this.update(); } @@ -6742,9 +8463,7 @@ jQuery.fx.prototype = { jQuery.extend( jQuery.fx, { tick: function() { - var timers = jQuery.timers; - - for ( var i = 0; i < timers.length; i++ ) { + for ( var timers = jQuery.timers, i = 0 ; i < timers.length ; ++i ) { if ( !timers[i]() ) { timers.splice(i--, 1); } @@ -6792,17 +8511,45 @@ if ( jQuery.expr && jQuery.expr.filters ) { }; } +// Try to restore the default display value of an element function defaultDisplay( nodeName ) { + if ( !elemdisplay[ nodeName ] ) { - var elem = jQuery("<" + nodeName + ">").appendTo("body"), - display = elem.css("display"); + + var elem = jQuery( "<" + nodeName + ">" ).appendTo( "body" ), + display = elem.css( "display" ); elem.remove(); + // If the simple way fails, + // get element's real default display by attaching it to a temp iframe if ( display === "none" || display === "" ) { - display = "block"; + // No iframe to use yet, so create it + if ( !iframe ) { + iframe = document.createElement( "iframe" ); + iframe.frameBorder = iframe.width = iframe.height = 0; + } + + document.body.appendChild( iframe ); + + // Create a cacheable copy of the iframe document on first call. + // IE and Opera will allow us to reuse the iframeDoc without re-writing the fake html + // document to it, Webkit & Firefox won't allow reusing the iframe document + if ( !iframeDoc || !iframe.createElement ) { + iframeDoc = ( iframe.contentWindow || iframe.contentDocument ).document; + iframeDoc.write( "<!doctype><html><body></body></html>" ); + } + + elem = iframeDoc.createElement( nodeName ); + + iframeDoc.body.appendChild( elem ); + + display = jQuery.css( elem, "display" ); + + document.body.removeChild( iframe ); } + // Store the correct default display elemdisplay[ nodeName ] = display; } @@ -6819,7 +8566,7 @@ if ( "getBoundingClientRect" in document.documentElement ) { jQuery.fn.offset = function( options ) { var elem = this[0], box; - if ( options ) { + if ( options ) { return this.each(function( i ) { jQuery.offset.setOffset( this, options, i ); }); @@ -6842,15 +8589,15 @@ if ( "getBoundingClientRect" in document.documentElement ) { // Make sure we're not dealing with a disconnected DOM node if ( !box || !jQuery.contains( docElem, elem ) ) { - return box || { top: 0, left: 0 }; + return box ? { top: box.top, left: box.left } : { top: 0, left: 0 }; } var body = doc.body, win = getWindow(doc), clientTop = docElem.clientTop || body.clientTop || 0, clientLeft = docElem.clientLeft || body.clientLeft || 0, - scrollTop = (win.pageYOffset || jQuery.support.boxModel && docElem.scrollTop || body.scrollTop ), - scrollLeft = (win.pageXOffset || jQuery.support.boxModel && docElem.scrollLeft || body.scrollLeft), + scrollTop = win.pageYOffset || jQuery.support.boxModel && docElem.scrollTop || body.scrollTop, + scrollLeft = win.pageXOffset || jQuery.support.boxModel && docElem.scrollLeft || body.scrollLeft, top = box.top + scrollTop - clientTop, left = box.left + scrollLeft - clientLeft; @@ -6861,7 +8608,7 @@ if ( "getBoundingClientRect" in document.documentElement ) { jQuery.fn.offset = function( options ) { var elem = this[0]; - if ( options ) { + if ( options ) { return this.each(function( i ) { jQuery.offset.setOffset( this, options, i ); }); @@ -6963,7 +8710,6 @@ jQuery.offset = { this.doesNotIncludeMarginInBodyOffset = (body.offsetTop !== bodyMarginTop); body.removeChild( container ); - body = container = innerDiv = checkDiv = table = td = null; jQuery.offset.initialize = jQuery.noop; }, @@ -6980,7 +8726,7 @@ jQuery.offset = { return { top: top, left: left }; }, - + setOffset: function( elem, options, i ) { var position = jQuery.css( elem, "position" ); @@ -6993,17 +8739,19 @@ jQuery.offset = { curOffset = curElem.offset(), curCSSTop = jQuery.css( elem, "top" ), curCSSLeft = jQuery.css( elem, "left" ), - calculatePosition = (position === "absolute" && jQuery.inArray('auto', [curCSSTop, curCSSLeft]) > -1), + calculatePosition = (position === "absolute" || position === "fixed") && jQuery.inArray("auto", [curCSSTop, curCSSLeft]) > -1, props = {}, curPosition = {}, curTop, curLeft; - // need to be able to calculate position if either top or left is auto and position is absolute + // need to be able to calculate position if either top or left is auto and position is either absolute or fixed if ( calculatePosition ) { curPosition = curElem.position(); + curTop = curPosition.top; + curLeft = curPosition.left; + } else { + curTop = parseFloat( curCSSTop ) || 0; + curLeft = parseFloat( curCSSLeft ) || 0; } - curTop = calculatePosition ? curPosition.top : parseInt( curCSSTop, 10 ) || 0; - curLeft = calculatePosition ? curPosition.left : parseInt( curCSSLeft, 10 ) || 0; - if ( jQuery.isFunction( options ) ) { options = options.call( elem, i, curOffset ); } @@ -7014,7 +8762,7 @@ jQuery.offset = { if (options.left != null) { props.left = (options.left - curOffset.left) + curLeft; } - + if ( "using" in options ) { options.using.call( elem, props ); } else { @@ -7072,29 +8820,16 @@ jQuery.fn.extend({ jQuery.each( ["Left", "Top"], function( i, name ) { var method = "scroll" + name; - jQuery.fn[ method ] = function(val) { - var elem = this[0], win; - - if ( !elem ) { - return null; - } + jQuery.fn[ method ] = function( val ) { + var elem, win; - if ( val !== undefined ) { - // Set the scroll offset - return this.each(function() { - win = getWindow( this ); + if ( val === undefined ) { + elem = this[ 0 ]; - if ( win ) { - win.scrollTo( - !i ? val : jQuery(win).scrollLeft(), - i ? val : jQuery(win).scrollTop() - ); + if ( !elem ) { + return null; + } - } else { - this[ method ] = val; - } - }); - } else { win = getWindow( elem ); // Return the scroll offset @@ -7103,6 +8838,21 @@ jQuery.each( ["Left", "Top"], function( i, name ) { win.document.body[ method ] : elem[ method ]; } + + // Set the scroll offset + return this.each(function() { + win = getWindow( this ); + + if ( win ) { + win.scrollTo( + !i ? val : jQuery( win ).scrollLeft(), + i ? val : jQuery( win ).scrollTop() + ); + + } else { + this[ method ] = val; + } + }); }; }); @@ -7142,7 +8892,7 @@ jQuery.each([ "Height", "Width" ], function( i, name ) { if ( !elem ) { return size == null ? null : this; } - + if ( jQuery.isFunction( size ) ) { return this.each(function( i ) { var self = jQuery( this ); @@ -7152,8 +8902,10 @@ jQuery.each([ "Height", "Width" ], function( i, name ) { if ( jQuery.isWindow( elem ) ) { // Everyone else use document.documentElement or document.body depending on Quirks vs Standards mode - return elem.document.compatMode === "CSS1Compat" && elem.document.documentElement[ "client" + name ] || - elem.document.body[ "client" + name ]; + // 3rd condition allows Nokia support, as it supports the docElem prop but not CSS1Compat + var docElemProp = elem.document.documentElement[ "client" + name ]; + return elem.document.compatMode === "CSS1Compat" && docElemProp || + elem.document.body[ "client" + name ] || docElemProp; // Get document width or height } else if ( elem.nodeType === 9 ) { @@ -7180,4 +8932,5 @@ jQuery.each([ "Height", "Width" ], function( i, name ) { }); +window.jQuery = window.$ = jQuery; })(window); \ No newline at end of file diff --git a/browserid/static/jquery/lang/deparam/deparam.js b/browserid/static/jquery/lang/deparam/deparam.js new file mode 100644 index 0000000000000000000000000000000000000000..afdad997abc4ed94d8d2dcbef689d7337c39a281 --- /dev/null +++ b/browserid/static/jquery/lang/deparam/deparam.js @@ -0,0 +1,68 @@ +steal.plugins('jquery').then(function($){ + + var digitTest = /^\d+$/, + keyBreaker = /([^\[\]]+)|(\[\])/g; + + /** + * @add jQuery.String + */ + $.String = $.extend($.String || {}, { + + /** + * @function deparam + * + * Takes a string of name value pairs and returns a Object literal that represents those params. + * + * @param {String} params a string like <code>"foo=bar&person[age]=3"</code> + * @return {Object} A JavaScript Object that represents the params: + * + * { + * foo: "bar", + * person: { + * age: "3" + * } + * } + */ + deparam: function(params){ + + if(! params || ! params.match(/([^?#]*)(#.*)?$/) ) { + return {}; + } + + + var data = {}, + pairs = params.split('&'), + current; + + for(var i=0; i < pairs.length; i++){ + current = data; + var pair = pairs[i].split('='); + + // if we find foo=1+1=2 + if(pair.length != 2) { + pair = [pair[0], pair.slice(1).join("=")] + } + + var key = decodeURIComponent(pair[0]), + value = decodeURIComponent(pair[1]), + parts = key.match(keyBreaker); + + for ( var j = 0; j < parts.length - 1; j++ ) { + var part = parts[j]; + if (!current[part] ) { + current[part] = digitTest.test(part) || parts[j+1] == "[]" ? [] : {} + } + current = current[part]; + } + lastPart = parts[parts.length - 1]; + if(lastPart == "[]"){ + current.push(value) + }else{ + current[lastPart] = value; + } + } + return data; + } + }); + +}) diff --git a/browserid/static/jquery/lang/deparam/deparam_test.js b/browserid/static/jquery/lang/deparam/deparam_test.js new file mode 100644 index 0000000000000000000000000000000000000000..0fa41a35745e0d0a8adbc1aaa1206ea1df312538 --- /dev/null +++ b/browserid/static/jquery/lang/deparam/deparam_test.js @@ -0,0 +1,33 @@ +steal.plugins('funcunit/qunit','jquery/lang/deparam').then(function(){ + +module('jquery/lang/deparam') + +test("Basic deparam",function(){ + + var data = $.String.deparam("a=b"); + equals(data.a,"b") + + var data = $.String.deparam("a=b&c=d"); + equals(data.a,"b") + equals(data.c,"d") +}) +test("Nested deparam",function(){ + + var data = $.String.deparam("a[b]=1&a[c]=2"); + equals(data.a.b,1) + equals(data.a.c,2) + + var data = $.String.deparam("a[]=1&a[]=2"); + equals(data.a[0],1) + equals(data.a[1],2) + + var data = $.String.deparam("a[b][]=1&a[b][]=2"); + equals(data.a.b[0],1) + equals(data.a.b[1],2) + + var data = $.String.deparam("a[0]=1&a[1]=2"); + equals(data.a[0],1) + equals(data.a[1],2) +}) + +}) diff --git a/browserid/static/jquery/lang/deparam/qunit.html b/browserid/static/jquery/lang/deparam/qunit.html new file mode 100644 index 0000000000000000000000000000000000000000..6d17233d5dd231c10b00dfd488eb816cdccf1e92 --- /dev/null +++ b/browserid/static/jquery/lang/deparam/qunit.html @@ -0,0 +1,17 @@ +<html> + <head> + <link rel="stylesheet" type="text/css" href="../../../funcunit/qunit/qunit.css" /> + </head> + <body> + + <h1 id="qunit-header">Deparam Test Suite</h1> + <h2 id="qunit-banner"></h2> + <div id="qunit-testrunner-toolbar"></div> + <h2 id="qunit-userAgent"></h2> + <ol id="qunit-tests"></ol> + <div id="qunit-test-area"></div> + <a href='associations/qunit.html'>associations</a> + <a href='list/qunit.html'>list</a> + <script type='text/javascript' src='../../../steal/steal.js?jquery/lang/deparam/deparam_test.js'></script> + </body> +</html> \ No newline at end of file diff --git a/browserid/static/jquery/lang/lang.js b/browserid/static/jquery/lang/lang.js index dcf8eb5fc6d536bbda97884e31a7934116f4c13e..4ac837d1153428199b4ac5577e0325fc6efb6e83 100644 --- a/browserid/static/jquery/lang/lang.js +++ b/browserid/static/jquery/lang/lang.js @@ -1,3 +1,4 @@ +//string helpers steal.plugins('jquery').then(function( $ ) { // Several of the methods in this plugin use code adapated from Prototype // Prototype JavaScript framework, version 1.6.0.1 @@ -6,54 +7,78 @@ steal.plugins('jquery').then(function( $ ) { undHash: /_|-/, colons: /::/, words: /([A-Z]+)([A-Z][a-z])/g, - lowerUpper: /([a-z\d])([A-Z])/g, + lowUp: /([a-z\d])([A-Z])/g, dash: /([a-z\d])([A-Z])/g, - replacer: /\{([^\}]+)\}/g + replacer: /\{([^\}]+)\}/g, + dot: /\./ }, - getObject = function( objectName, currentin, remove ) { - var current = currentin || window, - parts = objectName ? objectName.split(/\./) : [], - ret, i = 0; - for (; i < parts.length - 1 && current; i++ ) { - current = current[parts[i]]; + getNext = function(current, nextPart, add){ + return current[nextPart] || ( add && (current[nextPart] = {}) ); + }, + isContainer = function(current){ + var type = typeof current; + return type && ( type == 'function' || type == 'object' ); + }, + getObject = function( objectName, roots, add ) { + + var parts = objectName ? objectName.split(regs.dot) : [], + length = parts.length, + currents = $.isArray(roots) ? roots : [roots || window], + current, + ret, + i, + c = 0, + type; + + if(length == 0){ + return currents[0]; } - ret = current[parts[i]]; - if ( remove ) { - delete current[parts[i]]; + while(current = currents[c++]){ + for (i =0; i < length - 1 && isContainer(current); i++ ) { + current = getNext(current, parts[i], add); + } + if( isContainer(current) ) { + + ret = getNext(current, parts[i], add); + + if( ret !== undefined ) { + + if ( add === false ) { + delete current[parts[i]]; + } + return ret; + + } + + } } - return ret; }, /** * @class jQuery.String + * + * A collection of useful string helpers. + * */ - str = ($.String = { + str = $.String = $.extend( $.String || {} , { /** - * @function strip - * @param {String} s returns a string with leading and trailing whitespace removed. + * @function + * Gets an object from a string. + * @param {String} name the name of the object to look for + * @param {Array} [roots] an array of root objects to look for the name + * @param {Boolean} [add] true to add missing objects to + * the path. false to remove found properties. undefined to + * not modify the root object */ - strip: function( string ) { - return string.replace(/^\s+/, '').replace(/\s+$/, ''); - }, + getObject : getObject, /** * Capitalizes a string - * @param {String} s the string to be lowercased. - * @return {String} a string with the first character capitalized, and everything else lowercased + * @param {String} s the string. + * @return {String} a string with the first character capitalized. */ capitalize: function( s, cache ) { return s.charAt(0).toUpperCase() + s.substr(1); }, - - /** - * Returns if string ends with another string - * @param {String} s String that is being scanned - * @param {String} pattern What the string might end with - * @return {Boolean} true if the string ends wtih pattern, false if otherwise - */ - endsWith: function( s, pattern ) { - var d = s.length - pattern.length; - return d >= 0 && s.lastIndexOf(pattern) === d; - }, /** * Capitalizes a string from something undercored. Examples: * @codestart @@ -64,28 +89,22 @@ steal.plugins('jquery').then(function( $ ) { * @return {String} a the camelized string */ camelize: function( s ) { - var parts = s.split(regs.undHash), - i = 1; - parts[0] = parts[0].charAt(0).toLowerCase() + parts[0].substr(1); - for (; i < parts.length; i++ ) { - parts[i] = str.capitalize(parts[i]); - } - - return parts.join(''); + s = str.classize(s); + return s.charAt(0).toLowerCase() + s.substr(1); }, /** * Like camelize, but the first part is also capitalized * @param {String} s * @return {String} the classized string */ - classize: function( s ) { + classize: function( s , join) { var parts = s.split(regs.undHash), i = 0; for (; i < parts.length; i++ ) { parts[i] = str.capitalize(parts[i]); } - return parts.join(''); + return parts.join(join || ''); }, /** * Like [jQuery.String.classize|classize], but a space separates each 'word' @@ -96,13 +115,7 @@ steal.plugins('jquery').then(function( $ ) { * @return {String} the niceName */ niceName: function( s ) { - var parts = s.split(regs.undHash), - i = 0; - for (; i < parts.length; i++ ) { - parts[i] = str.capitalize(parts[i]); - } - - return parts.join(' '); + str.classize(parts[i],' '); }, /** @@ -114,21 +127,33 @@ steal.plugins('jquery').then(function( $ ) { * @return {String} the underscored string */ underscore: function( s ) { - return s.replace(regs.colons, '/').replace(regs.words, '$1_$2').replace(regs.lowerUpper, '$1_$2').replace(regs.dash, '_').toLowerCase(); + return s.replace(regs.colons, '/').replace(regs.words, '$1_$2').replace(regs.lowUp, '$1_$2').replace(regs.dash, '_').toLowerCase(); }, /** - * Returns a string with {param} replaced with parameters - * from data. + * Returns a string with {param} replaced values from data. + * * $.String.sub("foo {bar}",{bar: "far"}) * //-> "foo far" - * @param {String} s - * @param {Object} data + * + * @param {String} s The string to replace + * @param {Object} data The data to be used to look for properties. If it's an array, multiple + * objects can be used. + * @param {Boolean} [remove] if a match is found, remove the property from the object */ sub: function( s, data, remove ) { - return s.replace(regs.replacer, function( whole, inside ) { + var obs = []; + obs.push(s.replace(regs.replacer, function( whole, inside ) { //convert inside to type - return getObject(inside, data, remove).toString(); //gets the value in options - }); + var ob = getObject(inside, data, typeof remove == 'boolean' ? !remove : remove), + type = typeof ob; + if((type === 'object' || type === 'function') && type !== null){ + obs.push(ob); + return ""; + }else{ + return ""+ob; + } + })); + return obs.length <= 1 ? obs[0] : obs; } }); diff --git a/browserid/static/jquery/lang/lang_test.js b/browserid/static/jquery/lang/lang_test.js index 05f8f8839bc4bd5878296273e35a0a0a9e393165..3b83005136cb259d3014f698ffeb5baa642af34c 100644 --- a/browserid/static/jquery/lang/lang_test.js +++ b/browserid/static/jquery/lang/lang_test.js @@ -14,8 +14,21 @@ test("$.String.sub", function(){ }); +test("$.String.sub double", function(){ + equals($.String.sub("{b} {d}",[{b: "c", d: "e"}]),"c e"); +}) + test("String.underscore", function(){ equals($.String.underscore("Foo.Bar.ZarDar"),"foo.bar.zar_dar") }) + + +test("$.String.getObject", function(){ + var obj = $.String.getObject("foo", [{a: 1}, {foo: 'bar'}]); + + equals(obj,'bar', 'got bar') +}); + + }); diff --git a/browserid/static/jquery/lang/openajax/openajax.js b/browserid/static/jquery/lang/openajax/openajax.js index 7237035165728c09f9b61e09157bb375d242698a..00e97bc76c3230c90514be3fd95e378d33e86c3c 100644 --- a/browserid/static/jquery/lang/openajax/openajax.js +++ b/browserid/static/jquery/lang/openajax/openajax.js @@ -198,5 +198,5 @@ if(!window["OpenAjax"]){ OpenAjax.hub.registerLibrary("OpenAjax", "http://openajax.org/hub", "1.0", {}); } -OpenAjax.hub.registerLibrary("JavaScriptMVC", "http://JavaScriptMVC.com", "1.5", {}); +OpenAjax.hub.registerLibrary("JavaScriptMVC", "http://JavaScriptMVC.com", "3.0", {}); }); \ No newline at end of file diff --git a/browserid/static/jquery/lang/rsplit/rsplit.js b/browserid/static/jquery/lang/rsplit/rsplit.js index bd2cea35fa4de95b273600d8494bb81d1c14484f..7c342bd1bbca06f347a7991d50355514b3008f62 100644 --- a/browserid/static/jquery/lang/rsplit/rsplit.js +++ b/browserid/static/jquery/lang/rsplit/rsplit.js @@ -5,8 +5,12 @@ steal.plugins('jquery/lang').then(function( $ ) { $.String. /** * Splits a string with a regex correctly cross browser - * @param {Object} string - * @param {Object} regex + * + * $.String.rsplit("a.b.c.d", /\./) //-> ['a','b','c','d'] + * + * @param {String} string The string to split + * @param {RegExp} regex A regular expression + * @return {Array} An array of strings */ rsplit = function( string, regex ) { var result = regex.exec(string), diff --git a/browserid/static/jquery/lang/vector/vector.js b/browserid/static/jquery/lang/vector/vector.js index eb99890862576a3d7e73e102163d0018aded3738..5081184a161e339d0275f80a2644b1a0a6846b88 100644 --- a/browserid/static/jquery/lang/vector/vector.js +++ b/browserid/static/jquery/lang/vector/vector.js @@ -1,19 +1,15 @@ -steal.then(function( $ ) { - var getSetZero = function( v ) { - return v !== undefined ? (this.array[0] = v) : this.array[0]; - }, - getSetOne = function( v ) { - return v !== undefined ? (this.array[1] = v) : this.array[1]; - }; - /** - * @class jQuery.Vector - * A vector class - * @constructor creates a new vector instance from the arguments. Example: - * @codestart - * new jQuery.Vector(1,2) - * @codeend - * - */ +steal.plugins('jquery').then(function($){ + var getSetZero = function(v){ return v !== undefined ? (this.array[0] = v) : this.array[0] }, + getSetOne = function(v){ return v !== undefined ? (this.array[1] = v) : this.array[1] } +/** + * @class jQuery.Vector + * A vector class + * @constructor creates a new vector instance from the arguments. Example: + * @codestart + * new jQuery.Vector(1,2) + * @codeend + * + */ $.Vector = function() { this.update($.makeArray(arguments)); }; @@ -155,4 +151,4 @@ steal.then(function( $ ) { return new $.Vector(this[which + "Width"](), this[which + "Height"]()); } }; -}); \ No newline at end of file +}); diff --git a/browserid/static/jquery/model/demo-convert.html b/browserid/static/jquery/model/demo-convert.html index f9ad742892e56a9bdf4680d24d75aa5ed6c138fe..c650df8b15ca3a560b7396d5015aa87063f508b3 100644 --- a/browserid/static/jquery/model/demo-convert.html +++ b/browserid/static/jquery/model/demo-convert.html @@ -1,20 +1,20 @@ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html lang="en"> - <head> - <title>Model Convert Demo</title> + <head> + <title>Model Convert Demo</title> <style type='text/css'> body {font-family: verdana} li {border: solid 1px gray; padding: 5px; width: 250px;} - li a {color: red; font-weight: bold;} - p {width: 400px;} + li a {color: red; font-weight: bold;} + p {width: 400px;} </style> - </head> - <body> + </head> + <body> <div id="demo-instructions"> - <h1>Model Convert Demo</h1> - <p>This demo shows converting date strings sent by the - server to JavaScript dates with attributes and convert.</p> + <h1>Model Convert Demo</h1> + <p>This demo shows converting date strings sent by the + server to JavaScript dates with attributes and convert.</p> </div> <div id="demo-html"> <ul id='contacts'></ul> @@ -23,57 +23,51 @@ src='../../steal/steal.js'> </script> <script type='text/javascript'> -steal.plugins('jquery/model', - 'jquery/dom/fixture', - 'jquery/model/list').start() -CONTACTS = [{'id': 1,'name' : 'Justin Meyer','birthday': '1982-10-20'}, - {'id': 2,'name' : 'Brian Moschel','birthday': '1983-11-10'}, - {'id': 3,'name' : 'Alex Gomes','birthday': '1980-2-10'}] -CONTACTS_FIXTURE = function(){ - return [CONTACTS]; - }; +steal.plugins('jquery/model').start() </script> <script type='text/javascript' id="demo-source"> -// A contact model $.Model.extend("Contact",{ - attributes : { - birthday : 'date' - }, - convert : { - date : function(raw){ - if(typeof raw == 'string'){ - var matches = raw.match(/(\d+)-(\d+)-(\d+)/) - return new Date( matches[1], - (+matches[2])-1, - matches[3] ) - }else if(raw instanceof Date){ - return raw; - } - } - }, - findAll : function(params, success, error){ - $.get("/recipes.json", {}, - this.callback(['wrapMany',success]), - "json", CONTACTS_FIXTURE ); - - } + attributes : { + // labels birthday a date + birthday : 'date' + }, + convert : { + // a date converter helper + date : function(raw){ + if(typeof raw == 'string'){ + // if a string, convert to a date + var matches = raw.match(/(\d+)-(\d+)-(\d+)/) + return new Date( matches[1], + (+matches[2])-1, + matches[3] ) + }else if(raw instanceof Date){ + return raw; + } + } + }, + findAll : function(params, success, error){ + // simulates the server giving a string date + success(this.wrapMany( + [{'id': 1,'name' : 'Justin Meyer','birthday': '1982-10-20'}, + {'id': 2,'name' : 'Brian Moschel','birthday': '1983-11-10'}, + {'id': 3,'name' : 'Alex Gomes','birthday': '1980-2-10'}])) + } },{ + // helper function that returns age in years + age : function(){ + return new Date().getFullYear() - + this.birthday.getFullYear() + } }); -// get the distance between years -var age = function(birthday){ - return new Date().getFullYear() - - birthday.getFullYear() -}; - // get all contacts and put them in the page Contact.findAll( {}, function( contacts ){ var html = []; for(var i =0; i < contacts.length; i++){ - html.push('<li>'+age(contacts[i].birthday) + '</li>') + html.push('<li>'+contacts[i].age() + '</li>') } $('#contacts').html( html.join('') ); }); </script> - </body> + </body> </html> \ No newline at end of file diff --git a/browserid/static/jquery/model/guesstype/guesstype.js b/browserid/static/jquery/model/guesstype/guesstype.js new file mode 100644 index 0000000000000000000000000000000000000000..e6fd5b9bef3d728b1c198a85d88dffe3a3133d39 --- /dev/null +++ b/browserid/static/jquery/model/guesstype/guesstype.js @@ -0,0 +1,37 @@ +steal.plugins('jquery/model').then(function(){ + + + /** + * @hide + * Guesses the type of an object. This is what sets the type if not provided in + * [jQuery.Model.static.attributes]. + * @param {Object} object the object you want to test. + * @return {String} one of string, object, date, array, boolean, number, function + */ + $.Model.guessType= function( object ) { + if ( typeof object != 'string' ) { + if ( object === null ) { + return typeof object; + } + if ( object.constructor == Date ) { + return 'date'; + } + if ( isArray(object) ) { + return 'array'; + } + return typeof object; + } + if ( object === "" ) { + return 'string'; + } + //check if true or false + if ( object == 'true' || object == 'false' ) { + return 'boolean'; + } + if (!isNaN(object) && isFinite(+object) ) { + return 'number'; + } + return typeof object; + }; + +}); diff --git a/browserid/static/jquery/model/guesstype/guesstype_test.js b/browserid/static/jquery/model/guesstype/guesstype_test.js new file mode 100644 index 0000000000000000000000000000000000000000..54b4bf1d8cb847753053002c5893565f0c87b46f --- /dev/null +++ b/browserid/static/jquery/model/guesstype/guesstype_test.js @@ -0,0 +1,12 @@ +module("jquery/model/guesstype") + +test("guess type", function(){ + equals("array", $.Model.guessType( [] ) ); + equals("date", $.Model.guessType( new Date() ) ); + equals("boolean", $.Model.guessType( true ) ); + equals("number", $.Model.guessType( "1" ) ); + equals("string", $.Model.guessType( "a" ) ); + + equals("string", $.Model.guessType( "1e234234324234" ) ); + equals("string", $.Model.guessType( "-1e234234324234" ) ); +}) diff --git a/browserid/static/jquery/model/list/list.js b/browserid/static/jquery/model/list/list.js index b17bc08c10d20a59622ea1f34ca78229f0cc67fa..5024d6d558f27da4517cb35d95144f229cce3f96 100644 --- a/browserid/static/jquery/model/list/list.js +++ b/browserid/static/jquery/model/list/list.js @@ -1,16 +1,18 @@ steal.plugins('jquery/model').then(function($){ -var add = function(data, inst){ - var id = inst.Class.id; - data[inst[id]] = inst; - }, - getArgs = function(args){ - if(args[0] !== undefined && args[0].length && typeof args[0] != 'string'){ +var getArgs = function(args){ + if(args[0] && ( $.isArray(args[0]) ) ){ return args[0] - }else{ + }else if(args[0] instanceof $.Model.List){ + return $.makeArray(args[0]) + } + else{ return $.makeArray(args) } - } + }, + //used for namespacing + id = 0, + expando = jQuery.expando; /** * @parent jQuery.Model * @download http://jmvcsite.heroku.com/pluginify?plugins[]=jquery/model/list/list.js @@ -95,9 +97,12 @@ $.Class.extend("jQuery.Model.List", * @Prototype */ { - init: function( instances ) { + init: function( instances , noEvents) { this.length = 0; + // a cache for quick lookup by id this._data = {}; + //a namespace so we can remove all events bound by this list + this._namespace = ".list"+(++id), this.push.apply(this, $.makeArray(instances || [] ) ); }, /** @@ -203,7 +208,12 @@ $.Class.extend("jQuery.Model.List", i++; } } - return new this.Class(list); + var ret = new this.Class(list); + if(ret.length){ + $([this]).trigger("remove",[ret]) + } + + return ret; }, publish: function( name, data ) { OpenAjax.hub.publish(this.Class.shortName+"."+name, data) @@ -214,20 +224,147 @@ $.Class.extend("jQuery.Model.List", */ elements: function( context ) { // TODO : this can probably be done with 1 query. - var jq = $(); - this.each(function(){ - jq.add("."+this.identity(), context) - }) - return jq; - } -}); - -var modifiers = { + return $( + this.map(function(item){return "."+item.identity()}).join(','), + context + ); + }, + model : function(){ + return this.Class.namespace + }, + /** + * Finds items and adds them to this list. This uses [jQuery.Model.static.findAll] + * to find items with the params passed. + * + * @param {Object} params options to refind the returned items + * @param {Function} success called with the list + * @param {Object} error + */ + findAll : function(params, success, error){ + var self = this; + this.model().findAll(params,function(items){ + self.push(items); + success && success(self) + },error) + }, + /** + * Destroys all items in this list. This will use the Model's + * [jQuery.Model.static.destroyAll] method if it exists, otherwise it will fall back to + * [jQuery.model.static.destroy]. + * + * @param {Function} success + * @param {Function} error + */ + destroyAll : function(success, error){ + var gId = function(item){ return item[item.Class.id]} + ids = this.map(gId), + model = this.model(), + self = this, + items = this.slice(0, this.length), + destroy = function(){ + this.destroyed(); + }; + + if(model.destroyAll){ + model.destroyAll(ids, function(){ + $.each(items, destroy)//success(self); //should call back with the destroyed elements (not removed) + }); + }else{ + this.each(function(i, item){ + model.destroy(gId(item), function(){ + item.destroyed() + }) + }); + } + }, + /** + * Listens for an events on this list. The only useful events are: + * + * . add - when new items are added + * . update - when an item is updated + * . remove - when items are removed from the list (typically because they are destroyed). + * + * ## Listen for items being added + * + * list.bind('add', function(ev, newItems){ + * + * }) + * + * ## Listen for items being removed + * + * list.bind('remove',function(ev, removedItems){ + * + * }) + * + * ## Listen for an item being updated + * + * list.bind('update',function(ev, updatedItem){ + * + * }) + */ + bind : function(){ + if(this[expando] === undefined){ + this.bindings(this); + // we should probably remove destroyed models here + } + $.fn.bind.apply($([this]),arguments); + return this; + }, + /** + * Unbinds an event on this list. Once all events are unbound, + * unbind stops listening to all elements in the collection. + * + * list.unbind("update") //unbinds all update events + */ + unbind : function(){ + $.fn.unbind.apply($([this]),arguments); + if(this[expando] === undefined){ + //console.log("unbinding all") + $(this).unbind(this._namespace) + } + return this; + }, + // listens to destroyed and updated on instances so when an item is + // updated - updated is called on model + // destroyed - it is removed from the list + bindings : function(items){ + var self= this; + $(items).bind("destroyed"+this._namespace, function(){ + //remove from me + self.remove(this); //triggers the remove event + }).bind("updated"+this._namespace, function(){ + $([self]).trigger("update", this) + }); + }, /** * @function push - * Pushs an instance onto the list + * Adds a instance or instances to the list + * + * list.push(new Recipe({id: 5, name: "Water"})) */ - push: [].push, + push: function(){ + var args = getArgs(arguments), + self = this; + //listen to events on this only if someone is listening on us, this means remove won't + //be called if we aren't listening for removes + if(this[expando] !== undefined){ + this.bindings(args); + } + + this._changed = true; + var res = push.apply( this, args ) + //do this first so we could prevent? + if( this[expando] && args.length ){ + $([this]).trigger("add",[args]); + } + + return res; + } +}); + +var push = [].push, + modifiers = { + /** * @function pop * Pops the last instance off the list @@ -252,7 +389,8 @@ var modifiers = { * @function sort * sorts the list */ - sort : [].sort + sort : [].sort//, + //slice : [].slice } $.each(modifiers, function(name, func){ diff --git a/browserid/static/jquery/model/list/memory.html b/browserid/static/jquery/model/list/memory.html new file mode 100644 index 0000000000000000000000000000000000000000..8cc3a12fd7b61d50f23b3edfcab77d4e57ebdf7f --- /dev/null +++ b/browserid/static/jquery/model/list/memory.html @@ -0,0 +1,93 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" + "http://www.w3.org/TR/html4/strict.dtd"> +<html lang="en"> + <head> + <title>List Helper Demo</title> + <style type='text/css'> + body {font-family: verdana} + li {border: solid 1px gray; padding: 5px; width: 250px;} + li a {color: red; font-weight: bold;} + p {width: 400px;} + </style> + </head> + <body> + <a href='javascript://' id='start'>Start</a> + <div id='items'></div> + + +<script type='text/javascript' + src='../../../steal/steal.js'> +</script> +<script type='text/javascript'> +steal.plugins('jquery/model', + 'jquery/dom/fixture', + 'jquery/model/list','jquery/controller').start() +</script> +<script type='text/javascript'> + var index = 0; + $.Model("Thing",{ + findAll : function(params, success){ + //return 100000 items + var items = [] + for(var i = 0; i < 100; i++){ + items.push({ + id: i+1+index, + name: "name "+i, + something : "sdfj alsfj als;f j;lsa jf;lsa jf;lsajf ;lsa jf;lsa jf;lsajf ;lsajfoiewjfj ;lsajf", + other : "asdf asf sadfsad fsadf salkdf j;lsa jf;lkdsa jf;lksa f;lksajf ;lsak jf;laks jflksaj f;lf;ljfdsaljsafd;l as;lk j;lkfsa ja" + }) + } + index = i; + success(this.wrapMany(items)) + } + },{}) + $.Model.List("Thing.List"); + + $.Controller("Tester",{ + init : function(){ + var self = this; + self.timer = setTimeout(function(){ + + self.options.list.findAll() + + self.timer = setTimeout(arguments.callee, 500) + + },500) + + }, + "{list} add" : function(list, ev, items){ + this.element.html(list.length) + }, + click : function(){ + clearTimeout(this.timer); + this.element.remove() + } + }) + var timer; + $('#start').click(function(){ + /*var list = new Thing.List(); + list.bind("add", function(){ + $('#items').html(this.length) + }); + timer = setTimeout(function(){ + + list.findAll() + + timer = setTimeout(arguments.callee, 500) + + },500) + + $('#items').one('click',function(){ + clearTimeout(timer); + list.unbind("add") + })*/ + $("#items").tester({list : new Thing.List()}); + }) + + +</script> +<script type='text/javascript' id="demo-source"> + +</script> + </body> +</html> \ No newline at end of file diff --git a/browserid/static/jquery/model/list/test/qunit/list_test.js b/browserid/static/jquery/model/list/test/qunit/list_test.js index 7e94fd66bccd9851a059aa8305d7e95852b2c5f8..1fa89a27e7a71adb2627afc66d436ed6b2a9f036 100644 --- a/browserid/static/jquery/model/list/test/qunit/list_test.js +++ b/browserid/static/jquery/model/list/test/qunit/list_test.js @@ -61,4 +61,41 @@ test("remove", function(){ test("list from wrapMany", function(){ var people = Person.wrapMany([{id: 1}, {id: 2}]); ok(people.destroy, "we can destroy a list") +}); + +test("events - add", 4, function(){ + var list = new Person.List; + list.bind("add", function(ev, items){ + ok(1, "add called"); + equals(items.length, 1, "we get an array") + }); + + var person = new Person({id: 1, name: "alex"}); + + + list.push(person); + + // check that we are listening to updates on person ... + + ok( $(person).data("events"), "person has events" ); + + list.unbind("add"); + + ok( !$(person).data("events"), "person has no events" ); + +}); + +test("events - update", function(){ + var list = new Person.List; + list.bind("update", function(ev, updated){ + ok(1, "update called"); + ok(person === updated, "we get the person back"); + + equals(updated.name, "Alex", "got the right name") + }); + + var person = new Person({id: 1, name: "justin"}); + list.push(person); + + person.updated({name: "Alex"}) }) diff --git a/browserid/static/jquery/model/model.js b/browserid/static/jquery/model/model.js index 24ef9683fa075b911ed499e2759708cef935cf80..0ff9e715aae8cc7c23a0faeb77e330bbb7a86958 100644 --- a/browserid/static/jquery/model/model.js +++ b/browserid/static/jquery/model/model.js @@ -2,6 +2,124 @@ steal.plugins('jquery/class', 'jquery/lang').then(function() { + //helper stuff for later. Eventually, might not need jQuery. + var underscore = $.String.underscore, + classize = $.String.classize, + isArray = $.isArray, + makeArray = $.makeArray, + extend = $.extend, + each = $.each, + reqType = /GET|POST|PUT|DELETE/i, + ajax = function(ajaxOb, attrs, success, error, fixture, type, dataType){ + var dataType = dataType || "json", + src = "", + tmp; + if(typeof ajaxOb == "string"){ + var sp = ajaxOb.indexOf(" ") + if( sp > 2 && sp <7){ + tmp = ajaxOb.substr(0,sp); + if(reqType.test(tmp)){ + type = tmp; + }else{ + dataType = tmp; + } + src = ajaxOb.substr(sp+1) + }else{ + src = ajaxOb; + } + } + attrs = extend({},attrs) + + var url = $.String.sub(src, attrs, true) + return $.ajax({ + url : url, + data : attrs, + success : success, + error: error, + type : type || "post", + dataType : dataType, + fixture: fixture + }); + }, + //guesses at a fixture name + fixture = function(extra, or){ + var u = underscore( this.shortName ), + f = "-"+u+(extra||""); + return $.fixture && $.fixture[f] ? f : or || + "//"+underscore( this.fullName ) + .replace(/\.models\..*/,"") + .replace(/\./g,"/")+"/fixtures/"+u+ + (extra || "")+".json"; + }, + addId = function(attrs, id){ + attrs = attrs || {}; + var identity = this.id; + if(attrs[identity] && attrs[identity] !== id){ + attrs["new"+$.String.capitalize(id)] = attrs[identity]; + delete attrs[identity]; + } + attrs[identity] = id; + return attrs; + }, + getList = function(type){ + var listType = type || $.Model.List || Array; + return new listType(); + }, + getId = function(inst){ + return inst[inst.Class.id] + }, + unique = function(items){ + var collect = []; + for(var i=0; i < items.length; i++){ + if(!items[i]["__u Nique"]){ + collect.push(items[i]); + items[i]["__u Nique"] = true; + } + } + for(i=0; i< collect.length; i++){ + delete collect[i]["__u Nique"]; + } + return collect; + }, + // makes a deferred request + makeRequest = function(self, type, success, error, method){ + var deferred = $.Deferred(), + resolve = function(data){ + self[method || type+"d"](data); + deferred.resolveWith(self,[self, data, type]); + }, + reject = function(data){ + deferred.rejectWith(self, [data]) + }, + args = [self.attrs(), resolve, reject]; + + if(type == 'destroy'){ + args.shift(); + } + + if(type !== 'create' ){ + args.unshift(getId(self)) + } + + deferred.then(success); + deferred.fail(error); + + self.Class[type].apply(self.Class, args); + + return deferred.promise(); + }, + // a quick way to tell if it's an object and not some string + isObject = function(obj){ + return typeof obj === 'object' && obj !== null && obj; + }, + $method = function(name){ + return function( eventType, handler ) { + $.fn[name].apply($([this]), arguments); + return this; + } + }, + bind = $method('bind'), + unbind = $method('unbind'); /** * @class jQuery.Model * @tag core @@ -11,7 +129,7 @@ steal.plugins('jquery/class', 'jquery/lang').then(function() { * * Models wrap an application's data layer. In large applications, a model is critical for: * - * - Encapsulating services so controllers + views don't care where data comes from. + * - [jquery.model.encapsulate Encapsulating] services so controllers + views don't care where data comes from. * * - Providing helper functions that make manipulating and abstracting raw service data easier. * @@ -34,10 +152,10 @@ steal.plugins('jquery/class', 'jquery/lang').then(function() { * Let's see how that might look without a model: * * @codestart - * $.Controller.extend("MyApp.Controllers.Tasks",{onDocument: true}, + * $.Controller("Tasks", * { * // get tasks when the page is ready - * ready: function() { + * init: function() { * $.get('/tasks.json', this.callback('gotTasks'), 'json') * }, * |* @@ -58,9 +176,11 @@ steal.plugins('jquery/class', 'jquery/lang').then(function() { * }, * // when a task is complete, get the id, make a request, remove it * ".task click" : function( el ) { - * $.post('/task_complete',{id: el.attr('data-taskid')}, function(){ - * el.remove(); - * }) + * $.post('/tasks/'+el.attr('data-taskid')+'.json', + * {complete: true}, + * function(){ + * el.remove(); + * }) * } * }) * @codeend @@ -76,27 +196,27 @@ steal.plugins('jquery/class', 'jquery/lang').then(function() { * a good model does for a controller before we learn how to make one: * * @codestart - * $.Controller.extend("MyApp.Controllers.Tasks",{onDocument: true}, + * $.Controller("Tasks", * { - * load: function() { - * Task.findAll({},this.callback('list')) + * init: function() { + * Task.findAll({}, this.callback('tasks')); * }, - * list: function( tasks ) { - * $("#tasks").html(this.view(tasks)) + * list : function(todos){ + * this.element.html("tasks.ejs", todos ); * }, * ".task click" : function( el ) { - * el.models()[0].complete(function(){ + * el.model().update({complete: true},function(){ * el.remove(); * }); * } - * }) + * }); * @codeend * - * In views/tasks/list.ejs + * In tasks.ejs * * @codestart html * <% for(var i =0; i < tasks.length; i++){ %> - * <div class='task <%= tasks[i].<b>identity</b>() %>'> + * <div <%= tasks[i] %>> * <label><%= tasks[i].name %></label> * <%= tasks[i].<b>timeRemaining</b>() %> * </div> @@ -107,203 +227,362 @@ steal.plugins('jquery/class', 'jquery/lang').then(function() { * also made our controller completely understandable. Now lets take a look at the model: * * @codestart - * $.Model.extend("Task", + * $.Model("Task", * { - * findAll: function( params,success ) { - * $.get("/tasks.json", params, this.callback(["wrapMany",success]),"json"); - * } + * findAll: "/tasks.json", + * update: "/tasks/{id}.json" * }, * { * timeRemaining: function() { * return new Date() - new Date(this.due_date) - * }, - * complete: function( success ) { - * $.get("/task_complete", {id: this.id }, success,"json"); * } * }) * @codeend * - * There, much better! Now you have a single place where you can organize Ajax functionality and - * wrap the data that it returned. Lets go through each bolded item in the controller and view.<br/> + * Much better! Now you have a single place where you + * can organize Ajax functionality and + * wrap the data that it returned. Lets go through + * each bolded item in the controller and view. * * ### Task.findAll * - * The findAll function requests data from "/tasks.json". When the data is returned, it it is run through - * the "wrapMany" function before being passed to the success callback.<br/> - * If you don't understand how the callback works, you might want to check out - * [jQuery.Model.static.wrapMany wrapMany] and [jQuery.Class.static.callback callback]. + * The findAll function requests data from "/tasks.json". When the data is returned, + * it converted by the [jQuery.Model.static.models models] function before being + * passed to the success callback. * - * ### el.models + * ### el.model * - * [jQuery.fn.models models] is a jQuery helper that returns model instances. It uses - * the jQuery's elements' shortNames to find matching model instances. For example: + * [jQuery.fn.model] is a jQuery helper that returns a model instance from an element. The + * list.ejs template assings tasks to elements with the following line: * * @codestart html - * <div class='task task_5'> ... </div> + * <div <%= tasks[i] %>> ... </div> * @codeend * - * It knows to return a task with id = 5. + * ### timeRemaining * - * ### complete + * timeRemaining is an example of wrapping your model's raw data with more useful functionality. * - * This should be pretty obvious. + * ## Other Good Stuff * - * ### identity + * This is just a tiny taste of what models can do. Check out these other features: * - * [jQuery.Model.prototype.identity Identity] returns a unique identifier that [jQuery.fn.models] can use - * to retrieve your model instance. + * ### [jquery.model.encapsulate Encapsulation] * - * ### timeRemaining + * Learn how to connect to services. + * + * $.Model("Task",{ + * findAll : "/tasks.json", + * findOne : "/tasks/{id}.json", + * create : "/tasks.json", + * update : "/tasks/{id}.json" + * },{}) + * + * ### [jquery.model.typeconversion Type Conversion] + * + * Convert data like "10-20-1982" into new Date(1982,9,20) auto-magically: + * + * $.Model("Task",{ + * attributes : {birthday : "date"} + * convert : { + * date : function(raw){ ... } + * } + * },{}) + * + * ### [jQuery.Model.List] + * + * Learn how to handle multiple instances with ease. + * + * $.Model.List("Task.List",{ + * destroyAll : function(){ + * var ids = this.map(function(c){ return c.id }); + * $.post("/destroy", + * ids, + * this.callback('destroyed'), + * 'json') + * }, + * destroyed : function(){ + * this.each(function(){ this.destroyed() }); + * } + * }); + * + * ".destroyAll click" : function(){ + * this.find('.destroy:checked') + * .closest('.task') + * .models() + * .destroyAll(); + * } + * + * ### [jquery.model.validations Validations] * - * timeRemaining is a good example of wrapping your model's raw data with more useful functionality. - * ## Validations + * Validate your model's attributes. * - * You can validate your model's attributes with another plugin. See [validation]. + * $.Model("Contact",{ + * init : function(){ + * this.validate("birthday",function(){ + * if(this.birthday > new Date){ + * return "your birthday needs to be in the past" + * } + * }) + * } + * ,{}); + * + * */ - - //helper stuff for later. - var underscore = $.String.underscore, - classize = $.String.classize, - ajax = function(str, attrs, success, error, fixture, type){ - attrs = $.extend({},attrs) - var url = $.String.sub(str, attrs, true) - $.ajax({ - url : url, - data : attrs, - success : success, - error: error, - type : type || "post", - dataType : "json", - fixture: fixture - }); - }, - fixture = function(){ - return "//"+$.String.underscore( this.fullName ) - .replace(/\.models\..*/,"") - .replace(/\./g,"/")+"/fixtures/"+$.String.underscore( this.shortName ) - }, - addId = function(attrs, id){ - attrs = attrs || {}; - if(attrs[this.id]){ - attrs["new"+$.String.capitalize(this.id)] = attrs[this.id]; - delete attrs[this.id]; - } - attrs[this.id] = id; - return attrs; - }, // methods that we'll weave into model if provided ajaxMethods = /** * @Static */ { - - /** - * Create is used to create a model instance on the server. By implementing - * create along with the rest of the [jquery.model.services service api], your models provide an abstract - * API for services. - * - * Create is called by save to create a new instance. If you want to be able to call save on an instance - * you have to implement create. - * - * The easist way to implement create is to just give it the url to post data to: - * - * $.Model("Recipe",{ - * create: "/recipes" - * },{}) - * - * This lets you create a recipe like: - * - * new Recipe({name: "hot dog"}).save(function(){ - * this.name //this is the new recipe - * }).save(callback) - * - * You can also implement create by yourself. You just need to call success back with - * an object that contains the id of the new instance and any other properties that should be - * set on the instance. - * - * For example, the following code makes a request - * to '/recipes.json?name=hot+dog' and gets back - * something that looks like: - * - * { - * id: 5, - * createdAt: 2234234329 - * } - * - * The code looks like: - * - * $.Model("Recipe", { - * create : function(attrs, success, error){ - * $.post("/recipes.json",attrs, success,"json"); - * } - * },{}) - * - * ## API - * - * @param {Object} attrs Attributes on the model instance - * @param {Function} success the callback function, it must be called with an object - * that has the id of the new instance and any other attributes the service needs to add. - * @param {Function} error a function to callback if something goes wrong. - */ create: function(str ) { + /** + * @function create + * Create is used to create a model instance on the server. By implementing + * create along with the rest of the [jquery.model.services service api], your models provide an abstract + * API for services. + * + * Create is called by save to create a new instance. If you want to be able to call save on an instance + * you have to implement create. + * + * The easiest way to implement create is to just give it the url to post data to: + * + * $.Model("Recipe",{ + * create: "/recipes" + * },{}) + * + * This lets you create a recipe like: + * + * new Recipe({name: "hot dog"}).save(function(){ + * this.name //this is the new recipe + * }).save(callback) + * + * You can also implement create by yourself. You just need to call success back with + * an object that contains the id of the new instance and any other properties that should be + * set on the instance. + * + * For example, the following code makes a request + * to '/recipes.json?name=hot+dog' and gets back + * something that looks like: + * + * { + * id: 5, + * createdAt: 2234234329 + * } + * + * The code looks like: + * + * $.Model("Recipe", { + * create : function(attrs, success, error){ + * $.post("/recipes.json",attrs, success,"json"); + * } + * },{}) + * + * ## API + * + * @param {Object} attrs Attributes on the model instance + * @param {Function} success(attrs) the callback function, it must be called with an object + * that has the id of the new instance and any other attributes the service needs to add. + * @param {Function} error a function to callback if something goes wrong. + */ return function(attrs, success, error){ - ajax(str, attrs, success, error, "-restCreate") + return ajax(str, attrs, success, error, "-restCreate") }; }, - /** - * Implement this function! - * Update is called by save to update an instance. If you want to be able to call save on an instance - * you have to implement update. - */ update: function( str ) { + /** + * @function update + * Update is used to update a model instance on the server. By implementing + * update along with the rest of the [jquery.model.services service api], your models provide an abstract + * API for services. + * + * Update is called by [jQuery.Model.prototype.save] or [jQuery.Model.prototype.update] + * on an existing model instance. If you want to be able to call save on an instance + * you have to implement update. + * + * The easist way to implement update is to just give it the url to put data to: + * + * $.Model("Recipe",{ + * create: "/recipes/{id}" + * },{}) + * + * This lets you update a recipe like: + * + * // PUT /recipes/5 {name: "Hot Dog"} + * recipe.update({name: "Hot Dog"}, + * function(){ + * this.name //this is the updated recipe + * }) + * + * If your server doesn't use PUT, you can change it to post like: + * + * $.Model("Recipe",{ + * create: "POST /recipes/{id}" + * },{}) + * + * Your server should send back an object with any new attributes the model + * should have. For example if your server udpates the "updatedAt" property, it + * should send back something like: + * + * // PUT /recipes/4 {name: "Food"} -> + * { + * updatedAt : "10-20-2011" + * } + * + * You can also implement create by yourself. You just need to call success back with + * an object that contains any properties that should be + * set on the instance. + * + * For example, the following code makes a request + * to '/recipes/5.json?name=hot+dog' and gets back + * something that looks like: + * + * { + * updatedAt: "10-20-2011" + * } + * + * The code looks like: + * + * $.Model("Recipe", { + * update : function(id, attrs, success, error){ + * $.post("/recipes/"+id+".json",attrs, success,"json"); + * } + * },{}) + * + * ## API + * + * @param {String} id the id of the model instance + * @param {Object} attrs Attributes on the model instance + * @param {Function} success(attrs) the callback function, it must be called with an object + * that has the id of the new instance and any other attributes the service needs to add. + * @param {Function} error a function to callback if something goes wrong. + */ return function(id, attrs, success, error){ - ajax(str, addId.call(this,attrs, id), success, error, "-restUpdate") + return ajax(str, addId.call(this,attrs, id), success, error, "-restUpdate","put") } }, - /** - * Implement this function! - * Destroy is called by destroy to remove an instance. If you want to be able to call destroy on an instance - * you have to implement update. - * @param {String|Number} id the id of the instance you want destroyed - */ destroy: function( str ) { + /** + * @function destroy + * Destroy is used to remove a model instance from the server. By implementing + * destroy along with the rest of the [jquery.model.services service api], your models provide an abstract + * service API. + * + * You can implement destroy with a string like: + * + * $.Model("Thing",{ + * destroy : "POST /thing/destroy/{id}" + * }) + * + * Or you can implement destroy manually like: + * + * $.Model("Thing",{ + * destroy : function(id, success, error){ + * $.post("/thing/destroy/"+id,{}, success); + * } + * }) + * + * You just have to call success if the destroy was successful. + * + * @param {String|Number} id the id of the instance you want destroyed + * @param {Function} success the callback function, it must be called with an object + * that has the id of the new instance and any other attributes the service needs to add. + * @param {Function} error a function to callback if something goes wrong. + */ return function( id, success, error ) { var attrs = {}; attrs[this.id] = id; - ajax(str, attrs, success, error, "-restDestroy") + return ajax(str, attrs, success, error, "-restDestroy","delete") } }, - /** - * Implement this function! - * @param {Object} params - * @param {Function} success - * @param {Function} error - */ + findAll: function( str ) { + /** + * @function findAll + * FindAll is used to retrive a model instances from the server. By implementing + * findAll along with the rest of the [jquery.model.services service api], your models provide an abstract + * service API. + * findAll returns a deferred ($.Deferred) + * + * You can implement findAll with a string: + * + * $.Model("Thing",{ + * findAll : "/things.json" + * },{}) + * + * Or you can implement it yourself. The 'dataType' attribute is used to convert a JSON array of attributes + * to an array of instances. For example: + * + * $.Model("Thing",{ + * findAll : function(params, success, error){ + * return $.ajax({ + * url: '/things.json', + * type: 'get', + * dataType: 'json thing.models', + * data: params, + * success: success, + * error: error}) + * } + * },{}) + * + * ## API + * + * @param {Object} params data to refine the results. An example might be passing {limit : 20} to + * limit the number of items retrieved. + * @param {Function} success(items) called with an array (or Model.List) of model instances. + * @param {Function} error + */ return function(params, success, error){ - ajax(str, + return ajax(str || this.shortName+"s.json", params, - this.callback(['wrapMany',success]), + success, error, - fixture.call(this)+"s.json", - "get"); + fixture.call(this,"s"), + "get", + "json "+this._shortName+".models"); }; }, - /** - * Implement this function! - * @param {Object} params - * @param {Function} success - * @param {Function} error - */ findOne: function( str ) { + /** + * @function findOne + * FindOne is used to retrive a model instances from the server. By implementing + * findOne along with the rest of the [jquery.model.services service api], your models provide an abstract + * service API. + * + * You can implement findOne with a string: + * + * $.Model("Thing",{ + * findOne : "/things/{id}.json" + * },{}) + * + * Or you can implement it yourself. + * + * $.Model("Thing",{ + * findOne : function(params, success, error){ + * var self = this, + * id = params.id; + * delete params.id; + * return $.get("/things/"+id+".json", + * params, + * success, + * "json thing.model") + * } + * },{}) + * + * ## API + * + * @param {Object} params data to refine the results. This is often something like {id: 5}. + * @param {Function} success(item) called with a model instance + * @param {Function} error + */ return function(params, success, error){ - ajax(str, + return ajax(str, params, - this.callback(['wrap',success]), + success, error, - fixture.call(this)+".json", - "get"); + fixture.call(this), + "get", + "json "+this._shortName+".model"); }; } }; @@ -312,34 +591,31 @@ steal.plugins('jquery/class', 'jquery/lang').then(function() { - jQuery.Class.extend("jQuery.Model", { + jQuery.Class("jQuery.Model", { setup: function( superClass , stat, proto) { //we do not inherit attributes (or associations) - if (!this.attributes || superClass.attributes === this.attributes ) { - this.attributes = {}; - } - - if (!this.associations || superClass.associations === this.associations ) { - this.associations = {}; - } - if (!this.validations || superClass.validations === this.validations ) { - this.validations = {}; - } + var self=this; + each(["attributes","associations","validations"],function(i,name){ + if (!self[name] || superClass[name] === self[name] ) { + self[name] = {}; + } + }) //add missing converters if ( superClass.convert != this.convert ) { - this.convert = $.extend(superClass.convert, this.convert); + this.convert = extend(superClass.convert, this.convert); } this._fullName = underscore(this.fullName.replace(/\./g, "_")); + this._shortName = underscore(this.shortName); if ( this.fullName.substr(0, 7) == "jQuery." ) { return; } //add this to the collection of models - jQuery.Model.models[this._fullName] = this; + //jQuery.Model.models[this._fullName] = this; if ( this.listType ) { this.list = new this.listType([]); @@ -350,10 +626,19 @@ steal.plugins('jquery/class', 'jquery/lang').then(function() { } //@steal-remove-end for(var name in ajaxMethods){ - if(typeof this[name] === 'string'){ + if(typeof this[name] !== 'function'){ this[name] = ajaxMethods[name](this[name]); } } + + //add ajax converters + var converters = {}, + convertName = "* "+this._shortName+".model"; + converters[convertName+"s"] = this.callback('models'); + converters[convertName] = this.callback('model'); + $.ajaxSetup({ + converters : converters + }); }, /** * @attribute attributes @@ -365,7 +650,7 @@ steal.plugins('jquery/class', 'jquery/lang').then(function() { * The following converts dueDates to JavaScript dates: * * @codestart - * $.Model.extend("Contact",{ + * $.Model("Contact",{ * attributes : { * birthday : 'date' * }, @@ -386,25 +671,12 @@ steal.plugins('jquery/class', 'jquery/lang').then(function() { */ attributes: {}, /** - * @attribute defaults - * An object of default values to be set on all instances. This - * is useful if you want some value to be present when new instances are created. - * - * @codestart - * $.Model.extend("Recipe",{ - * defaults : { - * createdAt : new Date(); - * } - * },{}) - * - * var recipe = new Recipe(); - * - * recipe.createdAt //-> date + * @function wrap + * @hide + * @tag deprecated + * __warning__ : wrap is deprecated in favor of [jQuery.Model.static.model]. They + * provide the same functionality; however, model works better with Deferreds. * - * @codeend - */ - defaults: {}, - /** * Wrap is used to create a new instance from data returned from the server. * It is very similar to doing <code> new Model(attributes) </code> * except that wrap will check if the data passed has an @@ -420,38 +692,110 @@ steal.plugins('jquery/class', 'jquery/lang').then(function() { * @param {Object} attributes * @return {Model} an instance of the model */ - wrap: function( attributes ) { + // wrap place holder + /** + * $.Model.model is used as a [http://api.jquery.com/extending-ajax/#Converters Ajax converter] + * to convert the response of a [jQuery.Model.static.findOne] request + * into a model instance. + * + * You will never call this method directly. Instead, you tell $.ajax about it in findOne: + * + * $.Model('Recipe',{ + * findOne : function(params, success, error ){ + * return $.ajax({ + * url: '/services/recipes/'+params.id+'.json', + * type: 'get', + * + * dataType : 'json recipe.model' //LOOK HERE! + * }); + * } + * },{}) + * + * This makes the result of findOne a [http://api.jquery.com/category/deferred-object/ $.Deferred] + * that resolves to a model instance: + * + * var deferredRecipe = Recipe.findOne({id: 6}); + * + * deferredRecipe.then(function(recipe){ + * console.log('I am '+recipes.description+'.'); + * }) + * + * ## Non-standard Services + * + * $.jQuery.model expects data to be name-value pairs like: + * + * {id: 1, name : "justin"} + * + * It can also take an object with attributes in a data, attributes, or + * 'shortName' property. For a App.Models.Person model the following will all work: + * + * { data : {id: 1, name : "justin"} } + * + * { attributes : {id: 1, name : "justin"} } + * + * { person : {id: 1, name : "justin"} } + * + * + * ### Overwriting Model + * + * If your service returns data like: + * + * {id : 1, name: "justin", data: {foo : "bar"} } + * + * This will confuse $.Model.model. You will want to overwrite it to create + * an instance manually: + * + * $.Model('Person',{ + * model : function(data){ + * return new this(data); + * } + * },{}) + * + * ## API + * + * @param {Object} attributes An object of name-value pairs or an object that has a + * data, attributes, or 'shortName' property that maps to an object of name-value pairs. + * @return {Model} an instance of the model + */ + model: function( attributes ) { if (!attributes ) { return null; } return new this( - // checks for properties in an object (like rails 2.0 gives); - attributes[this.singularName] || attributes.data || attributes.attributes || attributes); + // checks for properties in an object (like rails 2.0 gives); + isObject(attributes[this._shortName]) || + isObject(attributes.data) || + isObject(attributes.attributes) || + attributes); }, /** - * Takes raw data from the server, and returns an array of model instances. - * Each item in the raw array becomes an instance of a model class. + * @function wrapMany + * @hide + * @tag deprecated * - * @codestart - * $.Model.extend("Recipe",{ - * helper : function(){ - * return i*i; - * } - * }) + * __warning__ : wrapMany is deprecated in favor of [jQuery.Model.static.models]. They + * provide the same functionality; however, models works better with Deferreds. * - * var recipes = Recipe.wrapMany([{id: 1},{id: 2}]) - * recipes[0].helper() //-> 1 - * @codeend + * $.Model.wrapMany converts a raw array of JavaScript Objects into an array (or [jQuery.Model.List $.Model.List]) of model instances. + * + * // a Recipe Model wi + * $.Model("Recipe",{ + * squareId : function(){ + * return this.id*this.id; + * } + * }) + * + * var recipes = Recipe.wrapMany([{id: 1},{id: 2}]) + * recipes[0].squareId() //-> 1 * * If an array is not passed to wrapMany, it will look in the object's .data * property. * * For example: * - * @codestart - * var recipes = Recipe.wrapMany({data: [{id: 1},{id: 2}]}) - * recipes[0].helper() //-> 1 - * @codeend + * var recipes = Recipe.wrapMany({data: [{id: 1},{id: 2}]}) + * recipes[0].squareId() //-> 1 + * * * Often wrapMany is used with this.callback inside a model's [jQuery.Model.static.findAll findAll] * method like: @@ -459,7 +803,7 @@ steal.plugins('jquery/class', 'jquery/lang').then(function() { * findAll : function(params, success, error){ * $.get('/url', * params, - * this.callback(['wrapMany',success]) ) + * this.callback(['wrapMany',success]), 'json' ) * } * * If you are having problems getting your model to callback success correctly, @@ -472,34 +816,107 @@ steal.plugins('jquery/class', 'jquery/lang').then(function() { * params, * function(data){ * var wrapped = self.wrapMany(data); - * success(data) - * }) + * success(wrapped) + * }, + * 'json') * } * * ## API * + * @param {Array} instancesRawData an array of raw name - value pairs like + * + * [{name: "foo", id: 4},{name: "bar", id: 5}] + * + * @return {Array} a JavaScript array of instances or a [jQuery.Model.List list] of instances + * if the model list plugin has been included. + */ + // wrapMany placeholder + /** + * $.Model.models is used as a [http://api.jquery.com/extending-ajax/#Converters Ajax converter] + * to convert the response of a [jQuery.Model.static.findAll] request + * into an array (or [jQuery.Model.List $.Model.List]) of model instances. + * + * You will never call this method directly. Instead, you tell $.ajax about it in findAll: + * + * $.Model('Recipe',{ + * findAll : function(params, success, error ){ + * return $.ajax({ + * url: '/services/recipes.json', + * type: 'get', + * data: params + * + * dataType : 'json recipe.models' //LOOK HERE! + * }); + * } + * },{}) + * + * This makes the result of findAll a [http://api.jquery.com/category/deferred-object/ $.Deferred] + * that resolves to a list of model instances: + * + * var deferredRecipes = Recipe.findAll({}); + * + * deferredRecipes.then(function(recipes){ + * console.log('I have '+recipes.length+'recipes.'); + * }) + * + * ## Non-standard Services + * + * $.jQuery.models expects data to be an array of name-value pairs like: + * + * [{id: 1, name : "justin"},{id:2, name: "brian"}, ...] + * + * It can also take an object with additional data about the array like: + * + * { + * count: 15000 //how many total items there might be + * data: [{id: 1, name : "justin"},{id:2, name: "brian"}, ...] + * } + * + * In this case, models will return an array of instances found in + * data, but with additional properties as expandos on the array: + * + * var people = Person.models({ + * count : 1500, + * data : [{id: 1, name: 'justin'}, ...] + * }) + * people[0].name // -> justin + * people.count // -> 1500 + * + * ### Overwriting Models + * + * If your service returns data like: + * + * {ballers: [{name: "justin", id: 5}]} + * + * You will want to overwrite models to pass the base models what it expects like: + * + * $.Model('Person',{ + * models : function(data){ + * this._super(data.ballers); + * } + * },{}) + * * @param {Array} instancesRawData an array of raw name - value pairs. * @return {Array} a JavaScript array of instances or a [jQuery.Model.List list] of instances * if the model list plugin has been included. */ - wrapMany: function( instancesRawData ) { + models: function( instancesRawData ) { if (!instancesRawData ) { return null; } - var listType = this.List || $.Model.List || Array, - res = new listType(), - arr = $.isArray(instancesRawData), + var res = getList(this.List), + arr = isArray(instancesRawData), raw = arr ? instancesRawData : instancesRawData.data, length = raw.length, i = 0; //@steal-remove-start if (! length ) { - steal.dev.warn("model.js wrapMany has no data. If you're trying to wrap 1 item, use wrap. ") + steal.dev.warn("model.js models has no data. If you have one item, use model") } //@steal-remove-end res._use_call = true; //so we don't call next function with all of these for (; i < length; i++ ) { - res.push(this.wrap(raw[i])); + res.push(this.model(raw[i])); } if (!arr ) { //push other stuff onto array for ( var prop in instancesRawData ) { @@ -517,7 +934,7 @@ steal.plugins('jquery/class', 'jquery/lang').then(function() { * For example, it's common in .NET to use Id. Your model might look like: * * @codestart - * $.Model.extend("Friends",{ + * $.Model("Friends",{ * id: "Id" * },{}); * @codeend @@ -536,11 +953,12 @@ steal.plugins('jquery/class', 'jquery/lang').then(function() { if ( this.associations[property] ) { return; } + stub = this.attributes[property] || (this.attributes[property] = type); return type; }, // a collection of all models - models: {}, + _models: {}, /** * If OpenAjax is available, * publishes to OpenAjax.hub. Always adds the shortName.event. @@ -555,44 +973,15 @@ steal.plugins('jquery/class', 'jquery/lang').then(function() { */ publish: function( event, data ) { //@steal-remove-start - steal.dev.log("Model.js - publishing " + underscore(this.shortName) + "." + event); + steal.dev.log("Model.js - publishing " + this._shortName + "." + event); //@steal-remove-end if ( window.OpenAjax ) { - OpenAjax.hub.publish(underscore(this.shortName) + "." + event, data); + OpenAjax.hub.publish(this._shortName + "." + event, data); } }, - /** - * @hide - * Guesses the type of an object. This is what sets the type if not provided in - * [jQuery.Model.static.attributes]. - * @param {Object} object the object you want to test. - * @return {String} one of string, object, date, array, boolean, number, function - */ - guessType: function( object ) { - if ( typeof object != 'string' ) { - if ( object === null ) { - return typeof object; - } - if ( object.constructor == Date ) { - return 'date'; - } - if ( $.isArray(object) ) { - return 'array'; - } - return typeof object; - } - if ( object === "" ) { - return 'string'; - } - //check if true or false - if ( object == 'true' || object == 'false' ) { - return 'boolean'; - } - if (!isNaN(object) && isFinite(+object) ) { - return 'number'; - } - return typeof object; + guessType : function(){ + return "string" }, /** * @attribute convert @@ -612,7 +1001,9 @@ steal.plugins('jquery/class', 'jquery/lang').then(function() { "boolean": function( val ) { return Boolean(val); } - } + }, + bind: bind, + unbind: unbind }, /** * @Prototype @@ -625,7 +1016,7 @@ steal.plugins('jquery/class', 'jquery/lang').then(function() { * Setup should never be called directly. * * @codestart - * $.Model.extend("Recipe") + * $.Model("Recipe") * var recipe = new Recipe({foo: "bar"}); * recipe.foo //-> "bar" * recipe.attr("foo") //-> "bar" @@ -634,15 +1025,10 @@ steal.plugins('jquery/class', 'jquery/lang').then(function() { * @param {Object} attributes a hash of attributes */ setup: function( attributes ) { - var stub; - // so we know not to fire events - this._initializing = true; - - stub = this.Class.defaults && this.attrs(this.Class.defaults); - - this.attrs(attributes); - delete this._initializing; + this._init = true; + this.attrs(extend({},this.Class.defaults,attributes)); + delete this._init; }, /** * Sets the attributes on this instance and calls save. @@ -676,7 +1062,7 @@ steal.plugins('jquery/class', 'jquery/lang').then(function() { * model/validations plugin. * * @codestart - * $.Model.extend("Task",{ + * $.Model("Task",{ * init : function(){ * this.validatePresenceOf("dueDate") * } @@ -690,12 +1076,12 @@ steal.plugins('jquery/class', 'jquery/lang').then(function() { */ errors: function( attrs ) { if ( attrs ) { - attrs = $.isArray(attrs) ? attrs : $.makeArray(arguments); + attrs = isArray(attrs) ? attrs : makeArray(arguments); } var errors = {}, self = this, addErrors = function( attr, funcs ) { - $.each(funcs, function( i, func ) { + each(funcs, function( i, func ) { var res = func.call(self); if ( res ) { if (!errors.hasOwnProperty(attr) ) { @@ -708,7 +1094,7 @@ steal.plugins('jquery/class', 'jquery/lang').then(function() { }); }; - $.each(attrs || this.Class.validations || {}, function( attr, funcs ) { + each(attrs || this.Class.validations || {}, function( attr, funcs ) { if ( typeof attr == 'number' ) { attr = funcs; funcs = self.Class.validations[attr]; @@ -728,7 +1114,7 @@ steal.plugins('jquery/class', 'jquery/lang').then(function() { * getters if available. * * @codestart - * $.Model.extend("Recipe") + * $.Model("Recipe") * var recipe = new Recipe(); * recipe.attr("foo","bar") * recipe.foo //-> "bar" @@ -742,7 +1128,7 @@ steal.plugins('jquery/class', 'jquery/lang').then(function() { * with the value and is expected to return the converted value. * * @codestart - * $.Model.extend("Recipe",{ + * $.Model("Recipe",{ * setCreatedAt : function(raw){ * return Date.parse(raw) * } @@ -761,7 +1147,7 @@ steal.plugins('jquery/class', 'jquery/lang').then(function() { * call success with the converted value. For example: * * @codestart - * $.Model.extend("Recipe",{ + * $.Model("Recipe",{ * setTitle : function(title, success, error){ * $.post( * "recipe/update/"+this.id+"/title", @@ -803,7 +1189,7 @@ steal.plugins('jquery/class', 'jquery/lang').then(function() { * every time the attribute value changes. For example: * * @codestart - * $.Model.extend("School") + * $.Model("School") * var school = new School(); * school.bind("address", function(ev, address){ * alert('address changed to '+address); @@ -814,7 +1200,7 @@ steal.plugins('jquery/class', 'jquery/lang').then(function() { * You can also bind to attribute errors. * * @codestart - * $.Model.extend("School",{ + * $.Model("School",{ * setName : function(name, success, error){ * if(!name){ * error("no name"); @@ -835,11 +1221,7 @@ steal.plugins('jquery/class', 'jquery/lang').then(function() { * @param {Function} handler a function to call back when an event happens on this model. * @return {model} the model instance for chaining */ - bind: function( eventType, handler ) { - var wrapped = $(this); - wrapped.bind.apply(wrapped, arguments); - return this; - }, + bind: bind, /** * Unbinds an event handler from this instance. * Read [jQuery.Model.prototype.bind] for @@ -847,11 +1229,7 @@ steal.plugins('jquery/class', 'jquery/lang').then(function() { * @param {String} eventType * @param {Function} handler */ - unbind: function( eventType, handler ) { - var wrapped = $(this); - wrapped.unbind.apply(wrapped, arguments); - return this; - }, + unbind: unbind, /** * Checks if there is a set_<i>property</i> value. If it returns true, lets it handle; otherwise * saves it. @@ -871,9 +1249,10 @@ steal.plugins('jquery/class', 'jquery/lang').then(function() { $(self).triggerHandler("error." + property, errors); }; - // if the setter returns nothing, do not set - // we might want to indicate if this was set ok - if ( this[setName] && (value = this[setName](value, this.callback('_updateProperty', property, value, old, success, errorCallback), errorCallback)) === undefined ) { + // provides getter / setters + // + if ( this[setName] && + (value = this[setName](value, this.callback('_updateProperty', property, value, old, success, errorCallback), errorCallback)) === undefined ) { return; } this._updateProperty(property, value, old, success, errorCallback); @@ -899,22 +1278,24 @@ steal.plugins('jquery/class', 'jquery/lang').then(function() { (converter ? converter.call(Class, value) : //convert it to something useful value)); //just return it //validate (only if not initializing, this is for performance) - if (!this._initializing ) { + if (!this._init ) { errors = this.errors(property); } if ( errors ) { + //get an array of errors errorCallback(errors); } else { - if ( old !== val && !this._initializing ) { - $(this).triggerHandler(property, val); + if ( old !== val && !this._init ) { + $(this).triggerHandler(property, [val]); + $(this).triggerHandler("updated.attr", [property,val, old]); // this is for 3.1 } stub = success && success(this); } //if this class has a global list, add / remove from the list. - if ( property == Class.id && val !== null && Class.list ) { + if ( property === Class.id && val !== null && Class.list ) { // if we didn't have an old id, add ourselves if (!old ) { Class.list.push(this); @@ -937,6 +1318,8 @@ steal.plugins('jquery/class', 'jquery/lang').then(function() { * }) * @codeend * + * This can be used nicely with [jquery.model.events]. + * * @param {Object} [attributes] if present, the list of attributes to send * @return {Object} the current attributes of the model */ @@ -972,7 +1355,7 @@ steal.plugins('jquery/class', 'jquery/lang').then(function() { * @return {Boolean} false if an id is set, true if otherwise. */ isNew: function() { - var id = this[this.Class.id]; + var id = getId(this); return (id === undefined || id === null); //if null or undefined }, /** @@ -992,16 +1375,7 @@ steal.plugins('jquery/class', 'jquery/lang').then(function() { * @param {Function} [error] called if the save was not successful. */ save: function( success, error ) { - var stub; - - if ( this.errors() ) { - //needs to send errors - return false; - } - stub = this.isNew() ? this.Class.create(this.attrs(), this.callback(['created', success]), error) : this.Class.update(this[this.Class.id], this.attrs(), this.callback(['updated', success]), error); - - //this.is_new_record = this.Class.new_record_func; - return true; + return makeRequest(this, this.isNew() ? 'create' : 'update' , success, error); }, /** @@ -1020,9 +1394,9 @@ steal.plugins('jquery/class', 'jquery/lang').then(function() { * @param {Function} [error] called if an unsuccessful destroy */ destroy: function( success, error ) { - this.Class.destroy(this[this.Class.id], this.callback(["destroyed", success]), error); + return makeRequest(this, 'destroy' , success, error , 'destroyed'); }, - + /** * Returns a unique identifier for the model instance. For example: @@ -1034,7 +1408,7 @@ steal.plugins('jquery/class', 'jquery/lang').then(function() { * @return {String} */ identity: function() { - var id = this[this.Class.id]; + var id = getId(this); return this.Class._fullName + '_' + (this.Class.escapeIdentity ? encodeURIComponent(id) : id); }, /** @@ -1065,22 +1439,40 @@ steal.plugins('jquery/class', 'jquery/lang').then(function() { return $("." + this.identity(), context); }, /** - * Publishes to open ajax hub - * @param {String} event - * @param {Object} [opt6] data if missing, uses the instance in {data: this} + * Publishes to OpenAjax.hub + * + * $.Model('Task', { + * complete : function(cb){ + * var self = this; + * $.post('/task/'+this.id, + * {complete : true}, + * function(){ + * self.attr('completed', true); + * self.publish('completed'); + * }) + * } + * }) + * + * + * @param {String} event The event type. The model's short name will be automatically prefixed. + * @param {Object} [data] if missing, uses the instance in {data: this} */ publish: function( event, data ) { this.Class.publish(event, data || this); }, hookup: function( el ) { - var shortName = underscore(this.Class.shortName), + var shortName = this.Class._shortName, models = $.data(el, "models") || $.data(el, "models", {}); $(el).addClass(shortName + " " + this.identity()); models[shortName] = this; } }); + // map wrapMany + $.Model.wrapMany = $.Model.models; + $.Model.wrap = $.Model.model; + - $.each([ + each([ /** * @function created * @hide @@ -1098,20 +1490,24 @@ steal.plugins('jquery/class', 'jquery/lang').then(function() { /** * @function destroyed * @hide - * Called after an instance is destroyed. Publishes - * "shortName.destroyed" + * Called after an instance is destroyed. + * - Publishes "shortName.destroyed". + * - Triggers a "destroyed" event on this model. + * - Removes the model from the global list if its used. + * */ "destroyed"], function( i, funcName ) { $.Model.prototype[funcName] = function( attrs ) { var stub; if ( funcName === 'destroyed' && this.Class.list ) { - this.Class.list.remove(this[this.Class.id]); + this.Class.list.remove(getId(this)); } - $(this).triggerHandler(funcName); stub = attrs && typeof attrs == 'object' && this.attrs(attrs.attrs ? attrs.attrs() : attrs); + $(this).triggerHandler(funcName); this.publish(funcName, this); - return [this].concat($.makeArray(arguments)); + $([this.Class]).triggerHandler(funcName, this); + return [this].concat(makeArray(arguments)); // return like this for this.callback chains }; }); @@ -1137,25 +1533,41 @@ steal.plugins('jquery/class', 'jquery/lang').then(function() { var collection = [], kind, ret, retType; this.each(function() { - $.each($.data(this, "models") || {}, function( name, instance ) { + each($.data(this, "models") || {}, function( name, instance ) { //either null or the list type shared by all classes - kind = kind === undefined ? instance.Class.List || null : (instance.Class.List === kind ? kind : null); + kind = kind === undefined ? + instance.Class.List || null : + (instance.Class.List === kind ? kind : null); collection.push(instance); }); }); - retType = kind || $.Model.List || Array; - ret = new retType(); + ret = getList(kind); - ret.push.apply(ret, $.unique(collection)); + ret.push.apply(ret, unique(collection)); return ret; }; /** * @function model * - * Returns the first model instance found from [jQuery.fn.models]. + * Returns the first model instance found from [jQuery.fn.models] or + * sets the model instance on an element. * - * @param {Object} type + * //gets an instance + * ".edit click" : function(el) { + * el.closest('.todo').model().destroy() + * }, + * // sets an instance + * list : function(items){ + * var el = this.element; + * $.each(item, function(item){ + * $('<div/>').model(item) + * .appendTo(el) + * }) + * } + * + * @param {Object} [type] The type of model to return. If a model instance is provided + * it will add the model to the element. */ $.fn.model = function( type ) { if ( type && type instanceof $.Model ) { @@ -1170,7 +1582,8 @@ steal.plugins('jquery/class', 'jquery/lang').then(function() { * @page jquery.model.services Service APIs * @parent jQuery.Model * - * Models provide an abstract API for connecting to your Services. By implementing static: + * Models provide an abstract API for connecting to your Services. + * By implementing static: * * - [jQuery.Model.static.findAll] * - [jQuery.Model.static.findOne] @@ -1178,8 +1591,58 @@ steal.plugins('jquery/class', 'jquery/lang').then(function() { * - [jQuery.Model.static.update] * - [jQuery.Model.static.destroy] * - * You can pass a model class to widgets and the widgets can interface with the - * model. This prevents the need for every widget to be configured with the ajax functionality - * necessary to make a request to your services. + * You can find more details on how to implement each method. + * Typically, you can just use templated service urls. But if you need to + * implement these methods yourself, the following + * is a useful quick reference: + * + * ### create(attrs, success([attrs]), error()) -> deferred + * + * - <code>attrs</code> - an Object of attribute / value pairs + * - <code>success([attrs])</code> - Create calls success when the request has completed + * successfully. Success can be called back with an object that represents + * additional properties that will be set on the instance. For example, the server might + * send back an updatedAt date. + * - <code>error</code> - Create should callback error if an error happens during the request + * - <code>deferred</code> - A deferred that gets resolved to any additional attrs + * that might need to be set on the model instance. + * + * + * ### findAll( params, success(items), error) -> deferred + * + * + * - <code>params</code> - an Object that filters the items returned + * - <code>success(items)</code> - success should be called with an Array of Model instances. + * - <code>error</code> - called if an error happens during the request + * - <code>deferred</code> - A deferred that gets resolved to the list of items + * + * ### findOne(params, success(items), error) -> deferred + * + * - <code>params</code> - an Object that filters the item returned + * - <code>success(item)</code> - success should be called with a model instance. + * - <code>error</code> - called if an error happens during the request + * - <code>deferred</code> - A deferred that gets resolved to a model instance + * + * ### update(id, attrs, success([attrs]), error()) -> deferred + * + * - <code>id</code> - the id of the instance you are updating + * - <code>attrs</code> - an Object of attribute / value pairs + * - <code>success([attrs])</code> - Call success when the request has completed + * successfully. Success can be called back with an object that represents + * additional properties that will be set on the instance. For example, the server might + * send back an updatedAt date. + * - <code>error</code> - Callback error if an error happens during the request + * - <code>deferred</code> - A deferred that gets resolved to any additional attrs + * that might need to be set on the model instance. + * + * ### destroy(id, success([attrs]), error()) -> deferred + * + * - <code>id</code> - the id of the instance you are destroying + * - <code>success([attrs])</code> - Calls success when the request has completed + * successfully. Success can be called back with an object that represents + * additional properties that will be set on the instance. + * - <code>error</code> - Create should callback error if an error happens during the request + * - <code>deferred</code> - A deferred that gets resolved to any additional attrs + * that might need to be set on the model instance. */ }); \ No newline at end of file diff --git a/browserid/static/jquery/model/pages/deferreds.js b/browserid/static/jquery/model/pages/deferreds.js new file mode 100644 index 0000000000000000000000000000000000000000..655e96116c5c911cab6308c244ca24c2c72c2027 --- /dev/null +++ b/browserid/static/jquery/model/pages/deferreds.js @@ -0,0 +1,51 @@ +/* +@page jquery.model.deferreds Deferreds +@parent jQuery.Model + +Models (and also views) work +with [http://api.jquery.com/category/deferred-object/ jQuery.Deferred]. If +you properly fill out a model's [jquery.model.services service API], asynchronous +requests done via the model will return a jQuery.Deferred. + +## findAll example + +The following example, requests tasks and people and waits for both requests +to be complete before alerting the user: + + var tasksDef = Task.findAll(), + peopleDef = People.findAll(); + + $.when(tasksDef,peopleDef).done(function(taskResponse, peopleResponse){ + alert("There are "+taskRespone[0].length+" tasks and "+ + peopleResponse[0].length+" people."); + }); + +__Note__ taskResponse[0] is an Array of tasks. + +## save and destroy example + +Calls to [jQuery.Model.prototype.save save] and [jQuery.Model.prototype.destroy] also +return a deferred. The deferred is resolved to the newly created, destroyed, or updated +model instance. + +The following creates a task, updates it, and destroys it: + + var taskD = new Task({name: "dishes"}).save(); + + taskD.done(function(task){ + + var taskD2 = task.update({name: "all the dishes"}) + + taskD2.done(function(task){ + + var taskD3 = task.destroy(); + + taskD3.done(function(){ + console.log("task destroyed"); + }) + + }) + + }); + +*/ \ No newline at end of file diff --git a/browserid/static/jquery/model/pages/encapsulate.js b/browserid/static/jquery/model/pages/encapsulate.js index b6300a6a3c7a823bc63202f607881ce0c239bee9..866ced736f34dd82d8217b3aea3ad0752fae0365 100644 --- a/browserid/static/jquery/model/pages/encapsulate.js +++ b/browserid/static/jquery/model/pages/encapsulate.js @@ -2,9 +2,8 @@ @page jquery.model.encapsulate Service Encapsulation @parent jQuery.Model -<h1>Service / Ajax Encapsulation</h1> - -Models encapsulate your application's raw data. +Models encapsulate your application's raw data. This promotes reuse and provide a +standard interface for widgets to talk to services. The majority of the time, the raw data comes from services your server provides. For example, @@ -28,18 +27,14 @@ The server might return something like: In most jQuery code, you'll see something like the following to retrieve contacts data: -@codestart -$.get('/contacts.json', - {type: 'tasty'}, - successCallback, - 'json')</code></pre> -@codeend + $.get('/contacts.json', + {type: 'tasty'}, + successCallback, + 'json') Instead, model encapsulates (wraps) this request so you call it like: -@codestart -Contact.findAll({type: 'old'}, successCallback); -@codeend + Contact.findAll({type: 'old'}, successCallback); And instead of raw data, findAll returns contact instances that let you do things like: @@ -62,7 +57,8 @@ The Grid demo shows using two different models with the same widget. ## How to Encapsulate -Think of models as a contract for creating, reading, updating, and deleting data. +Think of models as a contract for creating, reading, updating, and deleting data. + By filling out a model, you can pass that model to a widget and the widget will use the model as a proxy for your data. @@ -70,7 +66,7 @@ The following chart shows the methods most models provide: <table> <tr> - <td>Create</td><td><pre>Contact.create(attrs, success, error</pre></td> + <td>Create</td><td><pre>Contact.create(attrs, success, error)</pre></td> </tr> <tr> <td>Read</td><td><pre>Contact.findAll(params,success,error) @@ -85,8 +81,77 @@ Contact.findOne(params, success, error)</pre></td> </table> By filling out these methods, you get the benefits of encapsulation, -AND all the other magic Model provides. Lets see how we might fill out the -<code>Contact.findAll</code> function: +AND all the other magic Model provides. + +There are two ways to fill out these methods: + + - providing templated service urls + - implementing the method + +## Using Templated Service URLS + +If your server is REST-ish, you can simply provide +urls to your services. + +The following shows filling out a +Task model's urls. For each method it shows calling the function, +how the service request is made, and what the server's response +should look like: + + $.Model("Task",{ + + // Task.findAll({foo: "bar"}) + // -> GET /tasks.json?foo=bar + // <- [{id: 1, name: "foo"},{ ... }] + findAll : "/tasks.json", + + // Task.findOne({id: 5}) + // -> GET /tasks/5.json + findOne : "/tasks/{id}.json", + + // new Task({name: 'justin'}).save() + // -> POST /tasks.json id=5 + // <- {id : 5} + create : "/tasks.json", + + // task.update({name: 'justin'}) + // -> PUT /tasks/5.json name=justin + // <- {} + update : "/tasks/{id}.json", + + // task.destroy() + // -> DESTROY /tasks/5.json + // <- {} + destroy : "/tasks/{id}.json" + },{}) + +You can change the HTTP request type by putting a GET, POST, DELETE, PUT like: + + $.Model("Todo",{ + destroy: "POST /task/delete/{id}.json + },{}) + +<b>Note:</b> Even if your server doesn't respond with service data +in the same way, it's likely that $.Model will be able to figure it out. If not, +you can probably +overwrite [jQuery.Model.static.models models] +or [jQuery.Model.static.model model]. If that doesn't work, you can +always implement it yourself. + +## Implement Service Methods + +If providing a url doesn't work for you, you +might need to fill out the +service method yourself. Before doing this, it's good +to have an understanding of jQuery's Ajax converters and +deferreds. + + + + + +Lets see how we might fill out the +<code>Contact.findAll</code> function to work with JSONP: @codestart $.Model.extend('Contact', @@ -94,23 +159,10 @@ $.Model.extend('Contact', findAll : function(params, success, error){ // do the ajax request - $.get('/contacts.json', + return $.get('/contacts.jsonp', params, - function( json ){ - - // on success, create new Contact - // instances for each contact - var wrapped = []; - - for(var i =0; i< json.length;i++){ - wrapped.push( new Contact(json[i] ) ); - } - - //call success with the contacts - success( wrapped ); - - }, - 'json'); + success, + 'jsonp contact.models'); } }, { @@ -119,31 +171,7 @@ $.Model.extend('Contact', }); @codeend -Well, that would be annoying to write out every time. Fortunately, models have -the wrapMany method which will make it easier: -@codestart -findAll : function(params, success, error){ - $.get('/contacts.json', - params, - function( json ){ - success(Contact.wrapMany(json)); - }, - 'json'); - } -@codeend - -Model is based off JavaScriptMVC's <code>jQuery.Class</code>. It's callback allows us to pipe -wrapMany into the success handler and make our code even shorter: - -@codestart -findAll : function(params, success, error){ - $.get('/contacts.json', - params, - this.callback(['wrapMany', success]), - 'json') - } -@codeend If we wanted to make a list of contacts, we could do it like: diff --git a/browserid/static/jquery/model/test/people.json b/browserid/static/jquery/model/test/people.json new file mode 100644 index 0000000000000000000000000000000000000000..b5f86a2e0cfddfbe9a35e562b3fe5ed5991ec8b7 --- /dev/null +++ b/browserid/static/jquery/model/test/people.json @@ -0,0 +1,4 @@ +[{ + "id" : 5, + "name" : "Justin" +}] diff --git a/browserid/static/jquery/model/test/person.json b/browserid/static/jquery/model/test/person.json new file mode 100644 index 0000000000000000000000000000000000000000..ea58f4ea70d47239b2028a91e29fcbffeab5f2ed --- /dev/null +++ b/browserid/static/jquery/model/test/person.json @@ -0,0 +1,4 @@ +{ + "id" : 5, + "name" : "Justin" +} diff --git a/browserid/static/jquery/model/test/qunit/findAll.json b/browserid/static/jquery/model/test/qunit/findAll.json new file mode 100644 index 0000000000000000000000000000000000000000..8dc242fa82271ea320ad91dcde8842c0952b7b88 --- /dev/null +++ b/browserid/static/jquery/model/test/qunit/findAll.json @@ -0,0 +1,4 @@ +[{ + "id" : 1, + "name" : "Thing 1" +}] diff --git a/browserid/static/jquery/model/test/qunit/model_test.js b/browserid/static/jquery/model/test/qunit/model_test.js index 0eb3880b132f8c9eb8d162dca324a1f9d0228c61..6ded648db990a5ad4a23dfa9c756a8bd75ec9550 100644 --- a/browserid/static/jquery/model/test/qunit/model_test.js +++ b/browserid/static/jquery/model/test/qunit/model_test.js @@ -46,7 +46,135 @@ test("CRUD", function(){ equals(inst, person, "we get back the same instance"); equals(person.zoo, "monkeys", "updated to monkeys zoo! This tests that you callback with the attrs") }) -}) +}); + +test("findAll deferred", function(){ + $.Model.extend("Person",{ + findAll : function(params, success, error){ + return $.ajax({ + url : "/people", + data : params, + dataType : "json person.models", + fixture: "//jquery/model/test/people.json" + }) + } + },{}); + stop(); + var people = Person.findAll({}); + people.then(function(people){ + equals(people.length, 1, "we got a person back"); + equals(people[0].name, "Justin", "Got a name back"); + equals(people[0].Class.shortName, "Person", "got a class back"); + start(); + }) +}); + +test("findOne deferred", function(){ + $.Model.extend("Person",{ + findOne : function(params, success, error){ + return $.ajax({ + url : "/people/5", + data : params, + dataType : "json person.model", + fixture: "//jquery/model/test/person.json" + }) + } + },{}); + stop(); + var person = Person.findOne({}); + person.then(function(person){ + equals(person.name, "Justin", "Got a name back"); + equals(person.Class.shortName, "Person", "got a class back"); + start(); + }) +}); + +test("save deferred", function(){ + + $.Model("Person",{ + create : function(attrs, success, error){ + return $.ajax({ + url : "/people", + data : attrs, + type : 'post', + dataType : "json", + fixture: function(){ + return [{id: 5}] + }, + success : success + }) + } + },{}); + + var person = new Person({name: "Justin"}), + personD = person.save(); + + stop(); + personD.then(function(person){ + start() + equals(person.id, 5, "we got an id") + + }); + +}); + +test("update deferred", function(){ + + $.Model("Person",{ + update : function(id, attrs, success, error){ + return $.ajax({ + url : "/people/"+id, + data : attrs, + type : 'post', + dataType : "json", + fixture: function(){ + return [{thing: "er"}] + }, + success : success + }) + } + },{}); + + var person = new Person({name: "Justin", id:5}), + personD = person.save(); + + stop(); + personD.then(function(person){ + start() + equals(person.thing, "er", "we got updated") + + }); + +}); + +test("destroy deferred", function(){ + + $.Model("Person",{ + destroy : function(id, success, error){ + return $.ajax({ + url : "/people/"+id, + type : 'post', + dataType : "json", + fixture: function(){ + return [{thing: "er"}] + }, + success : success + }) + } + },{}); + + var person = new Person({name: "Justin", id:5}), + personD = person.destroy(); + + stop(); + personD.then(function(person){ + start() + equals(person.thing, "er", "we got destroyed") + + }); +}); + + test("hookup and model", function(){ var div = $("<div/>") var p = new Person({foo: "bar2", id: 5}); @@ -55,17 +183,23 @@ test("hookup and model", function(){ ok(div.hasClass("person_5"), "has person_5"); equals(p, div.model(),"gets model" ) }) -test("guess type", function(){ - equals("array", $.Model.guessType( [] ) ); - equals("date", $.Model.guessType( new Date() ) ); - equals("boolean", $.Model.guessType( true ) ); - equals("number", $.Model.guessType( "1" ) ); - equals("string", $.Model.guessType( "a" ) ); - - equals("string", $.Model.guessType( "1e234234324234" ) ); - equals("string", $.Model.guessType( "-1e234234324234" ) ); +// test that models returns an array of unique instances +test("unique models", function(){ + var div1 = $("<div/>") + var div2 = $("<div/>") + var div3 = $("<div/>") + var p = new Person({foo: "bar2", id: 5}); + var p2 = new Person({foo: "bar3", id: 4}); + p.hookup( div1[0] ); + p.hookup( div2[0] ); + p2.hookup( div3[0] ); + var models = div1.add(div2).add(div3).models(); + equals(p, models[0], "gets models" ) + equals(p2, models[1], "gets models" ) + equals(2, models.length, "gets models" ) }) + test("wrapMany", function(){ var people = Person.wrapMany([ {id: 1, name: "Justin"} @@ -73,6 +207,42 @@ test("wrapMany", function(){ equals(people[0].prettyName(),"Mr. Justin","wraps wrapping works") }); + + +test("async setters", function(){ + + /* + $.Model("Test.AsyncModel",{ + setName : function(newVal, success, error){ + + + setTimeout(function(){ + success(newVal) + }, 100) + } + }); + + var model = new Test.AsyncModel({ + name : "justin" + }); + equals(model.name, "justin","property set right away") + + //makes model think it is no longer new + model.id = 1; + + var count = 0; + + model.bind('name', function(ev, newName){ + equals(newName, "Brian",'new name'); + equals(++count, 1, "called once"); + ok(new Date() - now > 0, "time passed") + start(); + }) + var now = new Date(); + model.attr('name',"Brian"); + stop();*/ +}) + test("binding", 2,function(){ var inst = new Person({foo: "bar"}); @@ -104,11 +274,13 @@ test("error binding", 1, function(){ }) test("auto methods",function(){ + //turn off fixtures + $.fixture.on = false; var School = $.Model.extend("Jquery.Model.Models.School",{ findAll : steal.root.join("jquery/model/test")+"/{type}.json", findOne : steal.root.join("jquery/model/test")+"/{id}.json", create : steal.root.join("jquery/model/test")+"/create.json", - update : steal.root.join("jquery/model/test")+"/update{id}.json" + update : "POST "+steal.root.join("jquery/model/test")+"/update{id}.json" },{}) stop(5000); School.findAll({type:"schools"}, function(schools){ @@ -123,8 +295,10 @@ test("auto methods",function(){ new School({name: "Highland"}).save(function(){ equals(this.name,"Highland","create gets the right name") this.update({name: "LHS"}, function(){ - equals(this.name,"LHS","create gets the right name") start(); + equals(this.name,"LHS","create gets the right name") + + $.fixture.on = true; }) }) @@ -140,4 +314,77 @@ test("isNew", function(){ ok(p2.isNew(), "null id is new"); var p3 = new Person({id: 0}) ok(!p3.isNew(), "0 is not new"); +}); +test("findAll string", function(){ + $.fixture.on = false; + $.Model("Test.Thing",{ + findAll : steal.root.join("jquery/model/test/qunit/findAll.json") + },{}); + stop(); + Test.Thing.findAll({},function(things){ + equals(things.length, 1, "got an array"); + equals(things[0].id, 1, "an array of things"); + start(); + $.fixture.on = true; + }) }) +test("Empty uses fixtures", function(){ + $.Model("Test.Things"); + $.fixture.make("thing", 10, function(i){ + return { + id: i + } + }); + stop(); + Test.Thing.findAll({}, function(things){ + start(); + equals(things.length, 10,"got 10 things") + }) +}); + +test("Model events" , function(){ + var order = 0; + $.Model("Test.Event",{ + create : function(attrs, success){ + success({id: 1}) + }, + update : function(id, attrs, success){ + success(attrs) + }, + destroy : function(id, success){ + success() + } + },{}); + + stop(); + $([Test.Event]).bind('created',function(ev, passedItem){ + + ok(this === Test.Event, "got model") + ok(passedItem === item, "got instance") + equals(++order, 1, "order"); + passedItem.update({}); + + }).bind('updated', function(ev, passedItem){ + equals(++order, 2, "order"); + ok(this === Test.Event, "got model") + ok(passedItem === item, "got instance") + + passedItem.destroy({}); + + }).bind('destroyed', function(ev, passedItem){ + equals(++order, 3, "order"); + ok(this === Test.Event, "got model") + ok(passedItem === item, "got instance") + + start(); + + }) + + var item = new Test.Event(); + item.save(); + + + + + +}); diff --git a/browserid/static/jquery/model/test/qunit/qunit.js b/browserid/static/jquery/model/test/qunit/qunit.js index 6284accecc31b1344c301385929688cf0bd7fcf8..8910d23f8173d6371fa9e89fcc5c0a64e0a93c54 100644 --- a/browserid/static/jquery/model/test/qunit/qunit.js +++ b/browserid/static/jquery/model/test/qunit/qunit.js @@ -1,11 +1,10 @@ //we probably have to have this only describing where the tests are steal - .plugins("jquery/model") //load your app + .plugins("jquery/model","jquery/dom/fixture") //load your app .plugins('funcunit/qunit') //load qunit .then("model_test") .plugins( "jquery/model/associations/test/qunit", "jquery/model/backup/qunit", "jquery/model/list/test/qunit" - ).then("//jquery/model/validations/qunit/validations_test") diff --git a/browserid/static/jquery/model/validations/qunit/validations_test.js b/browserid/static/jquery/model/validations/qunit/validations_test.js index 29487526d9508cae7c38cb9886b4fe4492c84bea..f7909f46252d5a911df96598e95f0fe3ab1984e5 100644 --- a/browserid/static/jquery/model/validations/qunit/validations_test.js +++ b/browserid/static/jquery/model/validations/qunit/validations_test.js @@ -61,13 +61,42 @@ test("validatesFormatOf", function(){ }); test("validatesInclusionOf", function(){ + Person.validateInclusionOf("thing", ["yes", "no", "maybe"]); + + ok(!new Person({thing: "yes"}).errors(),"no errors"); + + var errors = new Person({thing: "foobar"}).errors(); + + ok(errors, "there are errors"); + equals(errors.thing.length,1,"one error on thing"); + + equals(errors.thing[0],"is not a valid option (perhaps out of range)","basic message"); + + Person.validateInclusionOf("otherThing", ["yes", "no", "maybe"],{message: "not a valid option"}); + var errors2 = new Person({thing: "yes", otherThing: "maybe not"}).errors(); -}) + equals(errors2.otherThing[0],"not a valid option", "can supply a custom message"); +}); test("validatesLengthOf", function(){ + Person.validateLengthOf("thing", 2, 5); + + ok(!new Person({thing: "yes"}).errors(),"no errors"); -}) + var errors = new Person({thing: "foobar"}).errors(); + + ok(errors, "there are errors"); + equals(errors.thing.length,1,"one error on thing"); + + equals(errors.thing[0],"is too long (max=5)","basic message"); + + Person.validateLengthOf("otherThing", 2, 5, {message: "invalid length"}); + + var errors2 = new Person({thing: "yes", otherThing: "too long"}).errors(); + + equals(errors2.otherThing[0],"invalid length", "can supply a custom message"); +}); test("validatesPresenceOf", function(){ $.Model.extend("Task",{ @@ -81,11 +110,42 @@ test("validatesPresenceOf", function(){ ok(errors) ok(errors.dueDate) - equals(errors.dueDate[0], "can't be empty" , "right message") + equals(errors.dueDate[0], "can't be empty" , "right message"); + + task = new Task({dueDate : "yes"}); + errors = task.errors();; + + ok(!errors, "no errors "+typeof errors); + + $.Model.extend("Task",{ + init : function(){ + this.validatePresenceOf("dueDate",{message : "You must have a dueDate"}) + } + },{}); + + task = new Task({dueDate : "yes"}); + errors = task.errors();; + + ok(!errors, "no errors "+typeof errors); }) test("validatesRangeOf", function(){ - -}) + Person.validateRangeOf("thing", 2, 5); + + ok(!new Person({thing: 4}).errors(),"no errors"); + + var errors = new Person({thing: 6}).errors(); + + ok(errors, "there are errors") + equals(errors.thing.length,1,"one error on thing"); + + equals(errors.thing[0],"is out of range [2,5]","basic message"); + + Person.validateRangeOf("otherThing", 2, 5, {message: "value out of range"}); + + var errors2 = new Person({thing: 4, otherThing: 6}).errors(); + + equals(errors2.otherThing[0],"value out of range", "can supply a custom message"); +}); }) diff --git a/browserid/static/jquery/model/validations/validations.js b/browserid/static/jquery/model/validations/validations.js index 00948246da87225192f651974e845654ab462b64..3cb2dc3cc8a51dce37d5f00a67b9775d50d1c724 100644 --- a/browserid/static/jquery/model/validations/validations.js +++ b/browserid/static/jquery/model/validations/validations.js @@ -1,7 +1,7 @@ steal.plugins('jquery/model').then(function($){ /** @page jquery.model.validations Validations -@plugin jquery/mode/validations +@plugin jquery/model/validations @download http://jmvcsite.heroku.com/pluginify?plugins[]=jquery/model/validations/validations.js @test jquery/model/validations/qunit.html @parent jQuery.Model @@ -60,7 +60,7 @@ var validate = function(attrNames, options, proc) { } self.validations[attrName].push(function(){ var res = proc.call(this, this[attrName]); - return options.message || res; + return res === undefined ? undefined : (options.message || res); }) }); diff --git a/browserid/static/jquery/test/qunit/integration.js b/browserid/static/jquery/test/qunit/integration.js new file mode 100644 index 0000000000000000000000000000000000000000..4d02ab74604a149e85a22082531cae33513d9941 --- /dev/null +++ b/browserid/static/jquery/test/qunit/integration.js @@ -0,0 +1,69 @@ +steal.plugins('funcunit/qunit', + 'jquery/model', + 'jquery/controller', + 'jquery/view/ejs', + 'jquery/dom/fixture') + .then(function(){ + +module('integration',{ + setup : function(){ + $("#qunit-test-area").html("") + } +}); + +test("controller can listen to model instances and model classes", function(){ + + + $("#qunit-test-area").html(""); + + + + $.Controller("Test.BinderThing",{ + "{model} created" : function(){ + ok(true,"model called"); + start(); + }, + "{instance} created" : function(){ + ok(true, "instance updated") + } + }); + + $.Model("Test.ModelThing",{ + create : function(attrs, success){ + success({id: 1}) + } + }); + + + var inst = new Test.ModelThing(); + + $("<div>").appendTo( $("#qunit-test-area") ) + .test_binder_thing({ + model : Test.ModelThing, + instance: inst + }); + + inst.save(); + stop(); +}) + + +test("Model and Views", function(){ + stop(); + + $.Model("Test.Thing",{ + findOne : "/thing" + },{}) + + $.fixture("/thing","//jquery/test/thing.json") + + var res = $.View("//jquery/test/template.ejs", + Test.Thing.findOne()); + + res.done(function(resolved){ + equals(resolved,"foo","works") + start() + }) +}) + +}) diff --git a/browserid/static/jquery/test/qunit/qunit.js b/browserid/static/jquery/test/qunit/qunit.js index ea10f0acae8548c3fe1c1634f2f62da3477fadb7..58b51e54200be5cf38229ef3490679ca4ff17715 100644 --- a/browserid/static/jquery/test/qunit/qunit.js +++ b/browserid/static/jquery/test/qunit/qunit.js @@ -1,18 +1,30 @@ //we probably have to have this only describing where the tests are -steal('//jquery/lang/lang_test').plugins( -'jquery/class/test/qunit', -'jquery/controller/test/qunit', +steal('//jquery/class/class_test', + '//jquery/controller/controller_test', + '//jquery/dom/compare/compare_test', + '//jquery/dom/cur_styles/cur_styles_test', + '//jquery/lang/lang_test', + '//jquery/lang/deparam/deparam_test', + '//jquery/dom/fixture/fixture_test', + '//jquery/event/default/default_test', + '//jquery/event/drag/drag_test', + '//jquery/event/key/key_test', + '//jquery/tie/tie_test' + + + ).plugins( + 'jquery/controller/view/test/qunit', -'jquery/dom/compare/test/qunit', -'jquery/dom/cur_styles/test/qunit', + + 'jquery/dom/dimensions/test/qunit', -'jquery/dom/fixture/test/qunit', + 'jquery/dom/form_params/test/qunit', -'jquery/event/default/test/qunit', + 'jquery/event/destroyed/test/qunit', 'jquery/event/hover/test/qunit', -'jquery/event/drag/test/qunit', + @@ -20,4 +32,5 @@ steal('//jquery/lang/lang_test').plugins( 'jquery/view/test/qunit', 'jquery/view/ejs/test/qunit' -) +).then('integration', + '//jquery/event/default/default_pause_test') diff --git a/browserid/static/jquery/test/template.ejs b/browserid/static/jquery/test/template.ejs new file mode 100644 index 0000000000000000000000000000000000000000..36f1f1b518781016990bd6da6eee7af6a8a37659 --- /dev/null +++ b/browserid/static/jquery/test/template.ejs @@ -0,0 +1 @@ +<%= name %> \ No newline at end of file diff --git a/browserid/static/jquery/test/thing.json b/browserid/static/jquery/test/thing.json new file mode 100644 index 0000000000000000000000000000000000000000..b3fa01ab75b6bcc7332d875da3cd29dab794c7dd --- /dev/null +++ b/browserid/static/jquery/test/thing.json @@ -0,0 +1,4 @@ +{ + "id" :1, + "name": "foo" +} diff --git a/browserid/static/jquery/tie/qunit.html b/browserid/static/jquery/tie/qunit.html index 853cfb5116906abd4062b84ff5e6e4d901f4ccdd..58385408b5e011b6d7c03be48513bfb2bbc7fb34 100644 --- a/browserid/static/jquery/tie/qunit.html +++ b/browserid/static/jquery/tie/qunit.html @@ -5,7 +5,7 @@ <script type='text/javascript'> steal = {ignoreControllers: true} </script> - <script type='text/javascript' src='../../steal/steal.js?jquery/tie/test/qunit'></script> + <script type='text/javascript' src='../../steal/steal.js?jquery/tie/tie_test.js'></script> </head> <body> diff --git a/browserid/static/jquery/tie/tie.js b/browserid/static/jquery/tie/tie.js index afefd82b3fd21ae8c4517775a3293fc87275c672..cc8f19b7fe2e44f22672d2e04353246252a253f8 100644 --- a/browserid/static/jquery/tie/tie.js +++ b/browserid/static/jquery/tie/tie.js @@ -35,7 +35,7 @@ $.Controller.extend("jQuery.Tie",{ this.bind(inst, attr, "attrChanged"); //destroy this controller if the model instance is destroyed - this.bind(inst, "destroyed", "destroy"); + this.bind(inst, "destroyed", "modelDestroyed"); var value = inst.attr(attr); //set the value @@ -56,6 +56,9 @@ $.Controller.extend("jQuery.Tie",{ this.lastValue = val; } }, + modelDestroyed : function(){ + this.destroy() + }, setVal : function(val){ if (this.type) { this.element[this.type]("val", val) @@ -77,7 +80,12 @@ $.Controller.extend("jQuery.Tie",{ }, destroy : function(){ this.inst = null; - this._super(); + if(! this._destroyed ){ + // assume it's because of the https://github.com/jupiterjs/jquerymx/pull/20 + // problem and don't throw an error + this._super(); + } + } }); diff --git a/browserid/static/jquery/tie/test/qunit/qunit.js b/browserid/static/jquery/tie/tie_test.js similarity index 84% rename from browserid/static/jquery/tie/test/qunit/qunit.js rename to browserid/static/jquery/tie/tie_test.js index 2fda153be2c0669258b352be18f440734c75fb67..c157cadb35baa93be70d3cfdeecffe1cc0df7996 100644 --- a/browserid/static/jquery/tie/test/qunit/qunit.js +++ b/browserid/static/jquery/tie/tie_test.js @@ -1,6 +1,6 @@ steal .plugins("funcunit/qunit", "jquery/tie",'jquery/model') - .then("tie_test").then(function(){ + .then(function(){ module("jquery/tie",{ @@ -80,6 +80,24 @@ steal ok(tie._destroyed, "Tie is destroyed") }) + test("removing html element removes the tie", function() { + var person1 = new Person({age: 5}); + var inp = $("<div/>").appendTo( $("#qunit-test-area") ); + + $.Controller("Foo",{ + val : function(value) {} + }); + + inp.foo().tie(person1,"age"); + var foo = inp.controller('foo'), + tie = inp.controller('tie'); + + inp.remove(); // crashes here + + ok(foo._destroyed, "Foo is destroyed"); + ok(tie._destroyed, "Tie is destroyed") + }); + test("tie on a specific controller", function(){}); test("no controller with val, only listen", function(){ diff --git a/browserid/static/jquery/view/ejs/ejs.js b/browserid/static/jquery/view/ejs/ejs.js index 1904bf3c2694270e2dd9b58c036d863d6792a3c7..167105205600c9ddb205889742f631f5d8d34cad 100644 --- a/browserid/static/jquery/view/ejs/ejs.js +++ b/browserid/static/jquery/view/ejs/ejs.js @@ -5,14 +5,24 @@ steal.plugins('jquery/view', 'jquery/lang/rsplit').then(function( $ ) { var myEval = function(script){ eval(script); - } - - //helpers we use - var chop = function( string ) { - return string.substr(0, string.length - 1); - }, + }, + chop = function( string ) { + return string.substr(0, string.length - 1); + }, + rSplit = $.String.rsplit, extend = $.extend, isArray = $.isArray, + clean = function( content ) { + return content.replace(/\\/g, '\\\\').replace(/\n/g, '\\n').replace(/"/g, '\\"'); + } + // from prototype http://www.prototypejs.org/ + escapeHTML = function(content){ + return content.replace(/&/g,'&') + .replace(/</g,'<') + .replace(/>/g,'>') + .replace(/"/g, '"') + .replace(/'/g, "'"); + }, EJS = function( options ) { //returns a renderer function if ( this.constructor != EJS ) { @@ -21,22 +31,16 @@ steal.plugins('jquery/view', 'jquery/lang/rsplit').then(function( $ ) { return ejs.render(data, helpers); }; } - + //so we can set the processor if ( typeof options == "function" ) { this.template = {}; this.template.process = options; return; } //set options on self - $.extend(this, EJS.options, options); - - var template = new EJS.Compiler(this.text, this.type); - - template.compile(options, this.name); - - this.template = template; - }, - defaultSplitter = /(\[%%)|(%%\])|(\[%=)|(\[%#)|(\[%)|(%\]\n)|(%\])|(\n)/; + extend(this, EJS.options, options); + this.template = compile(this.text, this.type, this.name); + }; /** * @class jQuery.EJS * @@ -49,8 +53,11 @@ steal.plugins('jquery/view', 'jquery/lang/rsplit').then(function( $ ) { * Ejs provides <a href="http://www.ruby-doc.org/stdlib/libdoc/erb/rdoc/">ERB</a> * style client side templates. Use them with controllers to easily build html and inject * it into the DOM. - * <h3>Example</h3> + * + * ### Example + * * The following generates a list of tasks: + * * @codestart html * <ul> * <% for(var i = 0; i < tasks.length; i++){ %> @@ -58,37 +65,53 @@ steal.plugins('jquery/view', 'jquery/lang/rsplit').then(function( $ ) { * <% } %> * </ul> * @codeend - * For the following examples, we assume this view is in <i>'views\tasks\list.ejs'</i> - * <h2>Use</h2> - * There are 2 common ways to use Views: - * <ul> - * <li>Controller's [jQuery.Controller.prototype.view view function]</li> - * <li>The jQuery Helpers: [jQuery.fn.after after], - * [jQuery.fn.append append], - * [jQuery.fn.before before], - * [jQuery.fn.before html], - * [jQuery.fn.before prepend], - * [jQuery.fn.before replace], and - * [jQuery.fn.before text].</li> - * </ul> - * <h3>View</h3> - * jQuery.Controller.prototype.view is the preferred way of rendering a view. - * You can find all the options for render in - * its [jQuery.Controller.prototype.view documentation], but here is a brief example of rendering the - * <i>list.ejs</i> view from a controller: - * @codestart - * $.Controller.extend("TasksController",{ - * init: function( el ) { - * Task.findAll({},this.callback('list')) - * }, - * list: function( tasks ) { - * this.element.html( - * this.view("list", {tasks: tasks}) - * ) - * } - * }) - * @codeend * + * For the following examples, we assume this view is in <i>'views\tasks\list.ejs'</i>. + * + * + * ## Use + * + * ### Loading and Rendering EJS: + * + * You should use EJS through the helper functions [jQuery.View] provides such as: + * + * - [jQuery.fn.after after] + * - [jQuery.fn.append append] + * - [jQuery.fn.before before] + * - [jQuery.fn.html html], + * - [jQuery.fn.prepend prepend], + * - [jQuery.fn.replaceWith replaceWith], and + * - [jQuery.fn.text text]. + * + * or [jQuery.Controller.prototype.view]. + * + * ### Syntax + * + * EJS uses 5 types of tags: + * + * - <code><% CODE %></code> - Runs JS Code. + * For example: + * + * <% alert('hello world') %> + * + * - <code><%= CODE %></code> - Runs JS Code and writes the result into the result of the template. + * For example: + * + * <h1><%= 'hello world' %></h1> + * + * - <code><%~ CODE %></code> - Runs JS Code and writes the _escaped_ result into the result of the template. + * For example: + * + * <%~ 'hello world' %> + * + * - <code><%%= CODE %></code> - Writes <%= CODE %> to the result of the template. This is very useful for generators. + * + * <%%= 'hello world' %> + * + * - <code><%# CODE %></code> - Used for comments. This does nothing. + * + * <%# 'hello world' %> + * * ## Hooking up controllers * * After drawing some html, you often want to add other widgets and plugins inside that html. @@ -170,9 +193,6 @@ steal.plugins('jquery/view', 'jquery/lang/rsplit').then(function( $ ) { this._extra_helpers = extraHelpers; var v = new EJS.Helpers(object, extraHelpers || {}); return this.template.process.call(object, object, v); - }, - out: function() { - return this.template.out; } }; /* @Static */ @@ -207,210 +227,129 @@ steal.plugins('jquery/view', 'jquery/lang/rsplit').then(function( $ ) { if ( typeof input == 'string' ) { return input; } - var myid; if ( input === null || input === undefined ) { return ''; } - if ( input instanceof Date ) { - return input.toDateString(); - } - if ( input.hookup ) { - myid = $.View.hookup(function( el, id ) { + var hook = + (input.hookup && function( el, id ) { input.hookup.call(input, el, id); - }); - return "data-view-id='" + myid + "'"; - } - if ( typeof input == 'function' ) { - return "data-view-id='" + $.View.hookup(input) + "'"; - } - - if ( isArray(input) ) { - myid = $.View.hookup(function( el, id ) { + }) + || + (typeof input == 'function' && input) + || + (isArray(input) && function( el, id ) { for ( var i = 0; i < input.length; i++ ) { var stub; stub = input[i].hookup ? input[i].hookup(el, id) : input[i](el, id); } }); - return "data-view-id='" + myid + "'"; - } - if ( input.nodeName || input.jQuery ) { - throw "elements in views are not supported"; + if(hook){ + return "data-view-id='" + $.View.hookup(hook) + "'"; } - - if ( input.toString ) { - return myid ? input.toString(myid) : input.toString(); - } - return ''; + return input.toString ? input.toString() : ""; }; - - - - - // used to break text into tolkens - EJS.Scanner = function( source, left, right ) { - - // add these properties to the scanner - extend(this, { - leftDelimiter: left + '%', - rightDelimiter: '%' + right, - doubleLeft: left + '%%', - doubleRight: '%%' + right, - leftEqual: left + '%=', - leftComment: left + '%#' - }); - - - // make a regexp that can split on these token - this.splitRegexp = (left == '[' ? defaultSplitter : new RegExp("(" + [this.doubleLeft, this.doubleRight, this.leftEqual, this.leftComment, this.leftDelimiter, this.rightDelimiter + '\n', this.rightDelimiter, '\n'].join(")|(") + ")")); - - this.source = source; - this.lines = 0; - }; - - - EJS.Scanner.prototype = { - // calls block with each token - scan: function( block ) { - var regex = this.splitRegexp; - if ( this.source ) { - var source_split = $.String.rsplit(this.source, /\n/); - for ( var i = 0; i < source_split.length; i++ ) { - var item = source_split[i]; - this.scanline(item, regex, block); - } - } - }, - scanline: function( line, regex, block ) { - this.lines++; - var line_split = $.String.rsplit(line, regex), - token; - for ( var i = 0; i < line_split.length; i++ ) { - token = line_split[i]; - if ( token !== null ) { - try { - block(token, this); - } catch (e) { - throw { - type: 'jQuery.EJS.Scanner', - line: this.lines - }; - } - } - } + EJS.clean = function(text){ + //return sanatized text + if(typeof text == 'string'){ + return escapeHTML(text) + }else{ + return ""; } - }; - - // a line and script buffer - // we use this so we know line numbers when there - // is an error. - // pre and post are setup and teardown for the buffer - EJS.Buffer = function( pre_cmd, post_cmd ) { - this.line = []; - this.script = []; - this.post_cmd = post_cmd; - - // add the pre commands to the first line - this.push.apply(this, pre_cmd); - }; - EJS.Buffer.prototype = { - //need to maintain your own semi-colons (for performance) - push: function() { - this.line.push.apply(this.line, arguments); - }, - - cr: function() { - this.script.push(this.line.join(''), "\n"); - this.line = []; - }, - //returns the script too - close: function() { - var stub; - - if ( this.line.length > 0 ) { - this.script.push(this.line.join('')); - this.line = []; + } + //returns something you can call scan on + var scan = function(scanner, source, block ) { + var source_split = rSplit(source, /\n/), + i=0; + for (; i < source_split.length; i++ ) { + scanline(scanner, source_split[i], block); + } + + }, + scanline= function(scanner, line, block ) { + scanner.lines++; + var line_split = rSplit(line, scanner.splitter), + token; + for ( var i = 0; i < line_split.length; i++ ) { + token = line_split[i]; + if ( token !== null ) { + block(token, scanner); } - - stub = this.post_cmd.length && this.push.apply(this, this.post_cmd); - - this.script.push(";"); //makes sure we always have an ending / - return this.script.join(""); } - - }; + }, + makeScanner = function(left, right){ + var scanner = {}; + extend(scanner, { + left: left + '%', + right: '%' + right, + dLeft: left + '%%', + dRight: '%%' + right, + eeLeft : left + '%==', + eLeft: left + '%=', + cmnt: left + '%#', + cleanLeft: left+"%~", + scan : scan, + lines : 0 + }); + scanner.splitter = new RegExp("(" + [scanner.dLeft, scanner.dRight, scanner.eeLeft, scanner.eLeft, scanner.cleanLeft, + scanner.cmnt, scanner.left, scanner.right + '\n', scanner.right, '\n'].join(")|("). + replace(/\[/g,"\\[").replace(/\]/g,"\\]") + ")"); + return scanner; + }, // compiles a template - EJS.Compiler = function( source, left ) { + compile = function( source, left, name ) { + source = source.replace(/\r\n/g, "\n").replace(/\r/g, "\n"); //normalize line endings - this.source = source.replace(/\r\n/g, "\n").replace(/\r/g, "\n"); - left = left || '<'; - var right = '>'; - switch ( left ) { - case '[': - right = ']'; - break; - case '<': - break; - default: - throw left + ' is not a supported deliminator'; - } - this.scanner = new EJS.Scanner(this.source, left, right); - this.out = ''; - }; - EJS.Compiler.prototype = { - compile: function( options, name ) { - - options = options || {}; - - this.out = ''; - - var put_cmd = "___v1ew.push(", - insert_cmd = put_cmd, - buff = new EJS.Buffer(['var ___v1ew = [];'], []), - content = '', - clean = function( content ) { - return content.replace(/\\/g, '\\\\').replace(/\n/g, '\\n').replace(/"/g, '\\"'); - }, - put = function( content ) { - buff.push(put_cmd, '"', clean(content), '");'); - }, - startTag = null; - - this.scanner.scan(function( token, scanner ) { + var put_cmd = "___v1ew.push(", + insert_cmd = put_cmd, + buff = new EJS.Buffer(['var ___v1ew = [];'], []), + content = '', + put = function( content ) { + buff.push(put_cmd, '"', clean(content), '");'); + }, + startTag = null, + empty = function(){ + content = '' + }; + + scan( makeScanner(left, left === '[' ? ']' : '>') , + source||"", + function( token, scanner ) { // if we don't have a start pair if ( startTag === null ) { switch ( token ) { case '\n': content = content + "\n"; put(content); - //buff.push(put_cmd , '"' , clean(content) , '");'); buff.cr(); - content = ''; + empty(); break; - case scanner.leftDelimiter: - case scanner.leftEqual: - case scanner.leftComment: + case scanner.left: + case scanner.eLeft: + case scanner.eeLeft: + case scanner.cleanLeft: + case scanner.cmnt: startTag = token; if ( content.length > 0 ) { put(content); } - content = ''; + empty(); break; // replace <%% with <% - case scanner.doubleLeft: - content = content + scanner.leftDelimiter; + case scanner.dLeft: + content += scanner.left; break; default: - content = content + token; + content += token; break; } } else { switch ( token ) { - case scanner.rightDelimiter: + case scanner.right: switch ( startTag ) { - case scanner.leftDelimiter: + case scanner.left: if ( content[content.length - 1] == '\n' ) { content = chop(content); buff.push(content, ";"); @@ -420,31 +359,79 @@ steal.plugins('jquery/view', 'jquery/lang/rsplit').then(function( $ ) { buff.push(content, ";"); } break; - case scanner.leftEqual: + case scanner.cleanLeft : + buff.push(insert_cmd, "(jQuery.EJS.clean(", content, ")));"); + break; + case scanner.eLeft: + buff.push(insert_cmd, "(jQuery.EJS.text(", content, ")));"); + break; + case scanner.eeLeft: buff.push(insert_cmd, "(jQuery.EJS.text(", content, ")));"); break; } startTag = null; - content = ''; + empty(); break; - case scanner.doubleRight: - content = content + scanner.rightDelimiter; + case scanner.dRight: + content += scanner.right; break; default: - content = content + token; + content += token; break; } } - }); - if ( content.length > 0 ) { - // Should be content.dump in Ruby - buff.push(put_cmd, '"', clean(content) + '");'); + }) + if ( content.length > 0 ) { + // Should be content.dump in Ruby + buff.push(put_cmd, '"', clean(content) + '");'); + } + var template = buff.close(), + out = { + out : 'try { with(_VIEW) { with (_CONTEXT) {' + template + " return ___v1ew.join('');}}}catch(e){e.lineNumber=null;throw e;}" + }; + //use eval instead of creating a function, b/c it is easier to debug + myEval.call(out,'this.process = (function(_CONTEXT,_VIEW){' + out.out + '});\r\n//@ sourceURL='+name+".js"); + return out; + }; + + + // a line and script buffer + // we use this so we know line numbers when there + // is an error. + // pre and post are setup and teardown for the buffer + EJS.Buffer = function( pre_cmd, post ) { + this.line = []; + this.script = []; + this.post = post; + + // add the pre commands to the first line + this.push.apply(this, pre_cmd); + }; + EJS.Buffer.prototype = { + //need to maintain your own semi-colons (for performance) + push: function() { + this.line.push.apply(this.line, arguments); + }, + + cr: function() { + this.script.push(this.line.join(''), "\n"); + this.line = []; + }, + //returns the script too + close: function() { + var stub; + + if ( this.line.length > 0 ) { + this.script.push(this.line.join('')); + this.line = []; } - var template = buff.close(); - this.out = 'try { with(_VIEW) { with (_CONTEXT) {' + template + " return ___v1ew.join('');}}}catch(e){e.lineNumber=null;throw e;}"; - //use eval instead of creating a function, b/c it is easier to debug - myEval.call(this,'this.process = (function(_CONTEXT,_VIEW){' + this.out + '});\r\n//@ sourceURL='+name+".js") + + stub = this.post.length && this.push.apply(this, this.post); + + this.script.push(";"); //makes sure we always have an ending / + return this.script.join(""); } + }; @@ -470,7 +457,6 @@ steal.plugins('jquery/view', 'jquery/lang/rsplit').then(function( $ ) { * */ EJS.options = { - cache: true, type: '<', ext: '.ejs' }; @@ -494,7 +480,7 @@ steal.plugins('jquery/view', 'jquery/lang/rsplit').then(function( $ ) { /* @prototype*/ EJS.Helpers.prototype = { /** - * Makes a plugin + * Hooks up a jQuery plugin on. * @param {String} name the plugin name */ plugin: function( name ) { @@ -522,7 +508,7 @@ steal.plugins('jquery/view', 'jquery/lang/rsplit').then(function( $ ) { script: function( id, src ) { return "jQuery.EJS(function(_CONTEXT,_VIEW) { " + new EJS({ text: src - }).out() + " })"; + }).template.out + " })"; }, renderer: function( id, text ) { var ejs = new EJS({ diff --git a/browserid/static/jquery/view/ejs/test/qunit/ejs_test.js b/browserid/static/jquery/view/ejs/test/qunit/ejs_test.js index 42d8b98315374b67cadfa3730daea78e8c336767..74794ad2a49df67820c4e7d8f2a1124fdec86742 100644 --- a/browserid/static/jquery/view/ejs/test/qunit/ejs_test.js +++ b/browserid/static/jquery/view/ejs/test/qunit/ejs_test.js @@ -59,4 +59,16 @@ test("multi line", function(){ equals(result, text) }) + +test("escapedContent", function(){ + var text = "<span><%~ tags %></span><label>&</label><input value='<%~ quotes %>'/>"; + var compiled = new $.EJS({text: text}).render({tags: "foo < bar < car > zar > poo", + quotes : "I use 'quote' fingers \"a lot\""}) ; + + var div = $('<div/>').html(compiled) + equals(div.find('span').text(), "foo < bar < car > zar > poo" ); + equals(div.find('input').val(), "I use 'quote' fingers \"a lot\"" ) + equals(div.find('label').html(), "&" ) + +}) //test("multi line sourc") diff --git a/browserid/static/jquery/view/test/compression/compression.js b/browserid/static/jquery/view/test/compression/compression.js index e1d5babbf4d37091559bf7b03370714f1b00ca6c..0707928f5598f3d61ad3709297429e8656046d2b 100644 --- a/browserid/static/jquery/view/test/compression/compression.js +++ b/browserid/static/jquery/view/test/compression/compression.js @@ -3,8 +3,8 @@ steal.plugins('jquery/view/ejs', 'jquery/view/ejs', 'jquery/view/tmpl') '//jquery/view/test/compression/views/absolute.ejs', 'tmplTest.tmpl') .then(function(){ - $(function(){ - $("#target").append($.View('//jquery/view/test/compression/views/relative.ejs', {} )) + $(document).ready(function(){ + $("#target").append('//jquery/view/test/compression/views/relative.ejs', {}) .append($.View('//jquery/view/test/compression/views/absolute.ejs', {} )) .append($.View('//jquery/view/test/compression/views/tmplTest.tmpl', {message: "Jquery Tmpl"} )) }) diff --git a/browserid/static/jquery/view/test/compression/production.js b/browserid/static/jquery/view/test/compression/production.js new file mode 100644 index 0000000000000000000000000000000000000000..c32525254baf6ce2912ab8dcab201b66a1ca17f3 --- /dev/null +++ b/browserid/static/jquery/view/test/compression/production.js @@ -0,0 +1,230 @@ +steal.plugins("jquery/view/ejs","jquery/view/ejs","jquery/view/tmpl").views("relative.ejs","//jquery/view/test/compression/views/absolute.ejs","tmplTest.tmpl").then(function(){$(document).ready(function(){$("#target").append("//jquery/view/test/compression/views/relative.ejs",{}).append($.View("//jquery/view/test/compression/views/absolute.ejs",{})).append($.View("//jquery/view/test/compression/views/tmplTest.tmpl",{message:"Jquery Tmpl"}))})}); +; +steal.end(); +steal.plugins("jquery/view","jquery/lang/rsplit").then(function(g){var p=function(a){eval(a)},q=function(a){return a.substr(0,a.length-1)},l=g.String.rsplit,j=g.extend,r=g.isArray,m=function(a){return a.replace(/\\/g,"\\\\").replace(/\n/g,"\\n").replace(/"/g,'\\"')};escapeHTML=function(a){return a.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")};EJS=function(a){if(this.constructor!=EJS){var b=new EJS(a);return function(c,e){return b.render(c, +e)}}if(typeof a=="function"){this.template={};this.template.process=a}else{j(this,EJS.options,a);this.template=s(this.text,this.type,this.name)}};g.EJS=EJS;EJS.prototype={constructor:EJS,render:function(a,b){a=a||{};this._extra_helpers=b;b=new EJS.Helpers(a,b||{});return this.template.process.call(a,a,b)}};EJS.text=function(a){if(typeof a=="string")return a;if(a===null||a===undefined)return"";var b=a.hookup&&function(c,e){a.hookup.call(a,c,e)}||typeof a=="function"&&a||r(a)&&function(c,e){for(var d= +0;d<a.length;d++)a[d].hookup?a[d].hookup(c,e):a[d](c,e)};if(b)return"data-view-id='"+g.View.hookup(b)+"'";return a.toString?a.toString():""};EJS.clean=function(a){return typeof a=="string"?escapeHTML(a):""};var n=function(a,b,c){b=l(b,/\n/);for(var e=0;e<b.length;e++)t(a,b[e],c)},t=function(a,b,c){a.lines++;b=l(b,a.splitter);for(var e,d=0;d<b.length;d++){e=b[d];e!==null&&c(e,a)}},u=function(a,b){var c={};j(c,{left:a+"%",right:"%"+b,dLeft:a+"%%",dRight:"%%"+b,eLeft:a+"%=",cmnt:a+"%#",cleanLeft:a+"%~", +scan:n,lines:0});c.splitter=new RegExp("("+[c.dLeft,c.dRight,c.eLeft,c.cleanLeft,c.cmnt,c.left,c.right+"\n",c.right,"\n"].join(")|(").replace(/\[/g,"\\[").replace(/\]/g,"\\]")+")");return c},s=function(a,b,c){a=a.replace(/\r\n/g,"\n").replace(/\r/g,"\n");b=b||"<";var e=new EJS.Buffer(["var ___v1ew = [];"],[]),d="",o=function(h){e.push("___v1ew.push(",'"',m(h),'");')},i=null,k=function(){d=""};n(u(b,b==="["?"]":">"),a||"",function(h,f){if(i===null)switch(h){case "\n":d+="\n";o(d);e.cr();k();break; +case f.left:case f.eLeft:case f.cleanLeft:case f.cmnt:i=h;d.length>0&&o(d);k();break;case f.dLeft:d+=f.left;break;default:d+=h;break}else switch(h){case f.right:switch(i){case f.left:if(d[d.length-1]=="\n"){d=q(d);e.push(d,";");e.cr()}else e.push(d,";");break;case f.cleanLeft:e.push("___v1ew.push(","(jQuery.EJS.clean(",d,")));");break;case f.eLeft:e.push("___v1ew.push(","(jQuery.EJS.text(",d,")));");break}i=null;k();break;case f.dRight:d+=f.right;break;default:d+=h;break}});d.length>0&&e.push("___v1ew.push(", +'"',m(d)+'");');a={out:"try { with(_VIEW) { with (_CONTEXT) {"+e.close()+" return ___v1ew.join('');}}}catch(e){e.lineNumber=null;throw e;}"};p.call(a,"this.process = (function(_CONTEXT,_VIEW){"+a.out+"});\r\n//@ sourceURL="+c+".js");return a};EJS.Buffer=function(a,b){this.line=[];this.script=[];this.post=b;this.push.apply(this,a)};EJS.Buffer.prototype={push:function(){this.line.push.apply(this.line,arguments)},cr:function(){this.script.push(this.line.join(""),"\n");this.line=[]},close:function(){if(this.line.length> +0){this.script.push(this.line.join(""));this.line=[]}this.post.length&&this.push.apply(this,this.post);this.script.push(";");return this.script.join("")}};EJS.options={type:"<",ext:".ejs"};EJS.Helpers=function(a,b){this._data=a;this._extras=b;j(this,b)};EJS.Helpers.prototype={plugin:function(){var a=g.makeArray(arguments),b=a.shift();return function(c){c=g(c);c[b].apply(c,a)}},view:function(a,b,c){c=c||this._extras;b=b||this._data;return g.View(a,b,c)}};g.View.register({suffix:"ejs",script:function(a, +b){return"jQuery.EJS(function(_CONTEXT,_VIEW) { "+(new EJS({text:b})).template.out+" })"},renderer:function(a,b){var c=new EJS({text:b,name:a});return function(e,d){return c.render.call(c,e,d)}}})}); +; +steal.end(); +steal.plugins("jquery").then(function(h){var x=function(a){return a.replace(/^\/\//,"").replace(/[\/\.]/g,"_")},y=1,e,o,l,r,m=function(a){return h.isFunction(a.promise)};e=h.View=function(a,b,c,d){var g=[];if(m(b))g=[b];else for(var f in b)m(b[f])&&g.push(b[f]);if(g.length){var j=h.Deferred();h.when.apply(h,g).then(function(z){var A=h.makeArray(arguments);if(m(b))b=z;else for(var s in b)if(m(b[s]))b[s]=A.shift();j.resolve(e(a,b,c,d))});return j.promise()}else{var i=a.match(/\.[\w\d]+$/),k;k=a;if(g= +document.getElementById(a))i=g.type.match(/\/[\d\w]+$/)[0].replace(/^\//,".");if(typeof c==="function"){d=c;c=undefined}if(!i){i=e.ext;k+=e.ext}f=x(k);if(k.match(/^\/\//))k=typeof steal==="undefined"?"/"+k.substr(2):steal.root.join(k.substr(2));i=e.types[i];return(k=e.cached[f]?e.cached[f]:(g=document.getElementById(a))?i.renderer(f,g.innerHTML):r(i,f,k,b,c,d))&&o(k,i,f,b,c,d)}};o=function(a,b,c,d,g,f){if(e.cache)e.cached[c]=a;a=a.call(b,d,g);f&&f(a);return a};l=function(a,b){if(!a.match(/[^\s]/))throw"$.View ERROR: There is no template or an empty template at "+ +b;};r=function(a,b,c,d,g,f){if(f)h.ajax({url:c,dataType:"text",error:function(){l("",c)},success:function(i){l(i,c);o(a.renderer(b,i),a,b,d,g,f)}});else{var j=h.ajax({async:false,url:c,dataType:"text",error:function(){l("",c)}}).responseText;l(j,c);return a.renderer(b,j)}};h.extend(e,{hookups:{},hookup:function(a){var b=++y;e.hookups[b]=a;return b},cached:{},cache:true,register:function(a){this.types["."+a.suffix]=a},types:{},ext:".ejs",registerScript:function(a,b,c){return"$.View.preload('"+b+"',"+ +e.types["."+a].script(b,c)+");"},preload:function(a,b){e.cached[a]=function(c,d){return b.call(c,c,d)}}});var t,n,u,v,w,p;t=function(a){var b=h.fn[a];h.fn[a]=function(){var c=h.makeArray(arguments),d,g,f=this;if(u(c)){if(d=v(c)){g=c[d];c[d]=function(j){n.call(f,[j],b);g.call(f,j)};e.apply(e,c);return this}c=e.apply(e,c);if(m(c)){c.done(function(j){n.call(f,[j],b)});return this}else c=[c]}return n.call(this,c,b)}};n=function(a,b){var c;for(var d in e.hookups)break;if(d){c=e.hookups;e.hookups={};a[0]= +h(a[0])}b=b.apply(this,a);d&&w(a[0],c);return b};u=function(a){var b=typeof a[1];return typeof a[0]=="string"&&(b=="object"||b=="function")&&!a[1].nodeType&&!a[1].jquery};v=function(a){return typeof a[3]==="function"?3:typeof a[2]==="function"&&2};w=function(a,b){var c,d=0,g,f;a=a.filter(function(){return this.nodeType!=3});a=a.add("[data-view-id]",a);for(c=a.length;d<c;d++)if(a[d].getAttribute&&(g=a[d].getAttribute("data-view-id"))&&(f=b[g])){f(a[d],g);delete b[g];a[d].removeAttribute("data-view-id")}h.extend(e.hookups, +b)};p=["prepend","append","after","before","text","html","replaceWith","val"];for(var q=0;q<p.length;q++)t(p[q])}); +; +steal.end(); +(function(I,z){function ta(a,b,d){if(d===z&&a.nodeType===1){d=a.getAttribute("data-"+b);if(typeof d==="string"){try{d=d==="true"?true:d==="false"?false:d==="null"?null:!c.isNaN(d)?parseFloat(d):Xa.test(d)?c.parseJSON(d):d}catch(e){}c.data(a,b,d)}else d=z}return d}function ka(a){for(var b in a)if(b!=="toJSON")return false;return true}function W(){return false}function aa(){return true}function ua(a,b,d){var e=c.extend({},d[0]);e.type=a;e.originalEvent={};e.liveFired=z;c.event.handle.call(b,e);e.isDefaultPrevented()&& +d[0].preventDefault()}function Ya(a){var b,d,e,f,g,j,o,n,l,r,y,C=[];f=[];g=c._data(this,"events");if(!(a.liveFired===this||!g||!g.live||a.target.disabled||a.button&&a.type==="click")){if(a.namespace)y=new RegExp("(^|\\.)"+a.namespace.split(".").join("\\.(?:.*\\.)?")+"(\\.|$)");a.liveFired=this;var B=g.live.slice(0);for(o=0;o<B.length;o++){g=B[o];g.origType.replace(ba,"")===a.type?f.push(g.selector):B.splice(o--,1)}f=c(a.target).closest(f,a.currentTarget);n=0;for(l=f.length;n<l;n++){r=f[n];for(o=0;o< +B.length;o++){g=B[o];if(r.selector===g.selector&&(!y||y.test(g.namespace))&&!r.elem.disabled){j=r.elem;e=null;if(g.preType==="mouseenter"||g.preType==="mouseleave"){a.type=g.preType;e=c(a.relatedTarget).closest(g.selector)[0]}if(!e||e!==j)C.push({elem:j,handleObj:g,level:r.level})}}}n=0;for(l=C.length;n<l;n++){f=C[n];if(d&&f.level>d)break;a.currentTarget=f.elem;a.data=f.handleObj.data;a.handleObj=f.handleObj;y=f.handleObj.origHandler.apply(f.elem,arguments);if(y===false||a.isPropagationStopped()){d= +f.level;if(y===false)b=false;if(a.isImmediatePropagationStopped())break}}return b}}function ca(a,b){return(a&&a!=="*"?a+".":"")+b.replace(Za,"`").replace($a,"&")}function va(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function wa(a,b,d){if(c.isFunction(b))return c.grep(a,function(f,g){return!!b.call(f,g,f)===d});else if(b.nodeType)return c.grep(a,function(f){return f===b===d});else if(typeof b==="string"){var e=c.grep(a,function(f){return f.nodeType===1});if(ab.test(b))return c.filter(b, +e,!d);else b=c.filter(b,e)}return c.grep(a,function(f){return c.inArray(f,b)>=0===d})}function bb(a){return c.nodeName(a,"table")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function xa(a,b){if(!(b.nodeType!==1||!c.hasData(a))){var d=c.expando,e=c.data(a),f=c.data(b,e);if(e=e[d]){a=e.events;f=f[d]=c.extend({},e);if(a){delete f.handle;f.events={};for(var g in a){d=0;for(e=a[g].length;d<e;d++)c.event.add(b,g+(a[g][d].namespace?".":"")+a[g][d].namespace, +a[g][d],a[g][d].data)}}}}}function ya(a,b){if(b.nodeType===1){var d=b.nodeName.toLowerCase();b.clearAttributes();b.mergeAttributes(a);if(d==="object")b.outerHTML=a.outerHTML;else if(d==="input"&&(a.type==="checkbox"||a.type==="radio")){if(a.checked)b.defaultChecked=b.checked=a.checked;if(b.value!==a.value)b.value=a.value}else if(d==="option")b.selected=a.defaultSelected;else if(d==="input"||d==="textarea")b.defaultValue=a.defaultValue;b.removeAttribute(c.expando)}}function da(a){return"getElementsByTagName"in +a?a.getElementsByTagName("*"):"querySelectorAll"in a?a.querySelectorAll("*"):[]}function cb(a,b){b.src?c.ajax({url:b.src,async:false,dataType:"script"}):c.globalEval(b.text||b.textContent||b.innerHTML||"");b.parentNode&&b.parentNode.removeChild(b)}function za(a,b,d){var e=b==="width"?a.offsetWidth:a.offsetHeight;if(d==="border")return e;c.each(b==="width"?db:eb,function(){d||(e-=parseFloat(c.css(a,"padding"+this))||0);if(d==="margin")e+=parseFloat(c.css(a,"margin"+this))||0;else e-=parseFloat(c.css(a, +"border"+this+"Width"))||0});return e}function Aa(a){return function(b,d){if(typeof b!=="string"){d=b;b="*"}if(c.isFunction(d)){b=b.toLowerCase().split(Ba);for(var e=0,f=b.length,g,j;e<f;e++){g=b[e];if(j=/^\+/.test(g))g=g.substr(1)||"*";g=a[g]=a[g]||[];g[j?"unshift":"push"](d)}}}}function ea(a,b,d,e,f,g){f=f||b.dataTypes[0];g=g||{};g[f]=true;f=a[f];for(var j=0,o=f?f.length:0,n=a===la,l;j<o&&(n||!l);j++){l=f[j](b,d,e);if(typeof l==="string")if(!n||g[l])l=z;else{b.dataTypes.unshift(l);l=ea(a,b,d,e, +l,g)}}if((n||!l)&&!g["*"])l=ea(a,b,d,e,"*",g);return l}function ma(a,b,d,e){if(c.isArray(b)&&b.length)c.each(b,function(g,j){d||fb.test(a)?e(a,j):ma(a+"["+(typeof j==="object"||c.isArray(j)?g:"")+"]",j,d,e)});else if(!d&&b!=null&&typeof b==="object")if(c.isArray(b)||c.isEmptyObject(b))e(a,"");else for(var f in b)ma(a+"["+f+"]",b[f],d,e);else e(a,b)}function gb(a,b,d){var e=a.contents,f=a.dataTypes,g=a.responseFields,j,o,n,l;for(o in g)if(o in d)b[g[o]]=d[o];for(;f[0]==="*";){f.shift();if(j===z)j= +a.mimeType||b.getResponseHeader("content-type")}if(j)for(o in e)if(e[o]&&e[o].test(j)){f.unshift(o);break}if(f[0]in d)n=f[0];else{for(o in d){if(!f[0]||a.converters[o+" "+f[0]]){n=o;break}l||(l=o)}n=n||l}if(n){n!==f[0]&&f.unshift(n);return d[n]}}function hb(a,b){if(a.dataFilter)b=a.dataFilter(b,a.dataType);var d=a.dataTypes,e={},f,g,j=d.length,o,n=d[0],l,r,y,C,B;for(f=1;f<j;f++){if(f===1)for(g in a.converters)if(typeof g==="string")e[g.toLowerCase()]=a.converters[g];l=n;n=d[f];if(n==="*")n=l;else if(l!== +"*"&&l!==n){r=l+" "+n;y=e[r]||e["* "+n];if(!y){B=z;for(C in e){o=C.split(" ");if(o[0]===l||o[0]==="*")if(B=e[o[1]+" "+n]){C=e[C];if(C===true)y=B;else if(B===true)y=C;break}}}y||B||c.error("No conversion from "+r.replace(" "," to "));if(y!==true)b=y?y(b):B(C(b))}}return b}function ib(){c(I).unload(function(){for(var a in X)X[a](0,1)})}function Ca(){try{return new I.XMLHttpRequest}catch(a){}}function jb(){try{return new I.ActiveXObject("Microsoft.XMLHTTP")}catch(a){}}function Y(a,b){var d={};c.each(Da.concat.apply([], +Da.slice(0,b)),function(){d[this]=a});return d}function Ea(a){if(!na[a]){var b=c("<"+a+">").appendTo("body"),d=b.css("display");b.remove();if(d==="none"||d==="")d="block";na[a]=d}return na[a]}function oa(a){return c.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:false}var v=I.document,c=function(){function a(){if(!b.isReady){try{v.documentElement.doScroll("left")}catch(i){setTimeout(a,1);return}b.ready()}}var b=function(i,q){return new b.fn.init(i,q,f)},d=I.jQuery,e=I.$,f,g=/^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]+)$)/, +j=/\S/,o=/^\s+/,n=/\s+$/,l=/\d/,r=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,y=/^[\],:{}\s]*$/,C=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,B=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,H=/(?:^|:|,)(?:\s*\[)+/g,K=/(webkit)[ \/]([\w.]+)/,L=/(opera)(?:.*version)?[ \/]([\w.]+)/,P=/(msie) ([\w.]+)/,G=/(mozilla)(?:.*? rv:([\w.]+))?/,h=navigator.userAgent,k=false,m,p="then done fail isResolved isRejected promise".split(" "),s,t=Object.prototype.toString,A=Object.prototype.hasOwnProperty,w=Array.prototype.push, +E=Array.prototype.slice,Q=String.prototype.trim,N=Array.prototype.indexOf,O={};b.fn=b.prototype={constructor:b,init:function(i,q,u){var x;if(!i)return this;if(i.nodeType){this.context=this[0]=i;this.length=1;return this}if(i==="body"&&!q&&v.body){this.context=v;this[0]=v.body;this.selector="body";this.length=1;return this}if(typeof i==="string")if((x=g.exec(i))&&(x[1]||!q))if(x[1]){u=(q=q instanceof b?q[0]:q)?q.ownerDocument||q:v;if(i=r.exec(i))if(b.isPlainObject(q)){i=[v.createElement(i[1])];b.fn.attr.call(i, +q,true)}else i=[u.createElement(i[1])];else{i=b.buildFragment([x[1]],[u]);i=(i.cacheable?b.clone(i.fragment):i.fragment).childNodes}return b.merge(this,i)}else{if((q=v.getElementById(x[2]))&&q.parentNode){if(q.id!==x[2])return u.find(i);this.length=1;this[0]=q}this.context=v;this.selector=i;return this}else return!q||q.jquery?(q||u).find(i):this.constructor(q).find(i);else if(b.isFunction(i))return u.ready(i);if(i.selector!==z){this.selector=i.selector;this.context=i.context}return b.makeArray(i, +this)},selector:"",jquery:"1.5.1",length:0,size:function(){return this.length},toArray:function(){return E.call(this,0)},get:function(i){return i==null?this.toArray():i<0?this[this.length+i]:this[i]},pushStack:function(i,q,u){var x=this.constructor();b.isArray(i)?w.apply(x,i):b.merge(x,i);x.prevObject=this;x.context=this.context;if(q==="find")x.selector=this.selector+(this.selector?" ":"")+u;else if(q)x.selector=this.selector+"."+q+"("+u+")";return x},each:function(i,q){return b.each(this,i,q)},ready:function(i){b.bindReady(); +m.done(i);return this},eq:function(i){return i===-1?this.slice(i):this.slice(i,+i+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(E.apply(this,arguments),"slice",E.call(arguments).join(","))},map:function(i){return this.pushStack(b.map(this,function(q,u){return i.call(q,u,q)}))},end:function(){return this.prevObject||this.constructor(null)},push:w,sort:[].sort,splice:[].splice};b.fn.init.prototype=b.fn;b.extend=b.fn.extend=function(){var i, +q,u,x,F,D=arguments[0]||{},J=1,M=arguments.length,S=false;if(typeof D==="boolean"){S=D;D=arguments[1]||{};J=2}if(typeof D!=="object"&&!b.isFunction(D))D={};if(M===J){D=this;--J}for(;J<M;J++)if((i=arguments[J])!=null)for(q in i){u=D[q];x=i[q];if(D!==x)if(S&&x&&(b.isPlainObject(x)||(F=b.isArray(x)))){if(F){F=false;u=u&&b.isArray(u)?u:[]}else u=u&&b.isPlainObject(u)?u:{};D[q]=b.extend(S,u,x)}else if(x!==z)D[q]=x}return D};b.extend({noConflict:function(i){I.$=e;if(i)I.jQuery=d;return b},isReady:false, +readyWait:1,ready:function(i){i===true&&b.readyWait--;if(!b.readyWait||i!==true&&!b.isReady){if(!v.body)return setTimeout(b.ready,1);b.isReady=true;if(!(i!==true&&--b.readyWait>0)){m.resolveWith(v,[b]);b.fn.trigger&&b(v).trigger("ready").unbind("ready")}}},bindReady:function(){if(!k){k=true;if(v.readyState==="complete")return setTimeout(b.ready,1);if(v.addEventListener){v.addEventListener("DOMContentLoaded",s,false);I.addEventListener("load",b.ready,false)}else if(v.attachEvent){v.attachEvent("onreadystatechange", +s);I.attachEvent("onload",b.ready);var i=false;try{i=I.frameElement==null}catch(q){}v.documentElement.doScroll&&i&&a()}}},isFunction:function(i){return b.type(i)==="function"},isArray:Array.isArray||function(i){return b.type(i)==="array"},isWindow:function(i){return i&&typeof i==="object"&&"setInterval"in i},isNaN:function(i){return i==null||!l.test(i)||isNaN(i)},type:function(i){return i==null?String(i):O[t.call(i)]||"object"},isPlainObject:function(i){if(!i||b.type(i)!=="object"||i.nodeType||b.isWindow(i))return false; +if(i.constructor&&!A.call(i,"constructor")&&!A.call(i.constructor.prototype,"isPrototypeOf"))return false;var q;for(q in i);return q===z||A.call(i,q)},isEmptyObject:function(i){for(var q in i)return false;return true},error:function(i){throw i;},parseJSON:function(i){if(typeof i!=="string"||!i)return null;i=b.trim(i);if(y.test(i.replace(C,"@").replace(B,"]").replace(H,"")))return I.JSON&&I.JSON.parse?I.JSON.parse(i):(new Function("return "+i))();else b.error("Invalid JSON: "+i)},parseXML:function(i, +q,u){if(I.DOMParser){u=new DOMParser;q=u.parseFromString(i,"text/xml")}else{q=new ActiveXObject("Microsoft.XMLDOM");q.async="false";q.loadXML(i)}u=q.documentElement;if(!u||!u.nodeName||u.nodeName==="parsererror")b.error("Invalid XML: "+i);return q},noop:function(){},globalEval:function(i){if(i&&j.test(i)){var q=v.head||v.getElementsByTagName("head")[0]||v.documentElement,u=v.createElement("script");if(b.support.scriptEval())u.appendChild(v.createTextNode(i));else u.text=i;q.insertBefore(u,q.firstChild); +q.removeChild(u)}},nodeName:function(i,q){return i.nodeName&&i.nodeName.toUpperCase()===q.toUpperCase()},each:function(i,q,u){var x,F=0,D=i.length,J=D===z||b.isFunction(i);if(u)if(J)for(x in i){if(q.apply(i[x],u)===false)break}else for(;F<D;){if(q.apply(i[F++],u)===false)break}else if(J)for(x in i){if(q.call(i[x],x,i[x])===false)break}else for(u=i[0];F<D&&q.call(u,F,u)!==false;u=i[++F]);return i},trim:Q?function(i){return i==null?"":Q.call(i)}:function(i){return i==null?"":i.toString().replace(o, +"").replace(n,"")},makeArray:function(i,q){q=q||[];if(i!=null){var u=b.type(i);i.length==null||u==="string"||u==="function"||u==="regexp"||b.isWindow(i)?w.call(q,i):b.merge(q,i)}return q},inArray:function(i,q){if(q.indexOf)return q.indexOf(i);for(var u=0,x=q.length;u<x;u++)if(q[u]===i)return u;return-1},merge:function(i,q){var u=i.length,x=0;if(typeof q.length==="number")for(var F=q.length;x<F;x++)i[u++]=q[x];else for(;q[x]!==z;)i[u++]=q[x++];i.length=u;return i},grep:function(i,q,u){var x=[],F;u= +!!u;for(var D=0,J=i.length;D<J;D++){F=!!q(i[D],D);u!==F&&x.push(i[D])}return x},map:function(i,q,u){for(var x=[],F,D=0,J=i.length;D<J;D++){F=q(i[D],D,u);if(F!=null)x[x.length]=F}return x.concat.apply([],x)},guid:1,proxy:function(i,q,u){if(arguments.length===2)if(typeof q==="string"){u=i;i=u[q];q=z}else if(q&&!b.isFunction(q)){u=q;q=z}if(!q&&i)q=function(){return i.apply(u||this,arguments)};if(i)q.guid=i.guid=i.guid||q.guid||b.guid++;return q},access:function(i,q,u,x,F,D){var J=i.length;if(typeof q=== +"object"){for(var M in q)b.access(i,M,q[M],x,F,u);return i}if(u!==z){x=!D&&x&&b.isFunction(u);for(M=0;M<J;M++)F(i[M],q,x?u.call(i[M],M,F(i[M],q)):u,D);return i}return J?F(i[0],q):z},now:function(){return(new Date).getTime()},_Deferred:function(){var i=[],q,u,x,F={done:function(){if(!x){var D=arguments,J,M,S,pa,fa;if(q){fa=q;q=0}J=0;for(M=D.length;J<M;J++){S=D[J];pa=b.type(S);if(pa==="array")F.done.apply(F,S);else pa==="function"&&i.push(S)}fa&&F.resolveWith(fa[0],fa[1])}return this},resolveWith:function(D, +J){if(!x&&!q&&!u){u=1;try{for(;i[0];)i.shift().apply(D,J)}catch(M){throw M;}finally{q=[D,J];u=0}}return this},resolve:function(){F.resolveWith(b.isFunction(this.promise)?this.promise():this,arguments);return this},isResolved:function(){return!!(u||q)},cancel:function(){x=1;i=[];return this}};return F},Deferred:function(i){var q=b._Deferred(),u=b._Deferred(),x;b.extend(q,{then:function(F,D){q.done(F).fail(D);return this},fail:u.done,rejectWith:u.resolveWith,reject:u.resolve,isRejected:u.isResolved, +promise:function(F){if(F==null){if(x)return x;x=F={}}for(var D=p.length;D--;)F[p[D]]=q[p[D]];return F}});q.done(u.cancel).fail(q.cancel);delete q.cancel;i&&i.call(q,q);return q},when:function(i){var q=arguments.length,u=q<=1&&i&&b.isFunction(i.promise)?i:b.Deferred(),x=u.promise();if(q>1){for(var F=E.call(arguments,0),D=q,J=function(M){return function(S){F[M]=arguments.length>1?E.call(arguments,0):S;--D||u.resolveWith(x,F)}};q--;)if((i=F[q])&&b.isFunction(i.promise))i.promise().then(J(q),u.reject); +else--D;D||u.resolveWith(x,F)}else u!==i&&u.resolve(i);return x},uaMatch:function(i){i=i.toLowerCase();i=K.exec(i)||L.exec(i)||P.exec(i)||i.indexOf("compatible")<0&&G.exec(i)||[];return{browser:i[1]||"",version:i[2]||"0"}},sub:function(){function i(u,x){return new i.fn.init(u,x)}b.extend(true,i,this);i.superclass=this;i.fn=i.prototype=this();i.fn.constructor=i;i.subclass=this.subclass;i.fn.init=function(u,x){if(x&&x instanceof b&&!(x instanceof i))x=i(x);return b.fn.init.call(this,u,x,q)};i.fn.init.prototype= +i.fn;var q=i(v);return i},browser:{}});m=b._Deferred();b.each("Boolean Number String Function Array Date RegExp Object".split(" "),function(i,q){O["[object "+q+"]"]=q.toLowerCase()});h=b.uaMatch(h);if(h.browser){b.browser[h.browser]=true;b.browser.version=h.version}if(b.browser.webkit)b.browser.safari=true;if(N)b.inArray=function(i,q){return N.call(q,i)};if(j.test("\u00a0")){o=/^[\s\xA0]+/;n=/[\s\xA0]+$/}f=b(v);if(v.addEventListener)s=function(){v.removeEventListener("DOMContentLoaded",s,false);b.ready()}; +else if(v.attachEvent)s=function(){if(v.readyState==="complete"){v.detachEvent("onreadystatechange",s);b.ready()}};return b}();(function(){c.support={};var a=v.createElement("div");a.style.display="none";a.innerHTML=" <link/><table></table><a href='/a' style='color:red;float:left;opacity:.55;'>a</a><input type='checkbox'/>";var b=a.getElementsByTagName("*"),d=a.getElementsByTagName("a")[0],e=v.createElement("select"),f=e.appendChild(v.createElement("option")),g=a.getElementsByTagName("input")[0]; +if(!(!b||!b.length||!d)){c.support={leadingWhitespace:a.firstChild.nodeType===3,tbody:!a.getElementsByTagName("tbody").length,htmlSerialize:!!a.getElementsByTagName("link").length,style:/red/.test(d.getAttribute("style")),hrefNormalized:d.getAttribute("href")==="/a",opacity:/^0.55$/.test(d.style.opacity),cssFloat:!!d.style.cssFloat,checkOn:g.value==="on",optSelected:f.selected,deleteExpando:true,optDisabled:false,checkClone:false,noCloneEvent:true,noCloneChecked:true,boxModel:null,inlineBlockNeedsLayout:false, +shrinkWrapBlocks:false,reliableHiddenOffsets:true};g.checked=true;c.support.noCloneChecked=g.cloneNode(true).checked;e.disabled=true;c.support.optDisabled=!f.disabled;var j=null;c.support.scriptEval=function(){if(j===null){var n=v.documentElement,l=v.createElement("script"),r="script"+c.now();try{l.appendChild(v.createTextNode("window."+r+"=1;"))}catch(y){}n.insertBefore(l,n.firstChild);if(I[r]){j=true;delete I[r]}else j=false;n.removeChild(l)}return j};try{delete a.test}catch(o){c.support.deleteExpando= +false}if(!a.addEventListener&&a.attachEvent&&a.fireEvent){a.attachEvent("onclick",function n(){c.support.noCloneEvent=false;a.detachEvent("onclick",n)});a.cloneNode(true).fireEvent("onclick")}a=v.createElement("div");a.innerHTML="<input type='radio' name='radiotest' checked='checked'/>";b=v.createDocumentFragment();b.appendChild(a.firstChild);c.support.checkClone=b.cloneNode(true).cloneNode(true).lastChild.checked;c(function(){var n=v.createElement("div"),l=v.getElementsByTagName("body")[0];if(l){n.style.width= +n.style.paddingLeft="1px";l.appendChild(n);c.boxModel=c.support.boxModel=n.offsetWidth===2;if("zoom"in n.style){n.style.display="inline";n.style.zoom=1;c.support.inlineBlockNeedsLayout=n.offsetWidth===2;n.style.display="";n.innerHTML="<div style='width:4px;'></div>";c.support.shrinkWrapBlocks=n.offsetWidth!==2}n.innerHTML="<table><tr><td style='padding:0;border:0;display:none'></td><td>t</td></tr></table>";var r=n.getElementsByTagName("td");c.support.reliableHiddenOffsets=r[0].offsetHeight===0;r[0].style.display= +"";r[1].style.display="none";c.support.reliableHiddenOffsets=c.support.reliableHiddenOffsets&&r[0].offsetHeight===0;n.innerHTML="";l.removeChild(n).style.display="none"}});b=function(n){var l=v.createElement("div");n="on"+n;if(!l.attachEvent)return true;var r=n in l;if(!r){l.setAttribute(n,"return;");r=typeof l[n]==="function"}return r};c.support.submitBubbles=b("submit");c.support.changeBubbles=b("change");a=b=d=null}})();var Xa=/^(?:\{.*\}|\[.*\])$/;c.extend({cache:{},uuid:0,expando:"jQuery"+(c.fn.jquery+ +Math.random()).replace(/\D/g,""),noData:{embed:true,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:true},hasData:function(a){a=a.nodeType?c.cache[a[c.expando]]:a[c.expando];return!!a&&!ka(a)},data:function(a,b,d,e){if(c.acceptData(a)){var f=c.expando,g=typeof b==="string",j=a.nodeType,o=j?c.cache:a,n=j?a[c.expando]:a[c.expando]&&c.expando;if(!((!n||e&&n&&!o[n][f])&&g&&d===z)){if(!n)if(j)a[c.expando]=n=++c.uuid;else n=c.expando;if(!o[n]){o[n]={};if(!j)o[n].toJSON=c.noop}if(typeof b==="object"|| +typeof b==="function")if(e)o[n][f]=c.extend(o[n][f],b);else o[n]=c.extend(o[n],b);a=o[n];if(e){a[f]||(a[f]={});a=a[f]}if(d!==z)a[b]=d;if(b==="events"&&!a[b])return a[f]&&a[f].events;return g?a[b]:a}}},removeData:function(a,b,d){if(c.acceptData(a)){var e=c.expando,f=a.nodeType,g=f?c.cache:a,j=f?a[c.expando]:c.expando;if(g[j]){if(b){var o=d?g[j][e]:g[j];if(o){delete o[b];if(!ka(o))return}}if(d){delete g[j][e];if(!ka(g[j]))return}b=g[j][e];if(c.support.deleteExpando||g!=I)delete g[j];else g[j]=null; +if(b){g[j]={};if(!f)g[j].toJSON=c.noop;g[j][e]=b}else if(f)if(c.support.deleteExpando)delete a[c.expando];else if(a.removeAttribute)a.removeAttribute(c.expando);else a[c.expando]=null}}},_data:function(a,b,d){return c.data(a,b,d,true)},acceptData:function(a){if(a.nodeName){var b=c.noData[a.nodeName.toLowerCase()];if(b)return!(b===true||a.getAttribute("classid")!==b)}return true}});c.fn.extend({data:function(a,b){var d=null;if(typeof a==="undefined"){if(this.length){d=c.data(this[0]);if(this[0].nodeType=== +1)for(var e=this[0].attributes,f,g=0,j=e.length;g<j;g++){f=e[g].name;if(f.indexOf("data-")===0){f=f.substr(5);ta(this[0],f,d[f])}}}return d}else if(typeof a==="object")return this.each(function(){c.data(this,a)});var o=a.split(".");o[1]=o[1]?"."+o[1]:"";if(b===z){d=this.triggerHandler("getData"+o[1]+"!",[o[0]]);if(d===z&&this.length){d=c.data(this[0],a);d=ta(this[0],a,d)}return d===z&&o[1]?this.data(o[0]):d}else return this.each(function(){var n=c(this),l=[o[0],b];n.triggerHandler("setData"+o[1]+ +"!",l);c.data(this,a,b);n.triggerHandler("changeData"+o[1]+"!",l)})},removeData:function(a){return this.each(function(){c.removeData(this,a)})}});c.extend({queue:function(a,b,d){if(a){b=(b||"fx")+"queue";var e=c._data(a,b);if(!d)return e||[];if(!e||c.isArray(d))e=c._data(a,b,c.makeArray(d));else e.push(d);return e}},dequeue:function(a,b){b=b||"fx";var d=c.queue(a,b),e=d.shift();if(e==="inprogress")e=d.shift();if(e){b==="fx"&&d.unshift("inprogress");e.call(a,function(){c.dequeue(a,b)})}d.length||c.removeData(a, +b+"queue",true)}});c.fn.extend({queue:function(a,b){if(typeof a!=="string"){b=a;a="fx"}if(b===z)return c.queue(this[0],a);return this.each(function(){var d=c.queue(this,a,b);a==="fx"&&d[0]!=="inprogress"&&c.dequeue(this,a)})},dequeue:function(a){return this.each(function(){c.dequeue(this,a)})},delay:function(a,b){a=c.fx?c.fx.speeds[a]||a:a;b=b||"fx";return this.queue(b,function(){var d=this;setTimeout(function(){c.dequeue(d,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])}});var Fa= +/[\n\t\r]/g,qa=/\s+/,kb=/\r/g,lb=/^(?:href|src|style)$/,mb=/^(?:button|input)$/i,nb=/^(?:button|input|object|select|textarea)$/i,ob=/^a(?:rea)?$/i,Ga=/^(?:radio|checkbox)$/i;c.props={"for":"htmlFor","class":"className",readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing",rowspan:"rowSpan",colspan:"colSpan",tabindex:"tabIndex",usemap:"useMap",frameborder:"frameBorder"};c.fn.extend({attr:function(a,b){return c.access(this,a,b,true,c.attr)},removeAttr:function(a){return this.each(function(){c.attr(this, +a,"");this.nodeType===1&&this.removeAttribute(a)})},addClass:function(a){if(c.isFunction(a))return this.each(function(l){var r=c(this);r.addClass(a.call(this,l,r.attr("class")))});if(a&&typeof a==="string")for(var b=(a||"").split(qa),d=0,e=this.length;d<e;d++){var f=this[d];if(f.nodeType===1)if(f.className){for(var g=" "+f.className+" ",j=f.className,o=0,n=b.length;o<n;o++)if(g.indexOf(" "+b[o]+" ")<0)j+=" "+b[o];f.className=c.trim(j)}else f.className=a}return this},removeClass:function(a){if(c.isFunction(a))return this.each(function(n){var l= +c(this);l.removeClass(a.call(this,n,l.attr("class")))});if(a&&typeof a==="string"||a===z)for(var b=(a||"").split(qa),d=0,e=this.length;d<e;d++){var f=this[d];if(f.nodeType===1&&f.className)if(a){for(var g=(" "+f.className+" ").replace(Fa," "),j=0,o=b.length;j<o;j++)g=g.replace(" "+b[j]+" "," ");f.className=c.trim(g)}else f.className=""}return this},toggleClass:function(a,b){var d=typeof a,e=typeof b==="boolean";if(c.isFunction(a))return this.each(function(f){var g=c(this);g.toggleClass(a.call(this, +f,g.attr("class"),b),b)});return this.each(function(){if(d==="string")for(var f,g=0,j=c(this),o=b,n=a.split(qa);f=n[g++];){o=e?o:!j.hasClass(f);j[o?"addClass":"removeClass"](f)}else if(d==="undefined"||d==="boolean"){this.className&&c._data(this,"__className__",this.className);this.className=this.className||a===false?"":c._data(this,"__className__")||""}})},hasClass:function(a){a=" "+a+" ";for(var b=0,d=this.length;b<d;b++)if((" "+this[b].className+" ").replace(Fa," ").indexOf(a)>-1)return true;return false}, +val:function(a){if(!arguments.length){var b=this[0];if(b){if(c.nodeName(b,"option")){var d=b.attributes.value;return!d||d.specified?b.value:b.text}if(c.nodeName(b,"select")){d=b.selectedIndex;var e=[],f=b.options;b=b.type==="select-one";if(d<0)return null;for(var g=b?d:0,j=b?d+1:f.length;g<j;g++){var o=f[g];if(o.selected&&(c.support.optDisabled?!o.disabled:o.getAttribute("disabled")===null)&&(!o.parentNode.disabled||!c.nodeName(o.parentNode,"optgroup"))){a=c(o).val();if(b)return a;e.push(a)}}if(b&& +!e.length&&f.length)return c(f[d]).val();return e}if(Ga.test(b.type)&&!c.support.checkOn)return b.getAttribute("value")===null?"on":b.value;return(b.value||"").replace(kb,"")}return z}var n=c.isFunction(a);return this.each(function(l){var r=c(this),y=a;if(this.nodeType===1){if(n)y=a.call(this,l,r.val());if(y==null)y="";else if(typeof y==="number")y+="";else if(c.isArray(y))y=c.map(y,function(B){return B==null?"":B+""});if(c.isArray(y)&&Ga.test(this.type))this.checked=c.inArray(r.val(),y)>=0;else if(c.nodeName(this, +"select")){var C=c.makeArray(y);c("option",this).each(function(){this.selected=c.inArray(c(this).val(),C)>=0});if(!C.length)this.selectedIndex=-1}else this.value=y}})}});c.extend({attrFn:{val:true,css:true,html:true,text:true,data:true,width:true,height:true,offset:true},attr:function(a,b,d,e){if(!a||a.nodeType===3||a.nodeType===8||a.nodeType===2)return z;if(e&&b in c.attrFn)return c(a)[b](d);e=a.nodeType!==1||!c.isXMLDoc(a);var f=d!==z;b=e&&c.props[b]||b;if(a.nodeType===1){var g=lb.test(b);if((b in +a||a[b]!==z)&&e&&!g){if(f){b==="type"&&mb.test(a.nodeName)&&a.parentNode&&c.error("type property can't be changed");if(d===null)a.nodeType===1&&a.removeAttribute(b);else a[b]=d}if(c.nodeName(a,"form")&&a.getAttributeNode(b))return a.getAttributeNode(b).nodeValue;if(b==="tabIndex")return(b=a.getAttributeNode("tabIndex"))&&b.specified?b.value:nb.test(a.nodeName)||ob.test(a.nodeName)&&a.href?0:z;return a[b]}if(!c.support.style&&e&&b==="style"){if(f)a.style.cssText=""+d;return a.style.cssText}f&&a.setAttribute(b, +""+d);if(!a.attributes[b]&&a.hasAttribute&&!a.hasAttribute(b))return z;a=!c.support.hrefNormalized&&e&&g?a.getAttribute(b,2):a.getAttribute(b);return a===null?z:a}if(f)a[b]=d;return a[b]}});var ba=/\.(.*)$/,ra=/^(?:textarea|input|select)$/i,Za=/\./g,$a=/ /g,pb=/[^\w\s.|`]/g,qb=function(a){return a.replace(pb,"\\$&")};c.event={add:function(a,b,d,e){if(!(a.nodeType===3||a.nodeType===8)){try{if(c.isWindow(a)&&a!==I&&!a.frameElement)a=I}catch(f){}if(d===false)d=W;else if(!d)return;var g,j;if(d.handler){g= +d;d=g.handler}if(!d.guid)d.guid=c.guid++;if(j=c._data(a)){var o=j.events,n=j.handle;if(!o)j.events=o={};if(!n)j.handle=n=function(){return typeof c!=="undefined"&&!c.event.triggered?c.event.handle.apply(n.elem,arguments):z};n.elem=a;b=b.split(" ");for(var l,r=0,y;l=b[r++];){j=g?c.extend({},g):{handler:d,data:e};if(l.indexOf(".")>-1){y=l.split(".");l=y.shift();j.namespace=y.slice(0).sort().join(".")}else{y=[];j.namespace=""}j.type=l;if(!j.guid)j.guid=d.guid;var C=o[l],B=c.event.special[l]||{};if(!C){C= +o[l]=[];if(!B.setup||B.setup.call(a,e,y,n)===false)if(a.addEventListener)a.addEventListener(l,n,false);else a.attachEvent&&a.attachEvent("on"+l,n)}if(B.add){B.add.call(a,j);if(!j.handler.guid)j.handler.guid=d.guid}C.push(j);c.event.global[l]=true}a=null}}},global:{},remove:function(a,b,d,e){if(!(a.nodeType===3||a.nodeType===8)){if(d===false)d=W;var f,g,j=0,o,n,l,r,y,C,B=c.hasData(a)&&c._data(a),H=B&&B.events;if(B&&H){if(b&&b.type){d=b.handler;b=b.type}if(!b||typeof b==="string"&&b.charAt(0)==="."){b= +b||"";for(f in H)c.event.remove(a,f+b)}else{for(b=b.split(" ");f=b[j++];){r=f;o=f.indexOf(".")<0;n=[];if(!o){n=f.split(".");f=n.shift();l=new RegExp("(^|\\.)"+c.map(n.slice(0).sort(),qb).join("\\.(?:.*\\.)?")+"(\\.|$)")}if(y=H[f])if(d){r=c.event.special[f]||{};for(g=e||0;g<y.length;g++){C=y[g];if(d.guid===C.guid){if(o||l.test(C.namespace)){e==null&&y.splice(g--,1);r.remove&&r.remove.call(a,C)}if(e!=null)break}}if(y.length===0||e!=null&&y.length===1){if(!r.teardown||r.teardown.call(a,n)===false)c.removeEvent(a, +f,B.handle);delete H[f]}}else for(g=0;g<y.length;g++){C=y[g];if(o||l.test(C.namespace)){c.event.remove(a,r,C.handler,g);y.splice(g--,1)}}}if(c.isEmptyObject(H)){if(b=B.handle)b.elem=null;delete B.events;delete B.handle;c.isEmptyObject(B)&&c.removeData(a,z,true)}}}}},trigger:function(a,b,d,e){var f=a.type||a;if(!e){a=typeof a==="object"?a[c.expando]?a:c.extend(c.Event(f),a):c.Event(f);if(f.indexOf("!")>=0){a.type=f=f.slice(0,-1);a.exclusive=true}if(!d){a.stopPropagation();c.event.global[f]&&c.each(c.cache, +function(){var y=this[c.expando];y&&y.events&&y.events[f]&&c.event.trigger(a,b,y.handle.elem)})}if(!d||d.nodeType===3||d.nodeType===8)return z;a.result=z;a.target=d;b=c.makeArray(b);b.unshift(a)}a.currentTarget=d;(e=c._data(d,"handle"))&&e.apply(d,b);e=d.parentNode||d.ownerDocument;try{if(!(d&&d.nodeName&&c.noData[d.nodeName.toLowerCase()]))if(d["on"+f]&&d["on"+f].apply(d,b)===false){a.result=false;a.preventDefault()}}catch(g){}if(!a.isPropagationStopped()&&e)c.event.trigger(a,b,e,true);else if(!a.isDefaultPrevented()){var j; +e=a.target;var o=f.replace(ba,""),n=c.nodeName(e,"a")&&o==="click",l=c.event.special[o]||{};if((!l._default||l._default.call(d,a)===false)&&!n&&!(e&&e.nodeName&&c.noData[e.nodeName.toLowerCase()])){try{if(e[o]){if(j=e["on"+o])e["on"+o]=null;c.event.triggered=true;e[o]()}}catch(r){}if(j)e["on"+o]=j;c.event.triggered=false}}},handle:function(a){var b,d,e,f;d=[];var g=c.makeArray(arguments);a=g[0]=c.event.fix(a||I.event);a.currentTarget=this;b=a.type.indexOf(".")<0&&!a.exclusive;if(!b){e=a.type.split("."); +a.type=e.shift();d=e.slice(0).sort();e=new RegExp("(^|\\.)"+d.join("\\.(?:.*\\.)?")+"(\\.|$)")}a.namespace=a.namespace||d.join(".");f=c._data(this,"events");d=(f||{})[a.type];if(f&&d){d=d.slice(0);f=0;for(var j=d.length;f<j;f++){var o=d[f];if(b||e.test(o.namespace)){a.handler=o.handler;a.data=o.data;a.handleObj=o;o=o.handler.apply(this,g);if(o!==z){a.result=o;if(o===false){a.preventDefault();a.stopPropagation()}}if(a.isImmediatePropagationStopped())break}}}return a.result},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "), +fix:function(a){if(a[c.expando])return a;var b=a;a=c.Event(b);for(var d=this.props.length,e;d;){e=this.props[--d];a[e]=b[e]}if(!a.target)a.target=a.srcElement||v;if(a.target.nodeType===3)a.target=a.target.parentNode;if(!a.relatedTarget&&a.fromElement)a.relatedTarget=a.fromElement===a.target?a.toElement:a.fromElement;if(a.pageX==null&&a.clientX!=null){b=v.documentElement;d=v.body;a.pageX=a.clientX+(b&&b.scrollLeft||d&&d.scrollLeft||0)-(b&&b.clientLeft||d&&d.clientLeft||0);a.pageY=a.clientY+(b&&b.scrollTop|| +d&&d.scrollTop||0)-(b&&b.clientTop||d&&d.clientTop||0)}if(a.which==null&&(a.charCode!=null||a.keyCode!=null))a.which=a.charCode!=null?a.charCode:a.keyCode;if(!a.metaKey&&a.ctrlKey)a.metaKey=a.ctrlKey;if(!a.which&&a.button!==z)a.which=a.button&1?1:a.button&2?3:a.button&4?2:0;return a},guid:1E8,proxy:c.proxy,special:{ready:{setup:c.bindReady,teardown:c.noop},live:{add:function(a){c.event.add(this,ca(a.origType,a.selector),c.extend({},a,{handler:Ya,guid:a.handler.guid}))},remove:function(a){c.event.remove(this, +ca(a.origType,a.selector),a)}},beforeunload:{setup:function(a,b,d){if(c.isWindow(this))this.onbeforeunload=d},teardown:function(a,b){if(this.onbeforeunload===b)this.onbeforeunload=null}}}};c.removeEvent=v.removeEventListener?function(a,b,d){a.removeEventListener&&a.removeEventListener(b,d,false)}:function(a,b,d){a.detachEvent&&a.detachEvent("on"+b,d)};c.Event=function(a){if(!this.preventDefault)return new c.Event(a);if(a&&a.type){this.originalEvent=a;this.type=a.type;this.isDefaultPrevented=a.defaultPrevented|| +a.returnValue===false||a.getPreventDefault&&a.getPreventDefault()?aa:W}else this.type=a;this.timeStamp=c.now();this[c.expando]=true};c.Event.prototype={preventDefault:function(){this.isDefaultPrevented=aa;var a=this.originalEvent;if(a)if(a.preventDefault)a.preventDefault();else a.returnValue=false},stopPropagation:function(){this.isPropagationStopped=aa;var a=this.originalEvent;if(a){a.stopPropagation&&a.stopPropagation();a.cancelBubble=true}},stopImmediatePropagation:function(){this.isImmediatePropagationStopped= +aa;this.stopPropagation()},isDefaultPrevented:W,isPropagationStopped:W,isImmediatePropagationStopped:W};var Ha=function(a){var b=a.relatedTarget;try{if(!(b!==v&&!b.parentNode)){for(;b&&b!==this;)b=b.parentNode;if(b!==this){a.type=a.data;c.event.handle.apply(this,arguments)}}}catch(d){}},Ia=function(a){a.type=a.data;c.event.handle.apply(this,arguments)};c.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){c.event.special[a]={setup:function(d){c.event.add(this,b,d&&d.selector?Ia:Ha,a)}, +teardown:function(d){c.event.remove(this,b,d&&d.selector?Ia:Ha)}}});if(!c.support.submitBubbles)c.event.special.submit={setup:function(){if(this.nodeName&&this.nodeName.toLowerCase()!=="form"){c.event.add(this,"click.specialSubmit",function(a){var b=a.target,d=b.type;if((d==="submit"||d==="image")&&c(b).closest("form").length)ua("submit",this,arguments)});c.event.add(this,"keypress.specialSubmit",function(a){var b=a.target,d=b.type;if((d==="text"||d==="password")&&c(b).closest("form").length&&a.keyCode=== +13)ua("submit",this,arguments)})}else return false},teardown:function(){c.event.remove(this,".specialSubmit")}};if(!c.support.changeBubbles){var Z,Ja=function(a){var b=a.type,d=a.value;if(b==="radio"||b==="checkbox")d=a.checked;else if(b==="select-multiple")d=a.selectedIndex>-1?c.map(a.options,function(e){return e.selected}).join("-"):"";else if(a.nodeName.toLowerCase()==="select")d=a.selectedIndex;return d},ga=function(a,b){var d=a.target,e,f;if(!(!ra.test(d.nodeName)||d.readOnly)){e=c._data(d,"_change_data"); +f=Ja(d);if(a.type!=="focusout"||d.type!=="radio")c._data(d,"_change_data",f);if(!(e===z||f===e))if(e!=null||f){a.type="change";a.liveFired=z;c.event.trigger(a,b,d)}}};c.event.special.change={filters:{focusout:ga,beforedeactivate:ga,click:function(a){var b=a.target,d=b.type;if(d==="radio"||d==="checkbox"||b.nodeName.toLowerCase()==="select")ga.call(this,a)},keydown:function(a){var b=a.target,d=b.type;if(a.keyCode===13&&b.nodeName.toLowerCase()!=="textarea"||a.keyCode===32&&(d==="checkbox"||d==="radio")|| +d==="select-multiple")ga.call(this,a)},beforeactivate:function(a){a=a.target;c._data(a,"_change_data",Ja(a))}},setup:function(){if(this.type==="file")return false;for(var a in Z)c.event.add(this,a+".specialChange",Z[a]);return ra.test(this.nodeName)},teardown:function(){c.event.remove(this,".specialChange");return ra.test(this.nodeName)}};Z=c.event.special.change.filters;Z.focus=Z.beforeactivate}v.addEventListener&&c.each({focus:"focusin",blur:"focusout"},function(a,b){function d(e){e=c.event.fix(e); +e.type=b;return c.event.handle.call(this,e)}c.event.special[b]={setup:function(){this.addEventListener(a,d,true)},teardown:function(){this.removeEventListener(a,d,true)}}});c.each(["bind","one"],function(a,b){c.fn[b]=function(d,e,f){if(typeof d==="object"){for(var g in d)this[b](g,e,d[g],f);return this}if(c.isFunction(e)||e===false){f=e;e=z}var j=b==="one"?c.proxy(f,function(n){c(this).unbind(n,j);return f.apply(this,arguments)}):f;if(d==="unload"&&b!=="one")this.one(d,e,f);else{g=0;for(var o=this.length;g< +o;g++)c.event.add(this[g],d,j,e)}return this}});c.fn.extend({unbind:function(a,b){if(typeof a==="object"&&!a.preventDefault)for(var d in a)this.unbind(d,a[d]);else{d=0;for(var e=this.length;d<e;d++)c.event.remove(this[d],a,b)}return this},delegate:function(a,b,d,e){return this.live(b,d,e,a)},undelegate:function(a,b,d){return arguments.length===0?this.unbind("live"):this.die(b,null,d,a)},trigger:function(a,b){return this.each(function(){c.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0]){a= +c.Event(a);a.preventDefault();a.stopPropagation();c.event.trigger(a,b,this[0]);return a.result}},toggle:function(a){for(var b=arguments,d=1;d<b.length;)c.proxy(a,b[d++]);return this.click(c.proxy(a,function(e){var f=(c._data(this,"lastToggle"+a.guid)||0)%d;c._data(this,"lastToggle"+a.guid,f+1);e.preventDefault();return b[f].apply(this,arguments)||false}))},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}});var Ka={focus:"focusin",blur:"focusout",mouseenter:"mouseover",mouseleave:"mouseout"}; +c.each(["live","die"],function(a,b){c.fn[b]=function(d,e,f,g){var j,o=0,n,l,r=g||this.selector;g=g?this:c(this.context);if(typeof d==="object"&&!d.preventDefault){for(j in d)g[b](j,e,d[j],r);return this}if(c.isFunction(e)){f=e;e=z}for(d=(d||"").split(" ");(j=d[o++])!=null;){n=ba.exec(j);l="";if(n){l=n[0];j=j.replace(ba,"")}if(j==="hover")d.push("mouseenter"+l,"mouseleave"+l);else{n=j;if(j==="focus"||j==="blur"){d.push(Ka[j]+l);j+=l}else j=(Ka[j]||j)+l;if(b==="live"){l=0;for(var y=g.length;l<y;l++)c.event.add(g[l], +"live."+ca(j,r),{data:e,selector:r,handler:f,origType:j,origHandler:f,preType:n})}else g.unbind("live."+ca(j,r),f)}}return this}});c.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error".split(" "),function(a,b){c.fn[b]=function(d,e){if(e==null){e=d;d=null}return arguments.length>0?this.bind(b,d,e):this.trigger(b)};if(c.attrFn)c.attrFn[b]=true});(function(){function a(h, +k,m,p,s,t){s=0;for(var A=p.length;s<A;s++){var w=p[s];if(w){var E=false;for(w=w[h];w;){if(w.sizcache===m){E=p[w.sizset];break}if(w.nodeType===1&&!t){w.sizcache=m;w.sizset=s}if(w.nodeName.toLowerCase()===k){E=w;break}w=w[h]}p[s]=E}}}function b(h,k,m,p,s,t){s=0;for(var A=p.length;s<A;s++){var w=p[s];if(w){var E=false;for(w=w[h];w;){if(w.sizcache===m){E=p[w.sizset];break}if(w.nodeType===1){if(!t){w.sizcache=m;w.sizset=s}if(typeof k!=="string"){if(w===k){E=true;break}}else if(l.filter(k,[w]).length>0){E= +w;break}}w=w[h]}p[s]=E}}}var d=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,e=0,f=Object.prototype.toString,g=false,j=true,o=/\\/g,n=/\W/;[0,0].sort(function(){j=false;return 0});var l=function(h,k,m,p){m=m||[];var s=k=k||v;if(k.nodeType!==1&&k.nodeType!==9)return[];if(!h||typeof h!=="string")return m;var t,A,w,E,Q,N=true,O=l.isXML(k),i=[],q=h;do{d.exec("");if(t=d.exec(q)){q=t[3];i.push(t[1]);if(t[2]){E=t[3]; +break}}}while(t);if(i.length>1&&y.exec(h))if(i.length===2&&r.relative[i[0]])A=G(i[0]+i[1],k);else for(A=r.relative[i[0]]?[k]:l(i.shift(),k);i.length;){h=i.shift();if(r.relative[h])h+=i.shift();A=G(h,A)}else{if(!p&&i.length>1&&k.nodeType===9&&!O&&r.match.ID.test(i[0])&&!r.match.ID.test(i[i.length-1])){t=l.find(i.shift(),k,O);k=t.expr?l.filter(t.expr,t.set)[0]:t.set[0]}if(k){t=p?{expr:i.pop(),set:H(p)}:l.find(i.pop(),i.length===1&&(i[0]==="~"||i[0]==="+")&&k.parentNode?k.parentNode:k,O);A=t.expr?l.filter(t.expr, +t.set):t.set;if(i.length>0)w=H(A);else N=false;for(;i.length;){t=Q=i.pop();if(r.relative[Q])t=i.pop();else Q="";if(t==null)t=k;r.relative[Q](w,t,O)}}else w=[]}w||(w=A);w||l.error(Q||h);if(f.call(w)==="[object Array]")if(N)if(k&&k.nodeType===1)for(h=0;w[h]!=null;h++){if(w[h]&&(w[h]===true||w[h].nodeType===1&&l.contains(k,w[h])))m.push(A[h])}else for(h=0;w[h]!=null;h++)w[h]&&w[h].nodeType===1&&m.push(A[h]);else m.push.apply(m,w);else H(w,m);if(E){l(E,s,m,p);l.uniqueSort(m)}return m};l.uniqueSort=function(h){if(L){g= +j;h.sort(L);if(g)for(var k=1;k<h.length;k++)h[k]===h[k-1]&&h.splice(k--,1)}return h};l.matches=function(h,k){return l(h,null,null,k)};l.matchesSelector=function(h,k){return l(k,null,null,[h]).length>0};l.find=function(h,k,m){var p;if(!h)return[];for(var s=0,t=r.order.length;s<t;s++){var A,w=r.order[s];if(A=r.leftMatch[w].exec(h)){var E=A[1];A.splice(1,1);if(E.substr(E.length-1)!=="\\"){A[1]=(A[1]||"").replace(o,"");p=r.find[w](A,k,m);if(p!=null){h=h.replace(r.match[w],"");break}}}}p||(p=typeof k.getElementsByTagName!== +"undefined"?k.getElementsByTagName("*"):[]);return{set:p,expr:h}};l.filter=function(h,k,m,p){for(var s,t,A=h,w=[],E=k,Q=k&&k[0]&&l.isXML(k[0]);h&&k.length;){for(var N in r.filter)if((s=r.leftMatch[N].exec(h))!=null&&s[2]){var O,i,q=r.filter[N];i=s[1];t=false;s.splice(1,1);if(i.substr(i.length-1)!=="\\"){if(E===w)w=[];if(r.preFilter[N])if(s=r.preFilter[N](s,E,m,w,p,Q)){if(s===true)continue}else t=O=true;if(s)for(var u=0;(i=E[u])!=null;u++)if(i){O=q(i,s,u,E);var x=p^!!O;if(m&&O!=null)if(x)t=true;else E[u]= +false;else if(x){w.push(i);t=true}}if(O!==z){m||(E=w);h=h.replace(r.match[N],"");if(!t)return[];break}}}if(h===A)if(t==null)l.error(h);else break;A=h}return E};l.error=function(h){throw"Syntax error, unrecognized expression: "+h;};var r=l.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,CLASS:/\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/, +TAG:/^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(h){return h.getAttribute("href")},type:function(h){return h.getAttribute("type")}},relative:{"+":function(h,k){var m= +typeof k==="string",p=m&&!n.test(k);m=m&&!p;if(p)k=k.toLowerCase();p=0;for(var s=h.length,t;p<s;p++)if(t=h[p]){for(;(t=t.previousSibling)&&t.nodeType!==1;);h[p]=m||t&&t.nodeName.toLowerCase()===k?t||false:t===k}m&&l.filter(k,h,true)},">":function(h,k){var m,p=typeof k==="string",s=0,t=h.length;if(p&&!n.test(k))for(k=k.toLowerCase();s<t;s++){if(m=h[s]){m=m.parentNode;h[s]=m.nodeName.toLowerCase()===k?m:false}}else{for(;s<t;s++)if(m=h[s])h[s]=p?m.parentNode:m.parentNode===k;p&&l.filter(k,h,true)}}, +"":function(h,k,m){var p,s=e++,t=b;if(typeof k==="string"&&!n.test(k)){p=k=k.toLowerCase();t=a}t("parentNode",k,s,h,p,m)},"~":function(h,k,m){var p,s=e++,t=b;if(typeof k==="string"&&!n.test(k)){p=k=k.toLowerCase();t=a}t("previousSibling",k,s,h,p,m)}},find:{ID:function(h,k,m){if(typeof k.getElementById!=="undefined"&&!m)return(h=k.getElementById(h[1]))&&h.parentNode?[h]:[]},NAME:function(h,k){if(typeof k.getElementsByName!=="undefined"){var m=[];k=k.getElementsByName(h[1]);for(var p=0,s=k.length;p< +s;p++)k[p].getAttribute("name")===h[1]&&m.push(k[p]);return m.length===0?null:m}},TAG:function(h,k){if(typeof k.getElementsByTagName!=="undefined")return k.getElementsByTagName(h[1])}},preFilter:{CLASS:function(h,k,m,p,s,t){h=" "+h[1].replace(o,"")+" ";if(t)return h;t=0;for(var A;(A=k[t])!=null;t++)if(A)if(s^(A.className&&(" "+A.className+" ").replace(/[\t\n\r]/g," ").indexOf(h)>=0))m||p.push(A);else if(m)k[t]=false;return false},ID:function(h){return h[1].replace(o,"")},TAG:function(h){return h[1].replace(o, +"").toLowerCase()},CHILD:function(h){if(h[1]==="nth"){h[2]||l.error(h[0]);h[2]=h[2].replace(/^\+|\s*/g,"");var k=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(h[2]==="even"&&"2n"||h[2]==="odd"&&"2n+1"||!/\D/.test(h[2])&&"0n+"+h[2]||h[2]);h[2]=k[1]+(k[2]||1)-0;h[3]=k[3]-0}else h[2]&&l.error(h[0]);h[0]=e++;return h},ATTR:function(h,k,m,p,s,t){k=h[1]=h[1].replace(o,"");if(!t&&r.attrMap[k])h[1]=r.attrMap[k];h[4]=(h[4]||h[5]||"").replace(o,"");if(h[2]==="~=")h[4]=" "+h[4]+" ";return h},PSEUDO:function(h,k,m,p,s){if(h[1]=== +"not")if((d.exec(h[3])||"").length>1||/^\w/.test(h[3]))h[3]=l(h[3],null,null,k);else{h=l.filter(h[3],k,m,true^s);m||p.push.apply(p,h);return false}else if(r.match.POS.test(h[0])||r.match.CHILD.test(h[0]))return true;return h},POS:function(h){h.unshift(true);return h}},filters:{enabled:function(h){return h.disabled===false&&h.type!=="hidden"},disabled:function(h){return h.disabled===true},checked:function(h){return h.checked===true},selected:function(h){return h.selected===true},parent:function(h){return!!h.firstChild}, +empty:function(h){return!h.firstChild},has:function(h,k,m){return!!l(m[3],h).length},header:function(h){return/h\d/i.test(h.nodeName)},text:function(h){return"text"===h.getAttribute("type")},radio:function(h){return"radio"===h.type},checkbox:function(h){return"checkbox"===h.type},file:function(h){return"file"===h.type},password:function(h){return"password"===h.type},submit:function(h){return"submit"===h.type},image:function(h){return"image"===h.type},reset:function(h){return"reset"===h.type},button:function(h){return"button"=== +h.type||h.nodeName.toLowerCase()==="button"},input:function(h){return/input|select|textarea|button/i.test(h.nodeName)}},setFilters:{first:function(h,k){return k===0},last:function(h,k,m,p){return k===p.length-1},even:function(h,k){return k%2===0},odd:function(h,k){return k%2===1},lt:function(h,k,m){return k<m[3]-0},gt:function(h,k,m){return k>m[3]-0},nth:function(h,k,m){return m[3]-0===k},eq:function(h,k,m){return m[3]-0===k}},filter:{PSEUDO:function(h,k,m,p){var s=k[1],t=r.filters[s];if(t)return t(h, +m,k,p);else if(s==="contains")return(h.textContent||h.innerText||l.getText([h])||"").indexOf(k[3])>=0;else if(s==="not"){k=k[3];m=0;for(p=k.length;m<p;m++)if(k[m]===h)return false;return true}else l.error(s)},CHILD:function(h,k){var m=k[1],p=h;switch(m){case "only":case "first":for(;p=p.previousSibling;)if(p.nodeType===1)return false;if(m==="first")return true;p=h;case "last":for(;p=p.nextSibling;)if(p.nodeType===1)return false;return true;case "nth":m=k[2];var s=k[3];if(m===1&&s===0)return true; +k=k[0];var t=h.parentNode;if(t&&(t.sizcache!==k||!h.nodeIndex)){var A=0;for(p=t.firstChild;p;p=p.nextSibling)if(p.nodeType===1)p.nodeIndex=++A;t.sizcache=k}h=h.nodeIndex-s;return m===0?h===0:h%m===0&&h/m>=0}},ID:function(h,k){return h.nodeType===1&&h.getAttribute("id")===k},TAG:function(h,k){return k==="*"&&h.nodeType===1||h.nodeName.toLowerCase()===k},CLASS:function(h,k){return(" "+(h.className||h.getAttribute("class"))+" ").indexOf(k)>-1},ATTR:function(h,k){var m=k[1];h=r.attrHandle[m]?r.attrHandle[m](h): +h[m]!=null?h[m]:h.getAttribute(m);m=h+"";var p=k[2];k=k[4];return h==null?p==="!=":p==="="?m===k:p==="*="?m.indexOf(k)>=0:p==="~="?(" "+m+" ").indexOf(k)>=0:!k?m&&h!==false:p==="!="?m!==k:p==="^="?m.indexOf(k)===0:p==="$="?m.substr(m.length-k.length)===k:p==="|="?m===k||m.substr(0,k.length+1)===k+"-":false},POS:function(h,k,m,p){var s=r.setFilters[k[2]];if(s)return s(h,m,k,p)}}},y=r.match.POS,C=function(h,k){return"\\"+(k-0+1)};for(var B in r.match){r.match[B]=new RegExp(r.match[B].source+/(?![^\[]*\])(?![^\(]*\))/.source); +r.leftMatch[B]=new RegExp(/(^(?:.|\r|\n)*?)/.source+r.match[B].source.replace(/\\(\d+)/g,C))}var H=function(h,k){h=Array.prototype.slice.call(h,0);if(k){k.push.apply(k,h);return k}return h};try{Array.prototype.slice.call(v.documentElement.childNodes,0)}catch(K){H=function(h,k){var m=0;k=k||[];if(f.call(h)==="[object Array]")Array.prototype.push.apply(k,h);else if(typeof h.length==="number")for(var p=h.length;m<p;m++)k.push(h[m]);else for(;h[m];m++)k.push(h[m]);return k}}var L,P;if(v.documentElement.compareDocumentPosition)L= +function(h,k){if(h===k){g=true;return 0}if(!h.compareDocumentPosition||!k.compareDocumentPosition)return h.compareDocumentPosition?-1:1;return h.compareDocumentPosition(k)&4?-1:1};else{L=function(h,k){var m,p,s=[],t=[];m=h.parentNode;p=k.parentNode;var A=m;if(h===k){g=true;return 0}else if(m===p)return P(h,k);else if(m){if(!p)return 1}else return-1;for(;A;){s.unshift(A);A=A.parentNode}for(A=p;A;){t.unshift(A);A=A.parentNode}m=s.length;p=t.length;for(A=0;A<m&&A<p;A++)if(s[A]!==t[A])return P(s[A],t[A]); +return A===m?P(h,t[A],-1):P(s[A],k,1)};P=function(h,k,m){if(h===k)return m;for(h=h.nextSibling;h;){if(h===k)return-1;h=h.nextSibling}return 1}}l.getText=function(h){for(var k="",m,p=0;h[p];p++){m=h[p];if(m.nodeType===3||m.nodeType===4)k+=m.nodeValue;else if(m.nodeType!==8)k+=l.getText(m.childNodes)}return k};(function(){var h=v.createElement("div"),k="script"+(new Date).getTime(),m=v.documentElement;h.innerHTML="<a name='"+k+"'/>";m.insertBefore(h,m.firstChild);if(v.getElementById(k)){r.find.ID=function(p, +s,t){if(typeof s.getElementById!=="undefined"&&!t)return(s=s.getElementById(p[1]))?s.id===p[1]||typeof s.getAttributeNode!=="undefined"&&s.getAttributeNode("id").nodeValue===p[1]?[s]:z:[]};r.filter.ID=function(p,s){var t=typeof p.getAttributeNode!=="undefined"&&p.getAttributeNode("id");return p.nodeType===1&&t&&t.nodeValue===s}}m.removeChild(h);m=h=null})();(function(){var h=v.createElement("div");h.appendChild(v.createComment(""));if(h.getElementsByTagName("*").length>0)r.find.TAG=function(k,m){m= +m.getElementsByTagName(k[1]);if(k[1]==="*"){k=[];for(var p=0;m[p];p++)m[p].nodeType===1&&k.push(m[p]);m=k}return m};h.innerHTML="<a href='#'></a>";if(h.firstChild&&typeof h.firstChild.getAttribute!=="undefined"&&h.firstChild.getAttribute("href")!=="#")r.attrHandle.href=function(k){return k.getAttribute("href",2)};h=null})();v.querySelectorAll&&function(){var h=l,k=v.createElement("div");k.innerHTML="<p class='TEST'></p>";if(!(k.querySelectorAll&&k.querySelectorAll(".TEST").length===0)){l=function(p, +s,t,A){s=s||v;if(!A&&!l.isXML(s)){var w=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(p);if(w&&(s.nodeType===1||s.nodeType===9))if(w[1])return H(s.getElementsByTagName(p),t);else if(w[2]&&r.find.CLASS&&s.getElementsByClassName)return H(s.getElementsByClassName(w[2]),t);if(s.nodeType===9){if(p==="body"&&s.body)return H([s.body],t);else if(w&&w[3]){var E=s.getElementById(w[3]);if(E&&E.parentNode){if(E.id===w[3])return H([E],t)}else return H([],t)}try{return H(s.querySelectorAll(p),t)}catch(Q){}}else if(s.nodeType=== +1&&s.nodeName.toLowerCase()!=="object"){w=s;var N=(E=s.getAttribute("id"))||"__sizzle__",O=s.parentNode,i=/^\s*[+~]/.test(p);if(E)N=N.replace(/'/g,"\\$&");else s.setAttribute("id",N);if(i&&O)s=s.parentNode;try{if(!i||O)return H(s.querySelectorAll("[id='"+N+"'] "+p),t)}catch(q){}finally{E||w.removeAttribute("id")}}}return h(p,s,t,A)};for(var m in h)l[m]=h[m];k=null}}();(function(){var h=v.documentElement,k=h.matchesSelector||h.mozMatchesSelector||h.webkitMatchesSelector||h.msMatchesSelector,m=false; +try{k.call(v.documentElement,"[test!='']:sizzle")}catch(p){m=true}if(k)l.matchesSelector=function(s,t){t=t.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!l.isXML(s))try{if(m||!r.match.PSEUDO.test(t)&&!/!=/.test(t))return k.call(s,t)}catch(A){}return l(t,null,null,[s]).length>0}})();(function(){var h=v.createElement("div");h.innerHTML="<div class='test e'></div><div class='test'></div>";if(!(!h.getElementsByClassName||h.getElementsByClassName("e").length===0)){h.lastChild.className="e";if(h.getElementsByClassName("e").length!== +1){r.order.splice(1,0,"CLASS");r.find.CLASS=function(k,m,p){if(typeof m.getElementsByClassName!=="undefined"&&!p)return m.getElementsByClassName(k[1])};h=null}}})();l.contains=v.documentElement.contains?function(h,k){return h!==k&&(h.contains?h.contains(k):true)}:v.documentElement.compareDocumentPosition?function(h,k){return!!(h.compareDocumentPosition(k)&16)}:function(){return false};l.isXML=function(h){return(h=(h?h.ownerDocument||h:0).documentElement)?h.nodeName!=="HTML":false};var G=function(h, +k){var m,p=[],s="";for(k=k.nodeType?[k]:k;m=r.match.PSEUDO.exec(h);){s+=m[0];h=h.replace(r.match.PSEUDO,"")}h=r.relative[h]?h+"*":h;m=0;for(var t=k.length;m<t;m++)l(h,k[m],p);return l.filter(s,p)};c.find=l;c.expr=l.selectors;c.expr[":"]=c.expr.filters;c.unique=l.uniqueSort;c.text=l.getText;c.isXMLDoc=l.isXML;c.contains=l.contains})();var rb=/Until$/,sb=/^(?:parents|prevUntil|prevAll)/,tb=/,/,ab=/^.[^:#\[\.,]*$/,ub=Array.prototype.slice,vb=c.expr.match.POS,wb={children:true,contents:true,next:true, +prev:true};c.fn.extend({find:function(a){for(var b=this.pushStack("","find",a),d=0,e=0,f=this.length;e<f;e++){d=b.length;c.find(a,this[e],b);if(e>0)for(var g=d;g<b.length;g++)for(var j=0;j<d;j++)if(b[j]===b[g]){b.splice(g--,1);break}}return b},has:function(a){var b=c(a);return this.filter(function(){for(var d=0,e=b.length;d<e;d++)if(c.contains(this,b[d]))return true})},not:function(a){return this.pushStack(wa(this,a,false),"not",a)},filter:function(a){return this.pushStack(wa(this,a,true),"filter", +a)},is:function(a){return!!a&&c.filter(a,this).length>0},closest:function(a,b){var d=[],e,f,g=this[0];if(c.isArray(a)){var j,o={},n=1;if(g&&a.length){e=0;for(f=a.length;e<f;e++){j=a[e];o[j]||(o[j]=c.expr.match.POS.test(j)?c(j,b||this.context):j)}for(;g&&g.ownerDocument&&g!==b;){for(j in o){a=o[j];if(a.jquery?a.index(g)>-1:c(g).is(a))d.push({selector:j,elem:g,level:n})}g=g.parentNode;n++}}return d}j=vb.test(a)?c(a,b||this.context):null;e=0;for(f=this.length;e<f;e++)for(g=this[e];g;)if(j?j.index(g)> +-1:c.find.matchesSelector(g,a)){d.push(g);break}else{g=g.parentNode;if(!g||!g.ownerDocument||g===b)break}d=d.length>1?c.unique(d):d;return this.pushStack(d,"closest",a)},index:function(a){if(!a||typeof a==="string")return c.inArray(this[0],a?c(a):this.parent().children());return c.inArray(a.jquery?a[0]:a,this)},add:function(a,b){a=typeof a==="string"?c(a,b):c.makeArray(a);b=c.merge(this.get(),a);return this.pushStack(va(a[0])||va(b[0])?b:c.unique(b))},andSelf:function(){return this.add(this.prevObject)}}); +c.each({parent:function(a){return(a=a.parentNode)&&a.nodeType!==11?a:null},parents:function(a){return c.dir(a,"parentNode")},parentsUntil:function(a,b,d){return c.dir(a,"parentNode",d)},next:function(a){return c.nth(a,2,"nextSibling")},prev:function(a){return c.nth(a,2,"previousSibling")},nextAll:function(a){return c.dir(a,"nextSibling")},prevAll:function(a){return c.dir(a,"previousSibling")},nextUntil:function(a,b,d){return c.dir(a,"nextSibling",d)},prevUntil:function(a,b,d){return c.dir(a,"previousSibling", +d)},siblings:function(a){return c.sibling(a.parentNode.firstChild,a)},children:function(a){return c.sibling(a.firstChild)},contents:function(a){return c.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:c.makeArray(a.childNodes)}},function(a,b){c.fn[a]=function(d,e){var f=c.map(this,b,d),g=ub.call(arguments);rb.test(a)||(e=d);if(e&&typeof e==="string")f=c.filter(e,f);f=this.length>1&&!wb[a]?c.unique(f):f;if((this.length>1||tb.test(e))&&sb.test(a))f=f.reverse();return this.pushStack(f, +a,g.join(","))}});c.extend({filter:function(a,b,d){if(d)a=":not("+a+")";return b.length===1?c.find.matchesSelector(b[0],a)?[b[0]]:[]:c.find.matches(a,b)},dir:function(a,b,d){var e=[];for(a=a[b];a&&a.nodeType!==9&&(d===z||a.nodeType!==1||!c(a).is(d));){a.nodeType===1&&e.push(a);a=a[b]}return e},nth:function(a,b,d){b=b||1;for(var e=0;a;a=a[d])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){for(var d=[];a;a=a.nextSibling)a.nodeType===1&&a!==b&&d.push(a);return d}});var xb=/ jQuery\d+="(?:\d+|null)"/g, +sa=/^\s+/,La=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,Ma=/<([\w:]+)/,yb=/<tbody/i,zb=/<|&#?\w+;/,Na=/<(?:script|object|embed|option|style)/i,Oa=/checked\s*(?:[^=]|=\s*.checked.)/i,R={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1, +"<map>","</map>"],_default:[0,"",""]};R.optgroup=R.option;R.tbody=R.tfoot=R.colgroup=R.caption=R.thead;R.th=R.td;if(!c.support.htmlSerialize)R._default=[1,"div<div>","</div>"];c.fn.extend({text:function(a){if(c.isFunction(a))return this.each(function(b){var d=c(this);d.text(a.call(this,b,d.text()))});if(typeof a!=="object"&&a!==z)return this.empty().append((this[0]&&this[0].ownerDocument||v).createTextNode(a));return c.text(this)},wrapAll:function(a){if(c.isFunction(a))return this.each(function(d){c(this).wrapAll(a.call(this, +d))});if(this[0]){var b=c(a,this[0].ownerDocument).eq(0).clone(true);this[0].parentNode&&b.insertBefore(this[0]);b.map(function(){for(var d=this;d.firstChild&&d.firstChild.nodeType===1;)d=d.firstChild;return d}).append(this)}return this},wrapInner:function(a){if(c.isFunction(a))return this.each(function(b){c(this).wrapInner(a.call(this,b))});return this.each(function(){var b=c(this),d=b.contents();d.length?d.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){c(this).wrapAll(a)})}, +unwrap:function(){return this.parent().each(function(){c.nodeName(this,"body")||c(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this)});else if(arguments.length){var a= +c(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this.nextSibling)});else if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,c(arguments[0]).toArray());return a}},remove:function(a,b){for(var d=0,e;(e=this[d])!=null;d++)if(!a||c.filter(a,[e]).length){if(!b&&e.nodeType===1){c.cleanData(e.getElementsByTagName("*")); +c.cleanData([e])}e.parentNode&&e.parentNode.removeChild(e)}return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++)for(b.nodeType===1&&c.cleanData(b.getElementsByTagName("*"));b.firstChild;)b.removeChild(b.firstChild);return this},clone:function(a,b){a=a==null?false:a;b=b==null?a:b;return this.map(function(){return c.clone(this,a,b)})},html:function(a){if(a===z)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(xb,""):null;else if(typeof a==="string"&&!Na.test(a)&&(c.support.leadingWhitespace|| +!sa.test(a))&&!R[(Ma.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(La,"<$1></$2>");try{for(var b=0,d=this.length;b<d;b++)if(this[b].nodeType===1){c.cleanData(this[b].getElementsByTagName("*"));this[b].innerHTML=a}}catch(e){this.empty().append(a)}}else c.isFunction(a)?this.each(function(f){var g=c(this);g.html(a.call(this,f,g.html()))}):this.empty().append(a);return this},replaceWith:function(a){if(this[0]&&this[0].parentNode){if(c.isFunction(a))return this.each(function(b){var d=c(this),e=d.html(); +d.replaceWith(a.call(this,b,e))});if(typeof a!=="string")a=c(a).detach();return this.each(function(){var b=this.nextSibling,d=this.parentNode;c(this).remove();b?c(b).before(a):c(d).append(a)})}else return this.pushStack(c(c.isFunction(a)?a():a),"replaceWith",a)},detach:function(a){return this.remove(a,true)},domManip:function(a,b,d){var e,f,g,j=a[0],o=[];if(!c.support.checkClone&&arguments.length===3&&typeof j==="string"&&Oa.test(j))return this.each(function(){c(this).domManip(a,b,d,true)});if(c.isFunction(j))return this.each(function(y){var C= +c(this);a[0]=j.call(this,y,b?C.html():z);C.domManip(a,b,d)});if(this[0]){e=j&&j.parentNode;e=c.support.parentNode&&e&&e.nodeType===11&&e.childNodes.length===this.length?{fragment:e}:c.buildFragment(a,this,o);g=e.fragment;if(f=g.childNodes.length===1?(g=g.firstChild):g.firstChild){b=b&&c.nodeName(f,"tr");for(var n=0,l=this.length,r=l-1;n<l;n++)d.call(b?bb(this[n],f):this[n],e.cacheable||l>1&&n<r?c.clone(g,true,true):g)}o.length&&c.each(o,cb)}return this}});c.buildFragment=function(a,b,d){var e,f,g; +b=b&&b[0]?b[0].ownerDocument||b[0]:v;if(a.length===1&&typeof a[0]==="string"&&a[0].length<512&&b===v&&a[0].charAt(0)==="<"&&!Na.test(a[0])&&(c.support.checkClone||!Oa.test(a[0]))){f=true;if(g=c.fragments[a[0]])if(g!==1)e=g}if(!e){e=b.createDocumentFragment();c.clean(a,b,e,d)}if(f)c.fragments[a[0]]=g?e:1;return{fragment:e,cacheable:f}};c.fragments={};c.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){c.fn[a]=function(d){var e= +[];d=c(d);var f=this.length===1&&this[0].parentNode;if(f&&f.nodeType===11&&f.childNodes.length===1&&d.length===1){d[b](this[0]);return this}else{f=0;for(var g=d.length;f<g;f++){var j=(f>0?this.clone(true):this).get();c(d[f])[b](j);e=e.concat(j)}return this.pushStack(e,a,d.selector)}}});c.extend({clone:function(a,b,d){var e=a.cloneNode(true),f,g,j;if((!c.support.noCloneEvent||!c.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!c.isXMLDoc(a)){ya(a,e);f=da(a);g=da(e);for(j=0;f[j];++j)ya(f[j], +g[j])}if(b){xa(a,e);if(d){f=da(a);g=da(e);for(j=0;f[j];++j)xa(f[j],g[j])}}return e},clean:function(a,b,d,e){b=b||v;if(typeof b.createElement==="undefined")b=b.ownerDocument||b[0]&&b[0].ownerDocument||v;for(var f=[],g=0,j;(j=a[g])!=null;g++){if(typeof j==="number")j+="";if(j){if(typeof j==="string"&&!zb.test(j))j=b.createTextNode(j);else if(typeof j==="string"){j=j.replace(La,"<$1></$2>");var o=(Ma.exec(j)||["",""])[1].toLowerCase(),n=R[o]||R._default,l=n[0],r=b.createElement("div");for(r.innerHTML= +n[1]+j+n[2];l--;)r=r.lastChild;if(!c.support.tbody){l=yb.test(j);o=o==="table"&&!l?r.firstChild&&r.firstChild.childNodes:n[1]==="<table>"&&!l?r.childNodes:[];for(n=o.length-1;n>=0;--n)c.nodeName(o[n],"tbody")&&!o[n].childNodes.length&&o[n].parentNode.removeChild(o[n])}!c.support.leadingWhitespace&&sa.test(j)&&r.insertBefore(b.createTextNode(sa.exec(j)[0]),r.firstChild);j=r.childNodes}if(j.nodeType)f.push(j);else f=c.merge(f,j)}}if(d)for(g=0;f[g];g++)if(e&&c.nodeName(f[g],"script")&&(!f[g].type||f[g].type.toLowerCase()=== +"text/javascript"))e.push(f[g].parentNode?f[g].parentNode.removeChild(f[g]):f[g]);else{f[g].nodeType===1&&f.splice.apply(f,[g+1,0].concat(c.makeArray(f[g].getElementsByTagName("script"))));d.appendChild(f[g])}return f},cleanData:function(a){for(var b,d,e=c.cache,f=c.expando,g=c.event.special,j=c.support.deleteExpando,o=0,n;(n=a[o])!=null;o++)if(!(n.nodeName&&c.noData[n.nodeName.toLowerCase()]))if(d=n[c.expando]){if((b=e[d]&&e[d][f])&&b.events){for(var l in b.events)g[l]?c.event.remove(n,l):c.removeEvent(n, +l,b.handle);if(b.handle)b.handle.elem=null}if(j)delete n[c.expando];else n.removeAttribute&&n.removeAttribute(c.expando);delete e[d]}}});var Pa=/alpha\([^)]*\)/i,Ab=/opacity=([^)]*)/,Bb=/-([a-z])/ig,Cb=/([A-Z])/g,Qa=/^-?\d+(?:px)?$/i,Db=/^-?\d/,Eb={position:"absolute",visibility:"hidden",display:"block"},db=["Left","Right"],eb=["Top","Bottom"],$,T,ha,Fb=function(a,b){return b.toUpperCase()};c.fn.css=function(a,b){if(arguments.length===2&&b===z)return this;return c.access(this,a,b,true,function(d, +e,f){return f!==z?c.style(d,e,f):c.css(d,e)})};c.extend({cssHooks:{opacity:{get:function(a,b){if(b){a=$(a,"opacity","opacity");return a===""?"1":a}else return a.style.opacity}}},cssNumber:{zIndex:true,fontWeight:true,opacity:true,zoom:true,lineHeight:true},cssProps:{"float":c.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,b,d,e){if(!(!a||a.nodeType===3||a.nodeType===8||!a.style)){var f,g=c.camelCase(b),j=a.style,o=c.cssHooks[g];b=c.cssProps[g]||g;if(d!==z){if(!(typeof d==="number"&&isNaN(d)|| +d==null)){if(typeof d==="number"&&!c.cssNumber[g])d+="px";if(!o||!("set"in o)||(d=o.set(a,d))!==z)try{j[b]=d}catch(n){}}}else{if(o&&"get"in o&&(f=o.get(a,false,e))!==z)return f;return j[b]}}},css:function(a,b,d){var e,f=c.camelCase(b),g=c.cssHooks[f];b=c.cssProps[f]||f;if(g&&"get"in g&&(e=g.get(a,true,d))!==z)return e;else if($)return $(a,b,f)},swap:function(a,b,d){var e={};for(var f in b){e[f]=a.style[f];a.style[f]=b[f]}d.call(a);for(f in b)a.style[f]=e[f]},camelCase:function(a){return a.replace(Bb, +Fb)}});c.curCSS=c.css;c.each(["height","width"],function(a,b){c.cssHooks[b]={get:function(d,e,f){var g;if(e){if(d.offsetWidth!==0)g=za(d,b,f);else c.swap(d,Eb,function(){g=za(d,b,f)});if(g<=0){g=$(d,b,b);if(g==="0px"&&ha)g=ha(d,b,b);if(g!=null)return g===""||g==="auto"?"0px":g}if(g<0||g==null){g=d.style[b];return g===""||g==="auto"?"0px":g}return typeof g==="string"?g:g+"px"}},set:function(d,e){if(Qa.test(e)){e=parseFloat(e);if(e>=0)return e+"px"}else return e}}});if(!c.support.opacity)c.cssHooks.opacity= +{get:function(a,b){return Ab.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){a=a.style;a.zoom=1;b=c.isNaN(b)?"":"alpha(opacity="+b*100+")";var d=a.filter||"";a.filter=Pa.test(d)?d.replace(Pa,b):a.filter+" "+b}};if(v.defaultView&&v.defaultView.getComputedStyle)T=function(a,b,d){var e;d=d.replace(Cb,"-$1").toLowerCase();if(!(b=a.ownerDocument.defaultView))return z;if(b=b.getComputedStyle(a,null)){e=b.getPropertyValue(d);if(e=== +""&&!c.contains(a.ownerDocument.documentElement,a))e=c.style(a,d)}return e};if(v.documentElement.currentStyle)ha=function(a,b){var d,e=a.currentStyle&&a.currentStyle[b],f=a.runtimeStyle&&a.runtimeStyle[b],g=a.style;if(!Qa.test(e)&&Db.test(e)){d=g.left;if(f)a.runtimeStyle.left=a.currentStyle.left;g.left=b==="fontSize"?"1em":e||0;e=g.pixelLeft+"px";g.left=d;if(f)a.runtimeStyle.left=f}return e===""?"auto":e};$=T||ha;if(c.expr&&c.expr.filters){c.expr.filters.hidden=function(a){var b=a.offsetHeight;return a.offsetWidth=== +0&&b===0||!c.support.reliableHiddenOffsets&&(a.style.display||c.css(a,"display"))==="none"};c.expr.filters.visible=function(a){return!c.expr.filters.hidden(a)}}var Gb=/%20/g,fb=/\[\]$/,Ra=/\r?\n/g,Hb=/#.*$/,Ib=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,Jb=/^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,Kb=/^(?:GET|HEAD)$/,Lb=/^\/\//,Sa=/\?/,Mb=/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,Nb=/^(?:select|textarea)/i,Ba=/\s+/,Ob=/([?&])_=[^&]*/,Pb=/(^|\-)([a-z])/g, +Qb=function(a,b,d){return b+d.toUpperCase()},Ta=/^([\w\+\.\-]+:)\/\/([^\/?#:]*)(?::(\d+))?/,Ua=c.fn.load,la={},Va={},U,V;try{U=v.location.href}catch(Wb){U=v.createElement("a");U.href="";U=U.href}V=Ta.exec(U.toLowerCase());c.fn.extend({load:function(a,b,d){if(typeof a!=="string"&&Ua)return Ua.apply(this,arguments);else if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var f=a.slice(e,a.length);a=a.slice(0,e)}e="GET";if(b)if(c.isFunction(b)){d=b;b=z}else if(typeof b==="object"){b=c.param(b, +c.ajaxSettings.traditional);e="POST"}var g=this;c.ajax({url:a,type:e,dataType:"html",data:b,complete:function(j,o,n){n=j.responseText;if(j.isResolved()){j.done(function(l){n=l});g.html(f?c("<div>").append(n.replace(Mb,"")).find(f):n)}d&&g.each(d,[n,o,j])}});return this},serialize:function(){return c.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?c.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked|| +Nb.test(this.nodeName)||Jb.test(this.type))}).map(function(a,b){a=c(this).val();return a==null?null:c.isArray(a)?c.map(a,function(d){return{name:b.name,value:d.replace(Ra,"\r\n")}}):{name:b.name,value:a.replace(Ra,"\r\n")}}).get()}});c.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){c.fn[b]=function(d){return this.bind(b,d)}});c.each(["get","post"],function(a,b){c[b]=function(d,e,f,g){if(c.isFunction(e)){g=g||f;f=e;e=z}return c.ajax({type:b,url:d,data:e, +success:f,dataType:g})}});c.extend({getScript:function(a,b){return c.get(a,z,b,"script")},getJSON:function(a,b,d){return c.get(a,b,d,"json")},ajaxSetup:function(a,b){if(b)c.extend(true,a,c.ajaxSettings,b);else{b=a;a=c.extend(true,c.ajaxSettings,b)}for(var d in{context:1,url:1})if(d in b)a[d]=b[d];else if(d in c.ajaxSettings)a[d]=c.ajaxSettings[d];return a},ajaxSettings:{url:U,isLocal:/(?:^file|^widget|\-extension):$/.test(V[1]),global:true,type:"GET",contentType:"application/x-www-form-urlencoded", +processData:true,async:true,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":"*/*"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":I.String,"text html":true,"text json":c.parseJSON,"text xml":c.parseXML}},ajaxPrefilter:Aa(la),ajaxTransport:Aa(Va),ajax:function(a,b){function d(m,p,s,t){if(K!==2){K=2;H&&clearTimeout(H);B=z;y=t||"";G.readyState=m?4:0;var A, +w,E;s=s?gb(e,G,s):z;if(m>=200&&m<300||m===304){if(e.ifModified){if(t=G.getResponseHeader("Last-Modified"))c.lastModified[l]=t;if(t=G.getResponseHeader("Etag"))c.etag[l]=t}if(m===304){p="notmodified";A=true}else try{w=hb(e,s);p="success";A=true}catch(Q){p="parsererror";E=Q}}else{E=p;if(!p||m){p="error";if(m<0)m=0}}G.status=m;G.statusText=p;A?j.resolveWith(f,[w,p,G]):j.rejectWith(f,[G,p,E]);G.statusCode(n);n=z;if(L)g.trigger("ajax"+(A?"Success":"Error"),[G,e,A?w:E]);o.resolveWith(f,[G,p]);if(L){g.trigger("ajaxComplete", +[G,e]);--c.active||c.event.trigger("ajaxStop")}}}if(typeof a==="object"){b=a;a=z}b=b||{};var e=c.ajaxSetup({},b),f=e.context||e,g=f!==e&&(f.nodeType||f instanceof c)?c(f):c.event,j=c.Deferred(),o=c._Deferred(),n=e.statusCode||{},l,r={},y,C,B,H,K=0,L,P,G={readyState:0,setRequestHeader:function(m,p){K||(r[m.toLowerCase().replace(Pb,Qb)]=p);return this},getAllResponseHeaders:function(){return K===2?y:null},getResponseHeader:function(m){var p;if(K===2){if(!C)for(C={};p=Ib.exec(y);)C[p[1].toLowerCase()]= +p[2];p=C[m.toLowerCase()]}return p===z?null:p},overrideMimeType:function(m){if(!K)e.mimeType=m;return this},abort:function(m){m=m||"abort";B&&B.abort(m);d(0,m);return this}};j.promise(G);G.success=G.done;G.error=G.fail;G.complete=o.done;G.statusCode=function(m){if(m){var p;if(K<2)for(p in m)n[p]=[n[p],m[p]];else{p=m[G.status];G.then(p,p)}}return this};e.url=((a||e.url)+"").replace(Hb,"").replace(Lb,V[1]+"//");e.dataTypes=c.trim(e.dataType||"*").toLowerCase().split(Ba);if(!e.crossDomain){a=Ta.exec(e.url.toLowerCase()); +e.crossDomain=!!(a&&(a[1]!=V[1]||a[2]!=V[2]||(a[3]||(a[1]==="http:"?80:443))!=(V[3]||(V[1]==="http:"?80:443))))}if(e.data&&e.processData&&typeof e.data!=="string")e.data=c.param(e.data,e.traditional);ea(la,e,b,G);if(K===2)return false;L=e.global;e.type=e.type.toUpperCase();e.hasContent=!Kb.test(e.type);L&&c.active++===0&&c.event.trigger("ajaxStart");if(!e.hasContent){if(e.data)e.url+=(Sa.test(e.url)?"&":"?")+e.data;l=e.url;if(e.cache===false){a=c.now();var h=e.url.replace(Ob,"$1_="+a);e.url=h+(h=== +e.url?(Sa.test(e.url)?"&":"?")+"_="+a:"")}}if(e.data&&e.hasContent&&e.contentType!==false||b.contentType)r["Content-Type"]=e.contentType;if(e.ifModified){l=l||e.url;if(c.lastModified[l])r["If-Modified-Since"]=c.lastModified[l];if(c.etag[l])r["If-None-Match"]=c.etag[l]}r.Accept=e.dataTypes[0]&&e.accepts[e.dataTypes[0]]?e.accepts[e.dataTypes[0]]+(e.dataTypes[0]!=="*"?", */*; q=0.01":""):e.accepts["*"];for(P in e.headers)G.setRequestHeader(P,e.headers[P]);if(e.beforeSend&&(e.beforeSend.call(f,G,e)=== +false||K===2)){G.abort();return false}for(P in{success:1,error:1,complete:1})G[P](e[P]);if(B=ea(Va,e,b,G)){G.readyState=1;L&&g.trigger("ajaxSend",[G,e]);if(e.async&&e.timeout>0)H=setTimeout(function(){G.abort("timeout")},e.timeout);try{K=1;B.send(r,d)}catch(k){status<2?d(-1,k):c.error(k)}}else d(-1,"No Transport");return G},param:function(a,b){var d=[],e=function(g,j){j=c.isFunction(j)?j():j;d[d.length]=encodeURIComponent(g)+"="+encodeURIComponent(j)};if(b===z)b=c.ajaxSettings.traditional;if(c.isArray(a)|| +a.jquery&&!c.isPlainObject(a))c.each(a,function(){e(this.name,this.value)});else for(var f in a)ma(f,a[f],b,e);return d.join("&").replace(Gb,"+")}});c.extend({active:0,lastModified:{},etag:{}});var Rb=c.now(),ia=/(\=)\?(&|$)|()\?\?()/i;c.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return c.expando+"_"+Rb++}});c.ajaxPrefilter("json jsonp",function(a,b,d){var e=typeof a.data==="string";if(a.dataTypes[0]==="jsonp"||b.jsonpCallback||b.jsonp!=null||a.jsonp!==false&&(ia.test(a.url)||e&&ia.test(a.data))){var f, +g=a.jsonpCallback=c.isFunction(a.jsonpCallback)?a.jsonpCallback():a.jsonpCallback,j=I[g];b=a.url;var o=a.data,n="$1"+g+"$2",l=function(){I[g]=j;f&&c.isFunction(j)&&I[g](f[0])};if(a.jsonp!==false){b=b.replace(ia,n);if(a.url===b){if(e)o=o.replace(ia,n);if(a.data===o)b+=(/\?/.test(b)?"&":"?")+a.jsonp+"="+g}}a.url=b;a.data=o;I[g]=function(r){f=[r]};d.then(l,l);a.converters["script json"]=function(){f||c.error(g+" was not called");return f[0]};a.dataTypes[0]="json";return"script"}});c.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"}, +contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){c.globalEval(a);return a}}});c.ajaxPrefilter("script",function(a){if(a.cache===z)a.cache=false;if(a.crossDomain){a.type="GET";a.global=false}});c.ajaxTransport("script",function(a){if(a.crossDomain){var b,d=v.head||v.getElementsByTagName("head")[0]||v.documentElement;return{send:function(e,f){b=v.createElement("script");b.async="async";if(a.scriptCharset)b.charset=a.scriptCharset;b.src=a.url;b.onload=b.onreadystatechange= +function(g,j){if(!b.readyState||/loaded|complete/.test(b.readyState)){b.onload=b.onreadystatechange=null;d&&b.parentNode&&d.removeChild(b);b=z;j||f(200,"success")}};d.insertBefore(b,d.firstChild)},abort:function(){b&&b.onload(0,1)}}}});var Sb=c.now(),X;c.ajaxSettings.xhr=I.ActiveXObject?function(){return!this.isLocal&&Ca()||jb()}:Ca;T=c.ajaxSettings.xhr();c.support.ajax=!!T;c.support.cors=T&&"withCredentials"in T;T=z;c.support.ajax&&c.ajaxTransport(function(a){if(!a.crossDomain||c.support.cors){var b; +return{send:function(d,e){var f=a.xhr(),g,j;a.username?f.open(a.type,a.url,a.async,a.username,a.password):f.open(a.type,a.url,a.async);if(a.xhrFields)for(j in a.xhrFields)f[j]=a.xhrFields[j];a.mimeType&&f.overrideMimeType&&f.overrideMimeType(a.mimeType);if(!(a.crossDomain&&!a.hasContent)&&!d["X-Requested-With"])d["X-Requested-With"]="XMLHttpRequest";try{for(j in d)f.setRequestHeader(j,d[j])}catch(o){}f.send(a.hasContent&&a.data||null);b=function(n,l){var r,y,C,B,H;try{if(b&&(l||f.readyState===4)){b= +z;if(g){f.onreadystatechange=c.noop;delete X[g]}if(l)f.readyState!==4&&f.abort();else{r=f.status;C=f.getAllResponseHeaders();B={};if((H=f.responseXML)&&H.documentElement)B.xml=H;B.text=f.responseText;try{y=f.statusText}catch(K){y=""}if(!r&&a.isLocal&&!a.crossDomain)r=B.text?200:404;else if(r===1223)r=204}}}catch(L){l||e(-1,L)}B&&e(r,y,B,C)};if(!a.async||f.readyState===4)b();else{if(!X){X={};ib()}g=Sb++;f.onreadystatechange=X[g]=b}},abort:function(){b&&b(0,1)}}}});var na={},Tb=/^(?:toggle|show|hide)$/, +Ub=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,ja,Da=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];c.fn.extend({show:function(a,b,d){if(a||a===0)return this.animate(Y("show",3),a,b,d);else{d=0;for(var e=this.length;d<e;d++){a=this[d];b=a.style.display;if(!c._data(a,"olddisplay")&&b==="none")b=a.style.display="";b===""&&c.css(a,"display")==="none"&&c._data(a,"olddisplay",Ea(a.nodeName))}for(d=0;d<e;d++){a=this[d]; +b=a.style.display;if(b===""||b==="none")a.style.display=c._data(a,"olddisplay")||""}return this}},hide:function(a,b,d){if(a||a===0)return this.animate(Y("hide",3),a,b,d);else{a=0;for(b=this.length;a<b;a++){d=c.css(this[a],"display");d!=="none"&&!c._data(this[a],"olddisplay")&&c._data(this[a],"olddisplay",d)}for(a=0;a<b;a++)this[a].style.display="none";return this}},_toggle:c.fn.toggle,toggle:function(a,b,d){var e=typeof a==="boolean";if(c.isFunction(a)&&c.isFunction(b))this._toggle.apply(this,arguments); +else a==null||e?this.each(function(){var f=e?a:c(this).is(":hidden");c(this)[f?"show":"hide"]()}):this.animate(Y("toggle",3),a,b,d);return this},fadeTo:function(a,b,d,e){return this.filter(":hidden").css("opacity",0).show().end().animate({opacity:b},a,d,e)},animate:function(a,b,d,e){var f=c.speed(b,d,e);if(c.isEmptyObject(a))return this.each(f.complete);return this[f.queue===false?"each":"queue"](function(){var g=c.extend({},f),j,o=this.nodeType===1,n=o&&c(this).is(":hidden"),l=this;for(j in a){var r= +c.camelCase(j);if(j!==r){a[r]=a[j];delete a[j];j=r}if(a[j]==="hide"&&n||a[j]==="show"&&!n)return g.complete.call(this);if(o&&(j==="height"||j==="width")){g.overflow=[this.style.overflow,this.style.overflowX,this.style.overflowY];if(c.css(this,"display")==="inline"&&c.css(this,"float")==="none")if(c.support.inlineBlockNeedsLayout)if(Ea(this.nodeName)==="inline")this.style.display="inline-block";else{this.style.display="inline";this.style.zoom=1}else this.style.display="inline-block"}if(c.isArray(a[j])){(g.specialEasing= +g.specialEasing||{})[j]=a[j][1];a[j]=a[j][0]}}if(g.overflow!=null)this.style.overflow="hidden";g.curAnim=c.extend({},a);c.each(a,function(y,C){var B=new c.fx(l,g,y);if(Tb.test(C))B[C==="toggle"?n?"show":"hide":C](a);else{var H=Ub.exec(C),K=B.cur();if(H){C=parseFloat(H[2]);var L=H[3]||(c.cssNumber[y]?"":"px");if(L!=="px"){c.style(l,y,(C||1)+L);K=(C||1)/B.cur()*K;c.style(l,y,K+L)}if(H[1])C=(H[1]==="-="?-1:1)*C+K;B.custom(K,C,L)}else B.custom(K,C,"")}});return true})},stop:function(a,b){var d=c.timers; +a&&this.queue([]);this.each(function(){for(var e=d.length-1;e>=0;e--)if(d[e].elem===this){b&&d[e](true);d.splice(e,1)}});b||this.dequeue();return this}});c.each({slideDown:Y("show",1),slideUp:Y("hide",1),slideToggle:Y("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){c.fn[a]=function(d,e,f){return this.animate(b,d,e,f)}});c.extend({speed:function(a,b,d){var e=a&&typeof a==="object"?c.extend({},a):{complete:d||!d&&b||c.isFunction(a)&&a,duration:a, +easing:d&&b||b&&!c.isFunction(b)&&b};e.duration=c.fx.off?0:typeof e.duration==="number"?e.duration:e.duration in c.fx.speeds?c.fx.speeds[e.duration]:c.fx.speeds._default;e.old=e.complete;e.complete=function(){e.queue!==false&&c(this).dequeue();c.isFunction(e.old)&&e.old.call(this)};return e},easing:{linear:function(a,b,d,e){return d+e*a},swing:function(a,b,d,e){return(-Math.cos(a*Math.PI)/2+0.5)*e+d}},timers:[],fx:function(a,b,d){this.options=b;this.elem=a;this.prop=d;if(!b.orig)b.orig={}}});c.fx.prototype= +{update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this);(c.fx.step[this.prop]||c.fx.step._default)(this)},cur:function(){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];var a,b=c.css(this.elem,this.prop);return isNaN(a=parseFloat(b))?!b||b==="auto"?0:b:a},custom:function(a,b,d){function e(j){return f.step(j)}var f=this,g=c.fx;this.startTime=c.now();this.start=a;this.end=b;this.unit=d||this.unit||(c.cssNumber[this.prop]? +"":"px");this.now=this.start;this.pos=this.state=0;e.elem=this.elem;if(e()&&c.timers.push(e)&&!ja)ja=setInterval(g.tick,g.interval)},show:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.show=true;this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur());c(this.elem).show()},hide:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.hide=true;this.custom(this.cur(),0)},step:function(a){var b=c.now(),d=true;if(a||b>=this.options.duration+ +this.startTime){this.now=this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;for(var e in this.options.curAnim)if(this.options.curAnim[e]!==true)d=false;if(d){if(this.options.overflow!=null&&!c.support.shrinkWrapBlocks){var f=this.elem,g=this.options;c.each(["","X","Y"],function(o,n){f.style["overflow"+n]=g.overflow[o]})}this.options.hide&&c(this.elem).hide();if(this.options.hide||this.options.show)for(var j in this.options.curAnim)c.style(this.elem,j,this.options.orig[j]); +this.options.complete.call(this.elem)}return false}else{a=b-this.startTime;this.state=a/this.options.duration;b=this.options.easing||(c.easing.swing?"swing":"linear");this.pos=c.easing[this.options.specialEasing&&this.options.specialEasing[this.prop]||b](this.state,a,0,1,this.options.duration);this.now=this.start+(this.end-this.start)*this.pos;this.update()}return true}};c.extend(c.fx,{tick:function(){for(var a=c.timers,b=0;b<a.length;b++)a[b]()||a.splice(b--,1);a.length||c.fx.stop()},interval:13, +stop:function(){clearInterval(ja);ja=null},speeds:{slow:600,fast:200,_default:400},step:{opacity:function(a){c.style(a.elem,"opacity",a.now)},_default:function(a){if(a.elem.style&&a.elem.style[a.prop]!=null)a.elem.style[a.prop]=(a.prop==="width"||a.prop==="height"?Math.max(0,a.now):a.now)+a.unit;else a.elem[a.prop]=a.now}}});if(c.expr&&c.expr.filters)c.expr.filters.animated=function(a){return c.grep(c.timers,function(b){return a===b.elem}).length};var Vb=/^t(?:able|d|h)$/i,Wa=/^(?:body|html)$/i;c.fn.offset= +"getBoundingClientRect"in v.documentElement?function(a){var b=this[0],d;if(a)return this.each(function(j){c.offset.setOffset(this,a,j)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return c.offset.bodyOffset(b);try{d=b.getBoundingClientRect()}catch(e){}var f=b.ownerDocument,g=f.documentElement;if(!d||!c.contains(g,b))return d?{top:d.top,left:d.left}:{top:0,left:0};b=f.body;f=oa(f);return{top:d.top+(f.pageYOffset||c.support.boxModel&&g.scrollTop||b.scrollTop)-(g.clientTop||b.clientTop|| +0),left:d.left+(f.pageXOffset||c.support.boxModel&&g.scrollLeft||b.scrollLeft)-(g.clientLeft||b.clientLeft||0)}}:function(a){var b=this[0];if(a)return this.each(function(r){c.offset.setOffset(this,a,r)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return c.offset.bodyOffset(b);c.offset.initialize();var d,e=b.offsetParent,f=b,g=b.ownerDocument,j=g.documentElement,o=g.body;d=(g=g.defaultView)?g.getComputedStyle(b,null):b.currentStyle;for(var n=b.offsetTop,l=b.offsetLeft;(b=b.parentNode)&& +b!==o&&b!==j;){if(c.offset.supportsFixedPosition&&d.position==="fixed")break;d=g?g.getComputedStyle(b,null):b.currentStyle;n-=b.scrollTop;l-=b.scrollLeft;if(b===e){n+=b.offsetTop;l+=b.offsetLeft;if(c.offset.doesNotAddBorder&&!(c.offset.doesAddBorderForTableAndCells&&Vb.test(b.nodeName))){n+=parseFloat(d.borderTopWidth)||0;l+=parseFloat(d.borderLeftWidth)||0}f=e;e=b.offsetParent}if(c.offset.subtractsBorderForOverflowNotVisible&&d.overflow!=="visible"){n+=parseFloat(d.borderTopWidth)||0;l+=parseFloat(d.borderLeftWidth)|| +0}d=d}if(d.position==="relative"||d.position==="static"){n+=o.offsetTop;l+=o.offsetLeft}if(c.offset.supportsFixedPosition&&d.position==="fixed"){n+=Math.max(j.scrollTop,o.scrollTop);l+=Math.max(j.scrollLeft,o.scrollLeft)}return{top:n,left:l}};c.offset={initialize:function(){var a=v.body,b=v.createElement("div"),d,e,f,g=parseFloat(c.css(a,"marginTop"))||0;c.extend(b.style,{position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"});b.innerHTML="<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>"; +a.insertBefore(b,a.firstChild);d=b.firstChild;e=d.firstChild;f=d.nextSibling.firstChild.firstChild;this.doesNotAddBorder=e.offsetTop!==5;this.doesAddBorderForTableAndCells=f.offsetTop===5;e.style.position="fixed";e.style.top="20px";this.supportsFixedPosition=e.offsetTop===20||e.offsetTop===15;e.style.position=e.style.top="";d.style.overflow="hidden";d.style.position="relative";this.subtractsBorderForOverflowNotVisible=e.offsetTop===-5;this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==g;a.removeChild(b); +c.offset.initialize=c.noop},bodyOffset:function(a){var b=a.offsetTop,d=a.offsetLeft;c.offset.initialize();if(c.offset.doesNotIncludeMarginInBodyOffset){b+=parseFloat(c.css(a,"marginTop"))||0;d+=parseFloat(c.css(a,"marginLeft"))||0}return{top:b,left:d}},setOffset:function(a,b,d){var e=c.css(a,"position");if(e==="static")a.style.position="relative";var f=c(a),g=f.offset(),j=c.css(a,"top"),o=c.css(a,"left"),n=e==="absolute"&&c.inArray("auto",[j,o])>-1;e={};var l={};if(n)l=f.position();j=n?l.top:parseInt(j, +10)||0;o=n?l.left:parseInt(o,10)||0;if(c.isFunction(b))b=b.call(a,d,g);if(b.top!=null)e.top=b.top-g.top+j;if(b.left!=null)e.left=b.left-g.left+o;"using"in b?b.using.call(a,e):f.css(e)}};c.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),d=this.offset(),e=Wa.test(b[0].nodeName)?{top:0,left:0}:b.offset();d.top-=parseFloat(c.css(a,"marginTop"))||0;d.left-=parseFloat(c.css(a,"marginLeft"))||0;e.top+=parseFloat(c.css(b[0],"borderTopWidth"))||0;e.left+=parseFloat(c.css(b[0], +"borderLeftWidth"))||0;return{top:d.top-e.top,left:d.left-e.left}},offsetParent:function(){return this.map(function(){for(var a=this.offsetParent||v.body;a&&!Wa.test(a.nodeName)&&c.css(a,"position")==="static";)a=a.offsetParent;return a})}});c.each(["Left","Top"],function(a,b){var d="scroll"+b;c.fn[d]=function(e){var f=this[0],g;if(!f)return null;if(e!==z)return this.each(function(){if(g=oa(this))g.scrollTo(!a?e:c(g).scrollLeft(),a?e:c(g).scrollTop());else this[d]=e});else return(g=oa(f))?"pageXOffset"in +g?g[a?"pageYOffset":"pageXOffset"]:c.support.boxModel&&g.document.documentElement[d]||g.document.body[d]:f[d]}});c.each(["Height","Width"],function(a,b){var d=b.toLowerCase();c.fn["inner"+b]=function(){return this[0]?parseFloat(c.css(this[0],d,"padding")):null};c.fn["outer"+b]=function(e){return this[0]?parseFloat(c.css(this[0],d,e?"margin":"border")):null};c.fn[d]=function(e){var f=this[0];if(!f)return e==null?null:this;if(c.isFunction(e))return this.each(function(j){var o=c(this);o[d](e.call(this, +j,o[d]()))});if(c.isWindow(f)){var g=f.document.documentElement["client"+b];return f.document.compatMode==="CSS1Compat"&&g||f.document.body["client"+b]||g}else if(f.nodeType===9)return Math.max(f.documentElement["client"+b],f.body["scroll"+b],f.documentElement["scroll"+b],f.body["offset"+b],f.documentElement["offset"+b]);else if(e===z){f=c.css(f,d);g=parseFloat(f);return c.isNaN(g)?f:g}else return this.css(d,typeof e==="string"?e:e+"px")}});I.jQuery=I.$=c})(window); +; +steal.end(); +steal.plugins("jquery/lang").then(function(f){f.String.rsplit=function(a,e){for(var b=e.exec(a),c=[],d;b!==null;){d=b.index;if(d!==0){c.push(a.substring(0,d));a=a.slice(d)}c.push(b[0]);a=a.slice(b[0].length);b=e.exec(a)}a!==""&&c.push(a);return c}}); +; +steal.end(); +steal.plugins("jquery").then(function(j){var e={undHash:/_|-/,colons:/::/,words:/([A-Z]+)([A-Z][a-z])/g,lowerUpper:/([a-z\d])([A-Z])/g,dash:/([a-z\d])([A-Z])/g,replacer:/\{([^\}]+)\}/g},i=function(a,b,f){var c=b||window;a=a?a.split(/\./):[];for(var d=0;d<a.length-1&&c;d++)c=c[a[d]]||f&&(c[a[d]]={});if(a.length==0)return b;b=c[a[d]]||f&&(c[a[d]]={});f===false&&delete c[a[d]];return b},h=j.String={getObject:i,strip:function(a){return a.replace(/^\s+/,"").replace(/\s+$/,"")},capitalize:function(a){return a.charAt(0).toUpperCase()+ +a.substr(1)},endsWith:function(a,b){var f=a.length-b.length;return f>=0&&a.lastIndexOf(b)===f},camelize:function(a){a=a.split(e.undHash);var b=1;for(a[0]=a[0].charAt(0).toLowerCase()+a[0].substr(1);b<a.length;b++)a[b]=h.capitalize(a[b]);return a.join("")},classize:function(a){a=a.split(e.undHash);for(var b=0;b<a.length;b++)a[b]=h.capitalize(a[b]);return a.join("")},niceName:function(a){a=a.split(e.undHash);for(var b=0;b<a.length;b++)a[b]=h.capitalize(a[b]);return a.join(" ")},underscore:function(a){return a.replace(e.colons, +"/").replace(e.words,"$1_$2").replace(e.lowerUpper,"$1_$2").replace(e.dash,"_").toLowerCase()},sub:function(a,b,f){var c=[];c.push(a.replace(e.replacer,function(d,g){d=i(g,b,!f);g=typeof d;if((g==="object"||g==="function")&&g!==null){c.push(d);return""}else return""+d}));return c.length<=1?c[0]:c}}}); +; +steal.end(); +steal.plugins("jquery/view").then(function(){function r(a,b,d,c){c={data:c||(b?b.data:{}),_wrap:b?b._wrap:null,tmpl:null,parent:b||null,nodes:[],calls:H,nest:I,wrap:J,html:K,update:L};a&&jQuery.extend(c,a,{nodes:[],parent:b});if(d){c.tmpl=d;c._ctnt=c._ctnt||c.tmpl(jQuery,c);c.key=++s;(x.length?t:l)[s]=c}return c}function u(a,b,d){var c;d=d?jQuery.map(d,function(f){return typeof f==="string"?a.key?f.replace(/(<\w+)(?=[\s>])(?![^>]*_tmplitem)([^>]*)/g,"$1 "+p+'="'+a.key+'" $2'):f:u(f,a,f._ctnt)}):a; +if(b)return d;d=d.join("");d.replace(/^\s*([^<\s][^<]*)?(<[\w\W]+>)([^>]*[^>\s])?\s*$/,function(f,e,g,i){c=jQuery(g).get();B(c);if(e)c=y(e).concat(c);if(i)c=c.concat(y(i))});return c?c:y(d)}function y(a){var b=document.createElement("div");b.innerHTML=a;return jQuery.makeArray(b.childNodes)}function C(a){return new Function("jQuery","$item","var $=jQuery,call,_=[],$data=$item.data;with($data){_.push('"+jQuery.trim(a).replace(/([\\'])/g,"\\$1").replace(/[\r\t\n]/g," ").replace(/\$\{([^\}]*)\}/g,"{{= $1}}").replace(/\{\{(\/?)(\w+|.)(?:\(((?:[^\}]|\}(?!\}))*?)?\))?(?:\s+(.*?)?)?(\(((?:[^\}]|\}(?!\}))*?)\))?\s*\}\}/g, +function(b,d,c,f,e,g,i){b=jQuery.tmpl.tag[c];if(!b)throw"Template command not found: "+c;c=b._default||[];if(g&&!/\w$/.test(e)){e+=g;g=""}if(e){e=z(e);i=i?","+z(i)+")":g?")":"";i=g?e.indexOf(".")>-1?e+g:"("+e+").call($item"+i:e;g=g?i:"(typeof("+e+")==='function'?("+e+").call($item):("+e+"))"}else g=i=c.$1||"null";f=z(f);return"');"+b[d?"close":"open"].split("$notnull_1").join(e?"typeof("+e+")!=='undefined' && ("+e+")!=null":"true").split("$1a").join(g).split("$1").join(i).split("$2").join(f?f.replace(/\s*([^\(]+)\s*(\((.*?)\))?/g, +function(o,m,v,k){return(k=k?","+k+")":v?")":"")?"("+m+").call($item"+k:o}):c.$2||"")+"_.push('"})+"');}return _;")}function D(a,b){a._wrap=u(a,true,jQuery.isArray(b)?b:[E.test(b)?b:jQuery(b).html()]).join("")}function z(a){return a?a.replace(/\\'/g,"'").replace(/\\\\/g,"\\"):null}function M(a){var b=document.createElement("div");b.appendChild(a.cloneNode(true));return b.innerHTML}function B(a){function b(m){function v(A){A+=d;h=e[A]=e[A]||r(h,l[h.parent.key+d]||h.parent,null,true)}var k,j=m,h,q; +if(q=m.getAttribute(p)){for(;j.parentNode&&(j=j.parentNode).nodeType===1&&!(k=j.getAttribute(p)););if(k!==q){j=j.parentNode?j.nodeType===11?0:j.getAttribute(p)||0:0;if(!(h=l[q])){h=t[q];h=r(h,l[j]||t[j],null,true);h.key=++s;l[s]=h}n&&v(q)}m.removeAttribute(p)}else if(n&&(h=jQuery.data(m,"tmplItem"))){v(h.key);l[h.key]=h;j=(j=jQuery.data(m.parentNode,"tmplItem"))?j.key:0}if(h){for(k=h;k&&k.key!=j;){k.nodes.push(m);k=k.parent}delete h._ctnt;delete h._wrap;jQuery.data(m,"tmplItem",h)}}var d="_"+n,c, +f,e={},g,i,o;g=0;for(i=a.length;g<i;g++)if((c=a[g]).nodeType===1){f=c.getElementsByTagName("*");for(o=f.length-1;o>=0;o--)b(f[o]);b(c)}}function H(a,b,d,c){if(!a)return x.pop();x.push({_:a,tmpl:b,item:this,data:d,options:c})}function I(a,b,d){return jQuery.tmpl(jQuery.template(a),b,d,this)}function J(a,b){var d=a.options||{};d.wrapped=b;return jQuery.tmpl(jQuery.template(a.tmpl),a.data,d,a.item)}function K(a,b){var d=this._wrap;return jQuery.map(jQuery(jQuery.isArray(d)?d.join(""):d).filter(a||"*"), +function(c){return b?c.innerText||c.textContent:c.outerHTML||M(c)})}function L(){var a=this.nodes;jQuery.tmpl(null,null,null,this).insertBefore(a[0]);jQuery(a).remove()}var F=jQuery.fn.domManip,p="_tmplitem",E=/^[^<]*(<[\w\W]+>)[^>]*$|\{\{\! /,l={},t={},w,G={key:0,data:{}},s=0,n=0,x=[];jQuery.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){jQuery.fn[a]=function(d){var c=[];d=jQuery(d);var f,e,g;f=this.length===1&&this[0].parentNode; +w=l||{};if(f&&f.nodeType===11&&f.childNodes.length===1&&d.length===1){d[b](this[0]);c=this}else{e=0;for(g=d.length;e<g;e++){n=e;f=(e>0?this.clone(true):this).get();jQuery.fn[b].apply(jQuery(d[e]),f);c=c.concat(f)}n=0;c=this.pushStack(c,a,d.selector)}d=w;w=null;jQuery.tmpl.complete(d);return c}});jQuery.fn.extend({tmpl:function(a,b,d){return jQuery.tmpl(this[0],a,b,d)},tmplItem:function(){return jQuery.tmplItem(this[0])},template:function(a){return jQuery.template(a,this[0])},domManip:function(a,b, +d){if(a[0]&&a[0].nodeType){for(var c=jQuery.makeArray(arguments),f=a.length,e=0,g;e<f&&!(g=jQuery.data(a[e++],"tmplItem")););if(f>1)c[0]=[jQuery.makeArray(a)];if(g&&n)c[2]=function(i){jQuery.tmpl.afterManip(this,i,d)};F.apply(this,c)}else F.apply(this,arguments);n=0;w||jQuery.tmpl.complete(l);return this}});jQuery.extend({tmpl:function(a,b,d,c){var f=!c;if(f){c=G;a=jQuery.template[a]||jQuery.template(null,a);t={}}else if(!a){a=c.tmpl;l[c.key]=c;c.nodes=[];c.wrapped&&D(c,c.wrapped);return jQuery(u(c, +null,c.tmpl(jQuery,c)))}if(!a)return[];if(typeof b==="function")b=b.call(c||{});d&&d.wrapped&&D(d,d.wrapped);b=jQuery.isArray(b)?jQuery.map(b,function(e){return e?r(d,c,a,e):null}):[r(d,c,a,b)];return f?jQuery(u(c,null,b)):b},tmplItem:function(a){var b;if(a instanceof jQuery)a=a[0];for(;a&&a.nodeType===1&&!(b=jQuery.data(a,"tmplItem"))&&(a=a.parentNode););return b||G},template:function(a,b){if(b){if(typeof b==="string")b=C(b);else if(b instanceof jQuery)b=b[0]||{};if(b.nodeType)b=jQuery.data(b,"tmpl")|| +jQuery.data(b,"tmpl",C(b.innerHTML));return typeof a==="string"?(jQuery.template[a]=b):b}return a?typeof a!=="string"?jQuery.template(null,a):jQuery.template[a]||jQuery.template(null,E.test(a)?a:jQuery(a)):null},encode:function(a){return(""+a).split("<").join("<").split(">").join(">").split('"').join(""").split("'").join("'")}});jQuery.extend(jQuery.tmpl,{tag:{tmpl:{_default:{$2:"null"},open:"if($notnull_1){_=_.concat($item.nest($1,$2));}"},wrap:{_default:{$2:"null"},open:"$item.calls(_,$1,$2);_=[];", +close:"call=$item.calls();_=call._.concat($item.wrap(call,_));"},each:{_default:{$2:"$index, $value"},open:"if($notnull_1){$.each($1a,function($2){with(this){",close:"}});}"},"if":{open:"if(($notnull_1) && $1a){",close:"}"},"else":{_default:{$1:"true"},open:"}else if(($notnull_1) && $1a){"},html:{open:"if($notnull_1){_.push($1a);}"},"=":{_default:{$1:"$data"},open:"if($notnull_1){_.push($.encode($1a));}"},"!":{open:""}},complete:function(){l={}},afterManip:function(a,b,d){var c=b.nodeType===11?jQuery.makeArray(b.childNodes): +b.nodeType===1?[b]:[];d.call(a,b);B(c);n++}});$.View.register({suffix:"tmpl",renderer:function(a,b){return function(d){return $.tmpl(b,d)}},script:function(a,b){return"function(data){return ("+$.template(null,b)+").call(jQuery, jQuery, {data: data}).join(''); }"}});jQuery.View.ext=".tmpl"}); +; +steal.end(); +$.View.preload('jquery_view_test_compression_views_relative_ejs',jQuery.EJS(function(_CONTEXT,_VIEW) { try { with(_VIEW) { with (_CONTEXT) {var ___v1ew = [];___v1ew.push("<h1>Relative</h1>");; return ___v1ew.join('');}}}catch(e){e.lineNumber=null;throw e;} }));; +steal.end(); +$.View.preload('jquery_view_test_compression_views_absolute_ejs',jQuery.EJS(function(_CONTEXT,_VIEW) { try { with(_VIEW) { with (_CONTEXT) {var ___v1ew = [];___v1ew.push("<h1>Absolute</h1>");; return ___v1ew.join('');}}}catch(e){e.lineNumber=null;throw e;} }));; +steal.end(); +$.View.preload('jquery_view_test_compression_views_tmplTest_tmpl',function(data){return ( +function anonymous(jQuery, $item) { + var $ = jQuery, call, _ = [], $data = $item.data; + with ($data) { + _.push("<h1>"); + if (typeof (message) !== "undefined" && (message) != null) { + _.push($.encode((typeof (message) === "function" ? (message).call($item) : (message)))); + } + _.push("</h1>"); + } + return _; +} +).call(jQuery, jQuery, {data: data}).join(''); });; +steal.end() \ No newline at end of file diff --git a/browserid/static/jquery/view/test/qunit/deferred.ejs b/browserid/static/jquery/view/test/qunit/deferred.ejs new file mode 100644 index 0000000000000000000000000000000000000000..75a4bdd5a4398ce8ffef2075ba6584cc6c5e6cc7 --- /dev/null +++ b/browserid/static/jquery/view/test/qunit/deferred.ejs @@ -0,0 +1 @@ +<%= foo %> \ No newline at end of file diff --git a/browserid/static/jquery/view/test/qunit/deferreds.ejs b/browserid/static/jquery/view/test/qunit/deferreds.ejs new file mode 100644 index 0000000000000000000000000000000000000000..884157f511871f7b6df55f048b551f584b91841a --- /dev/null +++ b/browserid/static/jquery/view/test/qunit/deferreds.ejs @@ -0,0 +1 @@ +<%= foo %> and <%= bar %> \ No newline at end of file diff --git a/browserid/static/jquery/view/test/qunit/hookup.ejs b/browserid/static/jquery/view/test/qunit/hookup.ejs index 4b49fd2678c719330525b6bb0ffb14af1656329d..ab7cb6877240c0248453e62919cbe9d8b1c3eaed 100644 --- a/browserid/static/jquery/view/test/qunit/hookup.ejs +++ b/browserid/static/jquery/view/test/qunit/hookup.ejs @@ -1 +1 @@ -<%= function(){} %> \ No newline at end of file +<div <%= function(){} %> /> \ No newline at end of file diff --git a/browserid/static/jquery/view/test/qunit/view_test.js b/browserid/static/jquery/view/test/qunit/view_test.js index 918e573010bd0ece2964cc2d85171038294c8735..9ce55af2047a160d88807cd2d7ec04b74bcc95f4 100644 --- a/browserid/static/jquery/view/test/qunit/view_test.js +++ b/browserid/static/jquery/view/test/qunit/view_test.js @@ -1,6 +1,22 @@ -module("jquery/view") -test("multipel template types work", function(){ +module("jquery/view"); + +test("Ajax transport", function(){ + var order = 0; + $.ajax({ + url: "//jquery/view/test/qunit/template.ejs", + dataType : "view", + async : false + }).done(function(view){ + equals(++order,1, "called synchronously"); + equals(view({message: "hi"}).indexOf("<h3>hi</h3>"), 0, "renders stuff!") + }); + + equals(++order,2, "called synchronously"); +}) + + +test("multiple template types work", function(){ $.each(["micro","ejs","jaml", "tmpl"], function(){ $("#qunit-test-area").html(""); @@ -55,7 +71,7 @@ test("caching works", function(){ var lap2 = new Date - first , lap1 = first-startT; - ok(lap2 < lap1, "faster this time "+(lap1 - lap2) ) + ok( lap1 - lap2 > -20, "faster this time "+(lap1 - lap2) ) start(); $("#qunit-test-area").html(""); @@ -77,4 +93,58 @@ test("inline templates other than 'tmpl' like ejs", function(){ $("#qunit-test-area").html('test_ejs', {name: 'Henry'}); equal( $("#new_name").text(), 'Henry'); $("#qunit-test-area").html(""); -}) +}); + +test("object of deferreds", function(){ + var foo = $.Deferred(), + bar = $.Deferred(); + stop(1000); + $.View("//jquery/view/test/qunit/deferreds.ejs",{ + foo : foo.promise(), + bar : bar + }).then(function(result){ + equals(result, "FOO and BAR"); + start(); + }); + setTimeout(function(){ + foo.resolve("FOO"); + },100); + bar.resolve("BAR"); + +}); + +test("deferred", function(){ + var foo = $.Deferred(); + stop(); + $.View("//jquery/view/test/qunit/deferred.ejs",foo).then(function(result){ + equals(result, "FOO"); + start(); + }); + setTimeout(function(){ + foo.resolve({ + foo: "FOO" + }); + },100); + +}); + + +test("modifier with a deferred", function(){ + $("#qunit-test-area").html(""); + stop(); + + var foo = $.Deferred(); + $("#qunit-test-area").html("//jquery/view/test/qunit/deferred.ejs", foo ); + setTimeout(function(){ + foo.resolve({ + foo: "FOO" + }); + start(); + equals($("#qunit-test-area").html(), "FOO", "worked!"); + },100); + +}); + +/*test("bad url", function(){ + $.View("//asfdsaf/sadf.ejs") +});*/ diff --git a/browserid/static/jquery/view/tmpl/tmpl.js b/browserid/static/jquery/view/tmpl/tmpl.js index 067882258b8056ac09f1e800aa83048f8f608303..fd6361ea8914b5fa65e9efac62efafdd5ae05984 100644 --- a/browserid/static/jquery/view/tmpl/tmpl.js +++ b/browserid/static/jquery/view/tmpl/tmpl.js @@ -508,10 +508,11 @@ steal.plugins('jquery/view').then(function(){ $.View.register({ suffix : "tmpl", renderer: function( id, text ) { + var tmpl = $.template( null, text ); return function(data){ - return $.tmpl( text, data); + return tmpl.call($, $, {data: data}).join(''); //$(text).tmpl(data);//jQuery.render( text, data ); - } + }; }, script: function( id, str ) { var tmpl = $.template( null, str ); diff --git a/browserid/static/jquery/view/view.js b/browserid/static/jquery/view/view.js index 8a0d071874e90c48c499ec9e6b17fa7f13cb115c..c4cf77524b9232631257d49d5c883ce7a396feda 100644 --- a/browserid/static/jquery/view/view.js +++ b/browserid/static/jquery/view/view.js @@ -20,9 +20,10 @@ steal.plugins("jquery").then(function( $ ) { * * - Use views with jQuery extensions [jQuery.fn.after after], [jQuery.fn.append append], * [jQuery.fn.before before], [jQuery.fn.html html], [jQuery.fn.prepend prepend], - * [jQuery.fn.replace replace], [jQuery.fn.replaceWith replaceWith], [jQuery.fn.text text]. + * [jQuery.fn.replaceWith replaceWith], [jQuery.fn.text text]. * - Template loading from html elements and external files. * - Synchronous and asynchronous template loading. + * - Deferred Rendering. * - Template caching. * - Bundling of processed templates in production builds. * - Hookup jquery plugins directly in the template. @@ -57,7 +58,6 @@ steal.plugins("jquery").then(function( $ ) { * <tr><td>[jQuery.fn.after before] </td><td> <code>$('#bar').before('temp.jaml',{});</code></td></tr> * <tr><td>[jQuery.fn.after html] </td><td> <code>$('#bar').html('temp.jaml',{});</code></td></tr> * <tr><td>[jQuery.fn.after prepend] </td><td> <code>$('#bar').prepend('temp.jaml',{});</code></td></tr> - * <tr><td>[jQuery.fn.after replace] </td><td> <code>$('#bar').replace('temp.jaml',{});</code></td></tr> * <tr><td>[jQuery.fn.after replaceWith] </td><td> <code>$('#bar').replaceWidth('temp.jaml',{});</code></td></tr> * <tr><td>[jQuery.fn.after text] </td><td> <code>$('#bar').text('temp.jaml',{});</code></td></tr> * </table> @@ -134,6 +134,19 @@ steal.plugins("jquery").then(function( $ ) { * The callback function will be called with the result of the * rendered template and 'this' will be set to the original jQuery object. * + * ## Deferreds (3.0.6) + * + * If you pass deferreds to $.View or any of the jQuery + * modifiers, the view will wait until all deferreds resolve before + * rendering the view. This makes it a one-liner to make a request and + * use the result to render a template. + * + * The following makes a request for todos in parallel with the + * todos.ejs template. Once todos and template have been loaded, it with + * render the view with the todos. + * + * $('#todos').html("todos.ejs",Todo.findAll()); + * * ## Just Render Templates * * Sometimes, you just want to get the result of a rendered @@ -145,7 +158,7 @@ steal.plugins("jquery").then(function( $ ) { * * You can preload templates asynchronously like: * - * $.View('path/to/template.jaml',{}, function(){}); + * $.get('path/to/template.jaml',{},function(){},'view'); * * ## Supported Template Engines * @@ -200,98 +213,191 @@ steal.plugins("jquery").then(function( $ ) { * @param {Object} [callback] Optional callback function. If present, the template is * retrieved asynchronously. This is a good idea if you aren't compressing the templates * into your view. - * @return {String} The rendered result of the view. + * @return {String} The rendered result of the view or if deferreds are passed, a deferred that will contain + * the rendered result of the view. */ - var $view, render, checkText, get; + var $view, render, checkText, get, getRenderer + isDeferred = function(obj){ + return obj && $.isFunction(obj.always) // check if obj is a $.Deferred + }, + // gets an array of deferreds from an object + // this only goes one level deep + getDeferreds = function(data){ + var deferreds = []; + + // pull out deferreds + if(isDeferred(data)){ + return [data] + }else{ + for(var prop in data) { + if(isDeferred(data[prop])) { + deferreds.push(data[prop]); + } + } + } + return deferreds; + }, + // gets the useful part of deferred + // this is for Models and $.ajax that give arrays + usefulPart = function(resolved){ + return $.isArray(resolved) && + resolved.length ===3 && + resolved[1] === 'success' ? + resolved[0] : resolved + }; $view = $.View = function( view, data, helpers, callback ) { - var suffix = view.match(/\.[\w\d]+$/), - type, el, id, renderer, url = view; - // if we have an inline template, derive the suffix from the 'text/???' part - // this only supports '<script></script>' tags - if ( el = document.getElementById(view)) { - suffix = el.type.match(/\/[\d\w]+$/)[0].replace(/^\//, '.'); - } if ( typeof helpers === 'function' ) { callback = helpers; helpers = undefined; } - //if there is no suffix, add one - if (!suffix ) { - suffix = $.View.ext; - url = url + $.View.ext; - } - - //convert to a unique and valid id - id = toId(url); - - //if a absolute path, use steal to get it - if ( url.match(/^\/\//) ) { - url = steal.root.join(url.substr(2)); //can steal be removed? + + // see if we got passed any deferreds + var deferreds = getDeferreds(data); + + + if(deferreds.length) { // does data contain any deferreds? + + // the deferred that resolves into the rendered content ... + var deferred = $.Deferred(); + + // add the view request to the list of deferreds + deferreds.push(get(view, true)) + + // wait for the view and all deferreds to finish + $.when.apply($, deferreds).then(function(resolved) { + var objs = $.makeArray(arguments), + renderer = objs.pop()[0], + result; //get the view render function + + // make data look like the resolved deferreds + if (isDeferred(data)) { + data = usefulPart(resolved); + } + else { + for (var prop in data) { + if (isDeferred(data[prop])) { + data[prop] = usefulPart(objs.shift()); + } + } + } + result = renderer(data, helpers); + + //resolve with the rendered view + deferred.resolve( result ); // this does not work as is... + callback && callback(result); + }); + // return the deferred .... + return deferred.promise(); } + else { - //get the template engine - type = $.View.types[suffix]; - - //get the renderer function - renderer = - $.View.cached[id] ? // is it cached? - $.View.cached[id] : // use the cached version - ((el = document.getElementById(view)) ? //is it in the document? - type.renderer(id, el.innerHTML) : //use the innerHTML of the elemnt - get(type, id, url, data, helpers, callback) //do an ajax request for it - ); - // we won't always get a renderer (if async ajax) - return renderer && render(renderer, type, id, data, helpers, callback); - }; - // caches the template, renders the content, and calls back if it should - render = function( renderer, type, id, data, helpers, callback ) { - var res, stub; - if ( $.View.cache ) { - $.View.cached[id] = renderer; + var response, + async = typeof callback === "function", + deferred = get(view, async); + + if(async){ + response = deferred; + deferred.done(function(renderer){ + callback(renderer(data, helpers)) + }) + } else { + deferred.done(function(renderer){ + response = renderer(data, helpers); + }); + } + + return response; } - res = renderer.call(type, data, helpers); - stub = callback && callback(res); - return res; }; // makes sure there's a template checkText = function( text, url ) { if (!text.match(/[^\s]/) ) { + steal.dev.log("There is no template or an empty template at " + url) throw "$.View ERROR: There is no template or an empty template at " + url; } }; - // gets a template, if there's a callback, renders and calls back its;ef - get = function( type, id, url, data, helpers, callback ) { - if ( callback ) { - $.ajax({ - url: url, - dataType: "text", - error: function() { - checkText("", url); - }, - success: function( text ) { - checkText(text, url); - render(type.renderer(id, text), type, id, data, helpers, callback); - } - }); - } else { - var text = $.ajax({ - async: false, + get = function(url , async){ + return $.ajax({ url: url, - dataType: "text", - error: function() { - checkText("", url); + dataType : "view", + async : async + }); + }; + + // you can request a view renderer (a function you pass data to and get html) + $.ajaxTransport("view", function(options, orig){ + var view = orig.url, + suffix = view.match(/\.[\w\d]+$/), + type, el, id, renderer, url = view, + jqXHR, + response = function(text){ + var func = type.renderer(id, text); + if ( $view.cache ) { + $view.cached[id] = func; } - }).responseText; - checkText(text, url); - return type.renderer(id, text); + return { + view: func + }; + }; + + // if we have an inline template, derive the suffix from the 'text/???' part + // this only supports '<script></script>' tags + if ( el = document.getElementById(view)) { + suffix = el.type.match(/\/[\d\w]+$/)[0].replace(/^\//, '.'); + } + + //if there is no suffix, add one + if (!suffix ) { + suffix = $view.ext; + url = url + $view.ext; } - }; + //convert to a unique and valid id + id = toId(url); + + //if a absolute path, use steal to get it + if ( url.match(/^\/\//) ) { + if (typeof steal === "undefined") { + url = "/"+url.substr(2); + } + else { + url = steal.root.join(url.substr(2)); + } + } + //get the template engine + type = $view.types[suffix]; - $.extend($.View, { + return { + send : function(headers, callback){ + if($view.cached[id]){ + return callback( 200, "success", {view: $view.cached[id]} ); + } else if( el ) { + callback( 200, "success", response(el.innerHTML) ); + } else { + jqXHR = $.ajax({ + async : orig.async, + url: url, + dataType: "text", + error: function() { + checkText("", url); + callback(404); + }, + success: function( text ) { + checkText(text, url); + callback(200, "success", response(text) ) + } + }); + } + }, + abort : function(){ + jqXHR && jqXHR.abort(); + } + } + }) + $.extend($view, { /** * @attribute hookups * @hide @@ -300,7 +406,17 @@ steal.plugins("jquery").then(function( $ ) { hookups: {}, /** * @function hookup - * Registers a hookup function to be called back after the html is put on the page + * Registers a hookup function that can be called back after the html is + * put on the page. Typically this is handled by the template engine. Currently + * only EJS supports this functionality. + * + * var id = $.View.hookup(function(el){ + * //do something with el + * }), + * html = "<div data-view-id='"+id+"'>" + * $('.foo').html(html); + * + * * @param {Function} cb a callback function to be called with the element * @param {Number} the hookup number */ @@ -378,7 +494,7 @@ steal.plugins("jquery").then(function( $ ) { * @param {Object} src */ registerScript: function( type, id, src ) { - return "$.View.preload('" + id + "'," + $.View.types["." + type].script(id, src) + ");"; + return "$.View.preload('" + id + "'," + $view.types["." + type].script(id, src) + ");"; }, /** * @hide @@ -388,7 +504,7 @@ steal.plugins("jquery").then(function( $ ) { * @param {Function} renderer */ preload: function( id, renderer ) { - $.View.cached[id] = function( data, helpers ) { + $view.cached[id] = function( data, helpers ) { return renderer.call(data, data, helpers); }; } @@ -405,7 +521,10 @@ steal.plugins("jquery").then(function( $ ) { $.fn[func_name] = function() { var args = $.makeArray(arguments), - callbackNum, callback, self = this; + callbackNum, + callback, + self = this, + result; //check if a template if ( isTemplate(args) ) { @@ -417,12 +536,20 @@ steal.plugins("jquery").then(function( $ ) { modify.call(self, [result], old); callback.call(self, result); }; - $.View.apply($.View, args); + $view.apply($view, args); + return this; + } + result = $view.apply($view, args); + if(!isDeferred( result ) ){ + args = [result]; + }else{ + result.done(function(res){ + modify.call(self, [res], old); + }) return this; } - //otherwise do the template now - args = [$.View.apply($.View, args)]; + } return modify.call(this, args, old); @@ -433,14 +560,14 @@ steal.plugins("jquery").then(function( $ ) { var res, stub, hooks; //check if there are new hookups - for ( var hasHookups in jQuery.View.hookups ) { + for ( var hasHookups in $view.hookups ) { break; } //if there are hookups, get jQuery object if ( hasHookups ) { - hooks = $.View.hookups; - $.View.hookups = {}; + hooks = $view.hookups; + $view.hookups = {}; args[0] = $(args[0]); } res = old.apply(this, args); @@ -482,7 +609,7 @@ steal.plugins("jquery").then(function( $ ) { } } //copy remaining hooks back - $.extend($.View.hookups, hooks); + $.extend($view.hookups, hooks); }; /** @@ -513,12 +640,6 @@ steal.plugins("jquery").then(function( $ ) { * abc */ "before", - /** - * @function replace - * @parent jQuery.View - * abc - */ - "replace", /** * @function text * @parent jQuery.View @@ -536,7 +657,8 @@ steal.plugins("jquery").then(function( $ ) { * @parent jQuery.View * abc */ - "replaceWith"]; + "replaceWith", + "val"]; //go through helper funcs and convert for ( var i = 0; i < funcs.length; i++ ) { diff --git a/browserid/static/jquery/view/vieww.html b/browserid/static/jquery/view/vieww.html deleted file mode 100644 index 390461c946756d7282fa3efe41d57513d9dc3b1d..0000000000000000000000000000000000000000 --- a/browserid/static/jquery/view/vieww.html +++ /dev/null @@ -1,294 +0,0 @@ -<style> - body {font-family: verdana;} -</style> -<p> - Everyone loves client side templates. - They are a great way to create html - which is something JavaScript apps do all the time. -</p> - -<p>In February, a jQuery templating system was -<a href='http://github.com/nje/jquery/wiki/jquery-templates-proposal'>proposed<a/> and resulted in a tremendous amount of -<a href='http://forum.jquery.com/topic/jquery-templates-proposal'>discussion</a>, followed by an -official templating engine for jQuery - <a href='http://www.borismoore.com/2010/10/jquery-templates-is-now-official-jquery.html'>jquery-tmpl</a>. -</p> - -<p> -Although jquery-tmpl is a solid templating engine, -the discussion highlighted three -extremely important facts about developers and client side templates: -</p> - -<h4> -Fact 1: Everyone has their favorite templating engine -</h4> - -<p> -There's a whole slew of templating languages people like: -</p> - -<ul> - <li><a href='http://EmbeddedJS.com'>EmbeddedJS</a></li> - <li><a href='http://code.google.com/p/trimpath/wiki/JavaScriptTemplates'>TrimJunction's JavaScript Templates</a></li> - <li><a href='http://github.com/edspencer/jaml'>Jaml</a></li> - <li><a href='http://github.com/jquery/jquery-tmpl'>jquery-tmpl</a></li> - <li><a href='http://github.com/janl/mustache.js/'>mustache</a></li> - <li><a href='http://ejohn.org/blog/javascript-micro-templating/'>micro</a></li> - <li><a href='http://github.com/raid-ox/chain.js/wiki'>chain.js</a></li> -</ul> - -<p> -Most of these template engines have distinct advantages and dissadvantages. -It's impossible to expect a single template engine to meet everyone's needs. -</p> -<h4>Fact 2: Most templating engines provide the exact same features</h4> -<p> - I've yet to encounter a template that does't provide: -</p> -<ul> - <li>A way of loading templates (typically from HTMLElement or files)</li> - <li>A way of caching processed templates.</li> - <li>An interface to render the template with arbitrary data.</li> -</ul> -<h4>Fact 3: Very few people are familiar with -the complexities of using templates</h4> -<p>There's more than just syntax and magic tag preference that goes -into a templating system. Consider:</p> -<ul> - <li>How can I build and share plugins that uses templates?</li> - <li>How can I share templates across pages / apps?</li> - <li>How can I organize template files?</li> -</ul> -<h2>jQuery.View</h2> - -<p> -<a href='http://v3.javascriptmvc.com/index.html#&who=jQuery.View'>jQuery.View</a> -is a templating interface that takes -care of the complexities of using templates, -while being completely template agnostic. -</p> -<p>This means that you can use any templating language in the exact -same way and get all the additional features that jQuery.View provides.</p> - -<h2>Features</h2> -<ul> - <li>Convenient syntax.</li> - <li>Template loading from html elements and <b>external files</b>.</li> - <li>Synchronous and asynchronous template loading.</li> - <li>Template preloading.</li> - <li>Caching of processed templates.</li> - <li>Bundling of processed templates in production builds.</li> -</ul> -<h2>Downloads</h2> -<ul> - <li>jquery.view.js</li> - <li>jquery.view.ejs.js</li> - <li>jquery.view.jaml.js</li> - <li>jquery.view.micro.js</li> - <li>jquery.view.tmpl.js</li> -</ul> -<h2>Use</h2> - -<p> -When using views, you're almost always wanting to insert the results of a rendered template into the page. jQuery.View overwrites the jQuery modifiers so using a view is as easy as: -</p> - -<pre><code>$("#foo").html('mytemplate.ejs',{message: 'hello world'})</code> -</pre> - -<p> -This code:</p> -<ol> - <li> - <p>Loads the template a 'mytemplate.ejs'. It might look like:</p> - <pre><code><h2><%= message %></h2></code></pre> - </li> - <li> - <p>Renders it with {message: 'hello world'}, resulting in:</p> - <pre><code>"<h2>hello world</h2>"</code></pre> - </li> - <li> - <p>Inserts the result into the foo element. Foo might look like:</p> - <pre><code><div id='foo'><h2>hello world</h2></div></code></pre> - </li> -</ol> - - - -<h3>jQuery Modifiers</h3> -<p> -You can use a template with the following jQuery modifier methods: -</p> -<table> - <tr><td><a href='http://api.jquery.com/after/'>after</a> </td><td> <code>$('#bar').after('temp.jaml',{});</code></td></tr> - <tr><td><a href='http://api.jquery.com/append/'>append</a> </td><td> <code>$('#bar').append('temp.jaml',{});</code></td></tr> - <tr><td><a href='http://api.jquery.com/before/'>before</a> </td><td> <code>$('#bar').before('temp.jaml',{});</code></td></tr> - <tr><td><a href='http://api.jquery.com/html/'>html</a> </td><td> <code>$('#bar').html('temp.jaml',{});</code></td></tr> - <tr><td><a href='http://api.jquery.com/prepend/'>prepend</a> </td><td> <code>$('#bar').prepend('temp.jaml',{});</code></td></tr> - <tr><td><a href='http://api.jquery.com/replace/'>replace</a> </td><td> <code>$('#bar').replace('temp.jaml',{});</code></td></tr> - <tr><td><a href='http://api.jquery.com/replaceWidth/'>replaceWidth</a> </td><td> <code>$('#bar').replaceWidth('temp.jaml',{});</code></td></tr> - <tr><td><a href='http://api.jquery.com/text/'>text</a> </td><td> <code>$('#bar').text('temp.jaml',{});</code></td></tr> -</table> -<h3> -Template Locations -</h3> - -<p> -View can load from script tags or from files. To load from a script tag, create a script tag with your template and an id like: -</p> - -<p> -<pre><code><script type='text/ejs' id='recipes'> -<% for(var i=0; i < recipes.length; i++){ %> - <li><%=recipes[i].name %></li> -<%} %> -</script></code></pre> -</p> - -<p> -Render with this template like: -</p> -<pre><code>$("#foo").html('recipes',recipeData)</code></pre> -<p>Notice we passed the id of the element we want to render.</p> - -<h3>Packaging Templates</h3> -<p>If you're making heavy use of templates, -you want to organize them in files so they can be reused between pages and -applications.</p> -<p>But, this organization would come at a high price if the browser has -to retrieve each template individually. The additional HTTP requests would slow -down your app. -</p> - -<p> -Fortunately, <a href='htttp://stealjs.com'>StealJS</a> can build templates -into your production files. -You just have to point to the view file like: -</p> - -<pre><code>steal.views('path/to/the/view.ejs'); -</pre></code> -<p>This will pre-process the view and insert it into a compressed single file with -your other JS code.</p> -<p><i>Note: Steal 1.1 will even let you <b>not</b> load the view engine in -production if all your templates are packaged.</i></p> -<h3> -Asynchronous -</h3> - -<p> -By default, retrieving requests is done synchronously. -This is fine because <a href='http://stealjs.com/'>StealJS</a> - packages view templates with your JS download. </p> -<p> -However, some people might not be using StealJS or -want to delay loading templates until necessary. -If you have the need, you can provide a callback paramter like: -</p> - - -<pre><code>$("#foo").html('recipes',recipeData, function(result){ - this.fadeIn() -});</code></pre> -<p>The callback function will be called with the result of the -rendered template and 'this' will be set to the original jQuery -object.</p> - -<h3> -Just Render Templates -</h3> - -<p> -Sometimes, you just want to get the result of a rendered template without inserting it, you can do this with $.View: -</p> - -<pre><code>var out = $.View('path/to/template.jaml',{}); -</pre></code> - -<h3>Preloading Templates</h3> - -<p> -You can preload templates asynchronously like: -</p> - -<pre><code>$.View('path/to/template.jaml',{}, function(){});</pre></code> -<p> -When it comes time to use them in your app, they will be ready for the user. -</p> -<h3> -Supported Templates -</h3> -<p> -JavaScriptMVC comes with the following templates: -</p> -<ul> - <li><p>EmbeddedJS</p> - <pre><code><h2><%= message %></h2></code></pre></li> - <li><p>JAML</p> - <pre><code>h2(data.message);</code></pre></li> - <li><p>Micro</p> - <pre><code><h2>{%= message %}</h2></code></pre></li> - <li><p>jQuery.Tmpl</p> - <pre><code><h2>${message}</h2></code></pre></li> -</ul> -<p><a href='http://awardwinningfjords.com/2010/08/09/mustache-for-javascriptmvc-3.html'>Mustache</a> is supported in a 2nd party plugin. - -</p> - -<h3> -Using Other Templates: -</h3> - -<p> -Integrating into $.View (and StealJS's build process) is easy, you just have to register your script like: -</p> - -<pre><code>$.View.register({ - suffix : "tmpl", - renderer: function( id, text ) { - return function(data){ - return jQuery.render( text, data ); - } - }, - script: function( id, text ) { - var tmpl = $.tmpl(text).toString(); - return "function(data){return ("+ - tmpl+ - ").call(jQuery, jQuery, data); }"; - } -})</pre></code> - -<p> -Here's what each property does:</p> -<ul> - <li><code>suffix</code> - files that use this suffix will be processed by this template engine</li> - <li><code>renderer</code> - returns a function that will render the template provided by text</li> - <li><code>script</code> - returns a string form of the processed template function.</li> -</ul> - -<h2>Conclusion</h2> -<p> - Templates are great, but there's a lot of extra work that goes into - making a template engine useful. - But, almost all of that extra work can be abstracted and reused. -</p> -<p> This is exactly what jQuery.View is! It's a tool so future template -engines don't have to worry about loading, caching, and bundling templates.</p> -<p>Even better, as it is a uniform template API, it enables plugin authors -to write widgets that accept arbitrary template types.</p> - -<p>I personally feel like this would be a good canidate for jQuery an -official jQuery plugin of its own. Imagine customizing the layout of a -widget by passing it a template: - -</p> - -<pre><code>$("#upcoming").srchr_search_result({ - modelType : Srchr.Models.Upcoming, - resultView : "//srchr/views/upcoming.ejs" -});</code></pre> -<p>P.S. This is actual code from our -<a href='http://github.com/jupiterjs/srchr'>JavaScriptMVC version of Srchr</a>. -Read about it <a href='http://jupiterjs.com/news/organizing-a-jquery-application'>here</a>. -We customize search results panels with a Model used to retrieve -searches and a view to output the results.</p> \ No newline at end of file diff --git a/browserid/static/js.bat b/browserid/static/js.bat new file mode 100755 index 0000000000000000000000000000000000000000..de1f03ae211d76da4b2bfa6da64ff76d45ecc94d --- /dev/null +++ b/browserid/static/js.bat @@ -0,0 +1,68 @@ +:: This script checks for arguments, if they don't exist it opens the Rhino dialog +:: if arguments do exist, it loads the script in the first argument and passes the other arguments to the script +:: ie: js jmvc\script\controller Todo +@echo off +SETLOCAL ENABLEDELAYEDEXPANSION +if "%1"=="" ( + java -cp steal\rhino\js.jar org.mozilla.javascript.tools.shell.Main + GOTO END +) +if "%1"=="-h" GOTO PRINT_HELP +if "%1"=="-?" GOTO PRINT_HELP +if "%1"=="--help" GOTO PRINT_HELP + +if "%1"=="-d" ( + java -classpath funcunit/java/selenium-java-client-driver.jar;steal/rhino/js.jar org.mozilla.javascript.tools.debugger.Main + GOTO END +) +if "%1"=="-selenium" ( + java -jar funcunit\java\selenium-server.jar + GOTO END +) +SET CP=funcunit/java/selenium-java-client-driver.jar;steal\rhino\js.jar +if "%1"=="-mail" ( + SET CP=steal/rhino/mail.jar;funcunit/java/selenium-java-client-driver.jar;steal\rhino\js.jar + SHIFT /0 +) +SET ERRORLEV=0 +if "%1"=="-e" ( + SET ERRORLEV=1 + SHIFT /0 +) +SET ARGS=[ +SET FILENAME=%1 +SET FILENAME=%FILENAME:\=/% +::haven't seen any way to loop through all args yet, so for now this goes through arg 2-7 +for /f "tokens=2,3,4,5,6,7 delims= " %%a in ("%*") do SET ARGS=!ARGS!'%%a','%%b','%%c','%%d','%%e','%%f' +::remove the empty args +:: for %%a in (",''=") do ( call set ARGS=%%ARGS:%%~a%% ) +SET ARGS=%ARGS:,''=% +::remove the spaces +:: for /f "tokens=1*" %%A in ("%ARGS%") do SET ARGS=%%A +SET ARGS=%ARGS: =% +SET ARGS=%ARGS%] +set ARGS=%ARGS:\=/% +java -Xmx228m -Xss1024k -cp %CP% org.mozilla.javascript.tools.shell.Main -opt -1 -e _args=%ARGS% -e load('%FILENAME%') + +if "%ERRORLEV%"=="1" ( + if errorlevel 1 exit 1 +) + +GOTO END + +:PRINT_HELP +echo Load a command line Rhino JavaScript environment or run JavaScript script files in Rhino. +echo Available commands: +echo js Opens a command line JavaScript environment +echo js -d Opens the Rhino debugger +echo js -selenium Starts selenium server +echo js [FILE] Runs FILE in the Rhino environment + +echo JavaScriptMVC script usage: +echo js steal/generate/app [NAME] Creates a new JavaScriptMVC application +echo js steal/generate/page [APP] [PAGE] Generates a page for the application +echo js steal/generate/controller [NAME] Generates a Controller file +echo js steal/generate/model [TYPE] [NAME] Generates a Model file +echo js apps/[NAME]/compress.js Compress your application and generate documentation + +:END diff --git a/browserid/static/relay/relay.js b/browserid/static/relay/relay.js new file mode 100644 index 0000000000000000000000000000000000000000..4788aa531d8d74f2afa249259ecb9210dcafbc9c --- /dev/null +++ b/browserid/static/relay/relay.js @@ -0,0 +1,78 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla BrowserID. + * + * The Initial Developer of the Original Code is Mozilla. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/*globals steal + */ +window.console = window.console || { + log: function() {} +}; + +steal.resources('../../dialog/resources/jschannel') + + .then(function($) { + // XXX get rid of this setTimeout. It is in so that the build + // script can do its thing without creating the channel + setTimeout(function() { + var ipServer = "https://browserid.org"; + + var chan = Channel.build( { + window: window.parent, + origin: "*", + scope: "mozid" + } ); + + var transaction; + + chan.bind("getVerifiedEmail", function(trans, s) { + trans.delayReturn(true); + + transaction = trans; + }); + + window.browserid_relay = function(status, error) { + if(error) { + errorOut(transaction, error); + } + else { + try { + transaction.complete(status); + } catch(e) { + // The relay function is called a second time after the + // initial success, when the window is closing. + } + } + } + }, 100); + }); // adds views to be added to build diff --git a/browserid/static/relay/scripts/build.html b/browserid/static/relay/scripts/build.html new file mode 100644 index 0000000000000000000000000000000000000000..e11cf9d1ed7a8053f539e4149ddbeafa617b6680 --- /dev/null +++ b/browserid/static/relay/scripts/build.html @@ -0,0 +1,21 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" + "http://www.w3.org/TR/html4/strict.dtd"> +<html lang="en"> + <head> + <title>relay Build Page</title> + </head> + <body> + <h1>relay Build Page</h1> + <p>This is a dummy page that loads your app so steal can + get all the files. + </p> + <p>If you built your app + to depend on HTML in the page before DOMContent loaded or + onload, you can add the HTML here, or you can change the + build.js script to point to a better html file. + </p> + <script type='text/javascript' + src='../../steal/steal.js?relay'> + </script> + </body> +</html> diff --git a/browserid/static/relay/scripts/build.js b/browserid/static/relay/scripts/build.js new file mode 100644 index 0000000000000000000000000000000000000000..7b6fa82d15d2f7aae83787e7a579a9d161b32bbc --- /dev/null +++ b/browserid/static/relay/scripts/build.js @@ -0,0 +1,8 @@ + +load("steal/rhino/steal.js"); +steal.plugins('steal/build','steal/build/scripts','steal/build/styles',function() { + steal.build('../static/relay/scripts/build.html',{ + to: '../static/relay', + compressor: 'concatOnly' + }); +}); diff --git a/browserid/static/relay/scripts/clean.js b/browserid/static/relay/scripts/clean.js new file mode 100644 index 0000000000000000000000000000000000000000..fc1cb75b3f401569dedb1754d3259831dbec0ad3 --- /dev/null +++ b/browserid/static/relay/scripts/clean.js @@ -0,0 +1,17 @@ +//steal/js /web/browserid/browserid/static/dialog/dialog/scripts/compress.js + +load("steal/rhino/steal.js"); +steal.plugins('steal/clean',function(){ + steal.clean('/web/browserid/browserid/static/relay/relay.html',{ + indent_size: 1, + indent_char: '\t', + jslint : false, + ignore: /jquery\/jquery.js/, + predefined: { + steal: true, + jQuery: true, + $ : true, + window : true + } + }); +}); diff --git a/browserid/static/relay/scripts/docs.js b/browserid/static/relay/scripts/docs.js new file mode 100644 index 0000000000000000000000000000000000000000..e9f61d1bd4dbfb1eb8f81555d12a0e10b004000b --- /dev/null +++ b/browserid/static/relay/scripts/docs.js @@ -0,0 +1,6 @@ +//js /web/browserid/browserid/static/dialog/dialog/scripts/doc.js + +load('steal/rhino/steal.js'); +steal.plugins("documentjs").then(function(){ + DocumentJS('/web/browserid/browserid/static/dialog/dialog/dialog.html'); +}); \ No newline at end of file diff --git a/browserid/static/steal/build/build.js b/browserid/static/steal/build/build.js old mode 100644 new mode 100755 index c8096a420a48ee543801fe10f282026d93584d64..963469c5fcd5d0d031fbf06f64121166ea98c737 --- a/browserid/static/steal/build/build.js +++ b/browserid/static/steal/build/build.js @@ -10,49 +10,65 @@ steal(function( steal ) { * * @parent stealjs * - * <p>Builds an html page's JavaScript and CSS files by compressing and concatenating them into + * Builds an html page's JavaScript and CSS files by compressing and concatenating them into * a single or several files. - * </p> - * <p>Steal can also build multiple applications at the same time and separate - * shared dependencies into standalone cache-able scripts.</p> - * <h2>How it works</h2> - * <p><code>Steal.build</code> opens a page in Envjs to extract all scripts and styles + * + * Steal can also build multiple applications at the same time and separate + * shared dependencies into standalone cache-able scripts. + * + * ## How it works + * + * <code>Steal.build</code> opens a page in Envjs to extract all scripts and styles * from the page. It compresses the resources into production.js and production.css - * files.</p> - * <p>Steal.build works with or without using steal.js, so it could work with other script loaders.</p> + * files. + * + * Steal.build works with or without using steal.js, so it could work with other script loaders. + * + * ## Building with steal.js. * + * Building with steal is easy, just point the <code>steal/buildjs</code> script at your page and + * give it the name of your application folder: * - * <h2>Building with steal.js.</h2> - * <p>Building with steal is easy, just point the <code>steal/buildjs</code> script at your page and - * give it the name of your application folder:</p> * @codestart no-highlight * js steal/buildjs path/to/page.html -to myapp * @codeend - * <p>If you generated a steal app or plugin, there's a handy script already ready for you:</p> + * + * If you generated a steal app or plugin, there's a handy script already ready for you: + * * @codestart no-highlight * js myapp/scripts/build.js * @codeend - * <h2>Building without steal.js</h2> + * + * ## Building without steal.js + * * You can compress and package any page's JavaScript by adding <code>compress="true"</code> * attributes to your script tag like the following: + * * @codestart html * <script src="file1.js" type="text/javascript" compress="true"></script> * <script src="file2.js" type="text/javascript" compress="true"></script> * @codeend + * * and then running either: + * * @codestart no-highlight * js steal/buildjs path/to/page.html -to [OUTPUT_FOLDER] * @codeend + * * or: + * * @codestart no-highlight * js steal/buildjs http://hostname/path/page.html -to [OUTPUT_FOLDER] * @codeend + * * This will compress file1.js and file2.js into a file package named production.js an put it in OUTPUT_FOLDER. * - * <h2>Common Problems</h2> - * <p>If you are getting errors building a production build, it's almost certainly because Envjs is + * ## Common Problems + * + * If you are getting errors building a production build, it's almost certainly because Envjs is * close, but not quite a fully featured browser. So, you have to avoid doing things in your page that - * Envjs doesn't like before onload. The most common problems are:</p> + * Envjs doesn't like before onload. The most common problems are: + * * <h5>Malformed HTML or unescaped characters</h5> * <p>Steal does not have as tolerant of an HTML parser as Firefox. Make sure your page's tags look good. * Also, make sure you escape characters like & to &amp; @@ -91,7 +107,10 @@ steal(function( steal ) { * <tr><td>all</td> * <td>Concat and compress all scripts and styles. By default, this is set to false, meaning * scripts and styles have to opt into being compress with the <code>compress='true'</code> attribute.</td></tr> + * <tr><td>compressor</td> + * <td>The compressor to use: shrinksafe, localClosure, closureService or yui</td></tr> * </table> + * Note that you must install shrinksafe and YUI compressor manually, because they are not included in the JavaScriptMVC distribution. */ steal.build = function( url, options ) { @@ -100,7 +119,9 @@ steal(function( steal ) { //compress everything, regardless of what you find all: 1, //folder to build to, defaults to the folder the page is in - to: 1 + to: 1, + //compressor to use, e.g. shrinksafe, localClosure, closureService or yui + compressor: 1 }); // to is the folder packages will be put in @@ -114,14 +135,14 @@ steal(function( steal ) { steal.print("Building to " + options.to); var opener = steal.build.open(url); - + + // iterates through the types of builders. For now // there are just scripts and styles builders for ( var builder in steal.build.builders ) { steal.build.builders[builder](opener, options); } }; - // a place for the builders steal.build.builders = {}; //builders // a helper function that gets the src of a script and returns @@ -141,7 +162,7 @@ steal(function( steal ) { text = readFile("/" + url); } - if ( url.match(/^http\:/) ) { + if ( url.match(/^https?\:/) ) { text = readUrl(url); } @@ -223,6 +244,7 @@ steal(function( steal ) { } // get envjs load('steal/rhino/env.js'); //reload every time + var success = true; // open the url Envjs(url, { scriptTypes: { @@ -242,10 +264,16 @@ steal(function( steal ) { }, afterInlineScriptLoad: function( script ) { scripts.push(script); + }, + onScriptLoadError: function(script) { + success = false; }, dontPrintUserAgent: true, killTimersAfterLoad: true }); + if (!success) { + java.lang.System.exit(-1); + } // set back steal newSteal = window.steal; @@ -276,6 +304,7 @@ steal(function( steal ) { type = 'script'; } var scripts = document.getElementsByTagName(type); + for ( var i = 0; i < scripts.length; i++ ) { func(scripts[i], this.getScriptContent(scripts[i]), i); } @@ -289,4 +318,4 @@ steal(function( steal ) { }; }; -}); \ No newline at end of file +}); diff --git a/browserid/static/steal/build/pluginify/pluginify.js b/browserid/static/steal/build/pluginify/pluginify.js index 57127829e82a4518be6d1b9e1ebd359742a59d79..3da45f947f6575b30dc1179ed072c8224de685b3 100644 --- a/browserid/static/steal/build/pluginify/pluginify.js +++ b/browserid/static/steal/build/pluginify/pluginify.js @@ -4,12 +4,15 @@ // js steal\scripts\pluginify.js jquery/event/drag -exclude jquery/lang/vector/vector.js jquery/event/livehack/livehack.js // load("steal/rhino/steal.js"); -steal("//steal/build/pluginify/parse").plugins('steal/build/scripts').then( +steal.plugins('steal/parse','steal/build/scripts').then( function(s) { /** * Builds a 'steal-less' version of your application. To use this, files that use steal must - * have their code within a callback function. + * have their code within a callback function. + * + * js steal\pluginify jquery\controller -nojquery + * * @param {Object} plugin * @param {Object} opts */ @@ -24,7 +27,7 @@ steal("//steal/build/pluginify/parse").plugins('steal/build/scripts').then( "global" : 0, "compress" : 0 }), - destination = opts.destination || plugin+"/"+plugin.replace("/",".") + ".js"; + destination = opts.destination || plugin+"/"+plugin.replace(/\//g,".") + ".js"; opts.exclude = !opts.exclude ? [] : (steal.isArray(opts.exclude) ? opts.exclude : [opts.exclude]); @@ -36,6 +39,7 @@ steal("//steal/build/pluginify/parse").plugins('steal/build/scripts').then( opts.exclude.push("steal/dev/") rhinoLoader = { callback: function( s ) { + s.pluginify = true; s.plugins(plugin); } }; @@ -62,6 +66,8 @@ steal("//steal/build/pluginify/parse").plugins('steal/build/scripts').then( print(" > "+steals[i].path) out.push(steal.build.builders.scripts.clean(content)); } + }else{ + print(" Ignoring "+steals[i].path) } } @@ -100,8 +106,7 @@ steal("//steal/build/pluginify/parse").plugins('steal/build/scripts').then( } }; s.build.pluginify.getFunction = function(content, ith){ - - var p = s.build.parse(content), + var p = steal.parse(content), token, funcs = []; @@ -109,9 +114,6 @@ steal("//steal/build/pluginify/parse").plugins('steal/build/scripts').then( //print(token.value) if(token.type !== "string"){ switch(token.value){ - case "/" : - comment(p) - break; case "steal" : stealPull(p, content, function(func){ funcs.push(func) @@ -143,7 +145,7 @@ steal("//steal/build/pluginify/parse").plugins('steal/build/scripts').then( startToken = p.until("{"); - endToken = nextBracket(p); + endToken = p.partner("{"); cb(content.substring(token.from, endToken.to)) //print("CONTENT\n"+ ); p.moveNext(); @@ -152,42 +154,5 @@ steal("//steal/build/pluginify/parse").plugins('steal/build/scripts').then( } stealPull(p,content, cb ); - }, - //moves across a comment - comment = function(p){ //we don't really need this anymore - var n =p.next() - if(n.value == "*" && n.value != 'string'){ - p.until(["*","/"]) - } - }, - //gets the next bracket - nextBracket = function(p){ - var count = 1, token, last, prev; - while(token = p.moveNext()){ - //print(token.value) - if(token.type == 'operator'){ - switch(token.value){ - case "{": - - count++; - //print(" +"+count+" "+prev+" "+last) - break; - case "}" : - - count--; - //print(" -"+count+" "+prev+" "+last) - if(count === 0){ - return token; - } - break; - case "/" : - comment(p); - break; - } - } - - prev = last; - last = (token.value) - } - } + }; }); diff --git a/browserid/static/steal/build/pluginify/test/pluginify_test.js b/browserid/static/steal/build/pluginify/test/pluginify_test.js index a4c5f8733b646354aef3a53e32cea6d3bb65ae19..e0c567e59533fd68caf0e6ec187679868d60e3a5 100644 --- a/browserid/static/steal/build/pluginify/test/pluginify_test.js +++ b/browserid/static/steal/build/pluginify/test/pluginify_test.js @@ -25,28 +25,6 @@ steal.plugins('steal/test','steal/build/pluginify').then( function( s ) { var firstFunc = steal.build.pluginify.getFunction(js, 0); //print(firstFunc); }) - s.test.test("parse", function(t){ - var js = readFile('jquery/class/class.js'); - var tokens = js.tokens('=<>!+-*&|/%^', '=<>&|'); - - var js = readFile('jquery/view/ejs/ejs.js'); - var tokens = js.tokens('=<>!+-*&|/%^', '=<>&|'); - - var js = readFile('jquery/lang/vector/vector.js'); - var tokens = js.tokens('=<>!+-*&|/%^', '=<>&|'); - - var js = readFile('jquery/dom/fixture/fixture.js'); - var tokens = js.tokens('=<>!+-*&|/%^', '=<>&|'); - - var js = readFile('jquery/view/view.js'); - var tokens = js.tokens('=<>!+-*&|/%^', '=<>&|'); - - var js = readFile('jquery/lang/json/json.js'); - var tokens = js.tokens('=<>!+-*&|/%^', '=<>&|'); - - js = readFile('steal/build/pluginify/test/weirdRegexps.js'); - var tokens = js.tokens('=<>!+-*&|/%^', '=<>&|'); - - }) + }); \ No newline at end of file diff --git a/browserid/static/steal/build/pluginify/tokens.js b/browserid/static/steal/build/pluginify/tokens.js index 88af4eb6d37b46ac9df9dc0f88e4da0ab9222fe6..a04c4274dddf809e0c5f6aeee0bde96c5b62bba8 100644 --- a/browserid/static/steal/build/pluginify/tokens.js +++ b/browserid/static/steal/build/pluginify/tokens.js @@ -262,12 +262,16 @@ String.prototype.tokens = function (prefix, suffix) { // comment. } else if (c === '/' && this.charAt(i + 1) === '*') { - i += 1; + var str = c; + i += 1; for (;;) { c = this.charAt(i); + str += c; if (c === '*' && this.charAt(i+1) == "/") { i += 1; i += 1; + str+= "/"; + result.push(make('comment', str)); break; } i += 1; diff --git a/browserid/static/steal/build/scripts/scripts.js b/browserid/static/steal/build/scripts/scripts.js index 338b4eceade1369c1dcd72023ff36c56d730a9eb..0fc833905348daa9eea904f52d0223ed3b494f26 100644 --- a/browserid/static/steal/build/scripts/scripts.js +++ b/browserid/static/steal/build/scripts/scripts.js @@ -2,8 +2,13 @@ steal(function( steal ) { /** * Builds JavaScripts + * * @param {Object} opener the result of a steal.build.open * @param {Object} options options passed to the build script + * + * * __to__ - which folder the production.css files should be put in + * * __quite__ - tell the compressor to be less abnoxious about sending errors + * * __all__ - compress all scripts */ var scripts = (steal.build.builders.scripts = function( opener, options ) { steal.print("\nBUILDING SCRIPTS --------------- "); @@ -151,9 +156,39 @@ steal(function( steal ) { return outBaos.toString(); }; }, - concatOnly: function() { - steal.print("steal.compress - Not compressing resources, only concatenating"); - return function( src ) { return src; } - } + yui: function() { + // needs yuicompressor.jar at steal/build/scripts/yuicompressor.jar + steal.print("steal.compress - Using YUI compressor"); + + return function( src ) { + var rnd = Math.floor(Math.random() * 1000000 + 1), + filename = "tmp" + rnd + ".js", + tmpFile = new steal.File(filename); + + tmpFile.save(src); + + var outBaos = new java.io.ByteArrayOutputStream(), + output = new java.io.PrintStream(outBaos); + + runCommand( + "java", + "-jar", + "steal/build/scripts/yuicompressor.jar", + "--charset", + "utf-8", + filename, + { output: output } + ); + + tmpFile.remove(); + + return outBaos.toString(); + }; + }, + + concatOnly: function() { + steal.print("steal.compress - Not compressing resources, only concatenating"); + return function( src ) { return src; } + } }; -}); \ No newline at end of file +}); diff --git a/browserid/static/steal/build/styles/cssmin.js b/browserid/static/steal/build/styles/cssmin.js index 01300a566e9b8234f63cec4b6bb757cbaa5c0a18..ab63e59a1b23602e11d7ac0b4f4807c1536ce8f6 100644 --- a/browserid/static/steal/build/styles/cssmin.js +++ b/browserid/static/steal/build/styles/cssmin.js @@ -1,13 +1,236 @@ steal(function( steal ) { - var comments = /\/\*.*?\*\//g, - newLines = /\n*/g, - space = /[ ]+/g, - spaceChars = /\s?([;:{},+>])\s?/g, - lastSemi = /;}/g; - - - steal.cssMin = function( css ) { - //remove comments - return css.replace(comments, "").replace(newLines, "").replace(space, " ").replace(spaceChars, '$1').replace(lastSemi, '}') - } -}) \ No newline at end of file + /** + * cssmin.js + * Author: Stoyan Stefanov - http://phpied.com/ + * This is a JavaScript port of the CSS minification tool + * distributed with YUICompressor, itself a port + * of the cssmin utility by Isaac Schlueter - http://foohack.com/ + * Permission is hereby granted to use the JavaScript version under the same + * conditions as the YUICompressor (original YUICompressor note below). + */ + + /* + * YUI Compressor + * Author: Julien Lecomte - http://www.julienlecomte.net/ + * Copyright (c) 2009 Yahoo! Inc. All rights reserved. + * The copyrights embodied in the content of this file are licensed + * by Yahoo! Inc. under the BSD (revised) open source license. + */ + var YAHOO = YAHOO || {}; + YAHOO.compressor = YAHOO.compressor || {}; + YAHOO.compressor.cssmin = function (css, linebreakpos) { + + var startIndex = 0, + endIndex = 0, + i = 0, max = 0, + preservedTokens = [], + comments = [], + token = '', + totallen = css.length, + placeholder = ''; + + // collect all comment blocks... + while ((startIndex = css.indexOf("/*", startIndex)) >= 0) { + endIndex = css.indexOf("*/", startIndex + 2); + if (endIndex < 0) { + endIndex = totallen; + } + token = css.slice(startIndex + 2, endIndex); + comments.push(token); + css = css.slice(0, startIndex + 2) + "___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + (comments.length - 1) + "___" + css.slice(endIndex); + startIndex += 2; + } + + // preserve strings so their content doesn't get accidentally minified + css = css.replace(/("([^\\"]|\\.|\\)*")|('([^\\']|\\.|\\)*')/g, function (match) { + var i, max, quote = match.substring(0, 1); + + match = match.slice(1, -1); + + // maybe the string contains a comment-like substring? + // one, maybe more? put'em back then + if (match.indexOf("___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_") >= 0) { + for (i = 0, max = comments.length; i < max; i = i + 1) { + match = match.replace("___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + i + "___", comments[i]); + } + } + + // minify alpha opacity in filter strings + match = match.replace(/progid:DXImageTransform\.Microsoft\.Alpha\(Opacity=/gi, "alpha(opacity="); + + preservedTokens.push(match); + return quote + "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.length - 1) + "___" + quote; + }); + + // strings are safe, now wrestle the comments + for (i = 0, max = comments.length; i < max; i = i + 1) { + + token = comments[i]; + placeholder = "___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + i + "___"; + + // ! in the first position of the comment means preserve + // so push to the preserved tokens keeping the ! + if (token.charAt(0) === "!") { + preservedTokens.push(token); + css = css.replace(placeholder, "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.length - 1) + "___"); + continue; + } + + // \ in the last position looks like hack for Mac/IE5 + // shorten that to /*\*/ and the next one to /**/ + if (token.charAt(token.length - 1) === "\\") { + preservedTokens.push("\\"); + css = css.replace(placeholder, "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.length - 1) + "___"); + i = i + 1; // attn: advancing the loop + preservedTokens.push(""); + css = css.replace("___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + i + "___", "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.length - 1) + "___"); + continue; + } + + // keep empty comments after child selectors (IE7 hack) + // e.g. html >/**/ body + if (token.length === 0) { + startIndex = css.indexOf(placeholder); + if (startIndex > 2) { + if (css.charAt(startIndex - 3) === '>') { + preservedTokens.push(""); + css = css.replace(placeholder, "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.length - 1) + "___"); + } + } + } + + // in all other cases kill the comment + css = css.replace("/*" + placeholder + "*/", ""); + } + + + // Normalize all whitespace strings to single spaces. Easier to work with that way. + css = css.replace(/\s+/g, " "); + + // Remove the spaces before the things that should not have spaces before them. + // But, be careful not to turn "p :link {...}" into "p:link{...}" + // Swap out any pseudo-class colons with the token, and then swap back. + css = css.replace(/(^|\})(([^\{:])+:)+([^\{]*\{)/g, function (m) { + return m.replace(":", "___YUICSSMIN_PSEUDOCLASSCOLON___"); + }); + css = css.replace(/\s+([!{};:>+\(\)\],])/g, '$1'); + css = css.replace(/___YUICSSMIN_PSEUDOCLASSCOLON___/g, ":"); + + // retain space for special IE6 cases + css = css.replace(/:first-(line|letter)(\{|,)/g, ":first-$1 $2"); + + // no space after the end of a preserved comment + css = css.replace(/\*\/ /g, '*/'); + + + // If there is a @charset, then only allow one, and push to the top of the file. + css = css.replace(/^(.*)(@charset "[^"]*";)/gi, '$2$1'); + css = css.replace(/^(\s*@charset [^;]+;\s*)+/gi, '$1'); + + // Put the space back in some cases, to support stuff like + // @media screen and (-webkit-min-device-pixel-ratio:0){ + css = css.replace(/\band\(/gi, "and ("); + + + // Remove the spaces after the things that should not have spaces after them. + css = css.replace(/([!{}:;>+\(\[,])\s+/g, '$1'); + + // remove unnecessary semicolons + css = css.replace(/;+\}/g, "}"); + + // Replace 0(px,em,%) with 0. + css = css.replace(/([\s:])(0)(px|em|%|in|cm|mm|pc|pt|ex)/gi, "$1$2"); + + // Replace 0 0 0 0; with 0. + css = css.replace(/:0 0 0 0(;|\})/g, ":0$1"); + css = css.replace(/:0 0 0(;|\})/g, ":0$1"); + css = css.replace(/:0 0(;|\})/g, ":0$1"); + + // Replace background-position:0; with background-position:0 0; + // same for transform-origin + css = css.replace(/(background-position|transform-origin|webkit-transform-origin|moz-transform-origin|o-transform-origin|ms-transform-origin):0(;|\})/gi, function(all, prop, tail) { + return prop.toLowerCase() + ":0 0" + tail; + }); + + // Replace 0.6 to .6, but only when preceded by : or a white-space + css = css.replace(/(:|\s)0+\.(\d+)/g, "$1.$2"); + + // Shorten colors from rgb(51,102,153) to #336699 + // This makes it more likely that it'll get further compressed in the next step. + css = css.replace(/rgb\s*\(\s*([0-9,\s]+)\s*\)/gi, function () { + var i, rgbcolors = arguments[1].split(','); + for (i = 0; i < rgbcolors.length; i = i + 1) { + rgbcolors[i] = parseInt(rgbcolors[i], 10).toString(16); + if (rgbcolors[i].length === 1) { + rgbcolors[i] = '0' + rgbcolors[i]; + } + } + return '#' + rgbcolors.join(''); + }); + + + // Shorten colors from #AABBCC to #ABC. Note that we want to make sure + // the color is not preceded by either ", " or =. Indeed, the property + // filter: chroma(color="#FFFFFF"); + // would become + // filter: chroma(color="#FFF"); + // which makes the filter break in IE. + css = css.replace(/([^"'=\s])(\s*)#([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])/gi, function () { + var group = arguments; + if ( + group[3].toLowerCase() === group[4].toLowerCase() && + group[5].toLowerCase() === group[6].toLowerCase() && + group[7].toLowerCase() === group[8].toLowerCase() + ) { + return (group[1] + group[2] + '#' + group[3] + group[5] + group[7]).toLowerCase(); + } else { + return group[0].toLowerCase(); + } + }); + + // border: none -> border:0 + css = css.replace(/(border|border-top|border-right|border-bottom|border-right|outline|background):none(;|\})/gi, function(all, prop, tail) { + return prop.toLowerCase() + ":0" + tail; + }); + + // shorter opacity IE filter + css = css.replace(/progid:DXImageTransform\.Microsoft\.Alpha\(Opacity=/gi, "alpha(opacity="); + + // Remove empty rules. + css = css.replace(/[^\};\{\/]+\{\}/g, ""); + + if (linebreakpos >= 0) { + // Some source control tools don't like it when files containing lines longer + // than, say 8000 characters, are checked in. The linebreak option is used in + // that case to split long lines after a specific column. + startIndex = 0; + i = 0; + while (i < css.length) { + i = i + 1; + if (css[i - 1] === '}' && i - startIndex > linebreakpos) { + css = css.slice(0, i) + '\n' + css.slice(i); + startIndex = i; + } + } + } + + // Replace multiple semi-colons in a row by a single one + // See SF bug #1980989 + css = css.replace(/;;+/g, ";"); + + // restore preserved comments and strings + for (i = 0, max = preservedTokens.length; i < max; i = i + 1) { + css = css.replace("___YUICSSMIN_PRESERVED_TOKEN_" + i + "___", preservedTokens[i]); + } + + // Trim the final string (for any leading or trailing white spaces) + css = css.replace(/^\s+|\s+$/g, ""); + + return css; + }; + + steal.build.builders.styles.min = function( css ) { + //remove comments & minify + return YAHOO.compressor.cssmin(css); + } +}); diff --git a/browserid/static/steal/build/styles/styles.js b/browserid/static/steal/build/styles/styles.js index 88c9a6b3ce25c308ab29cafae8f39cb7b307a4ba..4f1ce103336507d76b941711560118adb21694c5 100644 --- a/browserid/static/steal/build/styles/styles.js +++ b/browserid/static/steal/build/styles/styles.js @@ -1,9 +1,12 @@ + steal(function( steal ) { /** * Builds and compresses CSS files. - * @param {Object} opener - * @param {Object} options + * @param {Object} opener a steal opener that can give the final version of scripts + * @param {Object} options options configuring the css building + * + * - __to__ where the css should be built. */ var styles = (steal.build.builders.styles = function( opener, options ) { steal.print("\nBUILDING STYLES --------------- "); @@ -20,25 +23,23 @@ steal(function( steal ) { steal.print(link.href) var loc = steal.File(pageFolder).join(link.href), - converted = convert(text, loc, folder) - - - currentPackage.push(steal.cssMin(converted)) - + converted = convert(text, loc, folder); + currentPackage.push(converted); } - }); steal.print("") if ( currentPackage.length ) { - steal.print("STYLE BUNDLE > " + folder + "/production.css\n") - steal.File(folder + "/production.css").save(currentPackage.join('\n')); + steal.print("STYLE BUNDLE > " + folder + "/production.css") + //now that we have all the css minify and save it + var raw_css = currentPackage.join(""), + minified_css = styles.min(raw_css); + steal.print("Nice! "+calcSavings(raw_css.length,minified_css.length)); + steal.File(folder + "/production.css").save(minified_css); } else { steal.print("no styles\n") } - - - }); + //used to convert css referencs in one file so they will make sense from prodLocation var convert = function( css, cssLocation, prodLocation ) { //how do we go from prod to css @@ -60,25 +61,18 @@ steal(function( steal ) { }); return newCSss; }, - isRelative = function( part ) { - // http://, https://, / - return !/^(http:\/\/|https:\/\/|\/)/.test(part) - } - - var comments = /\/\*.*?\*\//g, - newLines = /\n*/g, - space = /[ ]+/g, - spaceChars = /\s?([;:{},+>])\s?/g, - lastSemi = /;}/g; - - - steal.cssMin = function( css ) { - //remove comments - return css.replace(comments, "") - .replace(newLines, "") - .replace(space, " ") - .replace(spaceChars, '$1') - .replace(lastSemi, '}') - } - -}); \ No newline at end of file + isRelative = function( part ) { + // http://, https://, / + return !/^(http:\/\/|https:\/\/|\/)/.test(part) + }, + calcSavings = function(raw_len, minified_len) { + var diff_len = raw_len - minified_len, x = Math.pow(10,1); + return 'Compressed: '+(Math.round((diff_len/raw_len*100)*x)/x)+'% Before: '+ + string2size(raw_len)+' After: '+string2size(minified_len); + }, + string2size = function(bytes) { + var s = ['bytes','kb','mb','gb','tb','pb']; + var e = Math.floor(Math.log(bytes)/Math.log(1024)); + return (bytes/Math.pow(1024,Math.floor(e))).toFixed(1)+' '+s[e]; + }; +},'//steal/build/styles/cssmin'); \ No newline at end of file diff --git a/browserid/static/steal/build/styles/test/app/app.css b/browserid/static/steal/build/styles/test/app/app.css new file mode 100644 index 0000000000000000000000000000000000000000..1e19452e1a13dbeb3dc5f97ca197b0885c4e28da --- /dev/null +++ b/browserid/static/steal/build/styles/test/app/app.css @@ -0,0 +1,3 @@ +h1 { + border: solid 1px black; +} diff --git a/browserid/static/steal/build/styles/test/app/app.html b/browserid/static/steal/build/styles/test/app/app.html new file mode 100644 index 0000000000000000000000000000000000000000..9eb2783e3168dce2799fec903e5c9c30a6a75f86 --- /dev/null +++ b/browserid/static/steal/build/styles/test/app/app.html @@ -0,0 +1,10 @@ +<html> + <head> + + </head> + <body> + <h1>Bordered</h1> + <script type='text/javascript' + src='../../../../steal.js?steal/build/styles/test/app'></script> + </body> +</html> \ No newline at end of file diff --git a/browserid/static/steal/build/styles/test/app/app.js b/browserid/static/steal/build/styles/test/app/app.js new file mode 100644 index 0000000000000000000000000000000000000000..f49acf0a02e3b1d25c3b460bc90418582bcace12 --- /dev/null +++ b/browserid/static/steal/build/styles/test/app/app.js @@ -0,0 +1 @@ +steal.css('app','app') diff --git a/browserid/static/steal/build/styles/test/app/production.css b/browserid/static/steal/build/styles/test/app/production.css new file mode 100644 index 0000000000000000000000000000000000000000..b7bf2097f1281e003260bf047d64942d1efa0809 --- /dev/null +++ b/browserid/static/steal/build/styles/test/app/production.css @@ -0,0 +1 @@ +h1{border:solid 1px black} \ No newline at end of file diff --git a/browserid/static/steal/build/styles/test/multiline.css b/browserid/static/steal/build/styles/test/multiline.css new file mode 100644 index 0000000000000000000000000000000000000000..cc06f10560aef1241deed637840fde2c5330510c --- /dev/null +++ b/browserid/static/steal/build/styles/test/multiline.css @@ -0,0 +1,4 @@ +/** + * here is a multi line comment + */ +.foo { color: blue} diff --git a/browserid/static/steal/build/styles/test/production.css b/browserid/static/steal/build/styles/test/production.css index a4b524ffbc612155811be8562fb303b3e556446f..ee969b3173a470b117c10bfb7063a690fd1c73dd 100644 --- a/browserid/static/steal/build/styles/test/production.css +++ b/browserid/static/steal/build/styles/test/production.css @@ -1,2 +1 @@ -.background1a{background-image:url(css/justin.png)}.background1b{background-image:url(upload.png)}.background1c{background-image:url(css/justin.png)}.background1d{background-image:url(upload.png)} -.background2{background-image:url(upload.PNG)}.back{width:200px;height:200px}.background2b{background-image:url(/foo/bar.PNG)} \ No newline at end of file +.background1a{background-image:url(css/justin.png)}.background1b{background-image:url(upload.png)}.background1c{background-image:url(css/justin.png)}.background1d{background-image:url(upload.png)}.background2{background-image:url(upload.PNG)}.back{width:200px;height:200px}.background2b{background-image:url(/foo/bar.PNG)} \ No newline at end of file diff --git a/browserid/static/steal/build/styles/test/styles_test.js b/browserid/static/steal/build/styles/test/styles_test.js index 2bd47250e5c9d455fd650bde5a6d6b64f3fe827c..6831fd91eb10ddfde71296ac4d72786481c257e7 100644 --- a/browserid/static/steal/build/styles/test/styles_test.js +++ b/browserid/static/steal/build/styles/test/styles_test.js @@ -1,4 +1,4 @@ -// load('steal/compress/test/run.js') +// load('steal/build/styles/test/styles_test.js') /** * Tests compressing a very basic page and one that is using steal */ @@ -12,9 +12,7 @@ steal('//steal/test/test', function( s ) { s.test.test("css", function(){ load('steal/rhino/steal.js'); steal.plugins( - 'steal/build', - 'steal/build/scripts', - 'steal/build/styles', + 'steal/build','steal/build/styles', function(){ steal.build('steal/build/styles/test/page.html', {to: 'steal/build/styles/test'}); @@ -31,4 +29,32 @@ steal('//steal/test/test', function( s ) { s.test.clear(); }) + + s.test.test("min multiline comment", function(){ + load('steal/rhino/steal.js'); + steal.plugins('steal/build','steal/build/styles',function(){ + var input = readFile('steal/build/styles/test/multiline.css'), + out = steal.build.builders.styles.min(input); + + s.test.equals(out, ".foo{color:blue}", "multline comments wrong") + + }); + s.test.clear(); + }); + + s.test.test("load the same css twice, but only once in prod", function(){ + load('steal/rhino/steal.js'); + steal.plugins('steal/build', + 'steal/build/styles', + function(){ + steal.build('steal/build/styles/test/app/app.html', + {to: 'steal/build/styles/test/app'}); + }); + + var prod = readFile('steal/build/styles/test/app/production.css').replace(/\r|\n/g,""); + + s.test.equals(prod,"h1{border:solid 1px black}", "only one css"); + + s.test.clear(); + }) }); \ No newline at end of file diff --git a/browserid/static/steal/build/test/https.html b/browserid/static/steal/build/test/https.html new file mode 100644 index 0000000000000000000000000000000000000000..de2bf4c44e8aa01ce742feca26753f2994537500 --- /dev/null +++ b/browserid/static/steal/build/test/https.html @@ -0,0 +1,9 @@ +<html> + <head> + + </head> + <body> + <script type='text/javascript' src='../../steal.js?steal/build/test/https.js'></script> + </body> + +</html> \ No newline at end of file diff --git a/browserid/static/steal/build/test/https.js b/browserid/static/steal/build/test/https.js new file mode 100644 index 0000000000000000000000000000000000000000..1cc3e9e91077d5cc4d33db3c169bf13f9584da5d --- /dev/null +++ b/browserid/static/steal/build/test/https.js @@ -0,0 +1 @@ +steal('https://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.js'); \ No newline at end of file diff --git a/browserid/static/steal/build/test/run.js b/browserid/static/steal/build/test/run.js index 28264b46b40bf20f66afcb8c6eea3a80ec037958..4078b60101d93d203ebf41bdf07d363296782406 100644 --- a/browserid/static/steal/build/test/run.js +++ b/browserid/static/steal/build/test/run.js @@ -66,5 +66,6 @@ steal('//steal/test/test', function( s ) { s.test.remove('steal/build/test/foreignproduction.js') }); + }); \ No newline at end of file diff --git a/browserid/static/steal/clean/clean.js b/browserid/static/steal/clean/clean.js index 317ac03a55d65272b75d93012f2eb2f63c3e46d8..1c559093ec3606fbfc23bece3976733851643921 100644 --- a/browserid/static/steal/clean/clean.js +++ b/browserid/static/steal/clean/clean.js @@ -20,6 +20,7 @@ steal.plugins('steal/build').then('//steal/clean/beautify','//steal/clean/jslint Math.min(error.character+25, line.length)).replace(/^\s+/,"") ) + print(" "+error.reason); print(" ") } } diff --git a/browserid/static/steal/generate/generate.js b/browserid/static/steal/generate/generate.js index ddcbccce646070d55d6d51464cb846f74d08c660..48017fa816e43e00dbb6282754ef82a0f8a79410 100644 --- a/browserid/static/steal/generate/generate.js +++ b/browserid/static/steal/generate/generate.js @@ -175,7 +175,8 @@ steal("//steal/generate/ejs", '//steal/generate/inflector', '//steal/rhino/promp colons: /::/, words: /([A-Z]+)([A-Z][a-z])/g, lowerUpper: /([a-z\d])([A-Z])/g, - dash: /([a-z\d])([A-Z])/g + dash: /([a-z\d])([A-Z])/g, + undHash: /_|-/ }, underscore: function( s ) { var regs = this.regexps; @@ -185,6 +186,19 @@ steal("//steal/generate/ejs", '//steal/generate/inflector', '//steal/rhino/promp .replace(regs.dash, '_').toLowerCase(); }, //converts a name to a bunch of useful things + + /** + * @hide + * FooBar.ZedTed -> + * { + * appName : "foobar", + * className : "ZedTed", + * fullName : "FooBar.ZedTed", + * name : "FooBar.ZedTed", + * path : foo_bar, + * underscore : "zed_ted" + * } + */ convert: function( name ) { var className = name.match(/[^\.]*$/)[0]; //Customer var appName = name.split(".")[0]; //Customer @@ -195,7 +209,7 @@ steal("//steal/generate/ejs", '//steal/generate/inflector', '//steal/rhino/promp fullName: name, className: className, plural: steal.Inflector.pluralize(generate.underscore(className)), - appName: appName.toLowerCase() + appName: generate.underscore(appName) }; }, render: render diff --git a/browserid/static/steal/get/basic.js b/browserid/static/steal/get/basic.js new file mode 100644 index 0000000000000000000000000000000000000000..1f3c32ab5373357ae6a9680b76ca25210467b6af --- /dev/null +++ b/browserid/static/steal/get/basic.js @@ -0,0 +1,26 @@ +steal(function(s){ + +/** + * A basic getter + * @param {Object} content + * @param {Object} rawUrl + * @param {Object} originalUrl + */ +steal.get.basic = { + ls: function(content, rawUrl, originalUrl){ + var data = {}; + content.replace(/href\s*=\s*\"*([^\">]*)/ig, function(whole, link){ + if (!/svnindex.xsl$/.test(link) && !/^(\w*:|)\/\//.test(link) && !/^\./.test(link) ) { + data[link] = originalUrl + link + } + }) + return data; + + }, + // return the 'raw' place to either get folder's contents, or download file ... + raw: function(url){ + return url; + } +} + +}); \ No newline at end of file diff --git a/browserid/static/steal/get/dummysteal.js b/browserid/static/steal/get/dummysteal.js new file mode 100644 index 0000000000000000000000000000000000000000..658d9924e28d22c4aeaea3653e8f2a38443fc628 --- /dev/null +++ b/browserid/static/steal/get/dummysteal.js @@ -0,0 +1,54 @@ +// a dummy version of steal that can run on a file and extract data from it ... +steal(function(s){ + var makeFunc = function(name){ + return function(){ + for(var i =0; i < arguments.length; i++){ + this["_"+name].push(arguments[i]) + } + + return this; + } + } + + s.dummy = function(code){ + var args = []; + var dummy = function(){ + args.push(arguments); + } + + for(var prop in s){ + if(typeof s[prop] == 'function'){ + dummy["_"+prop] = [] + dummy[prop] = makeFunc(prop); + }else { + dummy[prop] = {}; + } + } + //additional funcs (for 3.0) + var funcs = ['plugins','views','models','controllers','css','less']; + for(var i =0; i < funcs.length; i++){ + var prop = funcs[i]; + + dummy["_"+prop] = [] + dummy[prop] = makeFunc(prop); + } + + //save current steal + var curSteal = steal; + //replace ... + + steal = dummy; + + eval(code) + + steal = curSteal; + for(var prop in dummy){ + if(prop.substr(0,1) === "_"){ + dummy[prop.substr(1)] = dummy[prop]; + } + } + return dummy; + } + + +})() diff --git a/browserid/static/steal/get/get.js b/browserid/static/steal/get/get.js index 72b44e707a15fac07ad5b1ac8a47a00fcf21e37a..a7be6f0a415cdb2704be1db34ef928dda1830064 100644 --- a/browserid/static/steal/get/get.js +++ b/browserid/static/steal/get/get.js @@ -1,102 +1,182 @@ -steal("//steal/get/json", "//steal/rhino/prompt", function( steal ) { +steal("//steal/get/json", + "//steal/rhino/prompt", + "//steal/get/dummysteal",function( steal ) { + + // a map of plugins that you just installed (prevents cycles) + var installed = {}; + /** + * @class steal.get * @parent stealjs - * Downloads and installs a plugin from a url. Normally this is run from the steal/getjs script. - * - * <p>The following copies the mustache-javascript repo to a local mustache folder.</p> - * - * @codestart text - * js steal/getjs "ttp://github.com/tdreyno/mustache-javascriptmvc mustache - * @codeend - * <p>Get will:</p> - * <ul> - * <li>Download the files that comprise the plugin.</li> - * <li>Prompt you to install dependencies found in its dependencies.json file.</li> - * <li>Prompt you to run an install script.</li> - * </ul> - * <h2>Offical Plugins</h2> - * <p>JavaScriptMVC maintains a list of offical plugins compatible with JavaScriptMVC 3.0. - * You can install these by simply typing there name. This is the current list of - * offical plugins: - * </p> - * <ul> - * <li><code>mustache</code> - mustache templates.</li> - * <li><code>steal</code> - script loader, and more.</li> - * <li><code>jquery</code> - jQuery 1.4.3 and the MVC components.</li> - * <li><code>funcunit</code> - Functional testing platform.</li> - * <li><code>mxui</code> - UI widgets.</li> - * <li><code>documentjs</code> - documentation engine.</li> - * </ul> - * <p>You can install these just by writing</p> - * @codestart text - * js steal/getjs funcunit - * @codeend - * <p>If you have something good, let us know on the forums and we can make your project official too!</p> - * <h2>The Get function</h2> - * get takes a url or official plugin name and installs it. + * + * Downloads and installs a plugin from a url. Normally + * this is run from the steal/getjs script. + * + * The following copies the mustache-javascript repo to a + * local mustache folder. + * + * + * js steal/getjs http://github.com/tdreyno/mustache-javascriptmvc mustache + * + * Get will: + * + * - Download the plugins files. + * - Prompt you to install dependencies. + * - Prompt you to run an install script. + * + * ## Offical Plugins + * + * JavaScriptMVC maintains a list of offical plugins compatible with JavaScriptMVC 3.0. + * You can install these by simply typing there name. This is the current list of + * offical plugins: + * + * - <code>mustache</code> - mustache templates. + * - <code>steal</code> - script loader, and more. + * - <code>jquery</code> - jQuery 1.4.3 and the MVC components. + * - <code>funcunit</code> - Functional testing platform. + * - <code>mxui</code> - UI widgets. + * - <code>documentjs</code> - documentation engine. + * + * You can install these just by writing + * + * js steal/getjs funcunit + * + * If you have something good, let us know on the forums and we can make your project official too! + * + * ## Making your own Getter + * + * This is easy to do and will be documented shortly. + * + * @constructor + * + * Get takes a url or official plugin name and installs it. + * * @param {String} url the path to a svn or github repo or a name of a recognized plugin. * @param {Object} options configure the download. - * <table class='options'> - * <tr> - * <th>Name</th><th>Description</th> - * </tr> - * <tr><td>name</td> - * <td>The name of the folder to put the download in.</td></tr> - * <tr><td>ignore</td> - * <td>An array of regexps that if the filename matches, these will be ignored.</td></tr> - * </table> * + * - __name__ - The name of the folder to put the download in. + * - __ignore__ - An array of regexps that if the filename matches, these will be ignored. + * + * @return {boolean} if the installation was successful */ var get = (steal.get = function( url, options ) { + options = steal.opts(options, { name: 1 }); - var getter, name = options.name, dependenciesUrl; - + + var getter, + name = options.name, + dependenciesUrl; + + // if not a url, get the url from the plugin list if (!url.match(/^http/) ) { name = url; - url = pluginList(name); + url = get.url(name); } + + // if we don't get a url back, throw an error if (!url ) { steal.print("There is no plugin named " + name); return; } - getter = url.indexOf("github.com") !== -1 ? get.github : get.getter; + // if we don't have a name (a place for the app) try to guess one from the url if (!name ) { - name = guessName(url); + name = get.guessName(url); } + options.name = name; + options.quiet = options.quiet === undefined ? true : options.quiet; + options.ignore = options.ignore === undefined ? [] : options.ignore; + + // pick the default getter or the github getter ... somehow getters should register themselves + options.getter = options.getter ? get[options.getter] : url.indexOf("github.com") !== -1 ? get.git : get.basic; + //make the folder for this plugin - new steal.File(name).mkdirs(); + //new steal.File(name).mkdirs(); + + // check for a dependency file + steal.print(" Checking dependencies ... "); + + + //dependenciesUrl = getter.dependenciesUrl(url); - dependenciesUrl = getter.dependenciesUrl(url); - - installDependencies(dependenciesUrl, name); + get.installDependencies(url, options ); + // check if we should be installing dependencies read from the JS file itself + if( name.indexOf("/") !== -1 ){ + + var steals = get.steals(url, options); + for(var i =0; i < steals.length; i++){ + get.installDependency(steals[i]); + } + } + + steal.print(" "); + //get contents - var fetcher = new getter(url, name, options); - fetcher.quiet = options.quiet || true; + get.fetch(url, + /\/$/.test(options.name) ? options.name : options.name+"/", + options); + + //var fetcher = new getter(url, name, options); + //fetcher.quiet = options.quiet || true; - fetcher.fetch(); + //fetcher.fetch(); steal.print("\n " + name + " plugin downloaded."); - runInstallScript(name); + get.runInstallScript(name); - }), + }), + folderTest = /\/$/, + trim = /\s+$/gm, + jarTest = /\.jar$/, + lastPart = /([^\/]+)\/$/; + + + steal.extend(get,{ /** - * @hide - * looks for a url elsewhere - * @param {Object} name + * Gets url from plugin name using the urls at: + * + * https://github.com/jupiterjs/steal/raw/master/get/gets.json + * + * Or locally at + * + * //gets.json + * + * ## API + * + * @param {String} name the name of the project (ex: 'funcunit') + * @return {String} the url of the repository (ex: 'http://github.com/jupiterjs/funcunit') */ - pluginList = function( name ) { - steal.print(" Looking for plugin ..."); + url: function( name ) { + //steal.print(" Looking for plugin ..."); var plugin_list_source = - readUrl("https://github.com/jupiterjs/steal/raw/master/get/gets.json"); - var plugin_list; + readUrl("https://github.com/jupiterjs/steal/raw/master/get/gets.json"), + plugin_list; + eval("plugin_list = " + plugin_list_source); if ( plugin_list[name] ) { return plugin_list[name]; } + // check if the first part matches .... + + var parts = name.split("/") + firstPart = parts.shift(); + if(plugin_list[firstPart]){ + var first = plugin_list[firstPart]; + if(/github\.com/.test(first) && !/tree\/\w+/.test(first)){ + // http://github.com/jupiterjs/mxui -> + // http://github.com/jupiterjs/mxui/tree/master/util/selectable/ + return first+"/tree/master/"+parts.join("/")+"/" + //first = first.replace(/[^\/]+$/g, function(end){ + // + //}) + } + return first; + } + + steal.print(" Looking in gets.json for your own plugin list") plugin_list_source = readFile("gets.json"); @@ -107,7 +187,7 @@ steal("//steal/get/json", "//steal/rhino/prompt", function( steal ) { }, //gets teh name from the url - guessName = function( url ) { + guessName: function( url ) { var name = new steal.File(url).basename(); if ( name === 'trunk' || !name ) { name = new steal.File(new steal.File(url).dir()).basename(); @@ -116,25 +196,9 @@ steal("//steal/get/json", "//steal/rhino/prompt", function( steal ) { }, // works for // https://github.com/jupiterjs/funcunit/raw/master/dependencies.json - installDependencies = function( depend_url, name ) { - steal.print(" Checking dependencies ..."); - var depend_text, dependencies; - - try { - depend_text = readUrl(depend_url); - } catch (e) {} + installDependencies: function( url, options ) { - if (!depend_text ) { - steal.print(" No dependancies"); - return; - } - - try { - dependencies = JSONparse(depend_text); - } catch (e) { - steal.print(" No or mailformed dependencies"); - return; - } + var dependencies = get.dependencies(url, options); for ( var plug_name in dependencies ) { if ( steal.prompt.yesno("Install dependency " + plug_name + "? (yN):") ) { @@ -145,9 +209,9 @@ steal("//steal/get/json", "//steal/rhino/prompt", function( steal ) { } } - steal.print(" Installed all dependencies for " + name); + //steal.print(" Installed all dependencies for " + name); }, - runInstallScript = function( name ) { + runInstallScript: function( name ) { if ( readFile(name + "/install.js") ) { var res = steal.prompt.yesno("\n " + name + " has an install script." + "\n WARNING! Install scripts may be evil. " + "\n You can run it manually after reading the file by running:" + "\n js " + name + "/install.js" + "\n\n Would you like to run it now? (yN):"); @@ -156,7 +220,295 @@ steal("//steal/get/json", "//steal/rhino/prompt", function( steal ) { load(name + "/install.js"); } } - }; + }, + pluginDependencies : function(url){ + //steal.print(" Checking plugin file ..."+url); + var script, dependencies; + + try { + script = readUrl(url); + } catch (e) { + steal.print("No plugin file"); + return; + } + if(/steal/.test(script)){ + try{ + var stealCalls = steal.dummy(script) + } catch(e){ + //steal.print("Unable to figure out plugins. Are you using steal in an unusual way?"); + //return; + } + } + // get non-jquery plugins and see if they want to install ... + var plugins = [] + for(var i = 0; i < stealCalls.plugins.length; i++){ + var plugin = stealCalls.plugins[i]; + if(!/^jquery\/|steal/.test(plugin) && plugin != 'jquery' ){ + plugins.push(stealCalls.plugins[i]) + } + } + + if (!plugins.length ) { + //steal.print(" No dependancies"); + return; + } + //print("length", plugins.length) + return plugins; + }, + installDependency : function(depend){ + if(installed[depend]){ + return; + } + if(steal.File(depend).exists()){ + installed[depend] = true; + if ( steal.prompt.yesno("Update dependency " + depend + "? (yN):") ) { + steal.print("Updating " + depend + "..."); + steal.get(depend, { + name: depend + }); + } + + return false; + }else{ + + if ( steal.prompt.yesno("Install dependency " + depend + "? (yN):") ) { + installed[depend] = true; + steal.print("Installing " + depend + "..."); + steal.get(depend, { + name: depend + }); + } + + return true; + } + }, + /** + * Recursively gets the contents of a folder at a url, and puts it at path. + * + * The following gets everything in the controller folder + * + * steal.fetch( + * "https://github.com/jupiterjs/jquerymx/tree/master/controller", + * "jquery/controller/controller", + * {getter: steal.get.git}) + * + * + * ## API + * + * @param {Object} url the 'human' folder name + * @param {Object} path a folder on the local filesystem to put the contents of the folder in. Must end in /. + * @param {Object} options options to configure the downloading. It has the following properties: + * + * * __ignore__ - an array of regular expressions that can be used to ignore certain paths. + * * __getter__ - a getter object with a raw and ls methods. + */ + fetch : function(url, path, options ){ + // make the new folder + + + var raw = options.getter.raw(url), + content = readUrl(raw), + // only make a folder the first time we put a file in the folder + madeFolder = false; + + //print("\nfetching "+url+"--------\n\n") + + var urls = options.getter.ls(content, raw, url); + + //separate folders and files ... + pathloop: + for(var newPath in urls){ + + //print(" -"+urls[newPath]+"\n") + + var updatedPath = path+newPath; + + for ( var i = 0; i < options.ignore.length; i++ ) { + if ( options.ignore[i].test( updatedPath ) ) { + steal.print(" I " + updatedPath); + continue pathloop; + } + } + + if(folderTest.test(newPath)){ + + get.fetch( urls[newPath], updatedPath, options ) + + } else { + if(!madeFolder){ + new steal.File(path).mkdirs(); + madeFolder = true; + } + get.download( urls[newPath], updatedPath, options) + } + } + + }, + /** + * Downloads a url to path. Reports if the file was: + * + * - A - added + * - U - updated + * + * ### Example + * + * The following downloads controller using the git getter: + * + * steal.get.download( + * "https://github.com/jupiterjs/jquerymx/blob/master/controller/controller.js", + * "jquery/controller/controller.js", + * {getter: steal.get.git}) + * + * ## API + * + * @param {Object} url + * @param {Object} path + * @param {Object} options + */ + download: function(url, path, options){ + var raw = options.getter.raw(url), + oldsrc = readFile(path), + tmp = new steal.File("tmps"), + pstar = " ", + newsrc; + + + try{ + tmp.download_from(raw, true); + newsrc = readFile("tmps"); + + }catch(e){ + tmp.remove(); + steal.print("\n"+pstar+"Error downloading "+path+"\n from "+raw+"\n"+e+"\n"); + return; + } + + // if we have an old one, lets compare it + if ( oldsrc ) { + + // if they are the same, do nothing + if ( (! jarTest.test(path) && oldsrc.replace(trim, '') == newsrc.replace(trim, '')) ) { + tmp.remove(); + return; + } + // move .. + steal.print(pstar + "U " + path); + tmp.copyTo(path); + } else { + steal.print(pstar + "A " + path); + tmp.copyTo(path); + } + tmp.remove(); + }, + /** + * Gets dependencies JSON from a folder url + * + * steal.get.dependencies("https://github.com/jupiterjs/funcunit", + * {getter: steal.get.git}); + * + * // -> { "funcunit/syn" : "http://github.com/jupiterjs/syn" } + * + * @param {Object} url + * @param {Object} options + * @return {Object} a map of plugin-url pairs. + */ + dependencies : function(url, options){ + var dependUrl = get.file(url, 'dependencies.json', options); + + if(dependUrl){ + var dependencyUrl = options.getter.raw(dependUrl), + dependencyText, + dependencies; + + try { + dependencyText = readUrl(dependencyUrl); + } catch (e) {} + + if (!dependencyText ) { + //steal.print(" No dependancies"); + return {}; + } + + try { + return JSONparse(dependencyText); + } catch (e) { + steal.print(" No or mailformed dependencies"); + return {}; + } + } + }, + /** + * @hide + * A helper that gets a file named file within a folderUrl + * @param {Object} folderUrl + * @param {Object} file + */ + file : function(folderUrl, file, options){ + var raw = options.getter.raw(folderUrl), + content = readUrl(raw); + var urls = options.getter.ls(content, raw, folderUrl); + return urls[file]; + }, + /** + * Gets a list of plugins stolen by a file (or plugin w/i a folder) + * + * steal.get.steals("https://github.com/jupiterjs/mxui/tree/master/data/grid", + * {getter: steal.get.git}); + * + * // -> ['mxui/layout/table_scroll','mxui/data','jquery/controller/view',..] + * + * @param {Object} url + * @param {Object} options + * @return {Array} an array of stolen JS files. + */ + steals : function(url, options){ + // is it a folder, if so, get it's contents and file ... + if(!/\.js$/.test(url) && !/\/$/.test(url)){ // has to be a folder (if it isn't already) + url = url +"/"; + } + + if(folderTest.test(url)){ + url = get.file(url, url.match(lastPart)[1]+".js", options); //p + if(!url){ + // there is no plugin file + return []; + } + } + var raw = options.getter.raw(url), + script, + stealCalls, + plugins = []; + + try { + script = readUrl(raw); + } catch (e) { + steal.print("Error reading file at "+raw); + return []; + } + + if(/steal/.test(script)){ + + try{ + var stealCalls = steal.dummy(script) + } catch(e){ + steal.print("Unable to figure out plugins. Are you using steal in an unusual way?"); + return []; + } + + // get non-jquery plugins and see if they want to install ... + for(var i = 0; i < stealCalls.plugins.length; i++){ + var plugin = stealCalls.plugins[i]; + if(!/^jquery\/|steal/.test(plugin) && plugin != 'jquery' ){ + plugins.push(stealCalls.plugins[i]) + } + } + } + + //print("length", plugins.length) + return plugins; + } + }) -}, "//steal/get/getter", "//steal/get/github"); \ No newline at end of file + +},"//steal/get/basic","//steal/get/git"); \ No newline at end of file diff --git a/browserid/static/steal/get/gets.json b/browserid/static/steal/get/gets.json index 85263219650bc75a009ee16f2c549d1ab570bb96..a7759006e4ce5ec7e5fcf58fde521f8165b6e7b5 100644 --- a/browserid/static/steal/get/gets.json +++ b/browserid/static/steal/get/gets.json @@ -1,12 +1,14 @@ { - "mustache" : "http://github.com/tdreyno/mustache-javascriptmvc", - "steal" : "http://github.com/jupiterjs/steal", - "jquery" : "http://github.com/jupiterjs/jquerymx", - "funcunit" : "http://github.com/jupiterjs/funcunit", - "mxui" : "http://github.com/jupiterjs/mxui", - "documentjs" : "http://github.com/jupiterjs/documentjs", - "ss/state_machine" : "http://github.com/secondstory/secondstoryjs-statemachine", - "ss/router" : "http://github.com/secondstory/secondstoryjs-router", + "mustache" : "https://github.com/tdreyno/mustache-javascriptmvc", + "steal" : "https://github.com/jupiterjs/steal", + "jquery" : "https://github.com/jupiterjs/jquerymx", + "funcunit" : "https://github.com/jupiterjs/funcunit", + "mxui" : "https://github.com/jupiterjs/mxui", + "documentjs" : "https://github.com/jupiterjs/documentjs", + "ss/state_machine" : "https://github.com/secondstory/secondstoryjs-statemachine", + "ss/router" : "https://github.com/secondstory/secondstoryjs-router", "srchr" : "https://github.com/jupiterjs/srchr/tree/master/srchr/", - "mxutil" : "http://github.com/jupiterjs/mxutil" + "mxutil" : "http://github.com/jupiterjs/mxutil", + "jqueryui" : "https://github.com/daffl/jqueryui", + "todo": "https://github.com/jupiterjs/todo/tree/master/todo/" } diff --git a/browserid/static/steal/get/git.js b/browserid/static/steal/get/git.js new file mode 100644 index 0000000000000000000000000000000000000000..19c3628c2cd3e01f061fa86236696408a822572f --- /dev/null +++ b/browserid/static/steal/get/git.js @@ -0,0 +1,100 @@ + + + +steal(function(s){ + + + +// gets the last commit for a project from github's api +var lastCommitId = function(inf){ + var commitsText = readUrl("https://github.com/api/v2/json/commits/list/" + + inf.user + "/" + + inf.repo + "/" + + inf.branch); + eval("var c = " + commitsText); + return c.commits[0].tree; +}, +// returns a url of commit data +lastCommitUrl = function(inf){ + return "https://github.com/api/v2/json/tree/show/" + + inf.user + "/" + + inf.repo + "/" + lastCommitId(inf); + + +}, +github = s.get.git = { + // get a map of names to urls ... urls don't have to be pretty ... + ls : function(content, rawUrl, originalUrl){ + var info = github.info(originalUrl); + + //print("item -- "+info.base) + + var data = {}; + + // if we are the top level folder, use lastCommitData + if( info.resource == "/" ) { + eval("var commits = " + content); + // use gitHub's commit API + commits.tree.forEach(function(item){ + if(item.name.indexOf(".git") === 0){ + + } else if ( item.type == "blob" ) { + data[item.name] = info.base + item.name; + } + else if ( item.type == "tree" ) { + data[item.name+"/"] = info.base + item.name+"/"; + } + }) + + } else { + content.replace(/href\s*=\s*\"*([^\">]*)/ig, function(whole, url){ + if(url.indexOf(".git") === 0){ + return; + } + data[url] = info.base + url + }) + } + + return data; + + }, + // return the 'raw' place to either get folder's contents, or download file ... + raw : function(url){ + // https://github.com/secondstory/secondstoryjs-plugins/ + // --> https://github.com/secondstory/secondstoryjs-plugins/ + var info = github.info(url); + if(info.resource == "/"){ // root level folder + return lastCommitUrl(info) + } else if( /\/$/.test(url) ) { // a folder + return "https://"+info.domain+"/"+info.user+"/"+info.repo+"/tree/"+info.branch+"/"+info.resource+"?raw=true" + } else { //download url ... + return "https://"+info.domain+"/"+info.user+"/"+info.repo+"/raw/"+info.branch+"/"+info.resource + } + }, + // helper to get info from a github url + info : function(url){ + var split = url.split("/"), + data = {}, + branch; + data.protcol = split.shift(); + split.shift(); + data.domain = split.shift(); + data.user = split.shift(); + data.repo = split.shift(); + + branch = split.shift(); + if(branch === 'tree' || branch === 'raw' || branch === 'blob'){ + branch = split.shift(); + } + data.branch = branch || 'master'; + + data.resource = split.join('/').replace(/\?.*/,"") || "/"; + data.base = "https://"+data.domain+"/"+data.user+"/"+data.repo+"/tree/"+data.branch+"/"+( data.resource == "/" ? "" : data.resource) + return data; + }, + lastCommitUrl : lastCommitUrl + }; + + + +})() diff --git a/browserid/static/steal/get/test/.gitignore b/browserid/static/steal/get/test/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..209b1559f2c28680dac09371a49924b24fbffec8 --- /dev/null +++ b/browserid/static/steal/get/test/.gitignore @@ -0,0 +1,5 @@ +.tmp* +*.log +docs/* +dist +synthetic/dist \ No newline at end of file diff --git a/browserid/static/steal/get/test/.gitmodules b/browserid/static/steal/get/test/.gitmodules new file mode 100644 index 0000000000000000000000000000000000000000..1e7130ff57700fbb99b88b7d31e3a26528eb46f6 --- /dev/null +++ b/browserid/static/steal/get/test/.gitmodules @@ -0,0 +1,4 @@ +[submodule "syn"] + path = syn + url = git://github.com/jupiterjs/syn.git + update = merge \ No newline at end of file diff --git a/browserid/static/steal/get/test/README b/browserid/static/steal/get/test/README new file mode 100644 index 0000000000000000000000000000000000000000..dee5b775581f3482634083a1150754ec3b74d841 --- /dev/null +++ b/browserid/static/steal/get/test/README @@ -0,0 +1,17 @@ +Relevant links: +1. http://jupiterit.com/#news/funcunit-fun-web-application-testing +2. http://groups.google.com/group/funcunit +3. http://twitter.com/funcunit +4. http://funcunit.com/ + + +Building a new FuncUnit +1. Clone the repository at http://github.com/jupiterjs/javascriptmvc +2. Use git submodule init and git submodule update to get the submodules. The only ones we'll need are steal and funcunit +3. Unzip funcunit/java/selenium-server.jar into a folder at funcunit/java/selenium-server/ +4. Make whatever changes you want to syn, funcunit, drivers, or selenium. +5. From the framework directory, on the command line run js funcunit/build.js. This will build the JS scripts and copy the relevant files. +6. Using a tool like 7-zip (for windows), zip up the java/selenium-server folder. Copy the selenium-server.jar into the funcunit/java folder. +7. Run js funcunit/build.js again (to copy the jars). + +That's it, funcunit/dist contains the same standalone funcunit placed into the standalone download. diff --git a/browserid/static/steal/get/test/get_test.js b/browserid/static/steal/get/test/get_test.js index ffabbfbe7ffe709aff258961c5c74c5b2dba1305..2bd96902acc536b6e146b68b3040fec6d9cf8d75 100644 --- a/browserid/static/steal/get/test/get_test.js +++ b/browserid/static/steal/get/test/get_test.js @@ -2,12 +2,35 @@ load('steal/rhino/steal.js') load('steal/rhino/test.js'); steal('//steal/get/get',function(rhinoSteal){ - _S = steal.test; + var _S = steal.test; - + _S _S.module("steal/get") - STEALPRINT = false; + // STEALPRINT = false; + + _S.test("pluginList", function(t){ + var url = rhinoSteal.get.url("mxui/util/selectable"); + + t.equals(url, "http://github.com/jupiterjs/mxui/tree/master/util/selectable/", "Right url") + }); + + + _S.test("dummySteal", function(t){ + var code = readFile('steal/get/test/stealCode1.js'); + var results = rhinoSteal.dummy(code); + t.equals(results.plugins[0], "foo/bar", "first is right"); + t.equals(results.plugins.length, 4, "has other plugins") + }); + + + _S.test("installDependency", function(t){ + rhinoSteal.File("jqueryui").removeDir(); + //t.equals( rhinoSteal.get.installDependency("jquery/controller") , false, "exists" ); + t.equals( rhinoSteal.get.installDependency("jqueryui/draggable") , true, "doesn't exist" ); + + + }); _S.test("root repo" , function(t){ @@ -15,7 +38,7 @@ steal('//steal/get/get',function(rhinoSteal){ var license = readFile("ss/router/LICENSE"); - t.ok(license, "srchr downloaded"); + t.ok(license, "ss downloaded"); rhinoSteal.File("ss").removeDir(); }); @@ -29,5 +52,112 @@ steal('//steal/get/get',function(rhinoSteal){ }); + var G = steal.get; + + + + _S.module("steal/get/github") + // STEALPRINT = false; + + _S.test("github.info", function(t){ + var info = G.git.info("https://github.com/secondstory/secondstoryjs-plugins/"); + + t.equals(info.user, "secondstory", "Right user"); + t.equals(info.repo, "secondstoryjs-plugins", "Right repo"); + t.equals(info.branch, "master", "Right branch"); + t.equals(info.resource, "/", "Right resource"); + }); + + _S.test("github.raw", function(t){ + // a file + var raw = G.git.raw("https://github.com/jupiterjs/srchr/tree/master/srchr/disabler/disabler.html"); + t.equals(raw, "https://github.com/jupiterjs/srchr/raw/master/srchr/disabler/disabler.html", "file"); + + raw = G.git.raw("https://github.com/secondstory/secondstoryjs-plugins/blob/master/jScrollPane/jScrollPane.js"); + t.equals(raw, "https://github.com/secondstory/secondstoryjs-plugins/raw/master/jScrollPane/jScrollPane.js", "file"); + + // folders + raw = G.git.raw("https://github.com/secondstory/secondstoryjs-plugins/tree/master/jScrollPane/") + t.equals(raw,"https://github.com/secondstory/secondstoryjs-plugins/tree/master/jScrollPane/?raw=true","folder") + + // root + raw = G.git.raw("https://github.com/jupiterjs/funcunit") + t.equals(raw.indexOf("https://github.com/api/v2/json/tree/show/jupiterjs/funcunit/"), 0, "root"); + + raw = G.git.raw("https://github.com/jupiterjs/funcunit/tree/2.0") + t.equals(raw.indexOf("https://github.com/api/v2/json/tree/show/jupiterjs/funcunit/"), 0, "root"); + + }); + + _S.test("github.ls", function(t){ + var raw = G.git.raw("https://github.com/jupiterjs/funcunit"), + contents = readUrl(raw); + + var map = G.git.ls(contents, raw, "https://github.com/jupiterjs/funcunit"); + + //for(var name in map){ + // print(name+" - "+map[name]) + //} + + }); + + _S.test("fetcher.download", function(t){ + var raw = G.git.raw("https://github.com/jupiterjs/funcunit/blob/master/dependencies.json"), + out = "steal/get/test/out.js"; + + + G.download(raw,out,{getter: G.git}); + + var stuff = readFile(out); + t.ok(stuff, "there is stuff"); + + new steal.File(out).remove(); + }); + + _S.test("fetch with git, ignore all", function(t){ + + G.fetch("https://github.com/jupiterjs/funcunit", + "steal/get/test/",{ + getter : G.git, + ignore : [/.*[^\/]$/] + }); + + }); + + _S.test("fetch with basic, ignore all", function(t){ + + G.fetch("http://jabbify.googlecode.com/svn/trunk/jabbify/apps/", + "steal/get/test/",{ + getter : G.basic, + ignore : [/.*[^\/]$/] + }); + + }); + + + _S.test("fetch dependencies", function(t){ + + var depends = steal.get.dependencies("https://github.com/jupiterjs/funcunit", + {getter: steal.get.git}); + + t.equals(depends["funcunit/syn"], "https://github.com/jupiterjs/syn", "dependency"); + + }); + + _S.test("steals dependencies", function(t){ + + var steals = steal.get.steals("https://github.com/jupiterjs/mxui/tree/master/data/grid", + {getter: steal.get.git}); + t.ok(steals.length, "we got something") + // -> ['mxui/layout/table_scroll','mxui/data','jquery/controller/view',..] + + for(var i =0; i < steals.length; i++){ + if(steals[i].indexOf('mxui') > -1){ + t.ok(true, "we have mxui") + } + } + + }); + }); diff --git a/browserid/static/steal/get/test/stealCode1.js b/browserid/static/steal/get/test/stealCode1.js new file mode 100644 index 0000000000000000000000000000000000000000..f016cdf7ece0385655ca7888b523b68ebd5b1c2b --- /dev/null +++ b/browserid/static/steal/get/test/stealCode1.js @@ -0,0 +1,10 @@ +steal.plugins("foo/bar", +/** + * Comment + * @param {Object} "something/else" + */ +"abc/def").then(function(){ + +}).plugins("something/else"); + +steal.plugins("one/two"); diff --git a/browserid/static/steal/js b/browserid/static/steal/js index 01c86b67685a49ffe3e7522c7c3fa90d88d1093b..3814a90f5433002b6682e91731ab535036486f64 100755 --- a/browserid/static/steal/js +++ b/browserid/static/steal/js @@ -20,6 +20,13 @@ then shift fi +ERRORLEV=0 +if [ $1 = "-e" ] +then + ERRORLEV=1 + shift +fi + if [ $1 = "-h" -o $1 = "-?" -o $1 = "--help" ] then echo Load a command line Rhino JavaScript environment or run JavaScript script files in Rhino. @@ -37,7 +44,6 @@ echo -e "./js apps/[NAME]/compress.js\t\tCompress your application and generate exit 127 fi - if [ $1 = "-d" ] then java -classpath steal/rhino/js.jar:steal/rhino/selenium-java-client-driver.jar org.mozilla.javascript.tools.debugger.Main @@ -54,3 +60,8 @@ do done ARGS=$ARGS] java -Xss1024k -cp $CP org.mozilla.javascript.tools.shell.Main -e _args=$ARGS -opt -1 -e 'load('"'"$1"'"')' + +if [ $ERRORLEV = "1" -a $? = "1" ] +then + exit $? +fi diff --git a/browserid/static/steal/js.bat b/browserid/static/steal/js.bat index 8ff1f2b5d2185190b943ebe20f61e4317a54047f..de1f03ae211d76da4b2bfa6da64ff76d45ecc94d 100644 --- a/browserid/static/steal/js.bat +++ b/browserid/static/steal/js.bat @@ -24,6 +24,11 @@ if "%1"=="-mail" ( SET CP=steal/rhino/mail.jar;funcunit/java/selenium-java-client-driver.jar;steal\rhino\js.jar SHIFT /0 ) +SET ERRORLEV=0 +if "%1"=="-e" ( + SET ERRORLEV=1 + SHIFT /0 +) SET ARGS=[ SET FILENAME=%1 SET FILENAME=%FILENAME:\=/% @@ -39,6 +44,10 @@ SET ARGS=%ARGS%] set ARGS=%ARGS:\=/% java -Xmx228m -Xss1024k -cp %CP% org.mozilla.javascript.tools.shell.Main -opt -1 -e _args=%ARGS% -e load('%FILENAME%') +if "%ERRORLEV%"=="1" ( + if errorlevel 1 exit 1 +) + GOTO END :PRINT_HELP diff --git a/browserid/static/steal/less/less_engine.js b/browserid/static/steal/less/less_engine.js index bef8789e3c5c2811554826dcbf4e431a7b497e7c..e9f38383771ab6cbe6d0737c28ba76c336984053 100644 --- a/browserid/static/steal/less/less_engine.js +++ b/browserid/static/steal/less/less_engine.js @@ -1,5 +1,5 @@ // -// LESS - Leaner CSS v1.0.40 +// LESS - Leaner CSS v1.0.41 // http://lesscss.org // // Copyright (c) 2010, Alexis Sellier @@ -1829,14 +1829,14 @@ tree.mixin.Definition = function (name, params, rules) { this.rules = rules; this._lookups = {}; this.required = params.reduce(function (count, p) { - if (p.name && !p.value) { return count + 1 } - else { return count } + if (!p.name || (p.name && !p.value)) { return count + 1 } + else { return count } }, 0); this.parent = tree.Ruleset.prototype; this.frames = []; }; tree.mixin.Definition.prototype = { - toCSS: function () { return "" }, + toCSS: function () { return "" }, variable: function (name) { return this.parent.variable.call(this, name) }, variables: function () { return this.parent.variables.call(this) }, find: function () { return this.parent.find.apply(this, arguments) }, @@ -1862,7 +1862,8 @@ tree.mixin.Definition.prototype = { match: function (args, env) { var argsLength = (args && args.length) || 0, len; - if (argsLength < this.required) { return false } + if (argsLength < this.required) { return false } + if ((this.required > 0) && (argsLength > this.params.length)) { return false } len = Math.min(argsLength, this.arity); @@ -2609,4 +2610,4 @@ function error(e, href) { } } -})(window); +})(window); \ No newline at end of file diff --git a/browserid/static/steal/make.js b/browserid/static/steal/make.js index ad2b488af64636c45242964e3abcbe96a23003b7..6ea67f2e343649b6b21ed1604ff2fea15d837a51 100644 --- a/browserid/static/steal/make.js +++ b/browserid/static/steal/make.js @@ -1,4 +1,5 @@ load('steal/rhino/steal.js'); steal.File('steal/js').copyTo('js') +steal.File('js').setExecutable() steal.File('steal/js.bat').copyTo('js.bat') diff --git a/browserid/static/steal/parse/parse.js b/browserid/static/steal/parse/parse.js new file mode 100644 index 0000000000000000000000000000000000000000..1805bdb5dae752fa4bd1b6501da7c3b2486bb258 --- /dev/null +++ b/browserid/static/steal/parse/parse.js @@ -0,0 +1,233 @@ +steal("//steal/parse/tokens"). + plugins('steal/build').then(function(steal){ + +var isArray = function( array ) { + return Object.prototype.toString.call( array ) === "[object Array]"; +}, +same = function(a, b){ + a = steal.extend({},a); + for(var name in b){ + if(b[name] != a[name]){ + return false; + }else{ + delete a[name]; + } + } + //there should be nothing left in a + for(var name in a){ + return false; + } + return true; +}, +// a is like b, but doesn't have to have all of b's properties +like = function(a, b){ + for(var name in a){ + if(b[name] != a[name]){ + return false; + } + } + return true; +}; + +/** + * @class steal.parse + * @parent stealjs + * Returns an pull parser useful for walking through + * token streams. + * + * var p = steal.parse(" steal.dev.log('fo(') "); + * + * //parses until it finds thing( + * p.until( [ "thing", "(" ] ); + * + * //parse until it finds the matching ) to ( + * p.partner("("); + * + * ## API + * @constructor + * @param {String} str + * @return {steal.parse} an object that can be used to pull tokens. + */ +steal.parse = function(str){ + //print("Breaking up strs") + var tokens = str.tokens('=<>!+-*&|/%^', '=<>&|'), + tokenNum = 0, + lines; + + var moveNext = function(ignoreComments){ + var next = tokens[tokenNum++]; + if(next){ + //print("Next TOken = "+next.value); + } + if(next && ignoreComments && next.type === 'comment'){ + return moveNext(ignoreComments); + }else{ + return next; + } + + }, + getLineNum = function(pos){ + if(!lines){ + lines = str.split("\n"); + } + var cur = 0, + line = 0; + while(pos < cur+lines[line].length){ + line++; + } + return line + } + + return { + /** + * @attribute ignoreComments + * A boolean you can set to ignore comments. + * Comments are ignored by default. + * + * p.ignoreComments = true + */ + ignoreComments : true, + /** + * Moves to the next token and returns it. + * + * var p = steal.parse("CONTENT"), + * cur + * while( cur = p.moveNext() ){ + * + * } + * + * @return {token} A token like: + * + * {from: 22, to: 24, value: "hi", type: "string"} + */ + moveNext : function(){ + return moveNext(this.ignoreComments) + }, + /** + * Returns the next token. + * + * @return {token} A token like: + * + * {from: 22, to: 24, value: "hi", type: "string"} + */ + next : function(){ + return tokens[tokenNum]; + }, + /** + * Returns the current token. + * + * @return {token} A token like: + * + * {from: 22, to: 24, value: "hi", type: "string"} + */ + cur : function(){ + return tokens[tokenNum-1]; + }, + lineNum : function(token){ + token = token || tokens[tokenNum]; + return getLineNum(token.from); + }, + line : function(tokenOrLineNum){ + token = token || tokens[tokenNum]; + }, + /** + * Parses it until it finds the right partner of the + * left parameter. + * + * p.partner("(", function(token){ + * + * }) + * + * @param {String} left a string like (,[,{,< + * @param {Function} cb a function that gets called + * with all tokens between the left and right token. + * @return {token} the ending token + */ + partner : function(left, cb){ + var right = { + "(" : ")", + "[" : "]", + "{" : "}", + "<" : ">" + }[left], + count = 1, + token, + last, + prev; + + if(this.cur().value != left){ + this.until(left); + } + while(token = this.moveNext()){ + if(token.type == 'operator'){ + if(token.value === left){ + count++; + }else if(token.value === right){ + count--; + //print(" -"+count+" "+prev+" "+last) + if(count === 0){ + return token; + } + }else if(token.value === "/"){ + print("YOU SHOULD NOT BE HERE") + this.comment(); + } + } + cb && cb(token) + prev = last; + last = (token.value) + } + }, + /** + * Parses until it finds something you are looking for. + * + * until("function",")") -> looks for function or ) + * until(["foo",".","bar"]) -> looks for foo.bar + * + * + */ + until: function(){ + var token, + //where in each Pattern we've got a match + patternMatchPosition = [], + // an array of pattern arrays ... + patterns = [], + //makes an option into a token that can be compared against + makeTokens = function(tokens){ + var res = []; + for(var i =0 ; i < tokens.length; i++){ + res.push(typeof tokens[i] === 'string' ? {value : tokens[i]} : tokens[i]) + } + return res; + }, + callback = function(){}; + + for(var i =0; i < arguments.length;i++){ + patternMatchPosition[i] =0; + if(isArray(arguments[i])){ + patterns.push(makeTokens(arguments[i])) + }else if(typeof arguments[i] == 'function'){ + callback = arguments[i]; + } + else{ + patterns.push(makeTokens([arguments[i]])) + } + } + while (token = this.moveNext() ) { + for(i =0; i< patterns.length; i++){ + var pattern = patterns[i]; + + if( token.type !== "string" && like( pattern[patternMatchPosition[i]], token) ){ + patternMatchPosition[i] = patternMatchPosition[i]+1; + if(patternMatchPosition[i] === pattern.length){ + return token; + } + }else{ + patternMatchPosition[i] = 0; + } + } + } + } + } + }; +}) \ No newline at end of file diff --git a/browserid/static/steal/parse/parse_test.js b/browserid/static/steal/parse/parse_test.js new file mode 100644 index 0000000000000000000000000000000000000000..58dd605bc3d32aa0271d84eec6e555c4614812bf --- /dev/null +++ b/browserid/static/steal/parse/parse_test.js @@ -0,0 +1,76 @@ +// load('steal/compress/test/run.js') +/** + * Tests compressing a very basic page and one that is using steal + */ +load('steal/rhino/steal.js'); + +steal.plugins('steal/test','steal/parse').then( function( s ) { + STEALPRINT = false; + s.test.module("steal/parse") + + s.test.test("parse", function(t){ + var js = readFile('jquery/class/class.js'); + var tokens = js.tokens('=<>!+-*&|/%^', '=<>&|'); + + var js = readFile('jquery/view/ejs/ejs.js'); + var tokens = js.tokens('=<>!+-*&|/%^', '=<>&|'); + + var js = readFile('jquery/lang/vector/vector.js'); + var tokens = js.tokens('=<>!+-*&|/%^', '=<>&|'); + + var js = readFile('jquery/dom/fixture/fixture.js'); + var tokens = js.tokens('=<>!+-*&|/%^', '=<>&|'); + + var js = readFile('jquery/view/view.js'); + var tokens = js.tokens('=<>!+-*&|/%^', '=<>&|'); + + var js = readFile('jquery/lang/json/json.js'); + var tokens = js.tokens('=<>!+-*&|/%^', '=<>&|'); + + js = readFile('steal/build/pluginify/test/weirdRegexps.js'); + var tokens = js.tokens('=<>!+-*&|/%^', '=<>&|'); + + }) + + s.test.test("parse", function(t){ + var parser = steal.parse(readFile('steal/parse/test/testCode.js')), + token, + tokens = []; + + while(token = parser.moveNext()){ + tokens.push(token) + } + t.equals(tokens[0].value,"FooBar"); + t.equals(tokens[1].value,"Blah"); + }); + + s.test.test("parse steal plugins", function(t){ + var parser = steal.parse(readFile('steal/parse/test/stealCode1.js')), + tokens = []; + + parser.until(["steal",".","plugins","("]); + parser.partner("(", function(token){ + tokens.push(token); + //print("TOKEN = "+token.value, token.type) + }) + + t.equals(tokens[0].value,"foo/bar"); + t.equals(tokens[1].value,","); + t.equals(tokens[2].value,"abc/def") + + }); + + s.test.test("parse logs", function(t){ + var parser = steal.parse(readFile('steal/parse/test/dev.js')), + tokens = []; + + parser.until(["steal",".","dev",".","log","("]); + parser.partner("(", function(token){ + tokens.push(token); + }) + + t.equals(tokens[0].value,"()"); + + }); + +}); \ No newline at end of file diff --git a/browserid/static/steal/parse/test/stealCode1.js b/browserid/static/steal/parse/test/stealCode1.js new file mode 100644 index 0000000000000000000000000000000000000000..f016cdf7ece0385655ca7888b523b68ebd5b1c2b --- /dev/null +++ b/browserid/static/steal/parse/test/stealCode1.js @@ -0,0 +1,10 @@ +steal.plugins("foo/bar", +/** + * Comment + * @param {Object} "something/else" + */ +"abc/def").then(function(){ + +}).plugins("something/else"); + +steal.plugins("one/two"); diff --git a/browserid/static/steal/parse/test/testCode.js b/browserid/static/steal/parse/test/testCode.js new file mode 100644 index 0000000000000000000000000000000000000000..a26bfd5252dce5eaaa0738a7e77c4180f876dd30 --- /dev/null +++ b/browserid/static/steal/parse/test/testCode.js @@ -0,0 +1,17 @@ +/** + * Here is a comment I care about + */ +FooBar + +/* I don't care about this */ +Blah + + /* I am at the end, but I should still probably work */ + + /** + * Another one + * + * Too + */ + + diff --git a/browserid/static/steal/parse/tokens.js b/browserid/static/steal/parse/tokens.js new file mode 100644 index 0000000000000000000000000000000000000000..fa96189fa93d4afd775eb8e3ef27177474253e51 --- /dev/null +++ b/browserid/static/steal/parse/tokens.js @@ -0,0 +1,349 @@ +// tokens.js +// 2009-05-17 + +// (c) 2006 Douglas Crockford + +// Produce an array of simple token objects from a string. +// A simple token object contains these members: +// type: 'name', 'string', 'number', 'operator' +// value: string or number value of the token +// from: index of first character of the token +// to: index of the last character + 1 + +// Comments of the // type are ignored. + +// Operators are by default single characters. Multicharacter +// operators can be made by supplying a string of prefix and +// suffix characters. +// characters. For example, +// '<>+-&', '=>&:' +// will match any of these: +// <= >> >>> <> >= +: -: &: &&: && + + + +String.prototype.tokens = function (prefix, suffix) { + var c; // The current character. + var from; // The index of the start of the token. + var i = 0; // The index of the current character. + var length = this.length; + var n; // The number value. + var q; // The quote character. + var str; // The string value. + + var result = []; // An array to hold the results. + var prereg = true; + var make = function (type, value) { + +// Make a token object. + + //prereg = i && + // (('(,=:[!&|?{};'.indexOf(i.charAt(i.length - 1)) >= 0) || + // i === 'return') + //print(type+":"+value+"-") + prereg = (type == 'operator' || type === 'name') && + (value === 'return' || ('(,=:[!&|?{};'.indexOf(value.charAt(value.length - 1)) >= 0 ) ) + //print(type+" : "+value+" - "+prereg) + return { + type: type, + value: value, + from: from, + to: i + }; + + }; + var has = function(thIs, before){ + var j = i+1; + for (;;) { + c = this.charAt(j); + if(c === thIs){ + return true; + } + //print("|"+c+"|"+(c=="\n" || c=="\r")); + if (before.test(c) || c === '') { + return false; + } + j += 1; + } + } + +// Begin tokenization. If the source string is empty, return nothing. + + if (!this) { + return; + } + +// If prefix and suffix strings are not provided, supply defaults. + + if (typeof prefix !== 'string') { + prefix = '<>+-&'; + } + if (typeof suffix !== 'string') { + suffix = '=>&:'; + } + + +// Loop through this text, one character at a time. + + c = this.charAt(i); + while (c) { + from = i; + //print(c); +// Ignore whitespace. + + if (c <= ' ') { + i += 1; + c = this.charAt(i); + +// name. + + } else if (c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z') { + str = c; + i += 1; + for (;;) { + c = this.charAt(i); + if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9') || c === '_') { + str += c; + i += 1; + } else { + break; + } + } + //print(str); + result.push(make('name', str)); + +// number. + +// A number cannot start with a decimal point. It must start with a digit, +// possibly '0'. + + } else if (c >= '0' && c <= '9') { + str = c; + i += 1; + +// Look for more digits. + + for (;;) { + c = this.charAt(i); + if (c < '0' || c > '9') { + break; + } + i += 1; + str += c; + } + +// Look for a decimal fraction part. + + if (c === '.') { + i += 1; + str += c; + for (;;) { + c = this.charAt(i); + if (c < '0' || c > '9') { + break; + } + i += 1; + str += c; + } + } + +// Look for an exponent part. + + if (c === 'e' || c === 'E') { + i += 1; + str += c; + c = this.charAt(i); + if (c === '-' || c === '+') { + i += 1; + str += c; + c = this.charAt(i); + } + if (c < '0' || c > '9') { + make('number', str).error("Bad exponent"); + } + do { + i += 1; + str += c; + c = this.charAt(i); + } while (c >= '0' && c <= '9'); + } + +// Make sure the next character is not a letter. + + if (c >= 'a' && c <= 'z') { + str += c; + i += 1; + print(this.substr(i-20,20)) + print(this.substr(i,20)) + make('number', str).error("Bad number"); + } + +// Convert the string value to a number. If it is finite, then it is a good +// token. + + n = +str; + if (isFinite(n)) { + result.push(make('number', n)); + } else { + make('number', str).error("Bad number"); + } + +// string + + } else if (c === '\'' || c === '"') { + str = ''; + q = c; + i += 1; + //print("----") + for (;;) { + c = this.charAt(i); + //print(this[i]) + if (c < ' ') { + print(this.substr(i-20,20)) + print(this.substr(i,20)) + make('string', str).error(c === '\n' || c === '\r' || c === '' ? + "Unterminated string." : + "Control character in string.", make('', str)); + } + +// Look for the closing quote. + + if (c === q) { + break; + } + +// Look for escapement. + + if (c === '\\') { + i += 1; + if (i >= length) { + make('string', str).error("Unterminated string"); + } + c = this.charAt(i); + switch (c) { + case 'b': + c = '\b'; + break; + case 'f': + c = '\f'; + break; + case 'n': + c = '\n'; + break; + case 'r': + c = '\r'; + break; + case 't': + c = '\t'; + break; + case 'u': + if (i >= length) { + make('string', str).error("Unterminated string"); + } + c = parseInt(this.substr(i + 1, 4), 16); + if (!isFinite(c) || c < 0) { + make('string', str).error("Unterminated string"); + } + c = String.fromCharCode(c); + i += 4; + break; + } + } + str += c; + i += 1; + } + i += 1; + //print("str = "+str) + result.push(make('string', str)); + c = this.charAt(i); + + +// comment. + + } else if (c === '/' && this.charAt(i + 1) === '*') { + var str = c; + i += 1; + for (;;) { + c = this.charAt(i); + str += c; + if (c === '*' && this.charAt(i+1) == "/") { + i += 1; + i += 1; + + str+= "/"; + c = this.charAt(i); + result.push(make('comment', str)); + break; + } + i += 1; + } + } else if (c === '/' && this.charAt(i + 1) === '/') { + i += 1; + for (;;) { + c = this.charAt(i); + if (c === '\n' || c === '\r' || c === '') { + break; + } + i += 1; + } +// regexp + } else if (c === '/' && has.call(this, "/", /[\n\r]/) && prereg) { // what about /2 + //print('matcing regexp') + i += 1; + var str = c; + for (;;) { + c = this.charAt(i); + if(c === "\\"){ //skip over \ + str += c; + i += 1; + //print("adding "+c) + c = this.charAt(i); + + str += c; + //print("adding "+c) + i += 1; + c = this.charAt(i); + continue; + } + + if (c === '/' ) { + str += c; + i += 1; + c = this.charAt(i); + while(/\w/.test(c)){ //get stuff after /a/m + str += c; + i += 1; + c = this.charAt(i); + } + result.push(make('regexp', str)); + //print("regexp = "+str) + break; + } + str += c; + i += 1; + } +// combining + } else if (prefix.indexOf(c) >= 0) { + str = c; + i += 1; + while (i < length) { + c = this.charAt(i); + if (suffix.indexOf(c) < 0) { + break; + } + str += c; + i += 1; + } + result.push(make('operator', str)); + +// single-character operator + + } else { + i += 1; + result.push(make('operator', c)); + c = this.charAt(i); + } + } + return result; +}; diff --git a/browserid/static/steal/patchfile b/browserid/static/steal/patchfile new file mode 100644 index 0000000000000000000000000000000000000000..b77af9531860ac70481cd828bf46fe85c4425637 --- /dev/null +++ b/browserid/static/steal/patchfile @@ -0,0 +1,43 @@ +diff --git build/build.js build/build.js +index 76ae198..87e4629 100644 +--- build/build.js ++++ build/build.js +@@ -223,6 +223,7 @@ steal(function( steal ) { + } + // get envjs + load('steal/rhino/env.js'); //reload every time ++ var success = true; + // open the url + Envjs(url, { + scriptTypes: { +@@ -241,8 +242,14 @@ steal(function( steal ) { + }, + afterInlineScriptLoad: function( script ) { + scripts.push(script); ++ }, ++ onScriptLoadError: function(script) { ++ success = false; + } + }); ++ if (!success) { ++ java.lang.System.exit(-1); ++ } + + // set back steal + newSteal = window.steal; +@@ -286,4 +293,4 @@ steal(function( steal ) { + }; + }; + +-}); +\ No newline at end of file ++}); +diff --git js.bat js.bat +index 761e15e..6f8d0ad 100644 +--- js.bat ++++ js.bat +@@ -58,3 +58,4 @@ echo js apps/[NAME]/compress.js Compress your application and generate documenta + + :END + ++exit %errorlevel% diff --git a/browserid/static/steal/rhino/env.js b/browserid/static/steal/rhino/env.js index 3a2565d3ca917b8ea45467488febb890db0ba5e7..1e91256cd98537008abe3b44e6101ffc684d98d1 100644 --- a/browserid/static/steal/rhino/env.js +++ b/browserid/static/steal/rhino/env.js @@ -1989,8 +1989,17 @@ Envjs.runAsync = function(fn, onInterupt){ try{ run = Envjs.sync(function(){ - fn(); - Envjs.wait(); + if(Envjs.exitOnError){ + try { fn(); } + catch(ex) { + console.log("Rhino shell error: " + ex.message); + java.lang.System.exit(1); + } + } else { + fn(); + } + + //Envjs.wait(); }); Envjs.spawn(run); }catch(e){ @@ -8200,6 +8209,14 @@ __extend__(HTMLElement.prototype, { get outerHTML(){ //Not in the specs but I'll leave it here for now. return this.xhtml; + }, + get clearAttributes(){ + //Not in the specs but I'll leave it here for now. + return; + }, + get mergeAttributes(src){ + //Not in the specs but I'll leave it here for now. + return; }, scrollIntoView: function(){ /*TODO*/ @@ -9851,6 +9868,11 @@ __extend__(HTMLInputElement.prototype, { }, toString: function() { return '[object HTMLInputElement]'; + }, + cloneNode : function(){ + var newnode = HTMLInputAreaCommon.prototype.cloneNode.apply(this, arguments); + newnode.checked = this.checked; + return newnode; } }); @@ -24500,7 +24522,13 @@ XMLHttpRequest.prototype = { if (!_this.aborted && !redirecting){ //console.log('did not abort so call onreadystatechange'); - _this.onreadystatechange(); + if(_this.async){ + setTimeout(function(){ + _this.onreadystatechange(); + },10) + } else { + _this.onreadystatechange(); + } } } diff --git a/browserid/static/steal/rhino/file.js b/browserid/static/steal/rhino/file.js index 0f8baaea9eb127fa90e0abcbb15dda08e92ed6df..91451c849a5c85ddca55d47024a6a99663cbf347 100644 --- a/browserid/static/steal/rhino/file.js +++ b/browserid/static/steal/rhino/file.js @@ -167,7 +167,6 @@ mkdir: function() { - print(this.path) var out = new java.io.File(this.path) out.mkdir(); }, @@ -197,9 +196,15 @@ copy(newMe, newYou) } } - return; + return this; } copy(me, you) + return this; + }, + setExecutable: function(){ + var me = new java.io.File(this.path) + me.setExecutable(true); + return this; }, save: function( src, encoding ) { var fout = new java.io.FileOutputStream(new java.io.File(this.path)); diff --git a/browserid/static/steal/rhino/loader b/browserid/static/steal/rhino/loader index 2b3210751acde8f80a8adb1c6c78d69f39ed7d0d..a426430a38326927ef706b1814d052e5ece96c9e 100644 --- a/browserid/static/steal/rhino/loader +++ b/browserid/static/steal/rhino/loader @@ -1,6 +1,13 @@ #!/bin/sh # This script is the common JS loader +ERRORLEV=0 +if [ $1 = "-e" ] +then + ERRORLEV=1 + shift +fi + ARGS=[ for arg do @@ -14,3 +21,8 @@ ARGS=$ARGS] #fi java -Xmx170m -Xss1024k -cp $CP -Dbasepath=$BASE -Dcmd=$CMD org.mozilla.javascript.tools.shell.Main -opt -1 -e _args="$ARGS" -e 'load('"'"$LOADPATH"'"')' + +if [ $ERRORLEV = "1" -a $? = "1" ] +then + exit $? +fi \ No newline at end of file diff --git a/browserid/static/steal/rhino/loader.bat b/browserid/static/steal/rhino/loader.bat index a2ccf002e295001cebcb3b6b091f7682d91feb3e..f4b222985d1bafc08fb3c8e12d3276425d0e9679 100644 --- a/browserid/static/steal/rhino/loader.bat +++ b/browserid/static/steal/rhino/loader.bat @@ -9,6 +9,12 @@ if not "%CMD%" == "" ( set CMD=%CMD:\=/% ) for /f "tokens=1*" %%A in ("%BASE%") do SET BASE=%%A for /f "tokens=1*" %%A in ("%CMD%") do SET CMD=%%A +SET ERRORLEV=0 +if "%1"=="-e" ( + SET ERRORLEV=1 + SHIFT /0 +) + :: handle args SET ARGS=[ for /f "tokens=1,2,3,4,5,6 delims= " %%a in ("%*") do SET ARGS=!ARGS!'%%a','%%b','%%c','%%d','%%e','%%f' @@ -32,4 +38,8 @@ set LOADPATH=%LOADPATH:\=/% :: invoke Rhino java -Xmx170m -Xss1024k -cp %CP% -Dbasepath="%BASE%" -Dcmd="%CMD%" org.mozilla.javascript.tools.shell.Main -opt -1 -e _args=%ARGS% -e load('%LOADPATH%') +if "%ERRORLEV%"=="1" ( + if errorlevel 1 exit 1 +) + :END \ No newline at end of file diff --git a/browserid/static/steal/rhino/steal.js b/browserid/static/steal/rhino/steal.js index 63965bdb8c8c3f87a8a47779b0428a493024eab5..2c80cefb5450f5608849b51877040c90981b9e1d 100644 --- a/browserid/static/steal/rhino/steal.js +++ b/browserid/static/steal/rhino/steal.js @@ -21,6 +21,7 @@ for ( var i = 0; i < arguments.length; i++ ) { var inc = arguments[i]; if ( typeof inc == 'string' ) { + // print(inc + "/" + inc.match(/\w+$/)[0] + ".js") load(inc + "/" + inc.match(/\w+$/)[0] + ".js") } else { inc(steal) diff --git a/browserid/static/steal/rhino/utils.js b/browserid/static/steal/rhino/utils.js index 1a13c975bea1db7ac25558471963424f8ca12dd6..0a8add2fad5f2f82b39fec0547ed672de23bc4f9 100644 --- a/browserid/static/steal/rhino/utils.js +++ b/browserid/static/steal/rhino/utils.js @@ -8,6 +8,10 @@ basePath = java.lang.System.getProperty("basepath"); var pathFromRoot = function(path){ + if(!basePath){ + return path; + } + if (!/^\/\//.test(path) && !/^\w\:\\/.test(path) && !/^http/.test(path) && basePath) { path = basePath + "../" + path } @@ -32,4 +36,4 @@ readFile = function( path ) { return oldReadFile(pathFromRoot(path)) } -})() \ No newline at end of file +})(); diff --git a/browserid/static/steal/steal.js b/browserid/static/steal/steal.js index 984687d8fb93d3d20c4aa94f894b46397c6756dc..9d61c1c3a4dd19ae1e2e7de02884ae4e4f6d0143 100644 --- a/browserid/static/steal/steal.js +++ b/browserid/static/steal/steal.js @@ -103,10 +103,10 @@ scriptTag += steal.loadErrorTimer(options); } scriptTag += '>' + (bodyText || '') + '</script>'; - if ( steal.support.load ) { + if ( steal.support.load && !browser.msie) { scriptTag += '<script type="text/javascript"' + '>steal.end()</script>'; } - else { + else { // this is here b/c IE will run a script above right away (before the script above it loads) scriptTag += '<script type="text/javascript" src="' + steal.root.join('steal/end.js') + '"></script>'; } document.write((options.src || bodyText ? scriptTag : '')); @@ -762,7 +762,9 @@ // current_steals = [], //steals that are pending to be steald - total = []; // + total = [], + //mapping of loaded css files + css = {}; extend(steal, { /** * Sets options from script @@ -770,7 +772,9 @@ */ setScriptOptions: function() { var scripts = document.getElementsByTagName("script"), - scriptOptions, commaSplit, stealReg = /steal\.(production\.)?js/; + scriptOptions, + commaSplit, + stealReg = /steal\.(production\.)?js/; //find the steal script and setup initial paths. for ( var i = 0; i < scripts.length; i++ ) { @@ -790,6 +794,7 @@ if ( src.indexOf('?') != -1 ) { scriptOptions = src.split('?')[1]; } + steal.options.evalAfter = /\w+/.test(scripts[i].text) && scripts[i].text } } @@ -857,8 +862,10 @@ steal.options.production = steal.options.production + (steal.options.production.indexOf('.js') == -1 ? '.js' : ''); } //we only load things with force = true - if ( steal.options.env == 'production' && steal.options.loadProduction ) { - if ( steal.options.production ) { + if ( steal.options.env == 'production' ) { + + // if we have a production script and we haven't been told not to load it + if ( steal.options.production && steal.options.loadProduction ) { first = false; //makes it so we call close after //steal(steal.options.startFile); steal({ @@ -983,6 +990,9 @@ return; }, done: function() { + if ( steal.options.evalAfter ){ + eval(steal.options.evalAfter); + } if ( typeof steal.options.done == "function" ) { steal.options.done(total); } @@ -993,9 +1003,7 @@ clearTimeout(steal.timer); // add steals that were just added to the end of the list steals = steals.concat(current_steals); - if (!steals.length ) { - return; - } + // take the last one var next = steals.pop(); @@ -1055,8 +1063,12 @@ } var current; for ( var i = 0; i < arguments.length; i++ ) { - current = File(arguments[i] + ".css").joinCurrent(); - steal.createLink(steal.root.join(current)); + current = steal.root.join( File(arguments[i] + ".css").joinCurrent() ); + if(!css[current]){ + steal.createLink(current); + css[current] = true; + } + } return this; }, @@ -1168,6 +1180,10 @@ return steal; }; }, + /** + * @function then + * A chainable alias for [steal]. + */ then: steal, total: total }); diff --git a/browserid/static/steal/test/envjs/qunit.html b/browserid/static/steal/test/envjs/qunit.html new file mode 100644 index 0000000000000000000000000000000000000000..67b8f805eaa13218254718e886c48b523960e5ef --- /dev/null +++ b/browserid/static/steal/test/envjs/qunit.html @@ -0,0 +1,15 @@ +<html> + <head> + <script type='text/javascript' src='../../steal/steal.js?steal/test/envjs/qunit.js'></script> + </head> + <body> + + <h1 id="qunit-header">cookbook Test Suite</h1> + <h2 id="qunit-banner"></h2> + <div id="qunit-testrunner-toolbar"></div> + <h2 id="qunit-userAgent"></h2> + <div id="test-content"></div> + <ol id="qunit-tests"></ol> + <div id="qunit-test-area"></div> + </body> +</html> \ No newline at end of file diff --git a/browserid/static/steal/test/envjs/qunit.js b/browserid/static/steal/test/envjs/qunit.js new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/browserid/static/steal/test/funcunit.html b/browserid/static/steal/test/funcunit.html new file mode 100644 index 0000000000000000000000000000000000000000..273e151e1e14423d7fc400da43107e4d3abe4961 --- /dev/null +++ b/browserid/static/steal/test/funcunit.html @@ -0,0 +1,14 @@ +<html> + <head> + <link rel="stylesheet" type="text/css" href="../../funcunit/qunit/qunit.css" /> + <title>contacts FuncUnit Test</title> + <script type='text/javascript' src='../steal.js?steal/test/funcunit'></script> + </head> + <body> + <h1 id="qunit-header">contacts Test Suite</h1> + <h2 id="qunit-banner"></h2> + <div id="qunit-testrunner-toolbar"></div> + <h2 id="qunit-userAgent"></h2> + <ol id="qunit-tests"></ol> + </body> +</html> \ No newline at end of file diff --git a/browserid/static/steal/test/funcunit/funcunit.js b/browserid/static/steal/test/funcunit/funcunit.js new file mode 100644 index 0000000000000000000000000000000000000000..6b2588537ada4466f0521dc2b6fbd9e413b9917f --- /dev/null +++ b/browserid/static/steal/test/funcunit/funcunit.js @@ -0,0 +1,3 @@ +steal + .plugins("funcunit") + .then("steal_test"); \ No newline at end of file diff --git a/browserid/static/steal/test/funcunit/steal_test.js b/browserid/static/steal/test/funcunit/steal_test.js new file mode 100644 index 0000000000000000000000000000000000000000..7e84d5e5102fb9d6a1d0025d8046f5b0e91c6cda --- /dev/null +++ b/browserid/static/steal/test/funcunit/steal_test.js @@ -0,0 +1,6 @@ +module("functional tests"); + +test("JSONP before steal", function(){ + S.open("//steal/test/jsonptest/breaking.html") + S("#out").text("works", 500) +}); \ No newline at end of file diff --git a/browserid/static/steal/test/qunit/qunit.js b/browserid/static/steal/test/qunit/qunit.js index 1c5903e87ada9421969cb6400f4147e9ce8ab8e4..eaa61e697ab3f8d2d7dccb44df47b9b745856efe 100644 --- a/browserid/static/steal/test/qunit/qunit.js +++ b/browserid/static/steal/test/qunit/qunit.js @@ -1,4 +1,5 @@ +//console.log('running qunit'); steal .plugins("funcunit/qunit") - .css('one','../two') + .css('one','../two','one') .then("steal_test") \ No newline at end of file diff --git a/browserid/static/steal/test/qunit/steal_test.js b/browserid/static/steal/test/qunit/steal_test.js index ffce4bda99af160cb7c323131fd2beab9a93ed5a..5ddf41eac0cb467d08ff615a0b0ce3efed48b489 100644 --- a/browserid/static/steal/test/qunit/steal_test.js +++ b/browserid/static/steal/test/qunit/steal_test.js @@ -203,5 +203,14 @@ test("File.normalize", function() { test("css", function(){ document.getElementById("qunit-test-area").innerHTML = ("<div id='makeBlue'>Blue</div><div id='makeGreen'>Green</div>"); equals(document.getElementById("makeBlue").clientWidth, 100, "relative in loaded"); - equals(document.getElementById("makeGreen").clientWidth, 50, "relative up loaded") + equals(document.getElementById("makeGreen").clientWidth, 50, "relative up loaded"); + + var els = document.getElementsByTagName('link'), + count = 0; + for(var i =0; i< els.length; i++){ + if(els[i].href.indexOf('one.css') > -1){ + count++; + } + } + equals(count, 1, "only one one.css loaded") }) diff --git a/browserid/views/dialog.ejs b/browserid/views/dialog.ejs index a2c41b7b3d434ee2dd1a6eec20272d5cb9509ff5..3a8a1882243fa1970d38244365ce5506c7a3ba12 100644 --- a/browserid/views/dialog.ejs +++ b/browserid/views/dialog.ejs @@ -1,18 +1,12 @@ <!doctype html> <html> <head> - <head> - <title>Browser ID</title> - <script type='text/javascript' src='steal/steal<%= production ? ".production" : "" %>.js?dialog'></script> - <!--[if lt IE 9]> - <script src="/dialog/html5shim.js"></script> - <![endif]--> - <script> - $(function() { - $('body').dialog().show(); - }); - </script> - </head> - <body> + <!--[if lt IE 9]> + <script src="/dialog/html5shim.js"></script> + <![endif]--> + <title>Browser ID</title> +</head> + <body> </body> </html> +<script type='text/javascript' src='steal/steal<%= production ? ".production" : "" %>.js?dialog'></script> diff --git a/browserid/views/layout.ejs b/browserid/views/layout.ejs index 969eaad11cb97cc519a42d03813aaa0181a9dc34..361abaa32fafe7d7a8ec2635a16a9c46db18a180 100644 --- a/browserid/views/layout.ejs +++ b/browserid/views/layout.ejs @@ -1,7 +1,7 @@ <!DOCTYPE html> <html> <head> - <meta charset=utf-8"> + <meta charset="utf-8"> <title><%- title %></title> <% if (production) { %> <link rel="stylesheet" type="text/css" href="/css/browserid.min.css"> diff --git a/browserid/views/relay.ejs b/browserid/views/relay.ejs new file mode 100644 index 0000000000000000000000000000000000000000..203d0c6c7854cd84ee1e0b430e214661882adffd --- /dev/null +++ b/browserid/views/relay.ejs @@ -0,0 +1,44 @@ +<!doctype html> +<html> +<head> + <meta charset="utf-8"> + <title>Browser ID</title> +</head> + <body> + Relay iframe. Woohoo! + <script type="text/javascript" src="https://browserid.org/dialog/resources/jschannel.js"></script> + <script type="text/javascript"> + var ipServer = "https://browserid.org"; + + var chan = Channel.build( { + window: window.parent, + origin: "*", + scope: "mozid" + } ); + + var transaction; + + chan.bind("getVerifiedEmail", function(trans, s) { + trans.delayReturn(true); + + transaction = trans; + }); + + window.browserid_relay = function(status, error) { + if(error) { + errorOut(transaction, error); + } + else { + try { + transaction.complete(status); + } catch(e) { + // The relay function is called a second time after the + // initial success, when the window is closing. + } + } + } + + </script> + <!--script type='text/javascript' src='https://browserid.org/steal/steal<%= production ? ".production" : "" %>.js?relay'></script--> + </body> +</html> diff --git a/rp/index.html b/rp/index.html index 53fd47c1e96512349a30a8db290754a1b24f36b4..06b071670dd11d27a44625bc8ac7127cb466d3fc 100644 --- a/rp/index.html +++ b/rp/index.html @@ -81,11 +81,12 @@ a:hover { border-bottom: 2px solid black ; } <script src="jquery-min.js"></script> <script src="https://browserid.org/include.js"></script> <script> - $(document).ready(function() { - $("#partyStarter").click(function() { + $(function() { + $("#partyStarter").click(function(event) { + event.preventDefault(); navigator.id.getVerifiedEmail(function(assertion) { if (!assertion) { - alert("couldn't get the users email address!"); + alert("couldn't get the users email address!"); } else { // Now we'll send this assertion over to the verification server for validation $("#oAssertion").empty().text(assertion); @@ -100,14 +101,17 @@ a:hover { border-bottom: 2px solid black ; } $.ajax({ url: "https://browserid.org/verify", - type: "POST", + type: "post", dataType: "json", data: data, success: function(data, textStatus, jqXHR) { $("#oVerificationResponse > pre").empty().text(JSON.stringify(data, null, 4)); }, error: function(jqXHR, textStatus, errorThrown) { - $("#oVerificationResponse > pre").empty().text(JSON.stringify(JSON.parse(jqXHR.responseText), null, 4)); + var statusEl = $("#oVerificationResponse > pre").empty(); + var resp = jqXHR.responseText ? + JSON.stringify(JSON.parse(jqXHR.responseText), null, 4) : errorThrown; + statusEl.text(resp); } }); } diff --git a/run.js b/run.js index 762290f5a7e572a0bdffe83b0a35f196d4ec1869..a801931299ec64f1b7cfb05010d1cc411bf95da3 100755 --- a/run.js +++ b/run.js @@ -50,6 +50,7 @@ require('./libs/logging.js').enableConsoleLogging(); var configuration = require('./libs/configuration.js'); + var PRIMARY_HOST = "127.0.0.1"; var boundServers = [ ]; @@ -76,9 +77,9 @@ function substitutionMiddleware(req, resp, next) { fromWithPort = from + ":80"; } to = to.substr(7); - + if (o.subPath) to += o.subPath; - + subs[fromWithPort] = to; subs[from] = to; }