From 99ce624a2172d28e43b9ee15bd49aff1d7786e29 Mon Sep 17 00:00:00 2001 From: Brian Moschel Date: Fri, 7 Oct 2011 01:54:11 -0400 Subject: [PATCH 001/103] fixture path uses mapJoin --- dom/fixture/fixture.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dom/fixture/fixture.js b/dom/fixture/fixture.js index 3cdd7df6..4383370d 100644 --- a/dom/fixture/fixture.js +++ b/dom/fixture/fixture.js @@ -24,7 +24,7 @@ steal('jquery/dom').then(function( $ ) { var url = settings.fixture; if (/^\/\//.test(url) ) { - url = steal.root.join(settings.fixture.substr(2)); + url = steal.root.mapJoin(settings.fixture.substr(2)); } //@steal-remove-start steal.dev.log("looking for fixture in " + url); From d7b47384094613889d890df6ea89d5031236a21d Mon Sep 17 00:00:00 2001 From: Marnus Weststrate Date: Sun, 16 Oct 2011 09:24:11 +0200 Subject: [PATCH 002/103] $.Vector: Fixed x() and y() description which was the wrong way around. --- lang/vector/vector.js | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/lang/vector/vector.js b/lang/vector/vector.js index 5bbf46f2..a1cc1501 100644 --- a/lang/vector/vector.js +++ b/lang/vector/vector.js @@ -77,28 +77,36 @@ steal('jquery').then(function($){ } return vec.update(arr); }, -/* - * Returns the 2nd value of the vector - * @return {Number} - */ + /** + * Returns the first value of the vector + * @return {Number} + */ x: getSetZero, - width: getSetZero, + /** + * same as x() + * @return {Number} + */ + left: getSetZero, /** * Returns the first value of the vector * @return {Number} */ + width: getSetZero, + /** + * Returns the 2nd value of the vector + * @return {Number} + */ y: getSetOne, - height: getSetOne, /** - * Same as x() + * Same as y() * @return {Number} */ top: getSetOne, /** - * same as y() + * Returns the 2nd value of the vector * @return {Number} */ - left: getSetZero, + height: getSetOne, /** * returns (x,y) * @return {String} From 916ac91336d458c787bcba5df16b9aa1e581d482 Mon Sep 17 00:00:00 2001 From: Marnus Weststrate Date: Sun, 16 Oct 2011 10:27:18 +0200 Subject: [PATCH 003/103] Fixed [validation] links which were missing route info and thus not displaying as links. --- model/validations/validations.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/model/validations/validations.js b/model/validations/validations.js index 281544fa..92291a53 100644 --- a/model/validations/validations.js +++ b/model/validations/validations.js @@ -68,7 +68,7 @@ $.extend($.Model, { /** * @function jQuery.Model.static.validate * @parent jquery.model.validations - * Validates each of the specified attributes with the given function. See [validation] for more on validations. + * Validates each of the specified attributes with the given function. See [jquery.model.validations validation] for more on validations. * @param {Array|String} attrNames Attribute name(s) to to validate * @param {Function} validateProc Function used to validate each given attribute. Returns nothing if valid and an error message otherwise. Function is called in the instance context and takes the value to validate. * @param {Object} options (optional) Options for the validations. Valid options include 'message' and 'testIf'. @@ -91,8 +91,8 @@ $.extend($.Model, { * * presence - "can't be empty" * * range - "is out of range" * - * It is important to ensure that you steal jquery/model/validations - * before overwriting the messages, otherwise the changes will + * It is important to steal jquery/model/validations before + * overwriting the messages, otherwise the changes will * be lost once steal loads it later. * * ## Example @@ -112,7 +112,7 @@ $.extend($.Model, { * @function jQuery.Model.static.validateFormatOf * @parent jquery.model.validations * Validates where the values of specified attributes are of the correct form by - * matching it against the regular expression provided. See [validation] for more on validations. + * matching it against the regular expression provided. See [jquery.model.validations validation] for more on validations. * @param {Array|String} attrNames Attribute name(s) to to validate * @param {RegExp} regexp Regular expression used to match for validation * @param {Object} options (optional) Options for the validations. Valid options include 'message' and 'testIf'. @@ -132,7 +132,7 @@ $.extend($.Model, { * @function jQuery.Model.static.validateInclusionOf * @parent jquery.model.validations * Validates whether the values of the specified attributes are available in a particular - * array. See [validation] for more on validations. + * array. See [jquery.model.validations validation] for more on validations. * @param {Array|String} attrNames Attribute name(s) to to validate * @param {Array} inArray Array of options to test for inclusion * @param {Object} options (optional) Options for the validations. Valid options include 'message' and 'testIf'. @@ -151,7 +151,7 @@ $.extend($.Model, { /** * @function jQuery.Model.static.validateLengthOf * @parent jquery.model.validations - * Validates that the specified attributes' lengths are in the given range. See [validation] for more on validations. + * Validates that the specified attributes' lengths are in the given range. See [jquery.model.validations validation] for more on validations. * @param {Array|String} attrNames Attribute name(s) to to validate * @param {Number} min Minimum length (inclusive) * @param {Number} max Maximum length (inclusive) @@ -170,7 +170,7 @@ $.extend($.Model, { /** * @function jQuery.Model.static.validatePresenceOf * @parent jquery.model.validations - * Validates that the specified attributes are not blank. See [validation] for more on validations. + * Validates that the specified attributes are not blank. See [jquery.model.validations validation] for more on validations. * @param {Array|String} attrNames Attribute name(s) to to validate * @param {Object} options (optional) Options for the validations. Valid options include 'message' and 'testIf'. * @@ -185,7 +185,7 @@ $.extend($.Model, { /** * @function jQuery.Model.static.validateRangeOf * @parent jquery.model.validations - * Validates that the specified attributes are in the given numeric range. See [validation] for more on validations. + * Validates that the specified attributes are in the given numeric range. See [jquery.model.validations validation] for more on validations. * @param {Array|String} attrNames Attribute name(s) to to validate * @param {Number} low Minimum value (inclusive) * @param {Number} hi Maximum value (inclusive) From 45e2ecc31e350bead314a03121a3def0dd872a93 Mon Sep 17 00:00:00 2001 From: Marnus Weststrate Date: Sun, 16 Oct 2011 10:28:44 +0200 Subject: [PATCH 004/103] Spelling mistake in dom_selection. --- dom/selection/selection.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dom/selection/selection.js b/dom/selection/selection.js index 40d78ce6..45cc2280 100644 --- a/dom/selection/selection.js +++ b/dom/selection/selection.js @@ -195,7 +195,7 @@ getCharElement = function( elems , range, len ) { * 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. + * - __end__ - The number of characters from the start of the element to the end of the selection. * - __range__ - A [jQuery.Range $.Range] that represents the current selection. * * This lets you get the selected text in a textarea like: From 32255a72a903b032f02a587dc4631ea942d3f830 Mon Sep 17 00:00:00 2001 From: Marnus Weststrate Date: Wed, 19 Oct 2011 15:57:07 +0200 Subject: [PATCH 005/103] Added a note to $.Model.attributes docs to clarify that a full model name type should get an additional '.model' postfix. --- model/model.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/model/model.js b/model/model.js index 22b3e62e..62be6fd0 100644 --- a/model/model.js +++ b/model/model.js @@ -835,6 +835,11 @@ steal('jquery/class', 'jquery/lang/string', function() { * Task.models and Person.model * to convert the raw data into an array of Tasks and a Person. * + * Note that the full names of the models themselves are App.Models.Task + * and App.Models.Person. The _.model_ and _.models_ parts are appended + * for the benefit of [jQuery.Model.static.convert convert] to identify the types as + * models. + * * @demo jquery/model/pages/associations.html * */ From bd46347780f42e1213a7be65cba73bee1eda4d9b Mon Sep 17 00:00:00 2001 From: Marnus Weststrate Date: Tue, 25 Oct 2011 13:28:08 +0200 Subject: [PATCH 006/103] Fixed and added @return to $.route.current() docs. --- dom/route/route.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dom/route/route.js b/dom/route/route.js index ef70387f..73e4a1a7 100644 --- a/dom/route/route.js +++ b/dom/route/route.js @@ -390,8 +390,9 @@ function( $ ) { }, props)) + ">" + name + ""; }, /** - * Returns if the options represent the current page. + * Returns true if the options represent the current page. * @param {Object} options + * @return {Boolean} */ current: function( options ) { return location.hash == "#!" + $route.param(options) From 4dd7a1438ddfdefad026c0bdb5fc182f53809260 Mon Sep 17 00:00:00 2001 From: Marnus Weststrate Date: Wed, 26 Oct 2011 10:24:26 +0200 Subject: [PATCH 007/103] Fixed Model.List.get() and .remove() function docs. --- model/list/list.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/model/list/list.js b/model/list/list.js index ed694d63..f924c02c 100644 --- a/model/list/list.js +++ b/model/list/list.js @@ -447,7 +447,8 @@ steal('jquery/model').then(function( $ ) { * * var match = list.get($('#content')[0]) * - * @param {Object} args element or id to remove + * @param {Object} args elements or ids to retrieve. + * @return {$.Model.List} A sub-Model.List with the elements that were queried. */ get: function() { if (!this.length ) { @@ -478,13 +479,14 @@ steal('jquery/model').then(function( $ ) { * * To remove by id: * - * var match = list.get(23); + * var match = list.remove(23); * * or to remove by element: * - * var match = list.get($('#content')[0]) + * var match = list.remove($('#content')[0]) * - * @param {Object} args element or id to remove + * @param {Object} args elements or ids to remove. + * @return {$.Model.List} A Model.List of the elements that were removed. */ remove: function( args ) { if (!this.length ) { From 88780b3ddb3ce0e19ebb7558c1028e4e1fbbf76f Mon Sep 17 00:00:00 2001 From: Marnus Weststrate Date: Wed, 26 Oct 2011 13:14:59 +0200 Subject: [PATCH 008/103] Some more clean-up and docs for Model.List. --- model/list/list.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/model/list/list.js b/model/list/list.js index f924c02c..064bf5bd 100644 --- a/model/list/list.js +++ b/model/list/list.js @@ -5,8 +5,7 @@ steal('jquery/model').then(function( $ ) { return args[0] } else if ( args[0] instanceof $.Model.List ) { return $.makeArray(args[0]) - } - else { + } else { return $.makeArray(args) } }, @@ -466,8 +465,10 @@ steal('jquery/model').then(function( $ ) { for ( var i = 0; i < args.length; i++ ) { if ( args[i].nodeName && (matches = args[i].className.match(test)) ) { + // If this is a dom element val = this._data[matches[1]] } else { + // Else an id was provided as a number or string. val = this._data[typeof args[i] == 'string' || typeof args[i] == 'number' ? args[i] : args[i][idName]] } val && list.push(val) @@ -693,13 +694,15 @@ steal('jquery/model').then(function( $ ) { }, /** * @function push - * Adds a instance or instances to the list + * Adds an instance or instances to the list * * list.push(new Recipe({id: 5, name: "Water"})) + * + * @param args {Object} The instance(s) to push onto the list. + * @return {Number} The number of elements in the list after the new element was pushed in. */ push: function() { - var args = getArgs(arguments), - self = this; + var args = getArgs(arguments); //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 ) { From 4524d482d7e4228de382d9dede755e7054356b0c Mon Sep 17 00:00:00 2001 From: Brian Moschel Date: Tue, 1 Nov 2011 01:57:05 -0400 Subject: [PATCH 009/103] fixing bug in getData and adding test --- dom/fixture/fixture.js | 3 ++- dom/fixture/fixture_test.js | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/dom/fixture/fixture.js b/dom/fixture/fixture.js index 4a46077d..86f13669 100644 --- a/dom/fixture/fixture.js +++ b/dom/fixture/fixture.js @@ -429,7 +429,8 @@ steal('jquery/dom', // gets data from a url like "/todo/{id}" given "todo/5" _getData : function(fixtureUrl, url){ var order = [], - res = new RegExp(fixtureUrl.replace(replacer, function(whole, part){ + fixtureUrlAdjusted = fixtureUrl.replace('.', '\\.').replace('?', '\\?'), + res = new RegExp(fixtureUrlAdjusted.replace(replacer, function(whole, part){ order.push(part) return "([^\/])+" })+"$").exec(url), diff --git a/dom/fixture/fixture_test.js b/dom/fixture/fixture_test.js index 581355c1..422cbc16 100644 --- a/dom/fixture/fixture_test.js +++ b/dom/fixture/fixture_test.js @@ -184,6 +184,8 @@ test("rand", function(){ test("_getData", function(){ var data = $.fixture._getData("/thingers/{id}", "/thingers/5"); equals(data.id, 5, "gets data"); + var data = $.fixture._getData("/thingers/5?hi.there", "/thingers/5?hi.there"); + deepEqual(data, {}, "gets data"); }) test("_compare", function(){ From 339590ee7491aa4635050359bc0437cce3c10d02 Mon Sep 17 00:00:00 2001 From: Marnus Weststrate Date: Wed, 2 Nov 2011 11:04:54 +0200 Subject: [PATCH 010/103] Docs fix: getobject -> getObject. --- lang/string/string.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lang/string/string.js b/lang/string/string.js index e4adc98b..99ff23e9 100644 --- a/lang/string/string.js +++ b/lang/string/string.js @@ -89,7 +89,7 @@ steal('jquery').then(function( $ ) { * 'object path' by removing or adding properties. * * Foo = {Bar: {Zar: {"Ted"}}} - * $.String.getobject("Foo.Bar.Zar") //-> "Ted" + * $.String.getObject("Foo.Bar.Zar") //-> "Ted" * * @param {String} name the name of the object to look for * @param {Array} [roots] an array of root objects to look for the From facb00bc6afaa9694abeb6b7cb54623ca2bd805a Mon Sep 17 00:00:00 2001 From: Brian Moschel Date: Mon, 7 Nov 2011 22:03:09 -0500 Subject: [PATCH 011/103] swipe needs to work with click in phantom until we support touch in funcunit --- event/swipe/swipe.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/event/swipe/swipe.js b/event/swipe/swipe.js index 8185b3d7..8a73f9f7 100644 --- a/event/swipe/swipe.js +++ b/event/swipe/swipe.js @@ -1,5 +1,7 @@ steal('jquery/event/livehack').then(function($){ -var supportTouch = "ontouchend" in document, +// TODO remove this, phantom supports touch AND click, but need to make funcunit support touch so its testable +var isPhantom = /Phantom/.test(navigator.userAgent), + supportTouch = !isPhantom && "ontouchend" in document, scrollEvent = "touchmove scroll", touchStartEvent = supportTouch ? "touchstart" : "mousedown", touchStopEvent = supportTouch ? "touchend" : "mouseup", From a44bf868d40fbf49d1d41d24f2c23b4998c6f7c3 Mon Sep 17 00:00:00 2001 From: David Luecke Date: Tue, 8 Nov 2011 08:13:12 -0700 Subject: [PATCH 012/103] form_params fixes and documentation updates --- dom/form_params/form_params.js | 11 ++++++++--- dom/form_params/form_params_test.js | 18 ++++++++++++++++-- dom/form_params/test/basics.micro | 7 +++++-- 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/dom/form_params/form_params.js b/dom/form_params/form_params.js index a47ca517..2644fe24 100644 --- a/dom/form_params/form_params.js +++ b/dom/form_params/form_params.js @@ -28,6 +28,9 @@ steal("jquery/dom").then(function( $ ) { * Returns an object of name-value pairs that represents values in a form. * It is able to nest values whose element's name has square brackets. * + * When convert is set to true strings that represent numbers and booleans will + * be converted and empty string will not be added to the object. + * * Example html: * @codestart html * <form> @@ -42,7 +45,8 @@ steal("jquery/dom").then(function( $ ) { * * @demo jquery/dom/form_params/form_params.html * - * @param {Boolean} [convert=false] True if strings that look like numbers and booleans should be converted. Defaults to true. + * @param {Boolean} [convert=false] True if strings that look like numbers and booleans should be converted and if + * empty string should not be added to the result. Defaults to false. * @return {Object} An object of name-value pairs. */ formParams: function( convert ) { @@ -98,7 +102,7 @@ steal("jquery/dom").then(function( $ ) { lastPart = parts[parts.length - 1]; //now we are on the last part, set the value - if ( lastPart in current && type === "checkbox" ) { + if (current[lastPart]) { if (!$.isArray(current[lastPart]) ) { current[lastPart] = current[lastPart] === undefined ? [] : [current[lastPart]]; } @@ -106,6 +110,7 @@ steal("jquery/dom").then(function( $ ) { current[lastPart].push(value); } } else if ( write || !current[lastPart] ) { + current[lastPart] = write ? value : undefined; } @@ -114,4 +119,4 @@ steal("jquery/dom").then(function( $ ) { } }); -}); \ No newline at end of file +}); diff --git a/dom/form_params/form_params_test.js b/dom/form_params/form_params_test.js index 44c2c2b1..93d50fd1 100644 --- a/dom/form_params/form_params_test.js +++ b/dom/form_params/form_params_test.js @@ -44,7 +44,15 @@ test("just strings",function(){ same(formParams.params.four,["4","1"],"four is right"); same(formParams.params.five,['2','3'],"five is right"); $("#qunit-test-area").html('') -}) +}); + +test("empty string conversion",function() { + $("#qunit-test-area").html("//jquery/dom/form_params/test/basics.micro",{}); + var formParams = $("#qunit-test-area form").formParams(false) ; + ok('' === formParams.empty, 'Default empty string conversion'); + formParams = $("#qunit-test-area form").formParams(true); + ok(undefined === formParams.empty, 'Default empty string conversion'); +}); test("missing names",function(){ $("#qunit-test-area").html("//jquery/dom/form_params/test/checkbox.micro",{}); @@ -52,4 +60,10 @@ test("missing names",function(){ ok(true, "does not break") }); -}); \ No newline at end of file +test("same input names to array", function() { + $("#qunit-test-area").html("//jquery/dom/form_params/test/basics.micro",{}); + var formParams = $("#qunit-test-area form").formParams(true); + same(formParams.param1, ['first', 'second', 'third']); +}); + +}); diff --git a/dom/form_params/test/basics.micro b/dom/form_params/test/basics.micro index 5c5b5be8..dfc247ff 100644 --- a/dom/form_params/test/basics.micro +++ b/dom/form_params/test/basics.micro @@ -12,7 +12,7 @@ - + - \ No newline at end of file + + + + From fe6ffd32ea8b9698ea27501aef0b6f2692d37cb5 Mon Sep 17 00:00:00 2001 From: David Luecke Date: Tue, 8 Nov 2011 09:04:55 -0700 Subject: [PATCH 013/103] Addressing issue #86. Empty string as model id should be ignored. --- model/model.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/model/model.js b/model/model.js index 359107fd..ff18892a 100644 --- a/model/model.js +++ b/model/model.js @@ -1586,7 +1586,7 @@ steal('jquery/class', 'jquery/lang/string', function() { */ isNew: function() { var id = getId(this); - return (id === undefined || id === null); //if null or undefined + return (id === undefined || id === null || id === ''); //if null or undefined }, /** * Creates or updates the instance using [jQuery.Model.static.create] or From 7ff4f53ef474f445f6d68ecd7e4089dc32ce74da Mon Sep 17 00:00:00 2001 From: Austin McDaniel Date: Tue, 8 Nov 2011 14:07:39 -0600 Subject: [PATCH 014/103] docs fix for Model.List.remove Signed-off-by: Austin McDaniel --- model/list/list.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/model/list/list.js b/model/list/list.js index ed694d63..f988dbcf 100644 --- a/model/list/list.js +++ b/model/list/list.js @@ -478,11 +478,11 @@ steal('jquery/model').then(function( $ ) { * * To remove by id: * - * var match = list.get(23); + * var match = list.remove(23); * * or to remove by element: * - * var match = list.get($('#content')[0]) + * var match = list.remove($('#content')[0]) * * @param {Object} args element or id to remove */ From 1d06a677bd145d95dfc983085a986bcf42aff680 Mon Sep 17 00:00:00 2001 From: Justin Meyer Date: Fri, 11 Nov 2011 07:16:35 -0600 Subject: [PATCH 015/103] observe improvements and some minor changes --- lang/observe/delegate/delegate.js | 66 +++++++-- lang/observe/delegate/delegate_test.js | 98 ++++++++++++- lang/observe/demo.html | 140 +++++++++++++++++++ lang/observe/observe.js | 181 +++++++++++++++++++++---- lang/observe/observe_test.js | 47 +++++-- 5 files changed, 474 insertions(+), 58 deletions(-) create mode 100644 lang/observe/demo.html diff --git a/lang/observe/delegate/delegate.js b/lang/observe/delegate/delegate.js index 8fadccf3..3a8d812a 100644 --- a/lang/observe/delegate/delegate.js +++ b/lang/observe/delegate/delegate.js @@ -4,24 +4,48 @@ steal('jquery/lang/observe',function(){ * @add jQuery.Observe.prototype */ + // ** - 'this' will be the deepest item changed + // * - 'this' will be any changes within *, but * will be the + // this returned + // tells if the parts part of a delegate matches the broken up props of the event + // gives the prop to use as 'this' + // - delegate - an object like {parts: ['foo','*']} + // - props - ['foo','bar','0'] + // - returns - 'foo.bar' var matches = function(delegate, props){ //check props parts are the same or var parts = delegate.parts, len = parts.length, - i =0; + i =0, + // keeps the matched props we will use + matchedProps = [], + prop; // if the event matches for(i; i< len; i++){ - if(parts[i] == "**") { - return true; - } else if( typeof props[i] == 'string' && ( props[i] === parts[i] || parts[i] === "*" ) ) { - + prop = props[i] + // if no more props (but we should be matching them) + // return null + if( typeof prop !== 'string' ) { + return null; + } else + // if we have a "**", match everything + if( parts[i] == "**" ) { + return props.join("."); + } else + // a match, but we want to delegate to "*" + if (parts[i] == "*"){ + // only do this if there is nothing after ... + matchedProps.push(prop); + } + else if( prop === parts[i] ) { + matchedProps.push(prop); } else { - return false; + return null; } } - return len === props.length; + return matchedProps.join("."); }, delegate = function(event, prop, how, newVal, oldVal){ var props = prop.split("."), @@ -34,13 +58,25 @@ steal('jquery/lang/observe',function(){ // check delegate.event delegate = delegates[i]; - if( delegate.event === 'change' && matches(delegate, props) ){ - delegate.callback.apply(this.attr(prop), arguments); - } else if(delegate.event === how && matches(delegate, props) ){ - delegate.callback.apply(this.attr(prop), [event,newVal, oldVal]); - } else if(delegate.event === 'set' && how == 'add' && matches(delegates[i], props)) { - delegate.callback.apply(this.attr(prop), [event,newVal, oldVal]); + // see if this delegate matches props + var attr = matches(delegate, props); + if(attr) { + var from = prop.replace(attr+".",""); + + if( delegate.event === 'change' ){ + arguments[1] = from; + event.curAttr = attr; + delegate.callback.apply(this.attr(attr), $.makeArray( arguments)); + } else if(delegate.event === how ){ + // TODO: change where from is + delegate.callback.apply(this.attr(attr), [event,newVal, oldVal, from]); + } else if(delegate.event === 'set' && + how == 'add' ) { + // TODO: change where from is + delegate.callback.apply(this.attr(attr), [event,newVal, oldVal, from]); + } } + } }; @@ -199,5 +235,7 @@ steal('jquery/lang/observe',function(){ } return this; } - }) + }); + // add helpers for testing .. + $.Observe.prototype.delegate.matches = matches; }) diff --git a/lang/observe/delegate/delegate_test.js b/lang/observe/delegate/delegate_test.js index 9ff04408..f9b25149 100644 --- a/lang/observe/delegate/delegate_test.js +++ b/lang/observe/delegate/delegate_test.js @@ -3,23 +3,94 @@ steal('funcunit/qunit','jquery/lang/observe',function(){ module('jquery/lang/observe/delegate') +var matches = $.Observe.prototype.delegate.matches; + +test("matches", function(){ + + equals( matches({parts: ['**']}, ['foo','bar','0']) , + 'foo.bar.0' , "everything" ); + + equals( matches({parts: ['*.**']}, ['foo']) , + null , "everything at least one level deep" ) + + equals( matches({parts: ['foo','*']}, ['foo','bar','0']) , + 'foo.bar' ) + + equals(matches({parts: ['*']}, + ['foo','bar','0']) , + 'foo' ); + + equals( matches({parts: [ '*', 'bar' ]}, + ['foo','bar','0']) , + 'foo.bar' ) + // - props - + // - returns - 'foo.bar' +}) + +test("list events", function(){ + + var list = new $.Observe.List([ + {name: 'Justin'}, + {name: 'Brian'}, + {name: 'Austin'}, + {name: 'Mihael'}]) + list.comparator = 'name'; + list.sort(); + // events on a list + // - move - item from one position to another + // due to changes in elements that change the sort order + // - add (items added to a list) + // - remove (items removed from a list) + // - reset (all items removed from the list) + // - change something happened + + // a move directly on this list + list.bind('move', function(ev, item, newPos, oldPos){ + ok(true,"move called"); + equals(item.name, "Zed"); + equals(newPos, 3); + equals(oldPos, 0); + }); + + // a remove directly on this list + list.bind('remove', function(ev, items, oldPos){ + ok(true,"remove called"); + equals(items.length,1); + equals(items[0].name, 'Alexis'); + equals(oldPos, 0, "put in right spot") + }) + list.bind('add', function(ev, items, newPos){ + ok(true,"add called"); + equals(items.length,1); + equals(items[0].name, 'Alexis'); + equals(newPos, 0, "put in right spot") + }); + + list.push({name: 'Alexis'}); + + // now lets remove alexis ... + list.splice(0,1); + list[0].attr('name',"Zed") +}) + + test("delegate", function(){ var state = new $.Observe({ properties : { - price : [] + prices : [] } }); - var price = state.attr('properties.price'); + var prices = state.attr('properties.prices'); state.delegate("properties.price","change", function(ev, attr, how, val, old){ - equals(attr, "properties.price", "correct change name") + equals(attr, "0", "correct change name") equals(how, "add") equals(val[0].attr("foo"),"bar", "correct") ok(this === price, "rooted element") }); - price.push({foo: "bar"}); + prices.push({foo: "bar"}); state.undelegate(); @@ -47,7 +118,24 @@ test("delegate set is called on add", function(){ equals(newVal, "bar","got newVal") }); state.attr("foo","bar") -}) +}); + +test("delegate on deep properties with *", function(){ + var state = new $.Observe({ + person : { + name : { + first : "justin", + last : "meyer" + } + } + }); + + state.delegate("person","set", function(ev, newVal, oldVal, attr){ + equals(this, state.attr('person'), "this is set right") + equals(attr, "name.first") + }); + state.attr("person.name.first","brian") +}) }); \ No newline at end of file diff --git a/lang/observe/demo.html b/lang/observe/demo.html new file mode 100644 index 00000000..301be853 --- /dev/null +++ b/lang/observe/demo.html @@ -0,0 +1,140 @@ + + + + object + + + +

My Unsorted Todo List

+
    + +
+

My Sorted Todo List

+
    + +
+ +
+ + + + + \ No newline at end of file diff --git a/lang/observe/observe.js b/lang/observe/observe.js index db91016d..b79493b0 100644 --- a/lang/observe/observe.js +++ b/lang/observe/observe.js @@ -13,19 +13,35 @@ steal('jquery/class').then(function() { // - parent the parent object of prop hookup = function( val, prop, parent ) { // if it's an array make a list, otherwise a val - if ( isArray(val) ) { + if (val instanceof $.Observe){ + // we have an observe already + // make sure it is not listening to this already + unhookup([val], parent._namespace) + } else if ( isArray(val) ) { val = new $.Observe.List(val) } else { val = new $.Observe(val) } - + // attr (like target, how you (delegate) to get to the target) + // currentAttr (how to get to you) + // delegateAttr (hot to get to the delegated Attr) + + // + // //listen to all changes and trigger upwards - val.bind("change" + parent._namespace, function( ev, attr, how, val, old ) { + val.bind("change" + parent._namespace, function( ev, attr ) { // trigger the type on this ... var args = $.makeArray(arguments), ev = args.shift(); - args[0] = prop + (args[0] != "*" ? "." + args[0] : ""); // change the attr - $([parent]).trigger(ev, args); + if(prop === "*"){ + args[0] = parent.indexOf(val)+"." + args[0] + } else { + args[0] = prop + "." + args[0] + } + // change the attr + //ev.origTarget = ev.origTarget || ev.target; + // the target should still be the original object ... + $.event.trigger(ev, args, parent) }); return val; @@ -55,12 +71,15 @@ steal('jquery/class').then(function() { // - event - the event name ("change") // - args - an array of arguments trigger = function( item, event, args ) { - var THIS = $([item]); + // send no events if initalizing + if (item._init) { + return; + } if (!collecting ) { - return THIS.trigger(event, args) + return $.event.trigger(event, args, item) } else { collecting.push({ - t: THIS, + t: item, ev: event, args: args }) @@ -74,7 +93,7 @@ steal('jquery/class').then(function() { collecting = null; for ( var i = 0; i < len; i++ ) { cur = items[i]; - $(cur.t).trigger(cur.ev, cur.args) + $.event.trigger(cur.ev, cur.args, cur.t) } }, @@ -204,7 +223,9 @@ steal('jquery/class').then(function() { // the namespace this object uses to listen to events this._namespace = ".observe" + (++id); // sets all attrs - this.attrs(obj) + this._init = true; + this.attrs(obj); + delete this._init; }, /** * Get or set an attribute on the observe. @@ -550,10 +571,81 @@ steal('jquery/class').then(function() { * @prototype */ { - init: function( instances ) { + init: function( instances, options ) { this.length = 0; this._namespace = ".list" + (++id); + this._init = true; + this.bind('change',this.proxy('_changes')); this.push.apply(this, makeArray(instances || [])); + $.extend(this, options); + if(this.comparator){ + this.sort() + } + delete this._init; + }, + _changes : function(ev, attr, how, newVal, oldVal){ + // detects an add, sorts it, re-adds? + //console.log("") + + + + // if we are sorting, and an attribute inside us changed + if(this.comparator && /^\d+./.test(attr) ) { + + // get the index + var index = +/^\d+/.exec(attr)[0], + // and item + item = this[index], + // and the new item + newIndex = this.sortedIndex(item); + + if(newIndex !== index){ + // move ... + [].splice.call(this, index, 1); + [].splice.call(this, newIndex, 0, item); + + trigger(this, "move", [item, newIndex, index]); + ev.stopImmediatePropagation(); + trigger(this,"change", [ + attr.replace(/^\d+/,newIndex), + how, + newVal, + oldVal + ]); + return; + } + } + + + // if we add items, we need to handle + // sorting and such + + // trigger direct add and remove events ... + if(attr.indexOf('.') === -1){ + + if( how === 'add' ) { + trigger(this, how, [newVal,+attr]); + } else if( how === 'remove' ) { + trigger(this, how, [oldVal, +attr]) + } + + } + // issue add, remove, and move events ... + }, + sortedIndex : function(item){ + var itemCompare = item.attr(this.comparator), + equaled = 0, + i; + for(var i =0; i < this.length; i++){ + if(item === this[i]){ + equaled = -1; + continue; + } + if(itemCompare <= this[i].attr(this.comparator) ) { + return i+equaled; + } + } + return i+equaled; }, __get : function(attr){ return attr ? this[attr] : this; @@ -611,7 +703,7 @@ steal('jquery/class').then(function() { * This creates 2 change events. The first event is the removal of * numbers one and two where it's callback values will be: * - * - attr - "*" - to indicate that multiple values have been changed at once + * - attr - "1" - indicates where the remove event took place * - how - "remove" * - newVals - undefined * - oldVals - [1,2] -the array of removed values @@ -620,7 +712,7 @@ steal('jquery/class').then(function() { * The second change event is the addition of the "a", and "b" values where * the callback values will be: * - * - attr - "*" - to indicate that multiple values have been changed at once + * - attr - "1" - indicates where the add event took place * - how - "added" * - newVals - ["a","b"] * - oldVals - [1, 2] - the array of removed values @@ -634,10 +726,10 @@ steal('jquery/class').then(function() { var args = makeArray(arguments), i; - for ( i = 0; i < args.length; i++ ) { + for ( i = 2; i < args.length; i++ ) { var val = args[i]; if ( isObject(val) ) { - args[i] = hookup(val, index + i, this) + args[i] = hookup(val, "*", this) } } if ( count === undefined ) { @@ -645,11 +737,11 @@ steal('jquery/class').then(function() { } var removed = [].splice.apply(this, args); if ( count > 0 ) { - trigger(this, "change", ["*", "remove", undefined, removed, index]); + trigger(this, "change", [""+index, "remove", undefined, removed]); unhookup(removed, this._namespace); } if ( args.length > 2 ) { - trigger(this, "change", ["*", "add", args.slice(2), removed, index]); + trigger(this, "change", [""+index, "add", args.slice(2), removed]); } return removed; }, @@ -692,6 +784,18 @@ steal('jquery/class').then(function() { if ( collectingStarted ) { sendCollection() } + }, + sort: function(method, silent){ + var comparator = this.comparator, + args = comparator ? [function(a, b){ + a = a[comparator] + b = b[comparator] + return a === b ? 0 : (a < b ? -1 : 1); + }] : [], + res = [].sort.apply(this, args); + + !silent && trigger(this, "reset"); + } }), @@ -753,19 +857,35 @@ steal('jquery/class').then(function() { for ( var i = 0; i < args.length; i++ ) { var val = args[i]; if ( isObject(val) ) { - args[i] = hookup(val, i, this) + args[i] = hookup(val, "*", this) } } + + // if we have a sort item, add that + if( args.length == 1 && this.comparator ) { + // add each item ... + // we could make this check if we are already adding in order + // but that would be confusing ... + var index = this.sortedIndex(args[0]); + this.splice(index, 0, args[0]); + return this.length; + } + // call the original method var res = [][name].apply(this, args) - + // cause the change where the args are: - // * - happend in an array + // len - where the additions happened // add - items added // args - the items added // undefined - the old value - // len - where the additions happened - trigger(this, "change", ["*", "add", args, undefined, len]) + if ( this.comparator && args.length > 1) { + this.sort(null, true); + trigger(this,"reset", [args]) + } else { + trigger(this, "change", [""+len, "add", args, undefined]) + } + return res; } @@ -809,6 +929,7 @@ steal('jquery/class').then(function() { function( name, where ) { list.prototype[name] = function() { + var args = getArgs(arguments), len = where && this.length ? this.length - 1 : 0; @@ -821,7 +942,7 @@ steal('jquery/class').then(function() { // undefined - the new values (there are none) // res - the old, removed values (should these be unbound) // len - where these items were removed - trigger(this, "change", ["*", "remove", undefined, [res], len]) + trigger(this, "change", [""+len, "remove", undefined, [res]]) if ( res && res.unbind ) { res.unbind("change" + this._namespace) @@ -829,5 +950,17 @@ steal('jquery/class').then(function() { return res; } }); + + list.prototype. + /** + * Returns the position of the item in the array. Returns -1 if the + * item is not in the array. + * @param {Object} item + * @return {Number} + */ + indexOf = [].indexOf || function(item){ + return $.inArray(item, this) + } + -}); \ No newline at end of file +}); diff --git a/lang/observe/observe_test.js b/lang/observe/observe_test.js index e2fed299..69b79daf 100644 --- a/lang/observe/observe_test.js +++ b/lang/observe/observe_test.js @@ -17,7 +17,7 @@ test("Basic Observe",9,function(){ var added; state.bind("change", function(ev, attr, how, val, old){ - equals(attr, "properties.brand", "correct change name") + equals(attr, "properties.brand.0", "correct change name") equals(how, "add") equals(val[0].attr("foo"),"bar", "correct") @@ -47,30 +47,30 @@ test("list splice", function(){ var l = new $.Observe.List([0,1,2,3]), first = true; - l.bind('change', function( ev, attr, how, newVals, oldVals, where ) { - equals (attr, "*") - equals(where, 1) + l.bind('change', function( ev, attr, how, newVals, oldVals ) { + equals (attr, "1") + // where comes from the attr ... + //equals(where, 1) if(first){ - equals( how, "remove" ) - equals( newVals, undefined ) + equals( how, "remove", "removing items" ) + equals( newVals, undefined, "no new Vals" ) } else { - same( newVals, ["a","b"] ) - equals( how, "add" ) + same( newVals, ["a","b"] , "got the right newVals") + equals( how, "add", "adding items" ) } first = false; }) l.splice(1,2, "a", "b"); - same(l.serialize(), [0,"a","b", 3]) + same(l.serialize(), [0,"a","b", 3], "serialized") }); test("list pop", function(){ var l = new $.Observe.List([0,1,2,3]); - l.bind('change', function( ev, attr, how, newVals, oldVals, where ) { - equals (attr, "*") - equals(where, 3) + l.bind('change', function( ev, attr, how, newVals, oldVals ) { + equals (attr, "3") equals( how, "remove" ) equals( newVals, undefined ) @@ -180,7 +180,7 @@ test("attrs", function(){ state.unbind("change"); state.bind("change", function(ev, attr, how, newVal){ - equals(attr, "properties.brand") + equals(attr, "properties.brand.0") equals(how,"add") same(newVal, ["bad"]) }); @@ -253,7 +253,7 @@ test("pop unbinds", function(){ equals(attr, '0.foo', "count is set"); } else if(count === 2 ){ equals(how, "remove"); - equals(attr, "*") + equals(attr, "0") } else { ok(false, "called too many times") } @@ -278,7 +278,7 @@ test("splice unbinds", function(){ equals(attr, '0.foo', "count is set"); } else if(count === 2 ){ equals(how, "remove"); - equals(attr, "*") + equals(attr, "0") } else { ok(false, "called too many times") } @@ -290,6 +290,23 @@ test("splice unbinds", function(){ o.attr('foo','car') l.splice(0,1); o.attr('foo','bad') +}); + + +test("always gets right attr even after moving array items", function(){ + var l = new $.Observe.List([{foo: 'bar'}]); + var o = l.attr(0); + l.unshift("A new Value") + + + l.bind('change', function(ev, attr, how){ + equals(attr, "1.foo") + }) + + + o.attr('foo','led you') }) + + }).then('./delegate/delegate_test.js'); From 86892706afd26983c8cc3e4c3265415d888ff542 Mon Sep 17 00:00:00 2001 From: Justin Meyer Date: Tue, 15 Nov 2011 00:47:37 -0600 Subject: [PATCH 016/103] model escapeIdentity works better --- model/model.js | 7 ++++++- model/test/qunit/model_test.js | 22 +++++++++++++++++++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/model/model.js b/model/model.js index c576c7ab..4c8a15f3 100644 --- a/model/model.js +++ b/model/model.js @@ -1656,7 +1656,12 @@ steal('jquery/class', 'jquery/lang/string', function() { * identity in their class name. */ elements: function( context ) { - return $("." + this.identity(), context); + var id = this.identity(); + if( this.constructor.escapeIdentity ) { + id = id.replace(/([ #;&,.+*~\'%:"!^$[\]()=>|\/])/g,'\\$1') + } + + return $("." + id, context); }, hookup: function( el ) { var shortName = this.constructor._shortName, diff --git a/model/test/qunit/model_test.js b/model/test/qunit/model_test.js index a0de9ce7..556b85ea 100644 --- a/model/test/qunit/model_test.js +++ b/model/test/qunit/model_test.js @@ -1,7 +1,7 @@ module("jquery/model", { setup: function() { var ids = 0; - $.Model.extend("Person",{ + $.Model("Person",{ findAll: function( params, success, error ) { success("findAll"); }, @@ -485,4 +485,24 @@ test("save error args", function(){ +}); + +test("hookup and elements", function(){ + $.Model('Escaper',{ + escapeIdentity : true + },{}); + + var ul = $('
'), + li = ul.find('li'); + + var esc = new Escaper({id: " some crazy #/ %ing stuff"}); + + li.model(esc); + + var res = esc.elements(ul); + + equals(res.length,1) + equals(res[0], li[0]) }) + + From 31bd4e560aefb9ef2391b1c1163a467acd7425e1 Mon Sep 17 00:00:00 2001 From: noah Date: Tue, 15 Nov 2011 14:14:56 -0600 Subject: [PATCH 017/103] Don't hookup val() or text(). The value passed to either of these must be HTML encoded, so it should not produce an element to hookup to. $(args[0]) causes problems when the value or text contains HTML (which should be encoded). See http://jsfiddle.net/mUxPq/ --- view/view.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/view/view.js b/view/view.js index b2f10ce4..befdfec2 100644 --- a/view/view.js +++ b/view/view.js @@ -587,7 +587,9 @@ steal("jquery").then(function( $ ) { //---- ADD jQUERY HELPERS ----- //converts jquery functions to use views - var convert, modify, isTemplate, isHTML, isDOM, getCallback, hookupView, funcs; + var convert, modify, isTemplate, isHTML, isDOM, getCallback, hookupView, funcs, + // text and val cannot produce an element, so don't run hookups on them + noHookup = {'val':true,'text':true}; convert = function( func_name ) { // save the old jQuery helper @@ -639,8 +641,8 @@ steal("jquery").then(function( $ ) { return this; } } - return modify.call(this, args, old); - + return noHookup[func_name] ? old.apply(this,args) : + modify.call(this, args, old); }; }; From ba845e1f84d10dbe74d37fca1a1f694d98dcf821 Mon Sep 17 00:00:00 2001 From: Ralph Holzmann Date: Tue, 15 Nov 2011 15:32:16 -0600 Subject: [PATCH 018/103] Added the option to pass an object map to DOM formParams to populate a form. In other words, formParams is now a getter and a setter. --- dom/form_params/form_params.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/dom/form_params/form_params.js b/dom/form_params/form_params.js index 2644fe24..cc1268f0 100644 --- a/dom/form_params/form_params.js +++ b/dom/form_params/form_params.js @@ -49,13 +49,25 @@ steal("jquery/dom").then(function( $ ) { * empty string should not be added to the result. Defaults to false. * @return {Object} An object of name-value pairs. */ - formParams: function( convert ) { - if ( this[0].nodeName.toLowerCase() == 'form' && this[0].elements ) { + formParams: function( params, convert ) { + if ( !! params === params ) { + convert = params; + params = null; + } + + if ( params ) { + return this.setParams( params ); + } else if ( this[0].nodeName.toLowerCase() == 'form' && this[0].elements ) { return jQuery(jQuery.makeArray(this[0].elements)).getParams(convert); } return jQuery("input[name], textarea[name], select[name]", this[0]).getParams(convert); }, + setParams: function( params ) { + this.find("[name]").val(function(){ + return params[ $(this).attr("name") ] || ''; + }); + }, getParams: function( convert ) { var data = {}, current; From b43e8f439e8cb7e615cc467ffc217aacc7a90478 Mon Sep 17 00:00:00 2001 From: Ralph Holzmann Date: Tue, 15 Nov 2011 15:39:09 -0600 Subject: [PATCH 019/103] Bug fix. --- dom/form_params/form_params.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/dom/form_params/form_params.js b/dom/form_params/form_params.js index cc1268f0..96e8409a 100644 --- a/dom/form_params/form_params.js +++ b/dom/form_params/form_params.js @@ -65,7 +65,10 @@ steal("jquery/dom").then(function( $ ) { }, setParams: function( params ) { this.find("[name]").val(function(){ - return params[ $(this).attr("name") ] || ''; + var value = params[ $(this).attr("name") ]; + if ( value ) { + return value; + } }); }, getParams: function( convert ) { From 6d4efd4e026c20f0d5f69095be50f8c7db4b1837 Mon Sep 17 00:00:00 2001 From: Nick Matthews Date: Wed, 16 Nov 2011 00:36:54 -0500 Subject: [PATCH 020/103] More specifically name jquerymx instead of javascriptmvc in the docs, when referring to a particular git project. --- README | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README b/README index 12846a17..19b1dfba 100644 --- a/README +++ b/README @@ -10,15 +10,15 @@ A. How to get (and contribute) JMVC http://github.com/jupiterjs/steal and http://github.com/jupiterjs/jquerymx - 3. Add steal and javascriptmvc as submodules of your project... + 3. Add steal and jquerymx as submodules of your project... git submodule add git@github.com:_YOU_/steal.git steal git submodule add git@github.com:_YOU_/jquerymx.git jquery - * Notice javascriptmvc is under the jquery folder + * Notice jquerymx is under the jquery folder 4. Learn a little more about submodules ... http://johnleach.co.uk/words/archives/2008/10/12/323/git-submodules-in-n-easy-steps - 5. Make changes in steal or jmvc, and push them back to your fork. + 5. Make changes in steal or jquerymx, and push them back to your fork. 6. Make a pull request to your fork. From 0404ba86c31810882539d4aed844ae69b96626e2 Mon Sep 17 00:00:00 2001 From: Ralph Holzmann Date: Wed, 16 Nov 2011 00:08:55 -0600 Subject: [PATCH 021/103] Added fixes for formParams setter. Now handles radio and checkboxes. --- dom/form_params/form_params.js | 39 ++++++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/dom/form_params/form_params.js b/dom/form_params/form_params.js index 96e8409a..cbfc7f6c 100644 --- a/dom/form_params/form_params.js +++ b/dom/form_params/form_params.js @@ -45,12 +45,17 @@ steal("jquery/dom").then(function( $ ) { * * @demo jquery/dom/form_params/form_params.html * - * @param {Boolean} [convert=false] True if strings that look like numbers and booleans should be converted and if - * empty string should not be added to the result. Defaults to false. + * @param {Object} [params] If an object is passed, the form will be repopulated + * with the values of the object based on the name of the inputs within + * the form + * @param {Boolean} [convert=false] True if strings that look like numbers + * and booleans should be converted and if empty string should not be added + * to the result. Defaults to false. * @return {Object} An object of name-value pairs. */ formParams: function( params, convert ) { + // Quick way to determine if something is a boolean if ( !! params === params ) { convert = params; params = null; @@ -64,12 +69,34 @@ steal("jquery/dom").then(function( $ ) { return jQuery("input[name], textarea[name], select[name]", this[0]).getParams(convert); }, setParams: function( params ) { - this.find("[name]").val(function(){ - var value = params[ $(this).attr("name") ]; + + // Find all the inputs + this.find("[name]").each(function() { + + var value = params[ $(this).attr("name") ], + $this; + + // Don't do all this work if there's no value if ( value ) { - return value; + $this = $(this); + + // Nested these if statements for performance + if ( $this.is(":radio") ) { + if ( $this.val() == value ) { + $this.attr("checked", true); + } + } else if ( $this.is(":checkbox") ) { + // Convert single value to an array to reduce + // complexity + value = $.isArray( value ) ? value : [value]; + if ( $.inArray( $this.val(), value ) > -1) { + $this.attr("checked", true); + } + } else { + $this.val( value ); + } } - }); + }); }, getParams: function( convert ) { var data = {}, From 8567d85310a2e11c7dad8a117868e240831fa0e6 Mon Sep 17 00:00:00 2001 From: farkashon Date: Wed, 16 Nov 2011 09:43:07 +0200 Subject: [PATCH 022/103] Fixed a mistake in the documentation example of $.Model.prototype.models. The 'return' was missing here (line 976): * $.Model('Person',{ * models : function(data){ * return this._super(data.ballers); * } * },{}) --- model/model.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/model/model.js b/model/model.js index e884cf91..cbdd0684 100644 --- a/model/model.js +++ b/model/model.js @@ -974,7 +974,7 @@ steal('jquery/class', 'jquery/lang/string', function() { * * $.Model('Person',{ * models : function(data){ - * this._super(data.ballers); + * return this._super(data.ballers); * } * },{}) * From b9d64ba61dd1adbc69fa45e97dfb7d1a72640d35 Mon Sep 17 00:00:00 2001 From: Austin McDaniel Date: Wed, 16 Nov 2011 17:36:32 -0600 Subject: [PATCH 023/103] Fix for ';' instead of ',' therefore blowing up in IE Signed-off-by: Austin McDaniel --- view/ejs/ejs.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/view/ejs/ejs.js b/view/ejs/ejs.js index 434a5b95..fdaf67ed 100644 --- a/view/ejs/ejs.js +++ b/view/ejs/ejs.js @@ -379,7 +379,7 @@ steal('jquery/view', 'jquery/lang/string/rsplit').then(function( $ ) { // the text that starts the view code (or block function) startTxt = 'var ___v1ew = [];', // the text that ends the view code (or block function) - finishTxt = "return ___v1ew.join('')"; + finishTxt = "return ___v1ew.join('')", // initialize a buffer buff = new EJS.Buffer([startTxt], []), // content is used as the current 'processing' string From ffe3316b9c5da8b0e20a97cd9abc4b4ce0efc2cc Mon Sep 17 00:00:00 2001 From: Alexis Abril Date: Thu, 17 Nov 2011 02:26:48 -0500 Subject: [PATCH 024/103] Fixed revert in rsplit link. --- download/download.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/download/download.html b/download/download.html index 3905efe4..98535d6c 100644 --- a/download/download.html +++ b/download/download.html @@ -343,8 +343,8 @@

Lang

- - + +
Splits a string with a regex correctly cross browser
From 843719b2bababf2862e56a26b75cc6c4b774ccec Mon Sep 17 00:00:00 2001 From: Brian Moschel Date: Thu, 17 Nov 2011 00:05:07 -0800 Subject: [PATCH 025/103] fixing docs --- model/pages/events.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/model/pages/events.md b/model/pages/events.md index 24fce17e..48bda2f2 100644 --- a/model/pages/events.md +++ b/model/pages/events.md @@ -56,11 +56,10 @@ Task.bind('created', function(ev, task){ }) @codeend -Typically, you'll subscribe with the -jquery/controller/subscribe plugin like: +Typically, you'll subscribe with templated events like: @codestart -$.Controller("Subscriber",{ +$.Controller("Todo",{ ... From 5c7a170f6f0e4cfb7c68aca5d816c7fb013dfb83 Mon Sep 17 00:00:00 2001 From: Justin Meyer Date: Thu, 24 Nov 2011 19:20:13 -0600 Subject: [PATCH 026/103] observe handles multiple property matching --- lang/observe/delegate/delegate.js | 144 +++++++++++++++++++------ lang/observe/delegate/delegate_test.js | 103 ++++++++++++++++-- lang/observe/observe.js | 105 +++++++++++++----- lang/observe/observe_test.js | 4 +- 4 files changed, 284 insertions(+), 72 deletions(-) diff --git a/lang/observe/delegate/delegate.js b/lang/observe/delegate/delegate.js index 3a8d812a..583802b1 100644 --- a/lang/observe/delegate/delegate.js +++ b/lang/observe/delegate/delegate.js @@ -10,13 +10,12 @@ steal('jquery/lang/observe',function(){ // tells if the parts part of a delegate matches the broken up props of the event // gives the prop to use as 'this' - // - delegate - an object like {parts: ['foo','*']} - // - props - ['foo','bar','0'] - // - returns - 'foo.bar' - var matches = function(delegate, props){ + // - parts - the attribute name of the delegate split in parts ['foo','*'] + // - props - the split props of the event that happened ['foo','bar','0'] + // - returns - the attribute to delegate too ('foo.bar'), or null if not a match + var matches = function(parts, props){ //check props parts are the same or - var parts = delegate.parts, - len = parts.length, + var len = parts.length, i =0, // keeps the matched props we will use matchedProps = [], @@ -47,33 +46,85 @@ steal('jquery/lang/observe',function(){ } return matchedProps.join("."); }, + // gets a change event and tries to figure out which + // delegates to call delegate = function(event, prop, how, newVal, oldVal){ + // pre-split properties to save some regexp time var props = prop.split("."), delegates = $.data(this,"_observe_delegates") || [], - delegate; + delegate, + len = delegates.length, + attr, + matchedAttr, + hasMatch, + valuesEqual; event.attr = prop; event.lastAttr = props[props.length -1 ]; - for(var i =0; i < delegates.length; i++){ - // check delegate.event + // for each delegate + for(var i =0; i < len; i++){ + delegate = delegates[i]; + // if there is a batchNum, this means that this + // event is part of a series of events caused by a single + // attrs call. We don't want to issue the same event + // multiple times + // setting the batchNum happens later + if(event.batchNum && delegate.batchNum === event.batchNum){ + continue; + } - // see if this delegate matches props - var attr = matches(delegate, props); - if(attr) { - var from = prop.replace(attr+".",""); + // reset match and values tests + hasMatch = undefined; + valuesEqual = true; + + // for each attr in a delegate + for(var a =0 ; a < delegate.attrs.length; a++){ + + attr = delegate.attrs[a]; + // check if it is a match + if(matchedAttr = matches(attr.parts, props)){ + hasMatch = matchedAttr; + } + // if it has a value, make sure it's the right value + // if it's set, we should probably check that it has a + // value no matter what + if(attr.value && valuesEqual /* || delegate.hasValues */){ + valuesEqual = attr.value === ""+this.attr(attr.attr) + } else if (valuesEqual && delegate.attrs.length > 1){ + // if there are multiple attributes, each has to at + // least have some value + valuesEqual = this.attr(attr.attr) !== undefined + } + } + + // if there is a match and valuesEqual ... call back + + if(hasMatch && valuesEqual) { + // how to get to the changed property from the delegate + var from = prop.replace(hasMatch+".",""); + + // if this event is part of a batch, set it on the delegate + // to only send one event + if(event.batchNum ){ + delegate.batchNum = event.batchNum + } + + // if we listen to change, fire those with the same attrs + // TODO: the attrs should probably be using from if( delegate.event === 'change' ){ arguments[1] = from; - event.curAttr = attr; - delegate.callback.apply(this.attr(attr), $.makeArray( arguments)); + event.curAttr = hasMatch; + delegate.callback.apply(this.attr(hasMatch), $.makeArray( arguments)); } else if(delegate.event === how ){ - // TODO: change where from is - delegate.callback.apply(this.attr(attr), [event,newVal, oldVal, from]); + + // if it's a match, callback with the location of the match + delegate.callback.apply(this.attr(hasMatch), [event,newVal, oldVal, from]); } else if(delegate.event === 'set' && how == 'add' ) { - // TODO: change where from is - delegate.callback.apply(this.attr(attr), [event,newVal, oldVal, from]); + // if we are listening to set, we should also listen to add + delegate.callback.apply(this.attr(hasMatch), [event,newVal, oldVal, from]); } } @@ -88,7 +139,7 @@ steal('jquery/lang/observe',function(){ * * * // create an observable - * var observe = new $.Observe({ + * var observe = $.O({ * foo : { * bar : "Hello World" * } @@ -97,6 +148,7 @@ steal('jquery/lang/observe',function(){ * //listen to changes on a property * observe.delegate("foo.bar","change", function(ev, prop, how, newVal, oldVal){ * // foo.bar has been added, set, or removed + * this //-> * }); * * // change the property @@ -178,19 +230,40 @@ steal('jquery/lang/observe',function(){ * properties. Using a double wildcard (**) matches * any deep property. * - * @param {String} attr the attribute you want to listen for changes in. + * ## Listening on multiple properties and values + * + * Delegate lets you listen on multiple values at once, for example, + * + * @param {String} selector the attributes you want to listen for changes in. * @param {String} event the event name * @param {Function} cb the callback handler * @return {jQuery.Delegate} the delegate for chaining */ - delegate : function(attr, event, cb){ - attr = $.trim(attr); + delegate : function(selector, event, cb){ + selector = $.trim(selector); var delegates = $.data(this, "_observe_delegates") || - $.data(this, "_observe_delegates", []); - attr = $.trim(attr); + $.data(this, "_observe_delegates", []), + attrs = []; + + // split selector by spaces + selector.replace(/([^\s=]+)=?([^\s]+)?/g, function(whole, attr, value){ + attrs.push({ + // the attribute name + attr: attr, + // the attribute's pre-split names (for speed) + parts: attr.split('.'), + // the value associated with this prop + value: value + }) + }); + + // delegates has pre-processed info about the event delegates.push({ - attr : attr, - parts : attr.split('.'), + // the attrs name for unbinding + selector : selector, + // an object of attribute names and values {type: 'recipe',id: undefined} + // undefined means a value was not defined + attrs : attrs, callback : cb, event: event }); @@ -205,28 +278,29 @@ steal('jquery/lang/observe',function(){ * * observe.undelegate("name","set", function(){ ... }) * - * @param {String} attr the attribute name of the object you want to undelegate from. + * @param {String} selector the attribute name of the object you want to undelegate from. * @param {String} event the event name * @param {Function} cb the callback handler * @return {jQuery.Delegate} the delegate for chaining */ - undelegate : function(attr, event, cb){ - attr = $.trim(attr); + undelegate : function(selector, event, cb){ + selector = $.trim(selector); var i =0, delegates = $.data(this, "_observe_delegates") || [], - delegate; - if(attr){ + delegateOb; + if(selector){ while(i < delegates.length){ - delegate = delegates[i]; - if( delegate.callback === cb || - (!cb && delegate.attr === attr) ){ + delegateOb = delegates[i]; + if( delegateOb.callback === cb || + (!cb && delegateOb.selector === selector) ){ delegates.splice(i,1) } else { i++; } } } else { + // remove all delegates delegates = []; } if(!delegates.length){ diff --git a/lang/observe/delegate/delegate_test.js b/lang/observe/delegate/delegate_test.js index f9b25149..ec61a9c4 100644 --- a/lang/observe/delegate/delegate_test.js +++ b/lang/observe/delegate/delegate_test.js @@ -7,20 +7,20 @@ var matches = $.Observe.prototype.delegate.matches; test("matches", function(){ - equals( matches({parts: ['**']}, ['foo','bar','0']) , + equals( matches(['**'], ['foo','bar','0']) , 'foo.bar.0' , "everything" ); - equals( matches({parts: ['*.**']}, ['foo']) , + equals( matches(['*.**'], ['foo']) , null , "everything at least one level deep" ) - equals( matches({parts: ['foo','*']}, ['foo','bar','0']) , + equals( matches(['foo','*'], ['foo','bar','0']) , 'foo.bar' ) - equals(matches({parts: ['*']}, + equals(matches(['*'], ['foo','bar','0']) , 'foo' ); - equals( matches({parts: [ '*', 'bar' ]}, + equals( matches([ '*', 'bar' ], ['foo','bar','0']) , 'foo.bar' ) // - props - @@ -74,7 +74,7 @@ test("list events", function(){ }) -test("delegate", function(){ +test("delegate", 4,function(){ var state = new $.Observe({ properties : { @@ -83,11 +83,11 @@ test("delegate", function(){ }); var prices = state.attr('properties.prices'); - state.delegate("properties.price","change", function(ev, attr, how, val, old){ + state.delegate("properties.prices","change", function(ev, attr, how, val, old){ equals(attr, "0", "correct change name") equals(how, "add") equals(val[0].attr("foo"),"bar", "correct") - ok(this === price, "rooted element") + ok(this === prices, "rooted element") }); prices.push({foo: "bar"}); @@ -95,7 +95,7 @@ test("delegate", function(){ state.undelegate(); }) -test("delegate on add", function(){ +test("delegate on add", 2, function(){ var state = new $.Observe({}); @@ -110,7 +110,7 @@ test("delegate on add", function(){ }) -test("delegate set is called on add", function(){ +test("delegate set is called on add", 2, function(){ var state = new $.Observe({}); state.delegate("foo","set", function(ev, newVal){ @@ -120,6 +120,41 @@ test("delegate set is called on add", function(){ state.attr("foo","bar") }); +test("delegate's this", 5, function(){ + var state = new $.Observe({ + person : { + name : { + first : "justin", + last : "meyer" + } + }, + prop : "foo" + }); + var n = state.attr('person.name'), + check + + // listen to person name changes + state.delegate("person.name","set", check = function(ev, newValue, oldVal, from){ + // make sure we are getting back the person.name + equals(this, n) + equals(newValue, "Brian"); + equals(oldVal, "justin"); + // and how to get there + equals(from,"first") + }); + n.attr('first',"Brian"); + state.undelegate("person.name",'set',check) + // stop listening + + // now listen to changes in prop + state.delegate("prop","set", function(){ + equals(this, 'food'); + }); // this is weird, probably need to support direct bind ... + + // update the prop + state.attr('prop','food') +}) + test("delegate on deep properties with *", function(){ var state = new $.Observe({ @@ -136,6 +171,54 @@ test("delegate on deep properties with *", function(){ equals(attr, "name.first") }); state.attr("person.name.first","brian") +}); + +test("compound sets", function(){ + + var state = new $.Observe({ + type : "person", + id: "5" + }); + var count = 0; + state.delegate("type=person id","set", function(){ + equals(state.type, "person","type is person") + ok(state.id !== undefined, "id has value"); + count++; + }) + + // should trigger a change + state.attr("id",0); + equals(count, 1, "changing the id to 0 caused a change"); + + // should not fire a set + state.removeAttr("id") + equals(count, 1, "removing the id changed nothing"); + + state.attr("id",3) + equals(count, 2, "adding an id calls callback"); + + state.attr("type","peter") + equals(count, 2, "changing the type does not fire callback"); + + state.removeAttr("type"); + state.removeAttr("id"); + + equals(count, 2, ""); + + state.attrs({ + type : "person", + id: "5" + }); + + equals(count, 3, "setting person and id only fires 1 event"); + + state.removeAttr("type"); + state.removeAttr("id"); + + state.attrs({ + type : "person" + }); + equals(count, 3, "setting person does not fire anything"); }) }); \ No newline at end of file diff --git a/lang/observe/observe.js b/lang/observe/observe.js index b79493b0..2d329772 100644 --- a/lang/observe/observe.js +++ b/lang/observe/observe.js @@ -85,17 +85,26 @@ steal('jquery/class').then(function() { }) } }, + // which batch of events this is for, might not want to send multiple + // messages on the same batch. This is mostly for + // event delegation + batchNum = 0, // sends all pending events sendCollection = function() { var len = collecting.length, items = collecting.slice(0), cur; collecting = null; + batchNum ++; for ( var i = 0; i < len; i++ ) { cur = items[i]; - $.event.trigger(cur.ev, cur.args, cur.t) + // batchNum + $.event.trigger({ + type: cur.ev, + batchNum : batchNum + }, cur.args, cur.t) } - + }, // a helper used to serialize an Observe or Observe.List where: // observe - the observable @@ -119,12 +128,32 @@ steal('jquery/class').then(function() { * @parent jquerymx.lang * @test jquery/lang/observe/qunit.html * - * Observe provides observable behavior on - * JavaScript Objects and Arrays. + * Observe provides the awesome observable pattern for + * JavaScript Objects and Arrays. It lets you + * + * - Set and remove property or property values on objects and arrays + * - Listen for changes in objects and arrays + * - Work with nested properties + * + * ## Creating an $.Observe + * + * To create an $.Observe, or $.Observe.List, you can simply use + * the `$.O(data)` shortcut like: + * + * var person = $.O({name: 'justin', age: 29}), + * hobbies = $.O(['programming', 'basketball', 'nose picking']) + * + * Depending on the type of data passed to $.O, it will create an instance of either: * - * ## Use + * - $.Observe, which is used for objects like: `{foo: 'bar'}`, and + * - [jQuery.Observe.List $.Observe.List], which is used for arrays like `['foo','bar']` + * + * $.Observe.List and $.Observe are very similar. In fact, + * $.Observe.List inherits $.Observe and only adds a few extra methods for + * manipulating arrays like [jQuery.Observe.List.prototype.push push]. Go to + * [jQuery.Observe.List $.Observe.List] for more information about $.Observe.List. * - * Create a new Observe with the data you want to observe: + * You can also create a `new $.Observe` simply by pass it the data you want to observe: * * var data = { * addresses : [ @@ -141,8 +170,14 @@ steal('jquery/class').then(function() { * }, * o = new $.Observe(data); * - * _o_ now represents an observable copy of _data_. You - * can read the property values of _o_ with + * _o_ now represents an observable copy of _data_. + * + * ## Getting and Setting Properties + * + * Use [jQuery.Observe.prototype.attr attr] and [jQuery.Observe.prototype.attr attrs] + * to get and set properties. + * + * For example, you can read the property values of _o_ with * `observe.attr( name )` like: * * // read name @@ -165,6 +200,25 @@ steal('jquery/class').then(function() { * state: 'NY' * }) * + * `attrs()` can be used to get all properties back from the observe: + * + * o.attrs() // -> + * { + * addresses : [ + * { + * city: 'Chicago', + * state: 'IL' + * }, + * { + * city: 'New York', + * state : 'MA' + * } + * ], + * name : "Brian Moschel" + * } + * + * ## Listening to property changes + * * When a property value is changed, it creates events * that you can listen to. There are two ways to listen * for events: @@ -183,29 +237,20 @@ steal('jquery/class').then(function() { * }) * * `delegate( attr, event, handler(ev, newVal, oldVal ) )` lets you listen - * to a specific even on a specific attribute. + * to a specific event on a specific attribute. * * // listen for name changes * o.delegate("name","set", function(){ * * }) * - * `attrs()` can be used to get all properties back from the observe: + * Delegate lets you specify multiple attributes and values to match + * for the callback. For example, * - * o.attrs() // -> - * { - * addresses : [ - * { - * city: 'Chicago', - * state: 'IL' - * }, - * { - * city: 'New York', - * state : 'MA' - * } - * ], - * name : "Brian Moschel" - * } + * r = $.O({type: "video", id : 5}) + * r.delegate("type=images id","set", function(){}) + * + * This is used heavily by [jQuery.route $.route]. * * @constructor * @@ -953,6 +998,7 @@ steal('jquery/class').then(function() { list.prototype. /** + * @function indexOf * Returns the position of the item in the array. Returns -1 if the * item is not in the array. * @param {Object} item @@ -962,5 +1008,14 @@ steal('jquery/class').then(function() { return $.inArray(item, this) } - + /** + * @class $.O + */ + $.O = function(data, options){ + if(isArray(data) || data instanceof $.Observe.List){ + return new $.Observe.List(data, options) + } else { + return new $.Observe(data, options) + } + } }); diff --git a/lang/observe/observe_test.js b/lang/observe/observe_test.js index 69b79daf..86c2ffe4 100644 --- a/lang/observe/observe_test.js +++ b/lang/observe/observe_test.js @@ -4,7 +4,7 @@ module('jquery/lang/observe') test("Basic Observe",9,function(){ - var state = new $.Observe({ + var state = new $.O({ category : 5, productType : 4, properties : { @@ -243,7 +243,7 @@ test("direct property access", function(){ }) test("pop unbinds", function(){ - var l = new $.Observe.List([{foo: 'bar'}]); + var l = new $.O([{foo: 'bar'}]); var o = l.attr(0), count = 0; l.bind('change', function(ev, attr, how, newVal, oldVal){ From a8e22331541c8e31aa5ab089b5021de3ef65abf9 Mon Sep 17 00:00:00 2001 From: Justin Meyer Date: Thu, 24 Nov 2011 19:21:45 -0600 Subject: [PATCH 027/103] changes to steal and some future fixture thoughts --- dom/fixture/fixture_test.js | 43 +++++++++++++++++++++++++++++++++++++ test/qunit/qunit.js | 3 +-- view/view.js | 4 ++-- 3 files changed, 46 insertions(+), 4 deletions(-) diff --git a/dom/fixture/fixture_test.js b/dom/fixture/fixture_test.js index 422cbc16..5df334cf 100644 --- a/dom/fixture/fixture_test.js +++ b/dom/fixture/fixture_test.js @@ -273,8 +273,51 @@ test("replacing and removing a fixture", function(){ },'json') +}); + +return; // future fixture stuff + +// returning undefined means you want to control timing? +$.fixture('GET /foo', function(orig, settings, headers, cb){ + setTimeout(function(){ + cb(200, "success",{json : "{}"},{}) + },1000); +}) + +// fixture that hooks into model / vice versa? + +// fixture that creates a nice store + +var store = $.fixture.store(1000, function(){ + }) +store.find() + +// make cloud + +var clouds = $.fixture.store(1, function(){ + return { + name: "ESCCloud", + DN : "ESCCloud-ESCCloud", + type : "ESCCloud" + } +}); + +var computeCluster = $.fixture.store(5, function(i){ + return { + name : "", + parentDN : clouds.find()[0].DN, + type: "ComputeCluster", + DN : "ComputeCluster-ComputeCluster"+i + } +}); + +$.fixture("GET /computeclusters", function(){ + return [] +}); + +// hacking models? diff --git a/test/qunit/qunit.js b/test/qunit/qunit.js index fd81543c..3341f1f0 100644 --- a/test/qunit/qunit.js +++ b/test/qunit/qunit.js @@ -29,8 +29,7 @@ steal('jquery').then(function(){ 'jquery/controller/view/test/qunit', 'jquery/model/test/qunit', -'jquery/view/test/qunit', -'jquery/view/ejs/test/qunit' +'jquery/view/test/qunit' ).then('./integration.js', diff --git a/view/view.js b/view/view.js index b2f10ce4..837a03fe 100644 --- a/view/view.js +++ b/view/view.js @@ -535,7 +535,7 @@ steal("jquery").then(function( $ ) { this.types["." + info.suffix] = info; if ( window.steal ) { - steal.type(info.suffix + " view js", function( options, orig, success, error ) { + steal.type(info.suffix + " view js", function( options, success, error ) { var type = $view.types["." + options.type], id = toId(options.rootSrc); @@ -576,7 +576,7 @@ steal("jquery").then(function( $ ) { }); if ( window.steal ) { - steal.type("view js", function( options, orig, success, error ) { + steal.type("view js", function( options, success, error ) { var type = $view.types["." + options.type], id = toId(options.rootSrc); From 036cf9147bdb975d2d2555c1f867b2f4a14354b9 Mon Sep 17 00:00:00 2001 From: Justin Meyer Date: Fri, 25 Nov 2011 08:30:01 -0600 Subject: [PATCH 028/103] fixes #94 adds an abort to deferred returned by save and destroy --- model/model.js | 17 ++++++++---- model/test/qunit/model_test.js | 49 ++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 5 deletions(-) diff --git a/model/model.js b/model/model.js index cbdd0684..157d7855 100644 --- a/model/model.js +++ b/model/model.js @@ -140,7 +140,9 @@ steal('jquery/class', 'jquery/lang/string', function() { // the args to pass to the ajax method args = [self.serialize(), resolve, reject], // the Model - model = self.constructor; + model = self.constructor, + jqXHR, + promise = deferred.promise(); // destroy does not need data if ( type == 'destroy' ) { @@ -156,10 +158,15 @@ steal('jquery/class', 'jquery/lang/string', function() { deferred.then(success); deferred.fail(error); - // call the - model[type].apply(model, args); - - return deferred.promise(); + // call the model's function and hook up + // abort + jqXHR = model[type].apply(model, args); + if(jqXHR && jqXHR.abort){ + promise.abort = function(){ + jqXHR.abort(); + } + } + return promise; }, // a quick way to tell if it's an object and not some string isObject = function( obj ) { diff --git a/model/test/qunit/model_test.js b/model/test/qunit/model_test.js index 556b85ea..c0f4b991 100644 --- a/model/test/qunit/model_test.js +++ b/model/test/qunit/model_test.js @@ -505,4 +505,53 @@ test("hookup and elements", function(){ equals(res[0], li[0]) }) +test('aborting create update and destroy', function(){ + stop(5000); + var delay = $.fixture.delay; + $.fixture.delay = 1000; + + $.fixture("POST /abort", function(){ + ok(false, "we should not be calling the fixture"); + return {}; + }) + + $.Model('Abortion',{ + create : "POST /abort", + update : "POST /abort", + destroy: "POST /abort" + },{}); + + var deferred = new Abortion({name: "foo"}).save(function(){ + ok(false, "success create") + }, function(){ + ok(true, "create error called"); + + + deferred = new Abortion({name: "foo",id: 5}) + .save(function(){},function(){ + ok(true, "error called in update") + + deferred = new Abortion({name: "foo",id: 5}).destroy(function(){}, + function(){ + ok(true,"destroy error called") + $.fixture.delay = delay; + start(); + }) + + setTimeout(function(){ + deferred.abort(); + },10) + + }) + + setTimeout(function(){ + deferred.abort(); + },10) + }); + setTimeout(function(){ + deferred.abort(); + },10) + + +}) From 76f8c762918c7161fad61bde2219c53760c93151 Mon Sep 17 00:00:00 2001 From: Justin Meyer Date: Fri, 25 Nov 2011 08:43:48 -0600 Subject: [PATCH 029/103] fixes #93 allows x- types in script views --- view/test/qunit/view_test.js | 10 ++++++++++ view/view.js | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/view/test/qunit/view_test.js b/view/test/qunit/view_test.js index 128e0fb4..f65ab19a 100644 --- a/view/test/qunit/view_test.js +++ b/view/test/qunit/view_test.js @@ -178,3 +178,13 @@ test("val set with a template within a hookup within another template", function /*test("bad url", function(){ $.View("//asfdsaf/sadf.ejs") });*/ + +test("hyphen in type", function(){ + $(document.body).append("") + + $("#qunit-test-area").html('hyphenEjs',{}); + + ok( /Hyphen/.test( $("#qunit-test-area").html() ), "has hyphen" ); +}) + + diff --git a/view/view.js b/view/view.js index 4764a2a9..0b3a5be8 100644 --- a/view/view.js +++ b/view/view.js @@ -387,7 +387,7 @@ steal("jquery").then(function( $ ) { // if we have an inline template, derive the suffix from the 'text/???' part // this only supports '' tags if ( el = document.getElementById(url) ) { - suffix = el.type.match(/\/[\d\w]+$/)[0].replace(/^\//, '.'); + suffix = "."+el.type.match(/\/(x\-)?(.+)/)[2]; } // if there is no suffix, add one From fa3ff62e93704e85b9719821d2df95c50fd1d29d Mon Sep 17 00:00:00 2001 From: Justin Meyer Date: Fri, 25 Nov 2011 10:16:08 -0600 Subject: [PATCH 030/103] fixes #88 subdir app generator --- generate/app | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/generate/app b/generate/app index 3e372c46..aeb906fd 100644 --- a/generate/app +++ b/generate/app @@ -8,14 +8,16 @@ if (!_args[0]) { load('steal/rhino/rhino.js'); steal('steal/generate','steal/generate/system.js',function(steal){ - var md = steal.generate.convert(_args[0]), + var classed = steal.generate.toClass(_args[0]), + md = steal.generate.convert(classed), data = steal.extend({ - path: md.path, - application_name: md.appName, + path: _args[0], + application_name: md.underscore, current_path: steal.File.cwdURL(), - path_to_steal : steal.File(md.path).pathToRoot() - }, steal.system) + path_to_steal : steal.File(_args[0]).pathToRoot() + }, steal.system); - steal.generate("jquery/generate/templates/app", md.path, data); + + steal.generate("jquery/generate/templates/app", _args[0], data); }); From 997f163c0cde808d99f0189ce218a8bff883d40d Mon Sep 17 00:00:00 2001 From: Justin Meyer Date: Fri, 25 Nov 2011 12:02:41 -0600 Subject: [PATCH 031/103] flexible model definitions, fixes #77 --- model/model.js | 58 ++++++++++++--------------- model/test/qunit/associations_test.js | 2 +- model/test/qunit/model_test.js | 33 +++++++++++++++ 3 files changed, 60 insertions(+), 33 deletions(-) diff --git a/model/model.js b/model/model.js index 9052176c..36a0036b 100644 --- a/model/model.js +++ b/model/model.js @@ -15,53 +15,47 @@ steal('jquery/class', 'jquery/lang/string', function() { trigger = function(obj, event, args){ $(obj).triggerHandler( event, args ); }, - reqType = /GET|POST|PUT|DELETE/i, // used to make an ajax request where - // ajaxOb - a string ajax name - // attrs - the attributes or data that will be sent + // ajaxOb - a bunch of options + // data - the attributes or data that will be sent // success - callback function // error - error callback // fixture - the name of the fixture (typically a path or something on $.fixture // type - the HTTP request type (defaults to "post") // dataType - how the data should return (defaults to "json") - ajax = function( ajaxOb, attrs, success, error, fixture, type, dataType ) { - // set the dataType - var dataType = dataType || "json", - // - src = "", - tmp; + ajax = function(ajaxOb, data, success, error, fixture, type, dataType ) { + + + // if we get a string, handle it if ( typeof ajaxOb == "string" ) { // if there's a space, it's probably the type var sp = ajaxOb.indexOf(" ") - if ( sp > 2 && sp < 7 ) { - tmp = ajaxOb.substr(0, sp); - if ( reqType.test(tmp) ) { - type = tmp; - } else { - dataType = tmp; + if ( sp > -1 ) { + ajaxOb = { + url : ajaxOb.substr(sp + 1), + type :ajaxOb.substr(0, sp) } - src = ajaxOb.substr(sp + 1) } else { - src = ajaxOb; + ajaxOb = {url : ajaxOb} } } // if we are a non-array object, copy to a new attrs - typeof attrs == "object" && (!isArray(attrs)) && (attrs = extend({}, attrs)); + ajaxOb.data = typeof data == "object" && !isArray(data) ? + extend(ajaxOb.data || {}, data) : data; + // get the url with any templated values filled out - var url = $String.sub(src, attrs, true); + ajaxOb.url = $String.sub(ajaxOb.url, ajaxOb.data, true); - return $.ajax({ - url: url, - data: attrs, - success: success, - error: error, + return $.ajax($.extend({ type: type || "post", - dataType: dataType, - fixture: fixture - }); + dataType: dataType ||"json", + fixture: fixture, + success : success, + error: error + },ajaxOb)); }, // guesses at a fixture name where // extra - where to look for 'MODELNAME'+extra fixtures (ex: "Create" -> '-recipeCreate') @@ -547,7 +541,7 @@ steal('jquery/class', 'jquery/lang/string', function() { * @param {Function} error a function to callback if something goes wrong. */ return function( attrs, success, error ) { - return ajax(str, attrs, success, error, fixture(this, "Create", "-restCreate")) + return ajax(str || "{_shortName}", attrs, success, error, fixture(this, "Create", "-restCreate")) }; }, update: function( str ) { @@ -622,7 +616,7 @@ steal('jquery/class', 'jquery/lang/string', function() { * @param {Function} error a function to callback if something goes wrong. */ return function( id, attrs, success, error ) { - return ajax(str, addId(this, attrs, id), success, error, fixture(this, "Update", "-restUpdate"), "put") + return ajax( str || "{_shortName}s/"+id+".json", addId(this, attrs, id), success, error, fixture(this, "Update", "-restUpdate"), "put") } }, destroy: function( str ) { @@ -656,7 +650,7 @@ steal('jquery/class', 'jquery/lang/string', function() { return function( id, success, error ) { var attrs = {}; attrs[this.id] = id; - return ajax(str, attrs, success, error, fixture(this, "Destroy", "-restDestroy"), "delete") + return ajax( str || "{_shortName}s/"+id+".json", attrs, success, error, fixture(this, "Destroy", "-restDestroy"), "delete") } }, @@ -696,7 +690,7 @@ steal('jquery/class', 'jquery/lang/string', function() { * @param {Function} error */ return function( params, success, error ) { - return ajax(str || this.shortName + "s.json", params, success, error, fixture(this, "s"), "get", "json " + this._shortName + ".models"); + return ajax( str || "{_shortName}s.json", params, success, error, fixture(this, "s"), "get", "json " + this._shortName + ".models"); }; }, findOne: function( str ) { @@ -732,7 +726,7 @@ steal('jquery/class', 'jquery/lang/string', function() { * @param {Function} error */ return function( params, success, error ) { - return ajax(str, params, success, error, fixture(this), "get", "json " + this._shortName + ".model"); + return ajax(str || "{_shortName}s/{"+this.id+"}.json", params, success, error, fixture(this), "get", "json " + this._shortName + ".model"); }; } }; diff --git a/model/test/qunit/associations_test.js b/model/test/qunit/associations_test.js index 9e402874..7e8a8559 100644 --- a/model/test/qunit/associations_test.js +++ b/model/test/qunit/associations_test.js @@ -9,7 +9,7 @@ module("jquery/model/associations",{ $.Model("MyTest.Loan"); $.Model("MyTest.Issue"); - $.Model.extend("MyTest.Customer", + $.Model("MyTest.Customer", { attributes : { person : "MyTest.Person.model", diff --git a/model/test/qunit/model_test.js b/model/test/qunit/model_test.js index c0f4b991..6d3d28a4 100644 --- a/model/test/qunit/model_test.js +++ b/model/test/qunit/model_test.js @@ -553,5 +553,38 @@ test('aborting create update and destroy', function(){ },10) +}); + +test("object definitions", function(){ + + $.Model('ObjectDef',{ + findAll : { + url : "/test/place" + }, + findOne : { + url : "/objectdef/{id}", + timeout : 1000 + }, + create : { + + }, + update : { + + }, + destroy : { + + } + },{}) + + $.fixture("GET /objectdef/{id}", function(original){ + equals(original.timeout,1000,"timeout set"); + return {yes: true} + }); + stop(3000); + ObjectDef.findOne({id: 5}, function(){ + start(); + }) }) + + From be6138730c61d8aa12daa4e7415a4422965c4241 Mon Sep 17 00:00:00 2001 From: Justin Meyer Date: Fri, 25 Nov 2011 12:07:39 -0600 Subject: [PATCH 032/103] fixes #80 problem with caching --- dom/within/within.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/dom/within/within.js b/dom/within/within.js index ca0f4401..818a18e0 100644 --- a/dom/within/within.js +++ b/dom/within/within.js @@ -65,7 +65,11 @@ $.fn.withinBox = function(left, top, width, height, cache){ if(this == document.documentElement) return this.ret.push(this); - var offset = cache ? jQuery.data(this,"offset", q.offset()) : q.offset(); + var offset = cache ? + jQuery.data(this,"offset") || + jQuery.data(this,"offset", q.offset()) : + q.offset(); + var ew = q.width(), eh = q.height(); From b5d266382cce3a83d561cbd2d7261e9eabff3a48 Mon Sep 17 00:00:00 2001 From: David Luecke Date: Fri, 25 Nov 2011 14:13:24 -0700 Subject: [PATCH 033/103] Added Array.reverse() modifier --- model/list/list.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/model/list/list.js b/model/list/list.js index 064bf5bd..529b094d 100644 --- a/model/list/list.js +++ b/model/list/list.js @@ -768,6 +768,14 @@ steal('jquery/model').then(function( $ ) { * */ sort: [].sort + /** + * @function reverse + * Reverse the list in place + * + * list.reverse() + * + */ + reverse: [].reverse } each(modifiers, function( name, func ) { From 1c6180ca1d4186f8cd62ad652ef29ccb4b6d58c1 Mon Sep 17 00:00:00 2001 From: Brian Moschel Date: Sun, 27 Nov 2011 23:12:50 -0500 Subject: [PATCH 034/103] trailing comma --- model/list/list.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/model/list/list.js b/model/list/list.js index 529b094d..42ce9acf 100644 --- a/model/list/list.js +++ b/model/list/list.js @@ -767,7 +767,7 @@ steal('jquery/model').then(function( $ ) { * list.sort(sortfunc) * */ - sort: [].sort + sort: [].sort, /** * @function reverse * Reverse the list in place From c4e3a9c3ff4fd3f39453cfa309f19aecc22287dc Mon Sep 17 00:00:00 2001 From: Brian Moschel Date: Tue, 29 Nov 2011 01:53:58 -0500 Subject: [PATCH 035/103] all jquerymx tests passing again --- dom/fixture/fixture_test.js | 4 +-- event/hover/hover_test.js | 2 +- model/store/store_test.js | 8 +++--- model/test/qunit/model_test.js | 6 ++--- qunit.html | 1 + test/qunit/qunit.js | 47 +++++++++++++++------------------- view/test/qunit/view_test.js | 7 +++-- 7 files changed, 34 insertions(+), 41 deletions(-) diff --git a/dom/fixture/fixture_test.js b/dom/fixture/fixture_test.js index 5df334cf..c3fe5902 100644 --- a/dom/fixture/fixture_test.js +++ b/dom/fixture/fixture_test.js @@ -4,7 +4,7 @@ module("jquery/dom/fixture"); test("static fixtures", function(){ - stop(3000); + stop(); $.fixture("GET something", "//jquery/dom/fixture/fixtures/test.json"); $.fixture("POST something", "//jquery/dom/fixture/fixtures/test.json"); @@ -236,7 +236,7 @@ test("fixture function gets id", function(){ name: "justin" } }) - stop(3000); + stop(); $.get("/thingers/5", {}, function(data){ start(); ok(data.id) diff --git a/event/hover/hover_test.js b/event/hover/hover_test.js index 322a3343..bd58d1f1 100644 --- a/event/hover/hover_test.js +++ b/event/hover/hover_test.js @@ -27,7 +27,7 @@ test("hovering", function(){ Syn("mouseover",{pageX: off.top, pageY: off.left}, hover[0]) ok(hoverinits, 'hoverinit'); ok(hoverenters === 0,"hoverinit hasn't been called"); - stop(1000); + stop(); setTimeout(function(){ ok(hoverenters === 1,"hoverenter has been called"); diff --git a/model/store/store_test.js b/model/store/store_test.js index 81787ef8..4cccdefd 100644 --- a/model/store/store_test.js +++ b/model/store/store_test.js @@ -45,7 +45,7 @@ test("store findAll", 5, function(){ var list = Item.Store.findAll({}); - stop(3000); + stop(); list.bind("add", function(ev, items){ console.log("here ...") start(); @@ -95,7 +95,7 @@ test("Store Compare", function(){ var list = Item.Store.findAll({count: 2}); - stop(3000); + stop(); list.bind("add", function(ev, items){ ok(items.length); ok(list.length) @@ -125,7 +125,7 @@ test("Store Remove", function(){ var list = Item.Store.findAll({parentId: 1}), len = 0, first; - stop(2000); + stop(); list.bind("add", function(ev, items){ ok(items.length, "there should be items"); len = items.length; @@ -162,7 +162,7 @@ test("Store Update", function(){ len = 0, first; - stop(2000); + stop(); var def1 = $.Deferred(), def2 = $.Deferred(), first, diff --git a/model/test/qunit/model_test.js b/model/test/qunit/model_test.js index 6d3d28a4..b9ffac43 100644 --- a/model/test/qunit/model_test.js +++ b/model/test/qunit/model_test.js @@ -282,7 +282,7 @@ test("auto methods",function(){ create : steal.root.join("jquery/model/test")+"/create.json", update : "POST "+steal.root.join("jquery/model/test")+"/update{id}.json" },{}) - stop(5000); + stop(); School.findAll({type:"schools"}, function(schools){ ok(schools,"findAll Got some data back"); equals(schools[0].constructor.shortName,"School","there are schools") @@ -506,7 +506,7 @@ test("hookup and elements", function(){ }) test('aborting create update and destroy', function(){ - stop(5000); + stop(); var delay = $.fixture.delay; $.fixture.delay = 1000; @@ -580,7 +580,7 @@ test("object definitions", function(){ equals(original.timeout,1000,"timeout set"); return {yes: true} }); - stop(3000); + stop(); ObjectDef.findOne({id: 5}, function(){ start(); }) diff --git a/qunit.html b/qunit.html index 995cbce9..f67f1712 100644 --- a/qunit.html +++ b/qunit.html @@ -3,6 +3,7 @@ + jQueryMX Test diff --git a/test/qunit/qunit.js b/test/qunit/qunit.js index 3341f1f0..6b478bbf 100644 --- a/test/qunit/qunit.js +++ b/test/qunit/qunit.js @@ -7,33 +7,26 @@ steal('jquery').then(function(){ $(function(){ isReady = true; }) -},'jquery/class/class_test.js', - 'jquery/controller/controller_test.js', - 'jquery/dom/compare/compare_test.js', - 'jquery/dom/cur_styles/cur_styles_test.js', - 'jquery/dom/dimensions/dimensions_test.js', - 'jquery/dom/form_params/form_params_test.js', - 'jquery/dom/route/route_test.js', - 'jquery/lang/lang_test.js', - 'jquery/dom/fixture/fixture_test.js', - 'jquery/event/default/default_test.js', - 'jquery/event/destroyed/destroyed_test.js', - 'jquery/event/drag/drag_test.js', - 'jquery/event/hover/hover_test.js', - 'jquery/event/key/key_test.js', - 'jquery/tie/tie_test.js' - - - ).then( - -'jquery/controller/view/test/qunit', -'jquery/model/test/qunit', - -'jquery/view/test/qunit' - - -).then('./integration.js', - 'jquery/event/default/default_pause_test.js',function(){ +},'jquery/class/class_test.js') +.then('jquery/controller/controller_test.js') +.then('jquery/dom/compare/compare_test.js') +.then('jquery/dom/cur_styles/cur_styles_test.js') +.then('jquery/dom/dimensions/dimensions_test.js') +.then('jquery/dom/form_params/form_params_test.js') +.then('jquery/dom/route/route_test.js') +.then('jquery/lang/lang_test.js') +.then('jquery/dom/fixture/fixture_test.js') +.then('jquery/event/default/default_test.js') +.then('jquery/event/destroyed/destroyed_test.js') +.then('jquery/event/drag/drag_test.js') +.then('jquery/event/hover/hover_test.js') +.then('jquery/event/key/key_test.js') +.then('jquery/tie/tie_test.js') +.then('jquery/controller/view/test/qunit') +.then('jquery/model/test/qunit') +.then('jquery/view/test/qunit') +.then('./integration.js') +.then('jquery/event/default/default_pause_test.js',function(){ stateAfterScript = isReady; module('jquery v steal'); diff --git a/view/test/qunit/view_test.js b/view/test/qunit/view_test.js index f65ab19a..ef917d84 100644 --- a/view/test/qunit/view_test.js +++ b/view/test/qunit/view_test.js @@ -68,10 +68,9 @@ test("caching works", function(){ $("#qunit-test-area").html(""); $("#qunit-test-area").html("//jquery/view/test/qunit/large.ejs",{"message" :"helloworld"}, function(text){ - var lap2 = new Date - first , + var lap2 = (new Date()) - first, lap1 = first-startT; - - ok( lap1 - lap2 > -20, "faster this time "+(lap1 - lap2) ) + // ok( lap1 > lap2, "faster this time "+(lap1 - lap2) ) start(); $("#qunit-test-area").html(""); @@ -98,7 +97,7 @@ test("inline templates other than 'tmpl' like ejs", function(){ test("object of deferreds", function(){ var foo = $.Deferred(), bar = $.Deferred(); - stop(1000); + stop(); $.View("//jquery/view/test/qunit/deferreds.ejs",{ foo : foo.promise(), bar : bar From 04b152a908fd03df5dead975b1f729b5dea8ac8c Mon Sep 17 00:00:00 2001 From: Justin Meyer Date: Tue, 29 Nov 2011 19:55:12 -0600 Subject: [PATCH 036/103] handle evil characters in routes --- dom/route/route.js | 12 +++++++----- dom/route/route_test.js | 5 +++++ 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/dom/route/route.js b/dom/route/route.js index 73e4a1a7..aaf95bfe 100644 --- a/dom/route/route.js +++ b/dom/route/route.js @@ -42,7 +42,9 @@ function( $ ) { }, // onready = true, - location = window.location; + location = window.location, + encode = encodeURIComponent, + decode = decodeURIComponent; /** * @class jQuery.route @@ -209,7 +211,7 @@ function( $ ) { var names = [], test = url.replace(matcher, function( whole, name ) { names.push(name) - return "([\\w\\.]*)" // The '\\' is for string-escaping giving single '\' for regEx escaping + return "([^\\/\\&]*)" // The '\\' is for string-escaping giving single '\' for regEx escaping }); // Add route in a form that can be easily figured out @@ -261,7 +263,7 @@ function( $ ) { // If the default value is found an empty string is inserted. res = route.route.replace(matcher, function( whole, name ) { delete cpy[name]; - return data[name] === route.defaults[name] ? "" : data[name]; + return data[name] === route.defaults[name] ? "" : encode( data[name] ); }), after; @@ -302,7 +304,7 @@ function( $ ) { var // Since RegEx backreferences are used in route.test (round brackets) // the parts will contain the full matched string and each variable (backreferenced) value. parts = url.match(route.test), - // start will contain the full mathced string; parts contain the variable values. + // start will contain the full matched string; parts contain the variable values. start = parts.shift(), // The remainder will be the &key=value list at the end of the URL. remainder = url.substr(start.length), @@ -314,7 +316,7 @@ function( $ ) { // Overwrite each of the default values in obj with those in parts if that part is not empty. for ( var p = 0; p < parts.length; p++ ) { if ( parts[p] ) { - obj[route.names[p]] = parts[p] + obj[route.names[p]] = decode( parts[p] ); } } return obj; diff --git a/dom/route/route_test.js b/dom/route/route_test.js index db048125..57dff03a 100644 --- a/dom/route/route_test.js +++ b/dom/route/route_test.js @@ -153,6 +153,11 @@ test("param-deparam", function(){ res = $.route.param(data); obj = $.route.deparam(res); same(data, obj) + + data = {page: " a ", type: " / "}; + res = $.route.param(data); + obj = $.route.deparam(res); + same(obj ,data ,"slashes and spaces") data = {page: "index", type: "foo", bar: "baz", where: "there"}; res = $.route.param(data); From 0c63aa61e7afe637d3b4755b40f0a0ef08c826d8 Mon Sep 17 00:00:00 2001 From: Joris Wijlens Date: Thu, 1 Dec 2011 23:07:42 +0100 Subject: [PATCH 037/103] test for issue 97 wildcard with position value --- dom/fixture/fixture_test.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/dom/fixture/fixture_test.js b/dom/fixture/fixture_test.js index c3fe5902..9c87b7d3 100644 --- a/dom/fixture/fixture_test.js +++ b/dom/fixture/fixture_test.js @@ -1,3 +1,4 @@ + steal("jquery/dom/fixture", "jquery/model",'funcunit/qunit',function(){ module("jquery/dom/fixture"); @@ -188,6 +189,11 @@ test("_getData", function(){ deepEqual(data, {}, "gets data"); }) +test("_getData with two position wildcard", function(){ + var data = $.fixture._getData("/days/{id}/time_slots.json", "/days/17/time_slots.json"); + equals(data.id, 17, "gets data"); +}); + test("_compare", function(){ var same = $.Object.same( {url : "/thingers/5"}, From 0fe78d7735f45a2added4a82d82ce4f3bc2893d1 Mon Sep 17 00:00:00 2001 From: Justin Meyer Date: Fri, 2 Dec 2011 05:13:13 -0600 Subject: [PATCH 038/103] removing guesstype, fixing fixtures, and cleaning up the resize demo --- dom/fixture/fixture.js | 2 +- dom/fixture/fixture_test.js | 2 +- event/resize/demo.html | 15 ++--- generate/templates/app/scripts/build.js.ejs | 2 +- lang/observe/observe.js | 2 +- model/guesstype/guesstype.js | 37 ----------- model/guesstype/guesstype_test.js | 12 ---- model/model.js | 71 +++++++++------------ 8 files changed, 41 insertions(+), 102 deletions(-) delete mode 100644 model/guesstype/guesstype.js delete mode 100644 model/guesstype/guesstype_test.js diff --git a/dom/fixture/fixture.js b/dom/fixture/fixture.js index 86f13669..f2a020dc 100644 --- a/dom/fixture/fixture.js +++ b/dom/fixture/fixture.js @@ -432,7 +432,7 @@ steal('jquery/dom', fixtureUrlAdjusted = fixtureUrl.replace('.', '\\.').replace('?', '\\?'), res = new RegExp(fixtureUrlAdjusted.replace(replacer, function(whole, part){ order.push(part) - return "([^\/])+" + return "([^\/]+)" })+"$").exec(url), data = {}; diff --git a/dom/fixture/fixture_test.js b/dom/fixture/fixture_test.js index 9c87b7d3..17ff1ce1 100644 --- a/dom/fixture/fixture_test.js +++ b/dom/fixture/fixture_test.js @@ -189,7 +189,7 @@ test("_getData", function(){ deepEqual(data, {}, "gets data"); }) -test("_getData with two position wildcard", function(){ +test("_getData with double character value", function(){ var data = $.fixture._getData("/days/{id}/time_slots.json", "/days/17/time_slots.json"); equals(data.id, 17, "gets data"); }); diff --git a/event/resize/demo.html b/event/resize/demo.html index 00571d78..1cdfa568 100644 --- a/event/resize/demo.html +++ b/event/resize/demo.html @@ -87,19 +87,16 @@

Folders:

steal('mxui/data/grid','mxui/layout/split', 'jquery/dom/fixture').then(function(){ - $('#content').mxui_layout_fill({ - parent : $(document.body) - }) + $('#content').mxui_layout_fill(document.body) - $('#split').mxui_layout_fill({ - parent : $('#content') - }).mxui_layout_split({ direction: "vertical", panelClass: "panel" }) + $('#split').mxui_layout_fill('#content') + .mxui_layout_split({ direction: "vertical", panelClass: "panel" }) - $('#vsplit').mxui_layout_fill({ - parent : $('#leftPanel') - }).mxui_layout_split({ direction: "horizontal" }); + + $('#vsplit').mxui_layout_fill($('#leftPanel')) + .mxui_layout_split({ direction: "horizontal" }); diff --git a/generate/templates/app/scripts/build.js.ejs b/generate/templates/app/scripts/build.js.ejs index f7a7d819..bccd83e5 100644 --- a/generate/templates/app/scripts/build.js.ejs +++ b/generate/templates/app/scripts/build.js.ejs @@ -1,4 +1,4 @@ -//steal/js <%= path %>/scripts/compress.js +//js <%= path %>/scripts/build.js load("steal/rhino/rhino.js"); steal('steal/build').then('steal/build/scripts','steal/build/styles',function(){ diff --git a/lang/observe/observe.js b/lang/observe/observe.js index 2d329772..0d664065 100644 --- a/lang/observe/observe.js +++ b/lang/observe/observe.js @@ -76,7 +76,7 @@ steal('jquery/class').then(function() { return; } if (!collecting ) { - return $.event.trigger(event, args, item) + return $.event.trigger(event, args, item, true) } else { collecting.push({ t: item, diff --git a/model/guesstype/guesstype.js b/model/guesstype/guesstype.js deleted file mode 100644 index e29f0b17..00000000 --- a/model/guesstype/guesstype.js +++ /dev/null @@ -1,37 +0,0 @@ -steal('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/model/guesstype/guesstype_test.js b/model/guesstype/guesstype_test.js deleted file mode 100644 index 54b4bf1d..00000000 --- a/model/guesstype/guesstype_test.js +++ /dev/null @@ -1,12 +0,0 @@ -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/model/model.js b/model/model.js index 36a0036b..056fb398 100644 --- a/model/model.js +++ b/model/model.js @@ -13,7 +13,7 @@ steal('jquery/class', 'jquery/lang/string', function() { extend = $.extend, each = $.each, trigger = function(obj, event, args){ - $(obj).triggerHandler( event, args ); + $.event.trigger(event, args, obj, true) }, // used to make an ajax request where @@ -33,8 +33,8 @@ steal('jquery/class', 'jquery/lang/string', function() { var sp = ajaxOb.indexOf(" ") if ( sp > -1 ) { ajaxOb = { - url : ajaxOb.substr(sp + 1), - type :ajaxOb.substr(0, sp) + url: ajaxOb.substr(sp + 1), + type: ajaxOb.substr(0, sp) } } else { ajaxOb = {url : ajaxOb} @@ -49,7 +49,7 @@ steal('jquery/class', 'jquery/lang/string', function() { // get the url with any templated values filled out ajaxOb.url = $String.sub(ajaxOb.url, ajaxOb.data, true); - return $.ajax($.extend({ + return $.ajax(extend({ type: type || "post", dataType: dataType ||"json", fixture: fixture, @@ -105,7 +105,7 @@ steal('jquery/class', 'jquery/lang/string', function() { each(items, function( i, item ) { if (!item["__u Nique"] ) { collect.push(item); - item["__u Nique"] = true; + item["__u Nique"] = 1; } }); // remove unique @@ -168,8 +168,7 @@ steal('jquery/class', 'jquery/lang/string', function() { }, $method = function( name ) { return function( eventType, handler ) { - $.fn[name].apply($([this]), arguments); - return this; + return $.fn[name].apply($([this]), arguments); } }, bind = $method('bind'), @@ -541,7 +540,7 @@ steal('jquery/class', 'jquery/lang/string', function() { * @param {Function} error a function to callback if something goes wrong. */ return function( attrs, success, error ) { - return ajax(str || "{_shortName}", attrs, success, error, fixture(this, "Create", "-restCreate")) + return ajax(str || this._shortName, attrs, success, error, fixture(this, "Create", "-restCreate")) }; }, update: function( str ) { @@ -616,7 +615,7 @@ steal('jquery/class', 'jquery/lang/string', function() { * @param {Function} error a function to callback if something goes wrong. */ return function( id, attrs, success, error ) { - return ajax( str || "{_shortName}s/"+id+".json", addId(this, attrs, id), success, error, fixture(this, "Update", "-restUpdate"), "put") + return ajax( str || this._shortName+"/{"+this.id+"}", addId(this, attrs, id), success, error, fixture(this, "Update", "-restUpdate"), "put") } }, destroy: function( str ) { @@ -650,7 +649,7 @@ steal('jquery/class', 'jquery/lang/string', function() { return function( id, success, error ) { var attrs = {}; attrs[this.id] = id; - return ajax( str || "{_shortName}s/"+id+".json", attrs, success, error, fixture(this, "Destroy", "-restDestroy"), "delete") + return ajax( str || this._shortName+"/{"+this.id+"}", attrs, success, error, fixture(this, "Destroy", "-restDestroy"), "delete") } }, @@ -690,7 +689,7 @@ steal('jquery/class', 'jquery/lang/string', function() { * @param {Function} error */ return function( params, success, error ) { - return ajax( str || "{_shortName}s.json", params, success, error, fixture(this, "s"), "get", "json " + this._shortName + ".models"); + return ajax( str || this._shortName, params, success, error, fixture(this, "s"), "get", "json " + this._shortName + ".models"); }; }, findOne: function( str ) { @@ -726,7 +725,7 @@ steal('jquery/class', 'jquery/lang/string', function() { * @param {Function} error */ return function( params, success, error ) { - return ajax(str || "{_shortName}s/{"+this.id+"}.json", params, success, error, fixture(this), "get", "json " + this._shortName + ".model"); + return ajax(str || this._shortName+"/{"+this.id+"}", params, success, error, fixture(this), "get", "json " + this._shortName + ".model"); }; } }; @@ -757,7 +756,7 @@ steal('jquery/class', 'jquery/lang/string', function() { this._fullName = underscore(fullName.replace(/\./g, "_")); this._shortName = underscore(this.shortName); - if ( fullName.substr(0, 7) == "jQuery." ) { + if ( fullName.indexOf("jQuery") == 0 ) { return; } @@ -771,11 +770,12 @@ steal('jquery/class', 'jquery/lang/string', function() { steal.dev.warn("model.js " + fullName + " has no static properties. You probably need ,{} ") } //@steal-remove-end - for ( var name in ajaxMethods ) { - if ( typeof this[name] !== 'function' ) { - this[name] = ajaxMethods[name](this[name]); + each(ajaxMethods, function(name, method){ + var prop = self[name]; + if ( typeof prop !== 'function' ) { + self[name] = method(prop); } - } + }); //add ajax converters var converters = {}, @@ -1019,17 +1019,15 @@ steal('jquery/class', 'jquery/lang/string', function() { 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.model(raw[i])); } if (!arr ) { //push other stuff onto array - for ( var prop in instancesRawData ) { + each(instancesRawData, function(prop, val){ if ( prop !== 'data' ) { - res[prop] = instancesRawData[prop]; + res[prop] = val; } - - } + }) } return res; }, @@ -1058,9 +1056,6 @@ steal('jquery/class', 'jquery/lang/string', function() { stub = attrs[property] || (attrs[property] = type); return type; }, - guessType: function() { - return "string" - }, /** * @attribute convert * @type Object @@ -1068,6 +1063,12 @@ steal('jquery/class', 'jquery/lang/string', function() { * Check out [jQuery.Model.static.attributes] or * [jquery.model.typeconversion type conversion] * for examples. + * + * Convert comes with the following types: + * + * - date - Converts to a JS date. Accepts integers or strings that work with Date.parse + * - number - an integer or number that can be passed to parseFloat + * - boolean - converts "false" to false, and puts everything else through Boolean() */ convert: { "date": function( str ) { @@ -1084,7 +1085,7 @@ steal('jquery/class', 'jquery/lang/string', function() { return parseFloat(val); }, "boolean": function( val ) { - return Boolean(val); + return Boolean(val === "false" ? 0 : val); }, "default": function( val, error, type ) { var construct = getObject(type), @@ -1109,7 +1110,7 @@ steal('jquery/class', 'jquery/lang/string', function() { * * For example, to serialize all dates to ISO format: * - * @codestart + * * $.Model("Contact",{ * attributes : { * birthday : 'date' @@ -1123,7 +1124,7 @@ steal('jquery/class', 'jquery/lang/string', function() { * * new Contact({ birthday: new Date("Oct 25, 1973") }).serialize() * // { "birthday" : "1973-10-25T05:00:00.000Z" } - * @codeend + * */ serialize: { "default": function( val, type ) { @@ -1402,16 +1403,6 @@ steal('jquery/class', 'jquery/lang/string', function() { * @param {Function} handler */ unbind: unbind, - // Checks if there is a setProperty value. - // If it returns true, lets it handle; otherwise - // property - the attribute name - // value - the value to set - // success - a successful callback - // error - an error callback - // capitalized - the clasized property value (expensive to recalculate) - _setProperty: function( property, value, success, error, capitalized ) { - - }, // Actually updates a property on a model. This // - Triggers events when a property has been updated // - uses converters to change the data type @@ -1424,7 +1415,7 @@ steal('jquery/class', 'jquery/lang/string', function() { // the value that we will set val, // the type of the attribute - type = Class.attributes[property] || Class.addAttr(property, Class.guessType(value)), + type = Class.attributes[property] || Class.addAttr(property, "string"), //the converter converter = Class.convert[type] || Class.convert['default'], // errors for this property @@ -1757,7 +1748,7 @@ steal('jquery/class', 'jquery/lang/string', function() { //@steal-remove-end // call event on the instance's Class - trigger([constructor],funcName, this); + trigger(constructor,funcName, this); return [this].concat(makeArray(arguments)); // return like this for this.proxy chains }; }); From bd52884a05ea12f3252272e3e058dffc61ab5a1d Mon Sep 17 00:00:00 2001 From: Justin Meyer Date: Fri, 2 Dec 2011 11:44:42 -0600 Subject: [PATCH 039/103] people like descriptions --- class/class.js | 1 + controller/controller.js | 1 + dom/dom.js | 2 + event/event.js | 2 + jquery.js | 3168 ++++++++++++++++++++------------------ jquerymx.md | 1 + lang/string/string.js | 2 + model/model.js | 1 + view/view.js | 2 + 9 files changed, 1706 insertions(+), 1474 deletions(-) diff --git a/class/class.js b/class/class.js index fd4cd611..cd7d6360 100644 --- a/class/class.js +++ b/class/class.js @@ -59,6 +59,7 @@ steal("jquery","jquery/lang/string",function( $ ) { * @parent jquerymx * @download dist/jquery/jquery.class.js * @test jquery/class/qunit.html + * @description Easy inheritance in JavaScript. * * Class provides simulated inheritance in JavaScript. Use clss to bridge the gap between * jQuery's functional programming style and Object Oriented Programming. It diff --git a/controller/controller.js b/controller/controller.js index 82c4c290..2f1fad9f 100644 --- a/controller/controller.js +++ b/controller/controller.js @@ -80,6 +80,7 @@ steal('jquery/class', 'jquery/lang/string', 'jquery/event/destroyed', function( * @download http://jmvcsite.heroku.com/pluginify?plugins[]=jquery/controller/controller.js * @test jquery/controller/qunit.html * @inherits jQuery.Class + * @description jQuery widget factory. * * jQuery.Controller helps create organized, memory-leak free, rapidly performing * jQuery widgets. Its extreme flexibility allows it to serve as both diff --git a/dom/dom.js b/dom/dom.js index 9343b5c9..a47d8438 100644 --- a/dom/dom.js +++ b/dom/dom.js @@ -1,6 +1,8 @@ /** @page dom DOM Helpers @parent jquerymx +@description jQuery DOM extension. + JavaScriptMVC adds a bunch of useful jQuery extensions for the dom. Check them out on the left. diff --git a/event/event.js b/event/event.js index 540abc98..c4ed299b 100644 --- a/event/event.js +++ b/event/event.js @@ -1,6 +1,8 @@ /** * @page specialevents Special Events * @parent jquerymx + * @description Special events like drag-drop. + * * JavaScriptMVC provides a bunch of useful special events. Find out more info on the left. The following is a * brief summary: * diff --git a/jquery.js b/jquery.js index 719e1d4e..74ce4119 100644 --- a/jquery.js +++ b/jquery.js @@ -1,5 +1,5 @@ /*! - * jQuery JavaScript Library v1.6.4 + * jQuery JavaScript Library v1.7.1 * http://jquery.com/ * * Copyright 2011, John Resig @@ -11,7 +11,7 @@ * Copyright 2011, The Dojo Foundation * Released under the MIT, BSD, and GPL Licenses. * - * Date: Mon Sep 12 18:54:48 2011 -0400 + * Date: Mon Nov 21 21:11:03 2011 -0500 */ (function( window, undefined ) { @@ -47,9 +47,6 @@ var jQuery = function( selector, context ) { trimLeft = /^\s+/, trimRight = /\s+$/, - // Check for digits - rdigit = /\d/, - // Match a standalone tag rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/, @@ -140,7 +137,7 @@ jQuery.fn = jQuery.prototype = { // HANDLE: $(html) -> $(array) if ( match[1] ) { context = context instanceof jQuery ? context[0] : context; - doc = (context ? context.ownerDocument || context : document); + 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 @@ -157,7 +154,7 @@ jQuery.fn = jQuery.prototype = { } else { ret = jQuery.buildFragment( [ match[1] ], [ doc ] ); - selector = (ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment).childNodes; + selector = ( ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment ).childNodes; } return jQuery.merge( this, selector ); @@ -187,7 +184,7 @@ jQuery.fn = jQuery.prototype = { // HANDLE: $(expr, $(...)) } else if ( !context || context.jquery ) { - return (context || rootjQuery).find( selector ); + return ( context || rootjQuery ).find( selector ); // HANDLE: $(expr, context) // (which is just equivalent to: $(context).find(expr) @@ -201,7 +198,7 @@ jQuery.fn = jQuery.prototype = { return rootjQuery.ready( selector ); } - if (selector.selector !== undefined) { + if ( selector.selector !== undefined ) { this.selector = selector.selector; this.context = selector.context; } @@ -213,7 +210,7 @@ jQuery.fn = jQuery.prototype = { selector: "", // The current version of jQuery being used - jquery: "1.6.4", + jquery: "1.7.1", // The default length of a jQuery object is 0 length: 0, @@ -258,7 +255,7 @@ jQuery.fn = jQuery.prototype = { ret.context = this.context; if ( name === "find" ) { - ret.selector = this.selector + (this.selector ? " " : "") + selector; + ret.selector = this.selector + ( this.selector ? " " : "" ) + selector; } else if ( name ) { ret.selector = this.selector + "." + name + "(" + selector + ")"; } @@ -279,15 +276,16 @@ jQuery.fn = jQuery.prototype = { jQuery.bindReady(); // Add the callback - readyList.done( fn ); + readyList.add( fn ); return this; }, eq: function( i ) { + i = +i; return i === -1 ? this.slice( i ) : - this.slice( i, +i + 1 ); + this.slice( i, i + 1 ); }, first: function() { @@ -434,11 +432,11 @@ jQuery.extend({ } // If there are functions bound, to execute - readyList.resolveWith( document, [ jQuery ] ); + readyList.fireWith( document, [ jQuery ] ); // Trigger any bound ready events if ( jQuery.fn.trigger ) { - jQuery( document ).trigger( "ready" ).unbind( "ready" ); + jQuery( document ).trigger( "ready" ).off( "ready" ); } } }, @@ -448,7 +446,7 @@ jQuery.extend({ return; } - readyList = jQuery._Deferred(); + readyList = jQuery.Callbacks( "once memory" ); // Catch cases where $(document).ready() is called after the // browser event has already occurred. @@ -504,8 +502,8 @@ jQuery.extend({ return obj && typeof obj === "object" && "setInterval" in obj; }, - isNaN: function( obj ) { - return obj == null || !rdigit.test( obj ) || isNaN( obj ); + isNumeric: function( obj ) { + return !isNaN( parseFloat(obj) ) && isFinite( obj ); }, type: function( obj ) { @@ -551,7 +549,7 @@ jQuery.extend({ }, error: function( msg ) { - throw msg; + throw new Error( msg ); }, parseJSON: function( data ) { @@ -573,7 +571,7 @@ jQuery.extend({ .replace( rvalidtokens, "]" ) .replace( rvalidbraces, "")) ) { - return (new Function( "return " + data ))(); + return ( new Function( "return " + data ) )(); } jQuery.error( "Invalid JSON: " + data ); @@ -688,8 +686,6 @@ jQuery.extend({ 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 ); @@ -703,18 +699,22 @@ jQuery.extend({ return ret; }, - inArray: function( elem, array ) { - if ( !array ) { - return -1; - } + inArray: function( elem, array, i ) { + var len; - if ( indexOf ) { - return indexOf.call( array, elem ); - } + if ( array ) { + if ( indexOf ) { + return indexOf.call( array, elem, i ); + } + + len = array.length; + i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0; - for ( var i = 0, length = array.length; i < length; i++ ) { - if ( array[ i ] === elem ) { - return i; + for ( ; i < len; i++ ) { + // Skip accessing in sparse arrays + if ( i in array && array[ i ] === elem ) { + return i; + } } } @@ -850,7 +850,7 @@ jQuery.extend({ }, now: function() { - return (new Date()).getTime(); + return ( new Date() ).getTime(); }, // Use of jQuery.browser is frowned upon. @@ -957,188 +957,360 @@ return jQuery; })(); -var // Promise methods - promiseMethods = "done fail isResolved isRejected promise then always pipe".split( " " ), - // Static reference to slice - sliceDeferred = [].slice; +// String to Object flags format cache +var flagsCache = {}; -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 ); +// Convert String-formatted flags into Object-formatted ones and store in cache +function createFlags( flags ) { + var object = flagsCache[ flags ] = {}, + i, length; + flags = flags.split( /\s+/ ); + for ( i = 0, length = flags.length; i < length; i++ ) { + object[ flags[i] ] = true; + } + return object; +} + +/* + * Create a callback list using the following parameters: + * + * flags: an optional list of space-separated flags that will change how + * the callback list behaves + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible flags: + * + * once: will ensure the callback list can only be fired once (like a Deferred) + * + * memory: will keep track of previous values and will call any callback added + * after the list has been fired right away with the latest "memorized" + * values (like a Deferred) + * + * unique: will ensure a callback can only be added once (no duplicate in the list) + * + * stopOnFalse: interrupt callings when a callback returns false + * + */ +jQuery.Callbacks = function( flags ) { + + // Convert flags from String-formatted to Object-formatted + // (we check in cache first) + flags = flags ? ( flagsCache[ flags ] || createFlags( flags ) ) : {}; + + var // Actual callback list + list = [], + // Stack of fire calls for repeatable lists + stack = [], + // Last fire value (for non-forgettable lists) + memory, + // Flag to know if list is currently firing + firing, + // First callback to fire (used internally by add and fireWith) + firingStart, + // End of the loop when firing + firingLength, + // Index of currently firing callback (modified by remove if needed) + firingIndex, + // Add one or several callbacks to the list + add = function( args ) { + var i, + length, + elem, + type, + actual; + for ( i = 0, length = args.length; i < length; i++ ) { + elem = args[ i ]; + type = jQuery.type( elem ); + if ( type === "array" ) { + // Inspect recursively + add( elem ); + } else if ( type === "function" ) { + // Add if not in unique mode and callback is not in + if ( !flags.unique || !self.has( elem ) ) { + list.push( elem ); + } + } + } + }, + // Fire callbacks + fire = function( context, args ) { + args = args || []; + memory = !flags.memory || [ context, args ]; + firing = true; + firingIndex = firingStart || 0; + firingStart = 0; + firingLength = list.length; + for ( ; list && firingIndex < firingLength; firingIndex++ ) { + if ( list[ firingIndex ].apply( context, args ) === false && flags.stopOnFalse ) { + memory = true; // Mark as halted + break; + } + } + firing = false; + if ( list ) { + if ( !flags.once ) { + if ( stack && stack.length ) { + memory = stack.shift(); + self.fireWith( memory[ 0 ], memory[ 1 ] ); + } + } else if ( memory === true ) { + self.disable(); + } else { + list = []; + } + } + }, + // Actual Callbacks object + self = { + // Add a callback or a collection of callbacks to the list + add: function() { + if ( list ) { + var length = list.length; + add( arguments ); + // Do we need to add the callbacks to the + // current firing batch? + if ( firing ) { + firingLength = list.length; + // With memory, if we're not firing then + // we should call right away, unless previous + // firing was halted (stopOnFalse) + } else if ( memory && memory !== true ) { + firingStart = length; + fire( memory[ 0 ], memory[ 1 ] ); + } + } + return this; + }, + // Remove a callback from the list + remove: function() { + if ( list ) { + var args = arguments, + argIndex = 0, + argLength = args.length; + for ( ; argIndex < argLength ; argIndex++ ) { + for ( var i = 0; i < list.length; i++ ) { + if ( args[ argIndex ] === list[ i ] ) { + // Handle firingIndex and firingLength + if ( firing ) { + if ( i <= firingLength ) { + firingLength--; + if ( i <= firingIndex ) { + firingIndex--; + } + } + } + // Remove the element + list.splice( i--, 1 ); + // If we have some unicity property then + // we only need to do this once + if ( flags.unique ) { + break; + } } } - 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 ); - } + } + return this; + }, + // Control if a given callback is in the list + has: function( fn ) { + if ( list ) { + var i = 0, + length = list.length; + for ( ; i < length; i++ ) { + if ( fn === list[ i ] ) { + return true; } - finally { - fired = [ context, args ]; - firing = 0; + } + } + return false; + }, + // Remove all callbacks from the list + empty: function() { + list = []; + return this; + }, + // Have the list do nothing anymore + disable: function() { + list = stack = memory = undefined; + return this; + }, + // Is it disabled? + disabled: function() { + return !list; + }, + // Lock the list in its current state + lock: function() { + stack = undefined; + if ( !memory || memory === true ) { + self.disable(); + } + return this; + }, + // Is it locked? + locked: function() { + return !stack; + }, + // Call all callbacks with the given context and arguments + fireWith: function( context, args ) { + if ( stack ) { + if ( firing ) { + if ( !flags.once ) { + stack.push( [ context, args ] ); } + } else if ( !( flags.once && memory ) ) { + fire( context, args ); } - return this; - }, + } + return this; + }, + // Call all the callbacks with the given arguments + fire: function() { + self.fireWith( this, arguments ); + return this; + }, + // To know if the callbacks have already been called at least once + fired: function() { + return !!memory; + } + }; - // resolve with this as context and given arguments - resolve: function() { - deferred.resolveWith( this, arguments ); - return this; - }, + return self; +}; - // 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) +var // Static reference to slice + sliceDeferred = [].slice; + +jQuery.extend({ + 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 ); + var doneList = jQuery.Callbacks( "once memory" ), + failList = jQuery.Callbacks( "once memory" ), + progressList = jQuery.Callbacks( "memory" ), + state = "pending", + lists = { + resolve: doneList, + reject: failList, + notify: progressList }, - 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 + "With" ]( this === deferred ? newDefer : this, [ returned ] ); - } - }); - } else { - deferred[ handler ]( newDefer[ action ] ); + promise = { + done: doneList.add, + fail: failList.add, + progress: progressList.add, + + state: function() { + return state; + }, + + // Deprecated + isResolved: doneList.fired, + isRejected: failList.fired, + + then: function( doneCallbacks, failCallbacks, progressCallbacks ) { + deferred.done( doneCallbacks ).fail( failCallbacks ).progress( progressCallbacks ); + return this; + }, + always: function() { + deferred.done.apply( deferred, arguments ).fail.apply( deferred, arguments ); + return this; + }, + pipe: function( fnDone, fnFail, fnProgress ) { + return jQuery.Deferred(function( newDefer ) { + jQuery.each( { + done: [ fnDone, "resolve" ], + fail: [ fnFail, "reject" ], + progress: [ fnProgress, "notify" ] + }, 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, newDefer.notify ); + } else { + newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ 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 ) { + obj = promise; + } else { + for ( var key in promise ) { + obj[ key ] = promise[ key ]; } - }); - }).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; } - return obj; - } - }); - // Make sure only one callback list will be used - deferred.done( failDeferred.cancel ).fail( deferred.cancel ); - // Unexpose cancel - delete deferred.cancel; + }, + deferred = promise.promise({}), + key; + + for ( key in lists ) { + deferred[ key ] = lists[ key ].fire; + deferred[ key + "With" ] = lists[ key ].fireWith; + } + + // Handle state + deferred.done( function() { + state = "resolved"; + }, failList.disable, progressList.lock ).fail( function() { + state = "rejected"; + }, doneList.disable, progressList.lock ); + // Call given func if any if ( func ) { func.call( deferred, deferred ); } + + // All done! return deferred; }, // Deferred helper when: function( firstParam ) { - var args = arguments, + var args = sliceDeferred.call( arguments, 0 ), i = 0, length = args.length, + pValues = new Array( length ), count = length, + pCount = length, deferred = length <= 1 && firstParam && jQuery.isFunction( firstParam.promise ) ? firstParam : - jQuery.Deferred(); + jQuery.Deferred(), + promise = deferred.promise(); 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 ) ); + deferred.resolveWith( deferred, args ); } }; } + function progressFunc( i ) { + return function( value ) { + pValues[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value; + deferred.notifyWith( promise, pValues ); + }; + } if ( length > 1 ) { - for( ; i < length; i++ ) { - if ( args[ i ] && jQuery.isFunction( args[ i ].promise ) ) { - args[ i ].promise().then( resolveFunc(i), deferred.reject ); + for ( ; i < length; i++ ) { + if ( args[ i ] && args[ i ].promise && jQuery.isFunction( args[ i ].promise ) ) { + args[ i ].promise().then( resolveFunc(i), deferred.reject, progressFunc(i) ); } else { --count; } @@ -1149,39 +1321,35 @@ jQuery.extend({ } else if ( deferred !== firstParam ) { deferred.resolveWith( deferred, length ? [ firstParam ] : [] ); } - return deferred.promise(); + return promise; } }); + jQuery.support = (function() { - var div = document.createElement( "div" ), - documentElement = document.documentElement, + var support, all, a, select, opt, input, marginDiv, - support, fragment, - body, - testElementParent, - testElement, - testElementStyle, tds, events, eventName, i, - isSupported; + isSupported, + div = document.createElement( "div" ), + documentElement = document.documentElement; // Preliminary tests div.setAttribute("className", "t"); div.innerHTML = "
a"; - all = div.getElementsByTagName( "*" ); a = div.getElementsByTagName( "a" )[ 0 ]; @@ -1201,11 +1369,11 @@ jQuery.support = (function() { // 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 instead) @@ -1213,12 +1381,12 @@ jQuery.support = (function() { // 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) // Use a regex to work around a WebKit issue. See #5145 - opacity: /^0.55$/.test( a.style.opacity ), + opacity: /^0.55/.test( a.style.opacity ), // Verify style float existence // (IE uses styleFloat instead of cssFloat) @@ -1236,6 +1404,13 @@ jQuery.support = (function() { // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7) getSetAttribute: div.className !== "t", + // Tests for enctype support on a form(#6743) + enctype: !!document.createElement("form").enctype, + + // Makes sure cloning an html5 element does not cause problems + // Where outerHTML is undefined, this still works + html5Clone: document.createElement("nav").cloneNode( true ).outerHTML !== "<:nav>", + // Will be defined later submitBubbles: true, changeBubbles: true, @@ -1273,7 +1448,7 @@ jQuery.support = (function() { div.cloneNode( true ).fireEvent( "onclick" ); } - // Check if a radio maintains it's value + // Check if a radio maintains its value // after being appended to the DOM input = document.createElement("input"); input.value = "t"; @@ -1283,82 +1458,18 @@ jQuery.support = (function() { input.setAttribute("checked", "checked"); div.appendChild( input ); fragment = document.createDocumentFragment(); - fragment.appendChild( div.firstChild ); + fragment.appendChild( div.lastChild ); // WebKit doesn't clone checked state correctly in fragments support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked; - div.innerHTML = ""; - - // Figure out if the W3C box model works as expected - div.style.width = div.style.paddingLeft = "1px"; - - body = document.getElementsByTagName( "body" )[ 0 ]; - // We use our own, invisible, body unless the body is already present - // in which case we use a div (#9239) - testElement = document.createElement( body ? "div" : "body" ); - testElementStyle = { - visibility: "hidden", - width: 0, - height: 0, - border: 0, - margin: 0, - background: "none" - }; - if ( body ) { - jQuery.extend( testElementStyle, { - position: "absolute", - left: "-1000px", - top: "-1000px" - }); - } - for ( i in testElementStyle ) { - testElement.style[ i ] = testElementStyle[ i ]; - } - testElement.appendChild( div ); - testElementParent = body || documentElement; - testElementParent.insertBefore( testElement, testElementParent.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 = "
"; - support.shrinkWrapBlocks = ( div.offsetWidth !== 2 ); - } - - div.innerHTML = "
t
"; - 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 ); + fragment.removeChild( input ); + fragment.appendChild( div ); - 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 @@ -1366,21 +1477,18 @@ jQuery.support = (function() { // 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 ) { + if ( window.getComputedStyle ) { marginDiv = document.createElement( "div" ); marginDiv.style.width = "0"; marginDiv.style.marginRight = "0"; + div.style.width = "2px"; div.appendChild( marginDiv ); support.reliableMarginRight = - ( parseInt( ( document.defaultView.getComputedStyle( marginDiv, null ) || { marginRight: 0 } ).marginRight, 10 ) || 0 ) === 0; + ( parseInt( ( window.getComputedStyle( marginDiv, null ) || { marginRight: 0 } ).marginRight, 10 ) || 0 ) === 0; } - // Remove the body element we added - testElement.innerHTML = ""; - testElementParent.removeChild( testElement ); - // Technique from Juriy Zaytsev - // http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/ + // http://perfectionkills.com/detecting-event-support-without-browser-sniffing/ // 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 @@ -1390,7 +1498,7 @@ jQuery.support = (function() { submit: 1, change: 1, focusin: 1 - } ) { + }) { eventName = "on" + i; isSupported = ( eventName in div ); if ( !isSupported ) { @@ -1401,15 +1509,111 @@ jQuery.support = (function() { } } - // Null connected elements to avoid leaks in IE - testElement = fragment = select = opt = body = marginDiv = div = input = null; + fragment.removeChild( div ); + + // Null elements to avoid leaks in IE + fragment = select = opt = marginDiv = div = input = null; + + // Run tests that need a body at doc ready + jQuery(function() { + var container, outer, inner, table, td, offsetSupport, + conMarginTop, ptlm, vb, style, html, + body = document.getElementsByTagName("body")[0]; + + if ( !body ) { + // Return for frameset docs that don't have a body + return; + } + + conMarginTop = 1; + ptlm = "position:absolute;top:0;left:0;width:1px;height:1px;margin:0;"; + vb = "visibility:hidden;border:0;"; + style = "style='" + ptlm + "border:5px solid #000;padding:0;'"; + html = "
" + + "" + + "
"; + + container = document.createElement("div"); + container.style.cssText = vb + "width:0;height:0;position:static;top:0;margin-top:" + conMarginTop + "px"; + body.insertBefore( container, body.firstChild ); + + // Construct the test element + div = document.createElement("div"); + container.appendChild( div ); + + // 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) + div.innerHTML = "
t
"; + tds = div.getElementsByTagName( "td" ); + 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 ); + + // Figure out if the W3C box model works as expected + div.innerHTML = ""; + div.style.width = div.style.paddingLeft = "1px"; + jQuery.boxModel = support.boxModel = div.offsetWidth === 2; + + if ( typeof div.style.zoom !== "undefined" ) { + // 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 = "
"; + support.shrinkWrapBlocks = ( div.offsetWidth !== 2 ); + } + + div.style.cssText = ptlm + vb; + div.innerHTML = html; + + outer = div.firstChild; + inner = outer.firstChild; + td = outer.nextSibling.firstChild.firstChild; + + offsetSupport = { + doesNotAddBorder: ( inner.offsetTop !== 5 ), + doesAddBorderForTableAndCells: ( td.offsetTop === 5 ) + }; + + inner.style.position = "fixed"; + inner.style.top = "20px"; + + // safari subtracts parent border width here which is 5px + offsetSupport.fixedPosition = ( inner.offsetTop === 20 || inner.offsetTop === 15 ); + inner.style.position = inner.style.top = ""; + + outer.style.overflow = "hidden"; + outer.style.position = "relative"; + + offsetSupport.subtractsBorderForOverflowNotVisible = ( inner.offsetTop === -5 ); + offsetSupport.doesNotIncludeMarginInBodyOffset = ( body.offsetTop !== conMarginTop ); + + body.removeChild( container ); + div = container = null; + + jQuery.extend( support, offsetSupport ); + }); return support; })(); -// Keep track of boxModel -jQuery.boxModel = jQuery.support.boxModel; - @@ -1437,7 +1641,6 @@ jQuery.extend({ hasData: function( elem ) { elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ]; - return !!elem && !isEmptyDataObject( elem ); }, @@ -1446,7 +1649,7 @@ jQuery.extend({ return; } - var thisCache, ret, + var privateCache, thisCache, ret, internalKey = jQuery.expando, getByName = typeof name === "string", @@ -1460,11 +1663,12 @@ jQuery.extend({ // 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; + id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey, + isEvents = name === "events"; // 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 ] && !cache[ id ][ internalKey ]))) && getByName && data === undefined ) { + if ( (!id || !cache[id] || (!isEvents && !pvt && !cache[id].data)) && getByName && data === undefined ) { return; } @@ -1472,18 +1676,17 @@ jQuery.extend({ // Only DOM nodes need a new unique ID for each element since their data // ends up in the global cache if ( isNode ) { - elem[ jQuery.expando ] = id = ++jQuery.uuid; + elem[ internalKey ] = id = ++jQuery.uuid; } else { - id = jQuery.expando; + id = internalKey; } } 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 + // Avoids exposing jQuery metadata on plain JS objects when the object + // is serialized using JSON.stringify if ( !isNode ) { cache[ id ].toJSON = jQuery.noop; } @@ -1493,34 +1696,33 @@ jQuery.extend({ // 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); + cache[ id ] = jQuery.extend( cache[ id ], name ); } else { - cache[ id ] = jQuery.extend(cache[ id ], name); + cache[ id ].data = jQuery.extend( cache[ id ].data, name ); } } - thisCache = cache[ id ]; + privateCache = thisCache = cache[ id ]; - // Internal jQuery data is stored in a separate object inside the object's data + // jQuery data() is stored in a separate object inside the object's internal data // cache in order to avoid key collisions between internal data and user-defined - // data - if ( pvt ) { - if ( !thisCache[ internalKey ] ) { - thisCache[ internalKey ] = {}; + // data. + if ( !pvt ) { + if ( !thisCache.data ) { + thisCache.data = {}; } - thisCache = thisCache[ internalKey ]; + thisCache = thisCache.data; } if ( data !== undefined ) { 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; + // Users should not attempt to inspect the internal events object using jQuery.data, + // it is undocumented and subject to change. But does anyone listen? No. + if ( isEvents && !thisCache[ name ] ) { + return privateCache.events; } // Check for both converted-to-camel and non-converted data property names @@ -1548,7 +1750,7 @@ jQuery.extend({ return; } - var thisCache, + var thisCache, i, l, // Reference to internal data cache key internalKey = jQuery.expando, @@ -1559,7 +1761,7 @@ jQuery.extend({ cache = isNode ? jQuery.cache : elem, // See jQuery.data for more information - id = isNode ? elem[ jQuery.expando ] : jQuery.expando; + id = isNode ? elem[ internalKey ] : internalKey; // If there is already no cache entry for this object, there is no // purpose in continuing @@ -1569,28 +1771,43 @@ jQuery.extend({ if ( name ) { - thisCache = pvt ? cache[ id ][ internalKey ] : cache[ id ]; + thisCache = pvt ? cache[ id ] : cache[ id ].data; if ( thisCache ) { - // Support interoperable removal of hyphenated or camelcased keys - if ( !thisCache[ name ] ) { - name = jQuery.camelCase( name ); + // Support array or space separated string names for data keys + if ( !jQuery.isArray( name ) ) { + + // try the string as a key before any manipulation + if ( name in thisCache ) { + name = [ name ]; + } else { + + // split the camel cased version by spaces unless a key with the spaces exists + name = jQuery.camelCase( name ); + if ( name in thisCache ) { + name = [ name ]; + } else { + name = name.split( " " ); + } + } } - delete thisCache[ name ]; + for ( i = 0, l = name.length; i < l; i++ ) { + delete thisCache[ name[i] ]; + } // If there is no data left in the cache, we want to continue // and let the cache object itself get destroyed - if ( !isEmptyDataObject(thisCache) ) { + if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) { return; } } } // See jQuery.data for more information - if ( pvt ) { - delete cache[ id ][ internalKey ]; + if ( !pvt ) { + delete cache[ id ].data; // Don't destroy the parent cache unless the internal data object // had been the only thing left in it @@ -1599,8 +1816,6 @@ jQuery.extend({ } } - 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 @@ -1611,32 +1826,18 @@ jQuery.extend({ 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 + // We destroyed the cache and need to eliminate the expando on the node to avoid // false lookups in the cache for entries that no longer exist - } else if ( isNode ) { + 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 ]; + delete elem[ internalKey ]; } else if ( elem.removeAttribute ) { - elem.removeAttribute( jQuery.expando ); + elem.removeAttribute( internalKey ); } else { - elem[ jQuery.expando ] = null; + elem[ internalKey ] = null; } } }, @@ -1662,14 +1863,15 @@ jQuery.extend({ jQuery.fn.extend({ data: function( key, value ) { - var data = null; + var parts, attr, name, + data = null; if ( typeof key === "undefined" ) { if ( this.length ) { data = jQuery.data( this[0] ); - if ( this[0].nodeType === 1 ) { - var attr = this[0].attributes, name; + if ( this[0].nodeType === 1 && !jQuery._data( this[0], "parsedAttrs" ) ) { + attr = this[0].attributes; for ( var i = 0, l = attr.length; i < l; i++ ) { name = attr[i].name; @@ -1679,6 +1881,7 @@ jQuery.fn.extend({ dataAttr( this[0], name, data[ name ] ); } } + jQuery._data( this[0], "parsedAttrs", true ); } } @@ -1690,7 +1893,7 @@ jQuery.fn.extend({ }); } - var parts = key.split("."); + parts = key.split("."); parts[1] = parts[1] ? "." + parts[1] : ""; if ( value === undefined ) { @@ -1708,12 +1911,12 @@ jQuery.fn.extend({ } else { return this.each(function() { - var $this = jQuery( this ), + var self = jQuery( this ), args = [ parts[0], value ]; - $this.triggerHandler( "setData" + parts[1] + "!", args ); + self.triggerHandler( "setData" + parts[1] + "!", args ); jQuery.data( this, key, value ); - $this.triggerHandler( "changeData" + parts[1] + "!", args ); + self.triggerHandler( "changeData" + parts[1] + "!", args ); }); } }, @@ -1739,7 +1942,7 @@ function dataAttr( elem, key, data ) { data = data === "true" ? true : data === "false" ? false : data === "null" ? null : - !jQuery.isNaN( data ) ? parseFloat( data ) : + jQuery.isNumeric( data ) ? parseFloat( data ) : rbrace.test( data ) ? jQuery.parseJSON( data ) : data; } catch( e ) {} @@ -1755,11 +1958,14 @@ function dataAttr( elem, key, data ) { return data; } -// 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 +// checks a cache object for emptiness function isEmptyDataObject( obj ) { for ( var name in obj ) { + + // if the public data object is empty, the private is still empty + if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) { + continue; + } if ( name !== "toJSON" ) { return false; } @@ -1775,17 +1981,17 @@ function handleQueueMarkDefer( elem, type, src ) { var deferDataKey = type + "defer", queueDataKey = type + "queue", markDataKey = type + "mark", - defer = jQuery.data( elem, deferDataKey, undefined, true ); + defer = jQuery._data( elem, deferDataKey ); if ( defer && - ( src === "queue" || !jQuery.data( elem, queueDataKey, undefined, true ) ) && - ( src === "mark" || !jQuery.data( elem, markDataKey, undefined, true ) ) ) { + ( src === "queue" || !jQuery._data(elem, queueDataKey) ) && + ( src === "mark" || !jQuery._data(elem, markDataKey) ) ) { // 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 ) ) { + if ( !jQuery._data( elem, queueDataKey ) && + !jQuery._data( elem, markDataKey ) ) { jQuery.removeData( elem, deferDataKey, true ); - defer.resolve(); + defer.fire(); } }, 0 ); } @@ -1795,8 +2001,8 @@ 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 ); + type = ( type || "fx" ) + "mark"; + jQuery._data( elem, type, (jQuery._data( elem, type ) || 0) + 1 ); } }, @@ -1809,9 +2015,9 @@ jQuery.extend({ if ( elem ) { type = type || "fx"; var key = type + "mark", - count = force ? 0 : ( (jQuery.data( elem, key, undefined, true) || 1 ) - 1 ); + count = force ? 0 : ( (jQuery._data( elem, key ) || 1) - 1 ); if ( count ) { - jQuery.data( elem, key, count, true ); + jQuery._data( elem, key, count ); } else { jQuery.removeData( elem, key, true ); handleQueueMarkDefer( elem, type, "mark" ); @@ -1820,13 +2026,15 @@ jQuery.extend({ }, queue: function( elem, type, data ) { + var q; if ( elem ) { - type = (type || "fx") + "queue"; - var q = jQuery.data( elem, type, undefined, true ); + type = ( type || "fx" ) + "queue"; + q = jQuery._data( elem, type ); + // 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 ); + q = jQuery._data( elem, type, jQuery.makeArray(data) ); } else { q.push( data ); } @@ -1840,7 +2048,7 @@ jQuery.extend({ var queue = jQuery.queue( elem, type ), fn = queue.shift(), - defer; + hooks = {}; // If the fx queue is dequeued, always remove the progress sentinel if ( fn === "inprogress" ) { @@ -1851,16 +2059,17 @@ jQuery.extend({ // Add a progress sentinel to prevent the fx queue from being // automatically dequeued if ( type === "fx" ) { - queue.unshift("inprogress"); + queue.unshift( "inprogress" ); } - fn.call(elem, function() { - jQuery.dequeue(elem, type); - }); + jQuery._data( elem, type + ".run", hooks ); + fn.call( elem, function() { + jQuery.dequeue( elem, type ); + }, hooks ); } if ( !queue.length ) { - jQuery.removeData( elem, type + "queue", true ); + jQuery.removeData( elem, type + "queue " + type + ".run", true ); handleQueueMarkDefer( elem, type, "queue" ); } } @@ -1892,14 +2101,14 @@ jQuery.fn.extend({ // 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; + 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 ); + return this.queue( type, function( next, hooks ) { + var timeout = setTimeout( next, time ); + hooks.stop = function() { + clearTimeout( timeout ); + }; }); }, clearQueue: function( type ) { @@ -1930,9 +2139,9 @@ jQuery.fn.extend({ 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 ) )) { + jQuery.data( elements[ i ], deferDataKey, jQuery.Callbacks( "once memory" ), true ) )) { count++; - tmp.done( resolve ); + tmp.add( resolve ); } } resolve(); @@ -1950,7 +2159,8 @@ var rclass = /[\n\t\r]/g, rfocusable = /^(?:button|input|object|select|textarea)$/i, rclickable = /^a(?:rea)?$/i, rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i, - nodeHook, boolHook; + getSetAttribute = jQuery.support.getSetAttribute, + nodeHook, boolHook, fixSpecified; jQuery.fn.extend({ attr: function( name, value ) { @@ -1962,11 +2172,11 @@ jQuery.fn.extend({ 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() { @@ -2025,7 +2235,7 @@ jQuery.fn.extend({ } if ( (value && typeof value === "string") || value === undefined ) { - classNames = (value || "").split( rspace ); + classNames = ( value || "" ).split( rspace ); for ( i = 0, l = this.length; i < l; i++ ) { elem = this[ i ]; @@ -2086,8 +2296,10 @@ jQuery.fn.extend({ }, hasClass: function( selector ) { - var className = " " + selector + " "; - for ( var i = 0, l = this.length; i < l; i++ ) { + var className = " " + selector + " ", + i = 0, + l = this.length; + for ( ; i < l; i++ ) { if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) { return true; } @@ -2097,9 +2309,9 @@ jQuery.fn.extend({ }, val: function( value ) { - var hooks, ret, + var hooks, ret, isFunction, elem = this[0]; - + if ( !arguments.length ) { if ( elem ) { hooks = jQuery.valHooks[ elem.nodeName.toLowerCase() ] || jQuery.valHooks[ elem.type ]; @@ -2110,17 +2322,17 @@ jQuery.fn.extend({ ret = elem.value; - return typeof ret === "string" ? + return typeof ret === "string" ? // handle most common string cases - ret.replace(rreturn, "") : + ret.replace(rreturn, "") : // handle cases where value is null/undef or number ret == null ? "" : ret; } - return undefined; + return; } - var isFunction = jQuery.isFunction( value ); + isFunction = jQuery.isFunction( value ); return this.each(function( i ) { var self = jQuery(this), val; @@ -2168,7 +2380,7 @@ jQuery.extend({ }, select: { get: function( elem ) { - var value, + var value, i, max, option, index = elem.selectedIndex, values = [], options = elem.options, @@ -2180,8 +2392,10 @@ jQuery.extend({ } // 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 ]; + i = one ? index : 0; + max = one ? index + 1 : options.length; + for ( ; i < max; i++ ) { + 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) && @@ -2233,18 +2447,14 @@ jQuery.extend({ height: true, offset: true }, - - attrFix: { - // Always normalize to ensure hook usage - tabindex: "tabIndex" - }, - + attr: function( elem, name, value, pass ) { - var nType = elem.nodeType; - + var ret, hooks, notxml, + nType = elem.nodeType; + // don't get/set attributes on text, comment and attribute nodes if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { - return undefined; + return; } if ( pass && name in jQuery.attrFn ) { @@ -2252,36 +2462,24 @@ jQuery.extend({ } // Fallback to prop when attributes are not supported - if ( !("getAttribute" in elem) ) { + if ( typeof elem.getAttribute === "undefined" ) { return jQuery.prop( elem, name, value ); } - var ret, hooks, - notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); + notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); - // Normalize the name if needed + // All attributes are lowercase + // Grab necessary hook if one is defined if ( notxml ) { - name = jQuery.attrFix[ name ] || name; - - hooks = jQuery.attrHooks[ name ]; - - if ( !hooks ) { - // Use boolHook for boolean attributes - if ( rboolean.test( name ) ) { - hooks = boolHook; - - // Use nodeHook if available( IE6/7 ) - } else if ( nodeHook ) { - hooks = nodeHook; - } - } + name = name.toLowerCase(); + hooks = jQuery.attrHooks[ name ] || ( rboolean.test( name ) ? boolHook : nodeHook ); } if ( value !== undefined ) { if ( value === null ) { jQuery.removeAttr( elem, name ); - return undefined; + return; } else if ( hooks && "set" in hooks && notxml && (ret = hooks.set( elem, value, name )) !== undefined ) { return ret; @@ -2305,17 +2503,29 @@ jQuery.extend({ } }, - removeAttr: function( elem, name ) { - var propName; - if ( elem.nodeType === 1 ) { - name = jQuery.attrFix[ name ] || name; + removeAttr: function( elem, value ) { + var propName, attrNames, name, l, + i = 0; + + if ( value && elem.nodeType === 1 ) { + attrNames = value.toLowerCase().split( rspace ); + l = attrNames.length; + + for ( ; i < l; i++ ) { + name = attrNames[ i ]; + + if ( name ) { + propName = jQuery.propFix[ name ] || name; - jQuery.attr( elem, name, "" ); - elem.removeAttribute( name ); + // See #9699 for explanation of this approach (setting first, then removal) + jQuery.attr( elem, name, "" ); + elem.removeAttribute( getSetAttribute ? name : propName ); - // Set corresponding property to false for boolean attributes - if ( rboolean.test( name ) && (propName = jQuery.propFix[ name ] || name) in elem ) { - elem[ propName ] = false; + // Set corresponding property to false for boolean attributes + if ( rboolean.test( name ) && propName in elem ) { + elem[ propName ] = false; + } + } } } }, @@ -2374,17 +2584,17 @@ jQuery.extend({ frameborder: "frameBorder", contenteditable: "contentEditable" }, - + prop: function( elem, name, value ) { - var nType = elem.nodeType; + var ret, hooks, notxml, + nType = elem.nodeType; // don't get/set properties on text, comment and attribute nodes if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { - return undefined; + return; } - var ret, hooks, - notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); + notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); if ( notxml ) { // Fix name and attach hooks @@ -2397,7 +2607,7 @@ jQuery.extend({ return ret; } else { - return (elem[ name ] = value); + return ( elem[ name ] = value ); } } else { @@ -2409,7 +2619,7 @@ jQuery.extend({ } } }, - + propHooks: { tabIndex: { get: function( elem ) { @@ -2427,16 +2637,17 @@ jQuery.extend({ } }); -// Add the tabindex propHook to attrHooks for back-compat -jQuery.attrHooks.tabIndex = jQuery.propHooks.tabIndex; +// Add the tabIndex propHook to attrHooks for back-compat (different case is intentional) +jQuery.attrHooks.tabindex = jQuery.propHooks.tabIndex; // Hook for boolean attributes boolHook = { get: function( elem, name ) { // Align boolean attributes with corresponding properties // Fall back to attribute presence where some booleans are not supported - var attrNode; - return jQuery.prop( elem, name ) === true || ( attrNode = elem.getAttributeNode( name ) ) && attrNode.nodeValue !== false ? + var attrNode, + property = jQuery.prop( elem, name ); + return property === true || typeof property !== "boolean" && ( attrNode = elem.getAttributeNode(name) ) && attrNode.nodeValue !== false ? name.toLowerCase() : undefined; }, @@ -2461,16 +2672,20 @@ boolHook = { }; // IE6/7 do not support getting/setting some attributes with get/setAttribute -if ( !jQuery.support.getSetAttribute ) { - +if ( !getSetAttribute ) { + + fixSpecified = { + name: true, + id: true + }; + // Use this for any attribute in IE6/7 // This fixes almost every IE6/7 issue nodeHook = jQuery.valHooks.button = { get: function( elem, name ) { var ret; ret = elem.getAttributeNode( name ); - // Return undefined if nodeValue is empty string - return ret && ret.nodeValue !== "" ? + return ret && ( fixSpecified[ name ] ? ret.nodeValue !== "" : ret.specified ) ? ret.nodeValue : undefined; }, @@ -2481,10 +2696,13 @@ if ( !jQuery.support.getSetAttribute ) { ret = document.createAttribute( name ); elem.setAttributeNode( ret ); } - return (ret.nodeValue = value + ""); + return ( ret.nodeValue = value + "" ); } }; + // Apply the nodeHook to tabindex + jQuery.attrHooks.tabindex.set = nodeHook.set; + // 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 ) { @@ -2497,6 +2715,18 @@ if ( !jQuery.support.getSetAttribute ) { } }); }); + + // Set contenteditable to false on removals(#10429) + // Setting to empty string throws an error as an invalid value + jQuery.attrHooks.contenteditable = { + get: nodeHook.get, + set: function( elem, value, name ) { + if ( value === "" ) { + value = "false"; + } + nodeHook.set( elem, value, name ); + } + }; } @@ -2520,7 +2750,7 @@ if ( !jQuery.support.style ) { return elem.style.cssText.toLowerCase() || undefined; }, set: function( elem, value ) { - return (elem.style.cssText = "" + value); + return ( elem.style.cssText = "" + value ); } }; } @@ -2545,6 +2775,11 @@ if ( !jQuery.support.optSelected ) { }); } +// IE6/7 call enctype encoding +if ( !jQuery.support.enctype ) { + jQuery.propFix.enctype = "encoding"; +} + // Radios and checkboxes getter/setter if ( !jQuery.support.checkOn ) { jQuery.each([ "radio", "checkbox" ], function() { @@ -2560,7 +2795,7 @@ 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); + return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 ); } } }); @@ -2569,116 +2804,118 @@ jQuery.each([ "radio", "checkbox" ], function() { -var rnamespaces = /\.(.*)$/, - rformElems = /^(?:textarea|input|select)$/i, - rperiod = /\./g, - rspaces = / /g, - rescape = /[^\w\s.|`]/g, - fcleanup = function( nm ) { - return nm.replace(rescape, "\\$&"); +var rformElems = /^(?:textarea|input|select)$/i, + rtypenamespace = /^([^\.]*)?(?:\.(.+))?$/, + rhoverHack = /\bhover(\.\S+)?\b/, + rkeyEvent = /^key/, + rmouseEvent = /^(?:mouse|contextmenu)|click/, + rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + rquickIs = /^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/, + quickParse = function( selector ) { + var quick = rquickIs.exec( selector ); + if ( quick ) { + // 0 1 2 3 + // [ _, tag, id, class ] + quick[1] = ( quick[1] || "" ).toLowerCase(); + quick[3] = quick[3] && new RegExp( "(?:^|\\s)" + quick[3] + "(?:\\s|$)" ); + } + return quick; + }, + quickIs = function( elem, m ) { + var attrs = elem.attributes || {}; + return ( + (!m[1] || elem.nodeName.toLowerCase() === m[1]) && + (!m[2] || (attrs.id || {}).value === m[2]) && + (!m[3] || m[3].test( (attrs[ "class" ] || {}).value )) + ); + }, + hoverHack = function( events ) { + return jQuery.event.special.hover ? events : events.replace( rhoverHack, "mouseenter$1 mouseleave$1" ); }; /* - * A number of helper functions used for managing events. - * Many of the ideas behind this code originated from - * Dean Edwards' addEvent library. + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. */ 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; - } + add: function( elem, types, handler, data, selector ) { + + var elemData, eventHandle, events, + t, tns, type, namespaces, handleObj, + handleObjIn, quick, handlers, special; - if ( handler === false ) { - handler = returnFalse; - } else if ( !handler ) { - // Fixes bug #7229. Fix recommended by jdalton + // Don't attach events to noData or text/comment nodes (allow plain objects tho) + if ( elem.nodeType === 3 || elem.nodeType === 8 || !types || !handler || !(elemData = jQuery._data( elem )) ) { return; } - var handleObjIn, handleObj; - + // Caller can pass in an object of custom data in lieu of the handler if ( handler.handler ) { handleObjIn = handler; handler = handleObjIn.handler; } - // Make sure that the function being executed has a unique ID + // Make sure that the handler has a unique ID, used to find/remove it later 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, - eventHandle = elemData.handle; - + // Init the element's event structure and main handler, if this is the first + events = elemData.events; if ( !events ) { elemData.events = events = {}; } - + eventHandle = elemData.handle; if ( !eventHandle ) { 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 ) : + jQuery.event.dispatch.apply( eventHandle.elem, arguments ) : undefined; }; + // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events + eventHandle.elem = elem; } - // 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(" "); + types = jQuery.trim( hoverHack(types) ).split( " " ); + for ( t = 0; t < types.length; t++ ) { - var type, i = 0, namespaces; + tns = rtypenamespace.exec( types[t] ) || []; + type = tns[1]; + namespaces = ( tns[2] || "" ).split( "." ).sort(); - 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 = ""; - } + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[ type ] || {}; - handleObj.type = type; - if ( !handleObj.guid ) { - handleObj.guid = handler.guid; - } + // If selector defined, determine special event api type, otherwise given type + type = ( selector ? special.delegateType : special.bindType ) || type; - // Get the current list of functions bound to this event - var handlers = events[ type ], - special = jQuery.event.special[ type ] || {}; + // Update special based on newly reset type + special = jQuery.event.special[ type ] || {}; - // Init the event handler queue + // handleObj is passed to all event handlers + handleObj = jQuery.extend({ + type: type, + origType: tns[1], + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + quick: quickParse( selector ), + namespace: namespaces.join(".") + }, handleObjIn ); + + // Init the event handler queue if we're the first + handlers = events[ type ]; if ( !handlers ) { handlers = events[ type ] = []; + handlers.delegateCount = 0; - // Check for a special event handler - // Only use addEventListener/attachEvent if the special - // events handler returns false + // 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 ) { @@ -2698,10 +2935,14 @@ jQuery.event = { } } - // Add the function to the element's handler list - handlers.push( handleObj ); + // Add to the element's handler list, delegates in front + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } - // Keep track of which events have been used, for event optimization + // Keep track of which events have ever been used, for event optimization jQuery.event.global[ type ] = true; } @@ -2712,129 +2953,80 @@ jQuery.event = { 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.hasData( elem ) && 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; - } + remove: function( elem, types, handler, selector, mappedTypes ) { - // 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 ); - } + var elemData = jQuery.hasData( elem ) && jQuery._data( elem ), + t, tns, type, origType, namespaces, origCount, + j, events, special, handle, eventType, handleObj; + if ( !elemData || !(events = elemData.events) ) { 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 = []; + // Once for each type.namespace in types; type may be omitted + types = jQuery.trim( hoverHack( types || "" ) ).split(" "); + for ( t = 0; t < types.length; t++ ) { + tns = rtypenamespace.exec( types[t] ) || []; + type = origType = tns[1]; + namespaces = tns[2]; - 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 ); - } + // Unbind all events (on this namespace, if provided) for the element + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); } - continue; } special = jQuery.event.special[ type ] || {}; + type = ( selector? special.delegateType : special.bindType ) || type; + eventType = events[ type ] || []; + origCount = eventType.length; + namespaces = namespaces ? new RegExp("(^|\\.)" + namespaces.split(".").sort().join("\\.(?:.*\\.)?") + "(\\.|$)") : null; - for ( j = pos || 0; j < eventType.length; j++ ) { + // Remove matching events + for ( j = 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 ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !namespaces || namespaces.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) { + eventType.splice( j--, 1 ); - if ( special.remove ) { - special.remove.call( elem, handleObj ); - } + if ( handleObj.selector ) { + eventType.delegateCount--; } - - if ( pos != null ) { - break; + if ( special.remove ) { + special.remove.call( elem, handleObj ); } } } - // remove generic event handler if no more handlers exist - if ( eventType.length === 0 || pos != null && eventType.length === 1 ) { + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if ( eventType.length === 0 && origCount !== eventType.length ) { 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; + handle = elemData.handle; if ( handle ) { handle.elem = null; } - delete elemData.events; - delete elemData.handle; - - if ( jQuery.isEmptyObject( elemData ) ) { - jQuery.removeData( elem, undefined, true ); - } + // removeData also checks for emptiness and clears the expando if empty + // so use it instead of delete + jQuery.removeData( elem, [ "events", "handle" ], 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: { @@ -2844,18 +3036,28 @@ jQuery.event = { }, trigger: function( event, data, elem, onlyHandlers ) { + // Don't do events on text and comment nodes + if ( elem && (elem.nodeType === 3 || elem.nodeType === 8) ) { + return; + } + // Event object or event type var type = event.type || event, namespaces = [], - exclusive; + cache, exclusive, i, cur, old, ontype, special, handle, eventPath, bubbleType; + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; + } - if ( type.indexOf("!") >= 0 ) { + 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 ) { + if ( type.indexOf( "." ) >= 0 ) { // Namespaced trigger; create a regexp to match event type in handle() namespaces = type.split("."); type = namespaces.shift(); @@ -2877,230 +3079,299 @@ jQuery.event = { new jQuery.Event( type ); event.type = type; + event.isTrigger = true; 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(); - } + event.namespace = namespaces.join( "." ); + event.namespace_re = event.namespace? new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.)?") + "(\\.|$)") : null; + ontype = type.indexOf( ":" ) < 0 ? "on" + type : ""; // 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 ); + cache = jQuery.cache; + for ( i in cache ) { + if ( cache[ i ].events && cache[ i ].events[ type ] ) { + jQuery.event.trigger( event, data, cache[ i ].handle.elem, true ); } - }); - return; - } - - // Don't do events on text and comment nodes - if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + } return; } // Clean up the event in case it is being reused event.result = undefined; - event.target = elem; + if ( !event.target ) { + event.target = elem; + } // Clone any incoming data and prepend the event, creating the handler arg list data = data != null ? jQuery.makeArray( data ) : []; data.unshift( event ); - var cur = elem, - // IE doesn't like method names with a colon (#3533, #8272) - ontype = type.indexOf(":") < 0 ? "on" + type : ""; + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (#9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + eventPath = [[ elem, special.bindType || type ]]; + if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + cur = rfocusMorph.test( bubbleType + type ) ? elem : elem.parentNode; + old = null; + for ( ; cur; cur = cur.parentNode ) { + eventPath.push([ cur, bubbleType ]); + old = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( old && old === elem.ownerDocument ) { + eventPath.push([ old.defaultView || old.parentWindow || window, bubbleType ]); + } + } + + // Fire handlers on the event path + for ( i = 0; i < eventPath.length && !event.isPropagationStopped(); i++ ) { - // Fire event on the current element, then bubble up the DOM tree - do { - var handle = jQuery._data( cur, "handle" ); + cur = eventPath[i][0]; + event.type = eventPath[i][1]; - event.currentTarget = cur; + handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" ); if ( handle ) { handle.apply( cur, data ); } - - // Trigger an inline bound script - if ( ontype && jQuery.acceptData( cur ) && cur[ ontype ] && cur[ ontype ].apply( cur, data ) === false ) { - event.result = false; + // Note that this is a bare JS function and not a jQuery handler + handle = ontype && cur[ ontype ]; + if ( handle && jQuery.acceptData( cur ) && handle.apply( cur, data ) === false ) { event.preventDefault(); } - - // Bubble up to document, then to window - cur = cur.parentNode || cur.ownerDocument || cur === event.target.ownerDocument && window; - } while ( cur && !event.isPropagationStopped() ); + } + event.type = type; // If nobody prevented the default action, do it now - if ( !event.isDefaultPrevented() ) { - var old, - special = jQuery.event.special[ type ] || {}; + if ( !onlyHandlers && !event.isDefaultPrevented() ) { - if ( (!special._default || special._default.call( elem.ownerDocument, event ) === false) && + if ( (!special._default || special._default.apply( elem.ownerDocument, data ) === 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 ( ontype && elem[ type ] ) { - // Don't re-trigger an onFOO event when we call its FOO() method - old = elem[ ontype ]; + // Can't use an .isFunction() check here because IE6/7 fails that test. + // Don't do default actions on window, that's where global variables be (#6170) + // IE<9 dies on focus/blur to hidden element (#1486) + if ( ontype && elem[ type ] && ((type !== "focus" && type !== "blur") || event.target.offsetWidth !== 0) && !jQuery.isWindow( elem ) ) { - if ( old ) { - elem[ ontype ] = null; - } + // Don't re-trigger an onFOO event when we call its FOO() method + old = elem[ ontype ]; - jQuery.event.triggered = type; - elem[ type ](); + if ( old ) { + elem[ ontype ] = null; } - } catch ( ieError ) {} - if ( old ) { - elem[ ontype ] = old; - } + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + elem[ type ](); + jQuery.event.triggered = undefined; - jQuery.event.triggered = undefined; + if ( old ) { + elem[ ontype ] = old; + } + } } } - + return event.result; }, - handle: function( event ) { + dispatch: function( event ) { + + // Make a writable jQuery.Event from the native event object 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), + + var handlers = ( (jQuery._data( this, "events" ) || {} )[ event.type ] || []), + delegateCount = handlers.delegateCount, + args = [].slice.call( arguments, 0 ), run_all = !event.exclusive && !event.namespace, - args = Array.prototype.slice.call( arguments, 0 ); + handlerQueue = [], + i, j, cur, jqcur, ret, selMatch, matched, matches, handleObj, sel, related; - // Use the fix-ed Event rather than the (read-only) native event + // Use the fix-ed jQuery.Event rather than the (read-only) native event args[0] = event; - event.currentTarget = this; - - 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(); + event.delegateTarget = this; + + // Determine handlers that should run if there are delegated events + // Avoid disabled elements in IE (#6911) and non-left-click bubbling in Firefox (#3861) + if ( delegateCount && !event.target.disabled && !(event.button && event.type === "click") ) { + + // Pregenerate a single jQuery object for reuse with .is() + jqcur = jQuery(this); + jqcur.context = this.ownerDocument || this; + + for ( cur = event.target; cur != this; cur = cur.parentNode || this ) { + selMatch = {}; + matches = []; + jqcur[0] = cur; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + sel = handleObj.selector; + + if ( selMatch[ sel ] === undefined ) { + selMatch[ sel ] = ( + handleObj.quick ? quickIs( cur, handleObj.quick ) : jqcur.is( sel ) + ); + } + if ( selMatch[ sel ] ) { + matches.push( handleObj ); } } + if ( matches.length ) { + handlerQueue.push({ elem: cur, matches: matches }); + } + } + } - if ( event.isImmediatePropagationStopped() ) { - break; + // Add the remaining (directly-bound) handlers + if ( handlers.length > delegateCount ) { + handlerQueue.push({ elem: this, matches: handlers.slice( delegateCount ) }); + } + + // Run delegates first; they may want to stop propagation beneath us + for ( i = 0; i < handlerQueue.length && !event.isPropagationStopped(); i++ ) { + matched = handlerQueue[ i ]; + event.currentTarget = matched.elem; + + for ( j = 0; j < matched.matches.length && !event.isImmediatePropagationStopped(); j++ ) { + handleObj = matched.matches[ j ]; + + // Triggered event must either 1) be non-exclusive and have no namespace, or + // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace). + if ( run_all || (!event.namespace && !handleObj.namespace) || event.namespace_re && event.namespace_re.test( handleObj.namespace ) ) { + + event.data = handleObj.data; + event.handleObj = handleObj; + + ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler ) + .apply( matched.elem, args ); + + if ( ret !== undefined ) { + event.result = ret; + if ( ret === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } } } } + 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(" "), + // Includes some event props shared by KeyEvent and MouseEvent + // *** attrChange attrName relatedNode srcElement are not normalized, non-W3C, deprecated, will be removed in 1.8 *** + props: "attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "), + + fixHooks: {}, + + keyHooks: { + props: "char charCode key keyCode".split(" "), + filter: function( event, original ) { + + // Add which for key events + if ( event.which == null ) { + event.which = original.charCode != null ? original.charCode : original.keyCode; + } + + return event; + } + }, + + mouseHooks: { + props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "), + filter: function( event, original ) { + var eventDoc, doc, body, + button = original.button, + fromElement = original.fromElement; + + // Calculate pageX/Y if missing and clientX/Y available + if ( event.pageX == null && original.clientX != null ) { + eventDoc = event.target.ownerDocument || document; + doc = eventDoc.documentElement; + body = eventDoc.body; + + event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 ); + event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 ); + } + + // Add relatedTarget, if necessary + if ( !event.relatedTarget && fromElement ) { + event.relatedTarget = fromElement === event.target ? original.toElement : fromElement; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + // Note: button is not normalized, so don't use it + if ( !event.which && button !== undefined ) { + event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) ); + } + + return event; + } + }, 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; + // Create a writable copy of the event object and normalize some properties + var i, prop, + originalEvent = event, + fixHook = jQuery.event.fixHooks[ event.type ] || {}, + copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props; + event = jQuery.Event( originalEvent ); - for ( var i = this.props.length, prop; i; ) { - prop = this.props[ --i ]; + for ( i = copy.length; i; ) { + prop = copy[ --i ]; event[ prop ] = originalEvent[ prop ]; } - // Fix target property, if necessary + // Fix target property, if necessary (#1925, IE 6/7/8 & Safari2) if ( !event.target ) { - // Fixes #1925 where srcElement might not be defined either - event.target = event.srcElement || document; + event.target = originalEvent.srcElement || document; } - // check if target is a textnode (safari) + // Target should not be a text node (#504, 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 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); - } - - // 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 ) { + // For mouse/key events; add metaKey if it's not there (#3368, IE6/7/8) + if ( event.metaKey === undefined ) { 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; + return fixHook.filter? fixHook.filter( event, originalEvent ) : 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 + setup: jQuery.bindReady }, - live: { - add: function( handleObj ) { - jQuery.event.add( this, - liveConvert( handleObj.origType, handleObj.selector ), - jQuery.extend({}, handleObj, {handler: liveHandler, guid: handleObj.handler.guid}) ); - }, + load: { + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, - remove: function( handleObj ) { - jQuery.event.remove( this, liveConvert( handleObj.origType, handleObj.selector ), handleObj ); - } + focus: { + delegateType: "focusin" + }, + blur: { + delegateType: "focusout" }, beforeunload: { @@ -3117,9 +3388,35 @@ jQuery.event = { } } } + }, + + simulate: function( type, elem, event, bubble ) { + // 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. + var e = jQuery.extend( + new jQuery.Event(), + event, + { type: type, + isSimulated: true, + originalEvent: {} + } + ); + if ( bubble ) { + jQuery.event.trigger( e, null, elem ); + } else { + jQuery.event.dispatch.call( elem, e ); + } + if ( e.isDefaultPrevented() ) { + event.preventDefault(); + } } }; +// Some plugins are using, but it's undocumented/deprecated and will be removed. +// The 1.7 special event interface should provide all the hooks needed now. +jQuery.event.handle = jQuery.event.dispatch; + jQuery.removeEvent = document.removeEventListener ? function( elem, type, handle ) { if ( elem.removeEventListener ) { @@ -3134,7 +3431,7 @@ jQuery.removeEvent = document.removeEventListener ? jQuery.Event = function( src, props ) { // Allow instantiation without the 'new' keyword - if ( !this.preventDefault ) { + if ( !(this instanceof jQuery.Event) ) { return new jQuery.Event( src, props ); } @@ -3145,8 +3442,8 @@ jQuery.Event = function( src, props ) { // 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; + this.isDefaultPrevented = ( src.defaultPrevented || src.returnValue === false || + src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse; // Event type } else { @@ -3158,9 +3455,8 @@ jQuery.Event = function( src, 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(); + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || jQuery.now(); // Mark it as fixed this[ jQuery.expando ] = true; @@ -3216,216 +3512,130 @@ jQuery.Event.prototype = { 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 related = event.relatedTarget, - inside = false, - eventType = event.type; - - event.type = event.data; - - if ( related !== this ) { - - if ( related ) { - inside = jQuery.contains( this, related ); - } - - if ( !inside ) { - - jQuery.event.handle.apply( this, arguments ); - - event.type = eventType; - } - } -}, - -// 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 +// Create mouseenter/leave events using mouseover/out and event-time checks 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 ); + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var target = this, + related = event.relatedTarget, + handleObj = event.handleObj, + selector = handleObj.selector, + ret; + + // For mousenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || (related !== target && !jQuery.contains( target, related )) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; } }; }); -// submit delegation +// IE submit delegation if ( !jQuery.support.submitBubbles ) { jQuery.event.special.submit = { - setup: function( data, namespaces ) { - if ( !jQuery.nodeName( this, "form" ) ) { - jQuery.event.add(this, "click.specialSubmit", function( e ) { - // Avoid triggering error on non-existent type attribute in IE VML (#7071) - var elem = e.target, - type = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.type : ""; - - if ( (type === "submit" || type === "image") && jQuery( elem ).closest("form").length ) { - trigger( "submit", this, arguments ); - } - }); - - jQuery.event.add(this, "keypress.specialSubmit", function( e ) { - var elem = e.target, - type = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.type : ""; + setup: function() { + // Only need this for delegated form submit events + if ( jQuery.nodeName( this, "form" ) ) { + return false; + } - if ( (type === "text" || type === "password") && jQuery( elem ).closest("form").length && e.keyCode === 13 ) { - trigger( "submit", this, arguments ); - } - }); + // Lazy-add a submit handler when a descendant form may potentially be submitted + jQuery.event.add( this, "click._submit keypress._submit", function( e ) { + // Node name check avoids a VML-related crash in IE (#9807) + var elem = e.target, + form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined; + if ( form && !form._submit_attached ) { + jQuery.event.add( form, "submit._submit", function( event ) { + // If form was submitted by the user, bubble the event up the tree + if ( this.parentNode && !event.isTrigger ) { + jQuery.event.simulate( "submit", this.parentNode, event, true ); + } + }); + form._submit_attached = true; + } + }); + // return undefined since we don't need an event listener + }, - } else { + teardown: function() { + // Only need this for delegated form submit events + if ( jQuery.nodeName( this, "form" ) ) { return false; } - }, - teardown: function( namespaces ) { - jQuery.event.remove( this, ".specialSubmit" ); + // Remove delegated handlers; cleanData eventually reaps submit handlers attached above + jQuery.event.remove( this, "._submit" ); } }; - } -// change delegation, happens here so we have bind. +// IE change delegation and checkbox/radio fix if ( !jQuery.support.changeBubbles ) { - var changeFilters, - - getVal = function( elem ) { - var type = jQuery.nodeName( elem, "input" ) ? 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 ( jQuery.nodeName( elem, "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; - jQuery.event.trigger( e, arguments[1], elem ); - } - }; - jQuery.event.special.change = { - filters: { - focusout: testChange, - - beforedeactivate: testChange, - click: function( e ) { - var elem = e.target, type = jQuery.nodeName( elem, "input" ) ? elem.type : ""; + setup: function() { - if ( type === "radio" || type === "checkbox" || jQuery.nodeName( elem, "select" ) ) { - testChange.call( this, e ); + if ( rformElems.test( this.nodeName ) ) { + // IE doesn't fire change on a check/radio until blur; trigger it on click + // after a propertychange. Eat the blur-change in special.change.handle. + // This still fires onchange a second time for check/radio after blur. + if ( this.type === "checkbox" || this.type === "radio" ) { + jQuery.event.add( this, "propertychange._change", function( event ) { + if ( event.originalEvent.propertyName === "checked" ) { + this._just_changed = true; + } + }); + jQuery.event.add( this, "click._change", function( event ) { + if ( this._just_changed && !event.isTrigger ) { + this._just_changed = false; + jQuery.event.simulate( "change", this, event, true ); + } + }); } - }, - - // 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 = jQuery.nodeName( elem, "input" ) ? elem.type : ""; + return false; + } + // Delegated event; lazy-add a change handler on descendant inputs + jQuery.event.add( this, "beforeactivate._change", function( e ) { + var elem = e.target; - if ( (e.keyCode === 13 && !jQuery.nodeName( elem, "textarea" ) ) || - (e.keyCode === 32 && (type === "checkbox" || type === "radio")) || - type === "select-multiple" ) { - testChange.call( this, e ); + if ( rformElems.test( elem.nodeName ) && !elem._change_attached ) { + jQuery.event.add( elem, "change._change", function( event ) { + if ( this.parentNode && !event.isSimulated && !event.isTrigger ) { + jQuery.event.simulate( "change", this.parentNode, event, true ); + } + }); + elem._change_attached = true; } - }, - - // 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; - } + handle: function( event ) { + var elem = event.target; - for ( var type in changeFilters ) { - jQuery.event.add( this, type + ".specialChange", changeFilters[type] ); + // Swallow native change events from checkbox/radio, we already triggered them above + if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) { + return event.handleObj.handler.apply( this, arguments ); } - - return rformElems.test( this.nodeName ); }, - teardown: function( namespaces ) { - jQuery.event.remove( this, ".specialChange" ); + teardown: function() { + jQuery.event.remove( this, "._change" ); 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 ) { - // 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 @@ -3433,7 +3643,10 @@ 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; + var attaches = 0, + handler = function( event ) { + jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true ); + }; jQuery.event.special[ fix ] = { setup: function() { @@ -3447,89 +3660,120 @@ if ( !jQuery.support.focusinBubbles ) { } } }; - - 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; - 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; +jQuery.fn.extend({ + + on: function( types, selector, data, fn, /*INTERNAL*/ one ) { + var origFn, type; - // Handle object literals - if ( typeof type === "object" ) { - for ( var key in type ) { - this[ name ](key, data, type[key], fn); + // Types can be a map of types/handlers + if ( typeof types === "object" ) { + // ( types-Object, selector, data ) + if ( typeof selector !== "string" ) { + // ( types-Object, data ) + data = selector; + selector = undefined; + } + for ( type in types ) { + this.on( type, selector, data, types[ type ], one ); } return this; } - if ( arguments.length === 2 || data === false ) { - fn = data; - data = undefined; + if ( data == null && fn == null ) { + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } } - - 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 ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return this; } - 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 ); - } + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + // Can use an empty set, since event contains the info + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); } - - 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.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + }); + }, + one: function( types, selector, data, fn ) { + return this.on.call( this, types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + if ( types && types.preventDefault && types.handleObj ) { + // ( event ) dispatched jQuery.Event + var handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace? handleObj.type + "." + handleObj.namespace : handleObj.type, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + // ( types-object [, selector] ) + for ( var type in types ) { + this.off( type, selector, types[ type ] ); } + return this; } + if ( selector === false || typeof selector === "function" ) { + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each(function() { + jQuery.event.remove( this, types, fn, selector ); + }); + }, + + bind: function( types, data, fn ) { + return this.on( types, null, data, fn ); + }, + unbind: function( types, fn ) { + return this.off( types, null, fn ); + }, + live: function( types, data, fn ) { + jQuery( this.context ).on( types, this.selector, data, fn ); + return this; + }, + die: function( types, fn ) { + jQuery( this.context ).off( types, this.selector || "**", fn ); return this; }, delegate: function( selector, types, data, fn ) { - return this.live( types, data, fn, selector ); + return this.on( types, selector, data, fn ); }, - undelegate: function( selector, types, fn ) { - if ( arguments.length === 0 ) { - return this.unbind( "live" ); - - } else { - return this.die( types, null, fn, selector ); - } + // ( namespace ) or ( selector, types [, fn] ) + return arguments.length == 1? this.off( selector, "**" ) : this.off( types, selector, fn ); }, trigger: function( type, data ) { @@ -3537,7 +3781,6 @@ jQuery.fn.extend({ jQuery.event.trigger( type, data, this ); }); }, - triggerHandler: function( type, data ) { if ( this[0] ) { return jQuery.event.trigger( type, data, this[0], true ); @@ -3551,8 +3794,8 @@ jQuery.fn.extend({ 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 ); + 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(); @@ -3575,178 +3818,9 @@ jQuery.fn.extend({ } }); -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 ( name === "die" && !types && - origSelector && origSelector.charAt(0) === "." ) { - - context.unbind( origSelector ); - - return this; - } - - if ( data === false || jQuery.isFunction( data ) ) { - fn = data || returnFalse; - 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 ( liveMap[ type ] ) { - 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, "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]; - - 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(rspaces, "&"); -} - 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 ) { + "change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) { // Handle event binding jQuery.fn[ name ] = function( data, fn ) { @@ -3756,13 +3830,21 @@ jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblcl } return arguments.length > 0 ? - this.bind( name, data, fn ) : + this.on( name, null, data, fn ) : this.trigger( name ); }; if ( jQuery.attrFn ) { jQuery.attrFn[ name ] = true; } + + if ( rkeyEvent.test( name ) ) { + jQuery.event.fixHooks[ name ] = jQuery.event.keyHooks; + } + + if ( rmouseEvent.test( name ) ) { + jQuery.event.fixHooks[ name ] = jQuery.event.mouseHooks; + } }); @@ -3776,11 +3858,13 @@ jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblcl (function(){ var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, + expando = "sizcache" + (Math.random() + '').replace('.', ''), done = 0, toString = Object.prototype.toString, hasDuplicate = false, baseHasDuplicate = true, rBackslash = /\\/g, + rReturn = /\r\n/g, rNonWord = /\W/; // Here we check if the JavaScript engine is using some sort of @@ -3832,7 +3916,7 @@ var Sizzle = function( selector, context, results, seed ) { if ( parts.length > 1 && origPOS.exec( selector ) ) { if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { - set = posProcess( parts[0] + parts[1], context ); + set = posProcess( parts[0] + parts[1], context, seed ); } else { set = Expr.relative[ parts[0] ] ? @@ -3846,7 +3930,7 @@ var Sizzle = function( selector, context, results, seed ) { selector += parts.shift(); } - set = posProcess( selector, set ); + set = posProcess( selector, set, seed ); } } @@ -3965,18 +4049,17 @@ Sizzle.matchesSelector = function( node, expr ) { }; Sizzle.find = function( expr, context, isXML ) { - var set; + var set, i, len, match, type, left; if ( !expr ) { return []; } - for ( var i = 0, l = Expr.order.length; i < l; i++ ) { - var match, - type = Expr.order[i]; + for ( i = 0, len = Expr.order.length; i < len; i++ ) { + type = Expr.order[i]; if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { - var left = match[1]; + left = match[1]; match.splice( 1, 1 ); if ( left.substr( left.length - 1 ) !== "\\" ) { @@ -4002,17 +4085,18 @@ Sizzle.find = function( expr, context, isXML ) { Sizzle.filter = function( expr, set, inplace, not ) { var match, anyFound, + type, found, item, filter, left, + i, pass, old = expr, result = [], curLoop = set, isXMLFilter = set && set[0] && Sizzle.isXML( set[0] ); while ( expr && set.length ) { - for ( var type in Expr.filter ) { + for ( type in Expr.filter ) { if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) { - var found, item, - filter = Expr.filter[ type ], - left = match[1]; + filter = Expr.filter[ type ]; + left = match[1]; anyFound = false; @@ -4038,10 +4122,10 @@ Sizzle.filter = function( expr, set, inplace, not ) { } if ( match ) { - for ( var i = 0; (item = curLoop[i]) != null; i++ ) { + for ( i = 0; (item = curLoop[i]) != null; i++ ) { if ( item ) { found = filter( item, match, i, curLoop ); - var pass = not ^ !!found; + pass = not ^ found; if ( inplace && found != null ) { if ( pass ) { @@ -4092,7 +4176,46 @@ Sizzle.filter = function( expr, set, inplace, not ) { }; Sizzle.error = function( msg ) { - throw "Syntax error, unrecognized expression: " + msg; + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +/** + * Utility function for retreiving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ +var getText = Sizzle.getText = function( elem ) { + var i, node, + nodeType = elem.nodeType, + ret = ""; + + if ( nodeType ) { + if ( nodeType === 1 || nodeType === 9 ) { + // Use textContent || innerText for elements + if ( typeof elem.textContent === 'string' ) { + return elem.textContent; + } else if ( typeof elem.innerText === 'string' ) { + // Replace IE's carriage returns + return elem.innerText.replace( rReturn, '' ); + } else { + // Traverse it's children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + } else { + + // If no nodeType, this is expected to be an array + for ( i = 0; (node = elem[i]); i++ ) { + // Do not traverse comment nodes + if ( node.nodeType !== 8 ) { + ret += getText( node ); + } + } + } + return ret; }; var Expr = Sizzle.selectors = { @@ -4482,7 +4605,7 @@ var Expr = Sizzle.selectors = { return filter( elem, i, match, array ); } else if ( name === "contains" ) { - return (elem.textContent || elem.innerText || Sizzle.getText([ elem ]) || "").indexOf(match[3]) >= 0; + return (elem.textContent || elem.innerText || getText([ elem ]) || "").indexOf(match[3]) >= 0; } else if ( name === "not" ) { var not = match[3]; @@ -4501,7 +4624,10 @@ var Expr = Sizzle.selectors = { }, CHILD: function( elem, match ) { - var type = match[1], + var first, last, + doneName, parent, cache, + count, diff, + type = match[1], node = elem; switch ( type ) { @@ -4529,18 +4655,18 @@ var Expr = Sizzle.selectors = { return true; case "nth": - var first = match[2], - last = match[3]; + first = match[2]; + last = match[3]; if ( first === 1 && last === 0 ) { return true; } - var doneName = match[0], - parent = elem.parentNode; + doneName = match[0]; + parent = elem.parentNode; - if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) { - var count = 0; + if ( parent && (parent[ expando ] !== doneName || !elem.nodeIndex) ) { + count = 0; for ( node = parent.firstChild; node; node = node.nextSibling ) { if ( node.nodeType === 1 ) { @@ -4548,10 +4674,10 @@ var Expr = Sizzle.selectors = { } } - parent.sizcache = doneName; + parent[ expando ] = doneName; } - var diff = elem.nodeIndex - last; + diff = elem.nodeIndex - last; if ( first === 0 ) { return diff === 0; @@ -4567,7 +4693,7 @@ var Expr = Sizzle.selectors = { }, TAG: function( elem, match ) { - return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match; + return (match === "*" && elem.nodeType === 1) || !!elem.nodeName && elem.nodeName.toLowerCase() === match; }, CLASS: function( elem, match ) { @@ -4577,7 +4703,9 @@ var Expr = Sizzle.selectors = { ATTR: function( elem, match ) { var name = match[1], - result = Expr.attrHandle[ name ] ? + result = Sizzle.attr ? + Sizzle.attr( elem, name ) : + Expr.attrHandle[ name ] ? Expr.attrHandle[ name ]( elem ) : elem[ name ] != null ? elem[ name ] : @@ -4588,6 +4716,8 @@ var Expr = Sizzle.selectors = { return result == null ? type === "!=" : + !type && Sizzle.attr ? + result != null : type === "=" ? value === check : type === "*=" ? @@ -4768,26 +4898,6 @@ if ( document.documentElement.compareDocumentPosition ) { }; } -// 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(){ @@ -5065,13 +5175,13 @@ function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { elem = elem[dir]; while ( elem ) { - if ( elem.sizcache === doneName ) { + if ( elem[ expando ] === doneName ) { match = checkSet[elem.sizset]; break; } if ( elem.nodeType === 1 && !isXML ){ - elem.sizcache = doneName; + elem[ expando ] = doneName; elem.sizset = i; } @@ -5098,14 +5208,14 @@ function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { elem = elem[dir]; while ( elem ) { - if ( elem.sizcache === doneName ) { + if ( elem[ expando ] === doneName ) { match = checkSet[elem.sizset]; break; } if ( elem.nodeType === 1 ) { if ( !isXML ) { - elem.sizcache = doneName; + elem[ expando ] = doneName; elem.sizset = i; } @@ -5153,7 +5263,7 @@ Sizzle.isXML = function( elem ) { return documentElement ? documentElement.nodeName !== "HTML" : false; }; -var posProcess = function( selector, context ) { +var posProcess = function( selector, context, seed ) { var match, tmpSet = [], later = "", @@ -5169,13 +5279,16 @@ var posProcess = function( selector, context ) { selector = Expr.relative[selector] ? selector + "*" : selector; for ( var i = 0, l = root.length; i < l; i++ ) { - Sizzle( selector, root[i], tmpSet ); + Sizzle( selector, root[i], tmpSet, seed ); } return Sizzle.filter( later, tmpSet ); }; // EXPOSE +// Override sizzle attribute retrieval +Sizzle.attr = jQuery.attr; +Sizzle.selectors.attrMap = {}; jQuery.find = Sizzle; jQuery.expr = Sizzle.selectors; jQuery.expr[":"] = jQuery.expr.filters; @@ -5261,43 +5374,33 @@ jQuery.fn.extend({ }, is: function( selector ) { - return !!selector && ( typeof selector === "string" ? - jQuery.filter( selector, this ).length > 0 : - this.filter( selector ).length > 0 ); + return !!selector && ( + typeof selector === "string" ? + // If this is a positional selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + POS.test( selector ) ? + jQuery( selector, this.context ).index( this[0] ) >= 0 : + jQuery.filter( selector, this ).length > 0 : + this.filter( selector ).length > 0 ); }, closest: function( selectors, context ) { var ret = [], i, l, cur = this[0]; - // Array + // Array (deprecated as of jQuery 1.7) 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 ] = POS.test( selector ) ? - jQuery( selector, context || this.context ) : - selector; - } - } + var level = 1; - while ( cur && cur.ownerDocument && cur !== context ) { - for ( selector in matches ) { - match = matches[ selector ]; + while ( cur && cur.ownerDocument && cur !== context ) { + for ( i = 0; i < selectors.length; i++ ) { - if ( match.jquery ? match.index( cur ) > -1 : jQuery( cur ).is( match ) ) { - ret.push({ selector: selector, elem: cur, level: level }); - } + if ( jQuery( cur ).is( selectors[ i ] ) ) { + ret.push({ selector: selectors[ i ], elem: cur, level: level }); } - - cur = cur.parentNode; - level++; } + + cur = cur.parentNode; + level++; } return ret; @@ -5414,12 +5517,7 @@ jQuery.each({ } }, function( name, fn ) { jQuery.fn[ name ] = function( until, selector ) { - 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); + var ret = jQuery.map( this, fn, until ); if ( !runtil.test( name ) ) { selector = until; @@ -5435,7 +5533,7 @@ jQuery.each({ ret = ret.reverse(); } - return this.pushStack( ret, name, args.join(",") ); + return this.pushStack( ret, name, slice.call( arguments ).join(",") ); }; }); @@ -5504,7 +5602,7 @@ function winnow( elements, qualifier, keep ) { } else if ( qualifier.nodeType ) { return jQuery.grep(elements, function( elem, i ) { - return (elem === qualifier) === keep; + return ( elem === qualifier ) === keep; }); } else if ( typeof qualifier === "string" ) { @@ -5520,20 +5618,38 @@ function winnow( elements, qualifier, keep ) { } return jQuery.grep(elements, function( elem, i ) { - return (jQuery.inArray( elem, qualifier ) >= 0) === keep; + return ( jQuery.inArray( elem, qualifier ) >= 0 ) === keep; }); } -var rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g, +function createSafeFragment( document ) { + var list = nodeNames.split( "|" ), + safeFrag = document.createDocumentFragment(); + + if ( safeFrag.createElement ) { + while ( list.length ) { + safeFrag.createElement( + list.pop() + ); + } + } + return safeFrag; +} + +var nodeNames = "abbr|article|aside|audio|canvas|datalist|details|figcaption|figure|footer|" + + "header|hgroup|mark|meter|nav|output|progress|section|summary|time|video", + rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g, rleadingWhitespace = /^\s+/, rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig, rtagName = /<([\w:]+)/, rtbody = /<(?:" + nodeNames + ")", "i"), // checked="checked" or checked rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i, rscriptType = /\/(java|ecma)script/i, @@ -5547,7 +5663,8 @@ var rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g, col: [ 2, "", "
" ], area: [ 1, "", "" ], _default: [ 0, "", "" ] - }; + }, + safeFragment = createSafeFragment( document ); wrapMap.optgroup = wrapMap.option; wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; @@ -5625,8 +5742,10 @@ jQuery.fn.extend({ }, wrap: function( html ) { - return this.each(function() { - jQuery( this ).wrapAll( html ); + var isFunction = jQuery.isFunction( html ); + + return this.each(function(i) { + jQuery( this ).wrapAll( isFunction ? html.call(this, i) : html ); }); }, @@ -5660,7 +5779,7 @@ jQuery.fn.extend({ this.parentNode.insertBefore( elem, this ); }); } else if ( arguments.length ) { - var set = jQuery(arguments[0]); + var set = jQuery.clean( arguments ); set.push.apply( set, this.toArray() ); return this.pushStack( set, "before", arguments ); } @@ -5673,7 +5792,7 @@ jQuery.fn.extend({ }); } else if ( arguments.length ) { var set = this.pushStack( this, "after", arguments ); - set.push.apply( set, jQuery(arguments[0]).toArray() ); + set.push.apply( set, jQuery.clean(arguments) ); return set; } }, @@ -5728,7 +5847,7 @@ jQuery.fn.extend({ null; // See if we can take a shortcut and just use innerHTML - } else if ( typeof value === "string" && !rnocache.test( value ) && + } else if ( typeof value === "string" && !rnoInnerhtml.test( value ) && (jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value )) && !wrapMap[ (rtagName.exec( value ) || ["", ""])[1].toLowerCase() ] ) { @@ -5854,7 +5973,7 @@ jQuery.fn.extend({ // 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) ? + results.cacheable || ( l > 1 && i < lastIndex ) ? jQuery.clone( fragment, true, true ) : fragment ); @@ -5883,27 +6002,26 @@ function cloneCopyEvent( src, dest ) { return; } - var internalKey = jQuery.expando, - oldData = jQuery.data( src ), - curData = jQuery.data( dest, oldData ); + var type, i, l, + oldData = jQuery._data( src ), + curData = jQuery._data( dest, oldData ), + events = oldData.events; - // 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 = {}; - if ( events ) { - delete curData.handle; - curData.events = {}; - - for ( var type in events ) { - 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 ); - } + for ( type in events ) { + for ( 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 ); } } } + + // make the cloned public data object a copy from the original + if ( curData.data ) { + curData.data = jQuery.extend( {}, curData.data ); + } } function cloneFixAttributes( src, dest ) { @@ -5965,16 +6083,17 @@ function cloneFixAttributes( src, dest ) { } jQuery.buildFragment = function( args, nodes, scripts ) { - var fragment, cacheable, cacheresults, doc; - - // nodes may contain either an explicit document object, - // a jQuery collection or context object. - // If nodes[0] contains a valid object to assign to doc - if ( nodes && nodes[0] ) { - doc = nodes[0].ownerDocument || nodes[0]; - } + var fragment, cacheable, cacheresults, doc, + first = args[ 0 ]; + + // nodes may contain either an explicit document object, + // a jQuery collection or context object. + // If nodes[0] contains a valid object to assign to doc + if ( nodes && nodes[0] ) { + doc = nodes[0].ownerDocument || nodes[0]; + } - // Ensure that an attr object doesn't incorrectly stand in as a document object + // Ensure that an attr object doesn't incorrectly stand in as a document object // Chrome and Firefox seem to allow this to occur and will throw exception // Fixes #8950 if ( !doc.createDocumentFragment ) { @@ -5985,12 +6104,15 @@ jQuery.buildFragment = function( args, nodes, scripts ) { // Cloning options loses the selected state, so don't cache them // IE 6 doesn't like it when you put or 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 && - args[0].charAt(0) === "<" && !rnocache.test( args[0] ) && (jQuery.support.checkClone || !rchecked.test( args[0] )) ) { + // Lastly, IE6,7,8 will not correctly reuse cached fragments that were created from unknown elems #10501 + if ( args.length === 1 && typeof first === "string" && first.length < 512 && doc === document && + first.charAt(0) === "<" && !rnocache.test( first ) && + (jQuery.support.checkClone || !rchecked.test( first )) && + (jQuery.support.html5Clone || !rnoshimcache.test( first )) ) { cacheable = true; - cacheresults = jQuery.fragments[ args[0] ]; + cacheresults = jQuery.fragments[ first ]; if ( cacheresults && cacheresults !== 1 ) { fragment = cacheresults; } @@ -6002,7 +6124,7 @@ jQuery.buildFragment = function( args, nodes, scripts ) { } if ( cacheable ) { - jQuery.fragments[ args[0] ] = cacheresults ? fragment : 1; + jQuery.fragments[ first ] = cacheresults ? fragment : 1; } return { fragment: fragment, cacheable: cacheable }; @@ -6028,7 +6150,7 @@ jQuery.each({ } else { for ( var i = 0, l = insert.length; i < l; i++ ) { - var elems = (i > 0 ? this.clone(true) : this).get(); + var elems = ( i > 0 ? this.clone(true) : this ).get(); jQuery( insert[i] )[ original ]( elems ); ret = ret.concat( elems ); } @@ -6039,10 +6161,10 @@ jQuery.each({ }); function getAll( elem ) { - if ( "getElementsByTagName" in elem ) { + if ( typeof elem.getElementsByTagName !== "undefined" ) { return elem.getElementsByTagName( "*" ); - } else if ( "querySelectorAll" in elem ) { + } else if ( typeof elem.querySelectorAll !== "undefined" ) { return elem.querySelectorAll( "*" ); } else { @@ -6058,19 +6180,33 @@ function fixDefaultChecked( elem ) { } // Finds all inputs and passes them to fixDefaultChecked function findInputs( elem ) { - if ( jQuery.nodeName( elem, "input" ) ) { + var nodeName = ( elem.nodeName || "" ).toLowerCase(); + if ( nodeName === "input" ) { fixDefaultChecked( elem ); - } else if ( "getElementsByTagName" in elem ) { + // Skip scripts, get other children + } else if ( nodeName !== "script" && typeof elem.getElementsByTagName !== "undefined" ) { jQuery.grep( elem.getElementsByTagName("input"), fixDefaultChecked ); } } +// Derived From: http://www.iecss.com/shimprove/javascript/shimprove.1-0-1.js +function shimCloneNode( elem ) { + var div = document.createElement( "div" ); + safeFragment.appendChild( div ); + + div.innerHTML = elem.outerHTML; + return div.firstChild; +} + jQuery.extend({ clone: function( elem, dataAndEvents, deepDataAndEvents ) { - var clone = elem.cloneNode(true), - srcElements, - destElements, - i; + var srcElements, + destElements, + i, + // IE<=8 does not properly clone detached, unknown element nodes + clone = jQuery.support.html5Clone || !rnoshimcache.test( "<" + elem.nodeName ) ? + elem.cloneNode( true ) : + shimCloneNode( elem ); if ( (!jQuery.support.noCloneEvent || !jQuery.support.noCloneChecked) && (elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) { @@ -6082,8 +6218,7 @@ jQuery.extend({ cloneFixAttributes( elem, clone ); - // Using Sizzle here is crazy slow, so we use getElementsByTagName - // instead + // Using Sizzle here is crazy slow, so we use getElementsByTagName instead srcElements = getAll( elem ); destElements = getAll( clone ); @@ -6148,11 +6283,20 @@ jQuery.extend({ elem = elem.replace(rxhtmlTag, "<$1>"); // Trim whitespace, otherwise indexOf won't work as expected - var tag = (rtagName.exec( elem ) || ["", ""])[1].toLowerCase(), + var tag = ( rtagName.exec( elem ) || ["", ""] )[1].toLowerCase(), wrap = wrapMap[ tag ] || wrapMap._default, depth = wrap[0], div = context.createElement("div"); + // Append wrapper element to unknown element safe doc fragment + if ( context === document ) { + // Use the fragment we've already created for this document + safeFragment.appendChild( div ); + } else { + // Use a fragment created with the owner document + createSafeFragment( context ).appendChild( div ); + } + // Go to html and back, then peel off extra wrappers div.innerHTML = wrap[1] + elem + wrap[2]; @@ -6233,7 +6377,9 @@ jQuery.extend({ }, cleanData: function( elems ) { - var data, id, cache = jQuery.cache, internalKey = jQuery.expando, special = jQuery.event.special, + var data, id, + cache = jQuery.cache, + special = jQuery.event.special, deleteExpando = jQuery.support.deleteExpando; for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { @@ -6244,7 +6390,7 @@ jQuery.extend({ id = elem[ jQuery.expando ]; if ( id ) { - data = cache[ id ] && cache[ id ][ internalKey ]; + data = cache[ id ]; if ( data && data.events ) { for ( var type in data.events ) { @@ -6506,7 +6652,7 @@ if ( !jQuery.support.opacity ) { set: function( elem, value ) { var style = elem.style, currentStyle = elem.currentStyle, - opacity = jQuery.isNaN( value ) ? "" : "alpha(opacity=" + value * 100 + ")", + opacity = jQuery.isNumeric( value ) ? "alpha(opacity=" + value * 100 + ")" : "", filter = currentStyle && currentStyle.filter || style.filter || ""; // IE has trouble with opacity if it does not have layout @@ -6563,11 +6709,8 @@ if ( document.defaultView && document.defaultView.getComputedStyle ) { name = name.replace( rupper, "-$1" ).toLowerCase(); - if ( !(defaultView = elem.ownerDocument.defaultView) ) { - return undefined; - } - - if ( (computedStyle = defaultView.getComputedStyle( elem, null )) ) { + if ( (defaultView = elem.ownerDocument.defaultView) && + (computedStyle = defaultView.getComputedStyle( elem, null )) ) { ret = computedStyle.getPropertyValue( name ); if ( ret === "" && !jQuery.contains( elem.ownerDocument.documentElement, elem ) ) { ret = jQuery.style( elem, name ); @@ -6580,25 +6723,32 @@ if ( document.defaultView && document.defaultView.getComputedStyle ) { if ( document.documentElement.currentStyle ) { currentStyle = function( elem, name ) { - var left, + var left, rsLeft, uncomputed, ret = elem.currentStyle && elem.currentStyle[ name ], - rsLeft = elem.runtimeStyle && elem.runtimeStyle[ name ], style = elem.style; + // Avoid setting ret to empty string here + // so we don't default to auto + if ( ret === null && style && (uncomputed = style[ name ]) ) { + ret = uncomputed; + } + // 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 && elem.runtimeStyle.left; // Put in the new values to get a computed value out if ( rsLeft ) { elem.runtimeStyle.left = elem.currentStyle.left; } - style.left = name === "fontSize" ? "1em" : (ret || 0); + style.left = name === "fontSize" ? "1em" : ( ret || 0 ); ret = style.pixelLeft + "px"; // Revert the changed values @@ -6618,20 +6768,22 @@ function getWH( elem, name, extra ) { // Start with offset property var val = name === "width" ? elem.offsetWidth : elem.offsetHeight, - which = name === "width" ? cssWidth : cssHeight; + which = name === "width" ? cssWidth : cssHeight, + i = 0, + len = which.length; if ( val > 0 ) { if ( extra !== "border" ) { - jQuery.each( which, function() { + for ( ; i < len; i++ ) { if ( !extra ) { - val -= parseFloat( jQuery.css( elem, "padding" + this ) ) || 0; + val -= parseFloat( jQuery.css( elem, "padding" + which[ i ] ) ) || 0; } if ( extra === "margin" ) { - val += parseFloat( jQuery.css( elem, extra + this ) ) || 0; + val += parseFloat( jQuery.css( elem, extra + which[ i ] ) ) || 0; } else { - val -= parseFloat( jQuery.css( elem, "border" + this + "Width" ) ) || 0; + val -= parseFloat( jQuery.css( elem, "border" + which[ i ] + "Width" ) ) || 0; } - }); + } } return val + "px"; @@ -6647,15 +6799,15 @@ function getWH( elem, name, extra ) { // Add padding, border, margin if ( extra ) { - jQuery.each( which, function() { - val += parseFloat( jQuery.css( elem, "padding" + this ) ) || 0; + for ( ; i < len; i++ ) { + val += parseFloat( jQuery.css( elem, "padding" + which[ i ] ) ) || 0; if ( extra !== "padding" ) { - val += parseFloat( jQuery.css( elem, "border" + this + "Width" ) ) || 0; + val += parseFloat( jQuery.css( elem, "border" + which[ i ] + "Width" ) ) || 0; } if ( extra === "margin" ) { - val += parseFloat( jQuery.css( elem, extra + this ) ) || 0; + val += parseFloat( jQuery.css( elem, extra + which[ i ] ) ) || 0; } - }); + } } return val + "px"; @@ -6666,7 +6818,7 @@ if ( jQuery.expr && jQuery.expr.filters ) { var width = elem.offsetWidth, height = elem.offsetHeight; - return (width === 0 && height === 0) || (!jQuery.support.reliableHiddenOffsets && (elem.style.display || jQuery.css( elem, "display" )) === "none"); + return ( width === 0 && height === 0 ) || (!jQuery.support.reliableHiddenOffsets && ((elem.style && elem.style.display) || jQuery.css( elem, "display" )) === "none"); }; jQuery.expr.filters.visible = function( elem ) { @@ -6720,7 +6872,7 @@ var r20 = /%20/g, // Document location segments ajaxLocParts, - + // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression allTypes = ["*/"] + ["*"]; @@ -6759,7 +6911,7 @@ function addToPrefiltersOrTransports( structure ) { placeBefore; // For each dataType in the dataTypeExpression - for(; i < length; i++ ) { + for ( ; i < length; i++ ) { dataType = dataTypes[ i ]; // We control if we're asked to add before // any existing element @@ -6790,7 +6942,7 @@ function inspectPrefiltersOrTransports( structure, options, originalOptions, jqX executeOnly = ( structure === prefilters ), selection; - for(; i < length && ( executeOnly || !selection ); i++ ) { + 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 @@ -6821,7 +6973,7 @@ function inspectPrefiltersOrTransports( structure, options, originalOptions, jqX function ajaxExtend( target, src ) { var key, deep, flatOptions = jQuery.ajaxSettings.flatOptions || {}; - for( key in src ) { + for ( key in src ) { if ( src[ key ] !== undefined ) { ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ]; } @@ -6938,7 +7090,7 @@ jQuery.fn.extend({ // 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 ); + return this.on( o, f ); }; }); @@ -7080,7 +7232,7 @@ jQuery.extend({ jQuery( callbackContext ) : jQuery.event, // Deferreds deferred = jQuery.Deferred(), - completeDeferred = jQuery._Deferred(), + completeDeferred = jQuery.Callbacks( "once memory" ), // Status-dependent callbacks statusCode = s.statusCode || {}, // ifModified key @@ -7230,7 +7382,7 @@ jQuery.extend({ // We extract error from statusText // then normalize statusText and status for non-aborts error = statusText; - if( !statusText || status ) { + if ( !statusText || status ) { statusText = "error"; if ( status < 0 ) { status = 0; @@ -7259,7 +7411,7 @@ jQuery.extend({ } // Complete - completeDeferred.resolveWith( callbackContext, [ jqXHR, statusText ] ); + completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); if ( fireGlobals ) { globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); @@ -7274,14 +7426,14 @@ jQuery.extend({ deferred.promise( jqXHR ); jqXHR.success = jqXHR.done; jqXHR.error = jqXHR.fail; - jqXHR.complete = completeDeferred.done; + jqXHR.complete = completeDeferred.add; // Status-dependent callbacks jqXHR.statusCode = function( map ) { if ( map ) { var tmp; if ( state < 2 ) { - for( tmp in map ) { + for ( tmp in map ) { statusCode[ tmp ] = [ statusCode[tmp], map[tmp] ]; } } else { @@ -7358,7 +7510,7 @@ jQuery.extend({ 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 : "" ); + s.url = ret + ( ( ret === s.url ) ? ( rquery.test( s.url ) ? "&" : "?" ) + "_=" + ts : "" ); } } @@ -7432,7 +7584,7 @@ jQuery.extend({ done( -1, e ); // Simply rethrow otherwise } else { - jQuery.error( e ); + throw e; } } } @@ -7536,7 +7688,7 @@ function ajaxHandleResponses( s, jqXHR, responses ) { firstDataType; // Fill responseXXX fields - for( type in responseFields ) { + for ( type in responseFields ) { if ( type in responses ) { jqXHR[ responseFields[type] ] = responses[ type ]; } @@ -7615,13 +7767,13 @@ function ajaxConvert( s, response ) { conv2; // For each dataType in the chain - for( i = 1; i < length; i++ ) { + for ( i = 1; i < length; i++ ) { // Create converters map // with lowercased keys if ( i === 1 ) { - for( key in s.converters ) { - if( typeof key === "string" ) { + for ( key in s.converters ) { + if ( typeof key === "string" ) { converters[ key.toLowerCase() ] = s.converters[ key ]; } } @@ -7632,7 +7784,7 @@ function ajaxConvert( s, response ) { current = dataTypes[ i ]; // If current is auto dataType, update it to prev - if( current === "*" ) { + if ( current === "*" ) { current = prev; // If no auto and dataTypes are actually different } else if ( prev !== "*" && prev !== current ) { @@ -7644,7 +7796,7 @@ function ajaxConvert( s, response ) { // If there is no direct converter, search transitively if ( !conv ) { conv2 = undefined; - for( conv1 in converters ) { + for ( conv1 in converters ) { tmp = conv1.split( " " ); if ( tmp[ 0 ] === prev || tmp[ 0 ] === "*" ) { conv2 = converters[ tmp[1] + " " + current ]; @@ -8083,11 +8235,11 @@ jQuery.fn.extend({ var elem, display; if ( speed || speed === 0 ) { - return this.animate( genFx("show", 3), speed, easing, callback); + return this.animate( genFx("show", 3), speed, easing, callback ); } else { for ( var i = 0, j = this.length; i < j; i++ ) { - elem = this[i]; + elem = this[ i ]; if ( elem.style ) { display = elem.style.display; @@ -8101,8 +8253,8 @@ jQuery.fn.extend({ // 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)); + if ( display === "" && jQuery.css(elem, "display") === "none" ) { + jQuery._data( elem, "olddisplay", defaultDisplay(elem.nodeName) ); } } } @@ -8110,13 +8262,13 @@ jQuery.fn.extend({ // 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]; + elem = this[ i ]; if ( elem.style ) { display = elem.style.display; if ( display === "" || display === "none" ) { - elem.style.display = jQuery._data(elem, "olddisplay") || ""; + elem.style.display = jQuery._data( elem, "olddisplay" ) || ""; } } } @@ -8130,12 +8282,17 @@ jQuery.fn.extend({ return this.animate( genFx("hide", 3), speed, easing, callback); } else { - for ( var i = 0, j = this.length; i < j; i++ ) { - if ( this[i].style ) { - var display = jQuery.css( this[i], "display" ); + var elem, display, + i = 0, + j = this.length; + + for ( ; i < j; i++ ) { + elem = this[i]; + if ( elem.style ) { + display = jQuery.css( elem, "display" ); - if ( display !== "none" && !jQuery._data( this[i], "olddisplay" ) ) { - jQuery._data( this[i], "olddisplay", display ); + if ( display !== "none" && !jQuery._data( elem, "olddisplay" ) ) { + jQuery._data( elem, "olddisplay", display ); } } } @@ -8180,7 +8337,7 @@ jQuery.fn.extend({ }, animate: function( prop, speed, easing, callback ) { - var optall = jQuery.speed(speed, easing, callback); + var optall = jQuery.speed( speed, easing, callback ); if ( jQuery.isEmptyObject( prop ) ) { return this.each( optall.complete, [ false ] ); @@ -8189,7 +8346,7 @@ jQuery.fn.extend({ // Do not change referenced properties as per-property easing will be lost prop = jQuery.extend( {}, prop ); - return this[ optall.queue === false ? "each" : "queue" ](function() { + function doAnimation() { // XXX 'this' does not always have a nodeName when running the // test suite @@ -8200,9 +8357,9 @@ jQuery.fn.extend({ var opt = jQuery.extend( {}, optall ), isElement = this.nodeType === 1, hidden = isElement && jQuery(this).is(":hidden"), - name, val, p, - display, e, - parts, start, end, unit; + name, val, p, e, + parts, start, end, unit, + method; // will store per property easing and be used to determine when an animation is complete opt.animatedProperties = {}; @@ -8238,25 +8395,17 @@ jQuery.fn.extend({ 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 + // 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 ) { + + // inline-level elements accept inline-block; + // block-level elements need to be inline with layout + if ( !jQuery.support.inlineBlockNeedsLayout || defaultDisplay( this.nodeName ) === "inline" ) { this.style.display = "inline-block"; } else { - 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; - } + this.style.zoom = 1; } } } @@ -8270,8 +8419,17 @@ jQuery.fn.extend({ e = new jQuery.fx( this, opt, p ); val = prop[ p ]; - if ( rfxtypes.test(val) ) { - e[ val === "toggle" ? hidden ? "show" : "hide" : val ](); + if ( rfxtypes.test( val ) ) { + + // Tracks whether to show or hide based on private + // data attached to the element + method = jQuery._data( this, "toggle" + p ) || ( val === "toggle" ? hidden ? "show" : "hide" : 0 ); + if ( method ) { + jQuery._data( this, "toggle" + p, method === "show" ? "hide" : "show" ); + e[ method ](); + } else { + e[ val ](); + } } else { parts = rfxnum.exec( val ); @@ -8284,7 +8442,7 @@ jQuery.fn.extend({ // We need to compute starting value if ( unit !== "px" ) { jQuery.style( this, p, (end || 1) + unit); - start = ((end || 1) / e.cur()) * start; + start = ( (end || 1) / e.cur() ) * start; jQuery.style( this, p, start + unit); } @@ -8303,39 +8461,71 @@ jQuery.fn.extend({ // For JS strict compliance return true; - }); + } + + return optall.queue === false ? + this.each( doAnimation ) : + this.queue( optall.queue, doAnimation ); }, - stop: function( clearQueue, gotoEnd ) { - if ( clearQueue ) { - this.queue([]); + stop: function( type, clearQueue, gotoEnd ) { + if ( typeof type !== "string" ) { + gotoEnd = clearQueue; + clearQueue = type; + type = undefined; } + if ( clearQueue && type !== false ) { + this.queue( type || "fx", [] ); + } + + return this.each(function() { + var index, + hadTimers = false, + timers = jQuery.timers, + data = jQuery._data( this ); - this.each(function() { - 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 - timers[i](true); - } - timers.splice(i, 1); + function stopQueue( elem, data, index ) { + var hooks = data[ index ]; + jQuery.removeData( elem, index, true ); + hooks.stop( gotoEnd ); + } + + if ( type == null ) { + for ( index in data ) { + if ( data[ index ] && data[ index ].stop && index.indexOf(".run") === index.length - 4 ) { + stopQueue( this, data, index ); + } } + } else if ( data[ index = type + ".run" ] && data[ index ].stop ){ + stopQueue( this, data, index ); } - }); - // start the next in the queue if the last step wasn't forced - if ( !gotoEnd ) { - this.dequeue(); - } + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && (type == null || timers[ index ].queue === type) ) { + if ( gotoEnd ) { - return this; + // force the next step to be the last + timers[ index ]( true ); + } else { + timers[ index ].saveState(); + } + hadTimers = true; + timers.splice( index, 1 ); + } + } + + // start the next in the queue if the last step wasn't forced + // timers currently will call their complete callbacks, which will dequeue + // but only if they were gotoEnd + if ( !( gotoEnd && hadTimers ) ) { + jQuery.dequeue( this, type ); + } + }); } }); @@ -8354,7 +8544,7 @@ function clearFxNow() { function genFx( type, num ) { var obj = {}; - jQuery.each( fxAttrs.concat.apply([], fxAttrs.slice(0,num)), function() { + jQuery.each( fxAttrs.concat.apply([], fxAttrs.slice( 0, num )), function() { obj[ this ] = type; }); @@ -8363,9 +8553,9 @@ function genFx( type, num ) { // Generate shortcuts for custom animations jQuery.each({ - slideDown: genFx("show", 1), - slideUp: genFx("hide", 1), - slideToggle: genFx("toggle", 1), + slideDown: genFx( "show", 1 ), + slideUp: genFx( "hide", 1 ), + slideToggle: genFx( "toggle", 1 ), fadeIn: { opacity: "show" }, fadeOut: { opacity: "hide" }, fadeToggle: { opacity: "toggle" } @@ -8377,25 +8567,31 @@ jQuery.each({ jQuery.extend({ speed: function( speed, easing, fn ) { - var opt = speed && typeof speed === "object" ? jQuery.extend({}, speed) : { + 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 + 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; + opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default; + + // normalize opt.queue - true/undefined/null -> "fx" + if ( opt.queue == null || opt.queue === true ) { + opt.queue = "fx"; + } // Queueing opt.old = opt.complete; + opt.complete = function( noUnmark ) { if ( jQuery.isFunction( opt.old ) ) { opt.old.call( this ); } - if ( opt.queue !== false ) { - jQuery.dequeue( this ); + if ( opt.queue ) { + jQuery.dequeue( this, opt.queue ); } else if ( noUnmark !== false ) { jQuery._unmark( this ); } @@ -8409,7 +8605,7 @@ jQuery.extend({ return firstNum + diff * p; }, swing: function( p, n, firstNum, diff ) { - return ((-Math.cos(p*Math.PI)/2) + 0.5) * diff + firstNum; + return ( ( -Math.cos( p*Math.PI ) / 2 ) + 0.5 ) * diff + firstNum; } }, @@ -8432,12 +8628,12 @@ jQuery.fx.prototype = { this.options.step.call( this.elem, this.now, this ); } - (jQuery.fx.step[this.prop] || jQuery.fx.step._default)( 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) ) { + if ( this.elem[ this.prop ] != null && (!this.elem.style || this.elem.style[ this.prop ] == null) ) { return this.elem[ this.prop ]; } @@ -8455,17 +8651,22 @@ jQuery.fx.prototype = { fx = jQuery.fx; this.startTime = fxNow || createFxNow(); - this.start = from; this.end = to; - this.unit = unit || this.unit || ( jQuery.cssNumber[ this.prop ] ? "" : "px" ); - this.now = this.start; + this.now = this.start = from; this.pos = this.state = 0; + this.unit = unit || this.unit || ( jQuery.cssNumber[ this.prop ] ? "" : "px" ); function t( gotoEnd ) { - return self.step(gotoEnd); + return self.step( gotoEnd ); } + t.queue = this.options.queue; t.elem = this.elem; + t.saveState = function() { + if ( self.options.hide && jQuery._data( self.elem, "fxshow" + self.prop ) === undefined ) { + jQuery._data( self.elem, "fxshow" + self.prop, self.start ); + } + }; if ( t() && jQuery.timers.push(t) && !timerId ) { timerId = setInterval( fx.tick, fx.interval ); @@ -8474,14 +8675,20 @@ jQuery.fx.prototype = { // Simple 'show' function show: function() { + var dataShow = jQuery._data( this.elem, "fxshow" + this.prop ); + // 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.orig[ this.prop ] = dataShow || 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()); + // Make sure that we start at a small width/height to avoid any flash of content + if ( dataShow !== undefined ) { + // This show is picking up where a previous hide or show left off + this.custom( this.cur(), dataShow ); + } else { + this.custom( this.prop === "width" || this.prop === "height" ? 1 : 0, this.cur() ); + } // Start by showing the element jQuery( this.elem ).show(); @@ -8490,20 +8697,20 @@ jQuery.fx.prototype = { // 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.orig[ this.prop ] = jQuery._data( this.elem, "fxshow" + this.prop ) || jQuery.style( this.elem, this.prop ); this.options.hide = true; // Begin the animation - this.custom(this.cur(), 0); + this.custom( this.cur(), 0 ); }, // Each step of an animation step: function( gotoEnd ) { - var t = fxNow || createFxNow(), + var p, n, complete, + t = fxNow || createFxNow(), done = true, elem = this.elem, - options = this.options, - i, n; + options = this.options; if ( gotoEnd || t >= options.duration + this.startTime ) { this.now = this.end; @@ -8512,8 +8719,8 @@ jQuery.fx.prototype = { options.animatedProperties[ this.prop ] = true; - for ( i in options.animatedProperties ) { - if ( options.animatedProperties[i] !== true ) { + for ( p in options.animatedProperties ) { + if ( options.animatedProperties[ p ] !== true ) { done = false; } } @@ -8522,25 +8729,36 @@ jQuery.fx.prototype = { // Reset the overflow if ( options.overflow != null && !jQuery.support.shrinkWrapBlocks ) { - jQuery.each( [ "", "X", "Y" ], function (index, value) { - elem.style[ "overflow" + value ] = options.overflow[index]; + jQuery.each( [ "", "X", "Y" ], function( index, value ) { + elem.style[ "overflow" + value ] = options.overflow[ index ]; }); } // Hide the element if the "hide" operation was done if ( options.hide ) { - jQuery(elem).hide(); + jQuery( elem ).hide(); } // Reset the properties, if the item has been hidden or shown if ( options.hide || options.show ) { - for ( var p in options.animatedProperties ) { - jQuery.style( elem, p, options.orig[p] ); + for ( p in options.animatedProperties ) { + jQuery.style( elem, p, options.orig[ p ] ); + jQuery.removeData( elem, "fxshow" + p, true ); + // Toggle data is no longer needed + jQuery.removeData( elem, "toggle" + p, true ); } } // Execute the complete function - options.complete.call( elem ); + // in the event that the complete function throws an exception + // we must ensure it won't be called twice. #5684 + + complete = options.complete; + if ( complete ) { + + options.complete = false; + complete.call( elem ); + } } return false; @@ -8554,8 +8772,8 @@ jQuery.fx.prototype = { 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); + 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(); @@ -8567,9 +8785,15 @@ jQuery.fx.prototype = { jQuery.extend( jQuery.fx, { tick: function() { - for ( var timers = jQuery.timers, i = 0 ; i < timers.length ; ++i ) { - if ( !timers[i]() ) { - timers.splice(i--, 1); + var timer, + timers = jQuery.timers, + i = 0; + + for ( ; i < timers.length; i++ ) { + timer = timers[ i ]; + // Checks the timer has not already been removed + if ( !timer() && timers[ i ] === timer ) { + timers.splice( i--, 1 ); } } @@ -8599,7 +8823,7 @@ jQuery.extend( jQuery.fx, { _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; + fx.elem.style[ fx.prop ] = fx.now + fx.unit; } else { fx.elem[ fx.prop ] = fx.now; } @@ -8607,6 +8831,14 @@ jQuery.extend( jQuery.fx, { } }); +// Adds width/height step functions +// Do not set anything below 0 +jQuery.each([ "width", "height" ], function( i, prop ) { + jQuery.fx.step[ prop ] = function( fx ) { + jQuery.style( fx.elem, prop, Math.max(0, fx.now) + fx.unit ); + }; +}); + if ( jQuery.expr && jQuery.expr.filters ) { jQuery.expr.filters.animated = function( elem ) { return jQuery.grep(jQuery.timers, function( fn ) { @@ -8623,7 +8855,6 @@ function defaultDisplay( nodeName ) { var body = document.body, elem = jQuery( "<" + nodeName + ">" ).appendTo( body ), display = elem.css( "display" ); - elem.remove(); // If the simple way fails, @@ -8651,7 +8882,6 @@ function defaultDisplay( nodeName ) { iframeDoc.body.appendChild( elem ); display = jQuery.css( elem, "display" ); - body.removeChild( iframe ); } @@ -8728,8 +8958,6 @@ if ( "getBoundingClientRect" in document.documentElement ) { return jQuery.offset.bodyOffset( elem ); } - jQuery.offset.initialize(); - var computedStyle, offsetParent = elem.offsetParent, prevOffsetParent = elem, @@ -8742,7 +8970,7 @@ if ( "getBoundingClientRect" in document.documentElement ) { left = elem.offsetLeft; while ( (elem = elem.parentNode) && elem !== body && elem !== docElem ) { - if ( jQuery.offset.supportsFixedPosition && prevComputedStyle.position === "fixed" ) { + if ( jQuery.support.fixedPosition && prevComputedStyle.position === "fixed" ) { break; } @@ -8754,7 +8982,7 @@ if ( "getBoundingClientRect" in document.documentElement ) { top += elem.offsetTop; left += elem.offsetLeft; - if ( jQuery.offset.doesNotAddBorder && !(jQuery.offset.doesAddBorderForTableAndCells && rtable.test(elem.nodeName)) ) { + if ( jQuery.support.doesNotAddBorder && !(jQuery.support.doesAddBorderForTableAndCells && rtable.test(elem.nodeName)) ) { top += parseFloat( computedStyle.borderTopWidth ) || 0; left += parseFloat( computedStyle.borderLeftWidth ) || 0; } @@ -8763,7 +8991,7 @@ if ( "getBoundingClientRect" in document.documentElement ) { offsetParent = elem.offsetParent; } - if ( jQuery.offset.subtractsBorderForOverflowNotVisible && computedStyle.overflow !== "visible" ) { + if ( jQuery.support.subtractsBorderForOverflowNotVisible && computedStyle.overflow !== "visible" ) { top += parseFloat( computedStyle.borderTopWidth ) || 0; left += parseFloat( computedStyle.borderLeftWidth ) || 0; } @@ -8776,7 +9004,7 @@ if ( "getBoundingClientRect" in document.documentElement ) { left += body.offsetLeft; } - if ( jQuery.offset.supportsFixedPosition && prevComputedStyle.position === "fixed" ) { + if ( jQuery.support.fixedPosition && prevComputedStyle.position === "fixed" ) { top += Math.max( docElem.scrollTop, body.scrollTop ); left += Math.max( docElem.scrollLeft, body.scrollLeft ); } @@ -8786,46 +9014,12 @@ if ( "getBoundingClientRect" in document.documentElement ) { } jQuery.offset = { - initialize: function() { - var body = document.body, container = document.createElement("div"), innerDiv, checkDiv, table, td, bodyMarginTop = parseFloat( jQuery.css(body, "marginTop") ) || 0, - html = "
"; - - 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 ); - jQuery.offset.initialize = jQuery.noop; - }, bodyOffset: function( body ) { var top = body.offsetTop, left = body.offsetLeft; - jQuery.offset.initialize(); - - if ( jQuery.offset.doesNotIncludeMarginInBodyOffset ) { + if ( jQuery.support.doesNotIncludeMarginInBodyOffset ) { top += parseFloat( jQuery.css(body, "marginTop") ) || 0; left += parseFloat( jQuery.css(body, "marginLeft") ) || 0; } @@ -8845,7 +9039,7 @@ jQuery.offset = { curOffset = curElem.offset(), curCSSTop = jQuery.css( elem, "top" ), curCSSLeft = jQuery.css( elem, "left" ), - calculatePosition = (position === "absolute" || position === "fixed") && 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 either absolute or fixed @@ -8862,11 +9056,11 @@ jQuery.offset = { options = options.call( elem, i, curOffset ); } - if (options.top != null) { - props.top = (options.top - curOffset.top) + curTop; + if ( options.top != null ) { + props.top = ( options.top - curOffset.top ) + curTop; } - if (options.left != null) { - props.left = (options.left - curOffset.left) + curLeft; + if ( options.left != null ) { + props.left = ( options.left - curOffset.left ) + curLeft; } if ( "using" in options ) { @@ -8879,6 +9073,7 @@ jQuery.offset = { jQuery.fn.extend({ + position: function() { if ( !this[0] ) { return null; @@ -8981,16 +9176,20 @@ jQuery.each([ "Height", "Width" ], function( i, name ) { // innerHeight and innerWidth jQuery.fn[ "inner" + name ] = function() { var elem = this[0]; - return elem && elem.style ? + return elem ? + elem.style ? parseFloat( jQuery.css( elem, type, "padding" ) ) : + this[ type ]() : null; }; // outerHeight and outerWidth jQuery.fn[ "outer" + name ] = function( margin ) { var elem = this[0]; - return elem && elem.style ? + return elem ? + elem.style ? parseFloat( jQuery.css( elem, type, margin ? "margin" : "border" ) ) : + this[ type ]() : null; }; @@ -9030,7 +9229,7 @@ jQuery.each([ "Height", "Width" ], function( i, name ) { var orig = jQuery.css( elem, type ), ret = parseFloat( orig ); - return jQuery.isNaN( ret ) ? orig : ret; + return jQuery.isNumeric( ret ) ? ret : orig; // Set the width or height on the element (default to pixels if value is unitless) } else { @@ -9041,6 +9240,27 @@ jQuery.each([ "Height", "Width" ], function( i, name ) { }); + + // Expose jQuery to the global object window.jQuery = window.$ = jQuery; -})(window); \ No newline at end of file + +// Expose jQuery as an AMD module, but only for AMD loaders that +// understand the issues with loading multiple versions of jQuery +// in a page that all might call define(). The loader will indicate +// they have special allowances for multiple jQuery versions by +// specifying define.amd.jQuery = true. Register as a named module, +// since jQuery can be concatenated with other files that may use define, +// but not use a proper concatenation script that understands anonymous +// AMD modules. A named AMD is safest and most robust way to register. +// Lowercase jquery is used because AMD module names are derived from +// file names, and jQuery is normally delivered in a lowercase file name. +// Do this after creating the global so that if an AMD module wants to call +// noConflict to hide this version of jQuery, it will work. +if ( typeof define === "function" && define.amd && define.amd.jQuery ) { + define( "jquery", [], function () { return jQuery; } ); +} + + + +})( window ); \ No newline at end of file diff --git a/jquerymx.md b/jquerymx.md index d5862d79..3d4f316c 100644 --- a/jquerymx.md +++ b/jquerymx.md @@ -1,5 +1,6 @@ @page jquerymx jQueryMX @parent index 0 +@description jQuery Model View Controller and extensions. jQueryMX is a collection of useful jQuery libraries that provide the missing functionality necessary to diff --git a/lang/string/string.js b/lang/string/string.js index 99ff23e9..e29fb807 100644 --- a/lang/string/string.js +++ b/lang/string/string.js @@ -1,6 +1,8 @@ /** * @page jquerymx.lang Language Helpers * @parent jquerymx + * @description A collection of language helpers for things like String, Objects, etc. + * * JavaScriptMVC has several lightweight language helper plugins. * * ## [jQuery.Object Object] diff --git a/model/model.js b/model/model.js index 056fb398..043d83cb 100644 --- a/model/model.js +++ b/model/model.js @@ -180,6 +180,7 @@ steal('jquery/class', 'jquery/lang/string', function() { * @download http://jmvcsite.heroku.com/pluginify?plugins[]=jquery/model/model.js * @test jquery/model/qunit.html * @plugin jquery/model + * @description Models and apps data layer. * * Models super-charge an application's * data layer, making it easy to: diff --git a/view/view.js b/view/view.js index 0b3a5be8..5f31cf82 100644 --- a/view/view.js +++ b/view/view.js @@ -16,6 +16,8 @@ steal("jquery").then(function( $ ) { * @test jquery/view/qunit.html * @download dist/jquery.view.js * + * @description A JavaScript template framework. + * * View provides a uniform interface for using templates with * jQuery. When template engines [jQuery.View.register register] * themselves, you are able to: From b2a2471a40c83edda6ed9f8eeaaa66ac39ebda63 Mon Sep 17 00:00:00 2001 From: Nick Matthews Date: Mon, 5 Dec 2011 14:53:01 -0500 Subject: [PATCH 040/103] Fix typo in documentation. --- view/view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/view/view.js b/view/view.js index 5f31cf82..93e23a69 100644 --- a/view/view.js +++ b/view/view.js @@ -64,7 +64,7 @@ steal("jquery").then(function( $ ) { * [jQuery.fn.before before] $('#bar').before('temp.jaml',{}); * [jQuery.fn.html html] $('#bar').html('temp.jaml',{}); * [jQuery.fn.prepend prepend] $('#bar').prepend('temp.jaml',{}); - * [jQuery.fn.replaceWith replaceWith] $('#bar').replaceWidth('temp.jaml',{}); + * [jQuery.fn.replaceWith replaceWith] $('#bar').replaceWith('temp.jaml',{}); * [jQuery.fn.text text] $('#bar').text('temp.jaml',{}); * * From 4fce592b2bd9faa388f8faff22da1bc0846bbc09 Mon Sep 17 00:00:00 2001 From: Andy Kant Date: Mon, 5 Dec 2011 14:31:25 -0600 Subject: [PATCH 041/103] Reverting jQuery 1.7.1 back to 1.6.4 (was breaking drag/drop tests) --- jquery.js | 3166 +++++++++++++++++++++++++---------------------------- 1 file changed, 1473 insertions(+), 1693 deletions(-) diff --git a/jquery.js b/jquery.js index 74ce4119..719e1d4e 100644 --- a/jquery.js +++ b/jquery.js @@ -1,5 +1,5 @@ /*! - * jQuery JavaScript Library v1.7.1 + * jQuery JavaScript Library v1.6.4 * http://jquery.com/ * * Copyright 2011, John Resig @@ -11,7 +11,7 @@ * Copyright 2011, The Dojo Foundation * Released under the MIT, BSD, and GPL Licenses. * - * Date: Mon Nov 21 21:11:03 2011 -0500 + * Date: Mon Sep 12 18:54:48 2011 -0400 */ (function( window, undefined ) { @@ -47,6 +47,9 @@ var jQuery = function( selector, context ) { trimLeft = /^\s+/, trimRight = /\s+$/, + // Check for digits + rdigit = /\d/, + // Match a standalone tag rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/, @@ -137,7 +140,7 @@ jQuery.fn = jQuery.prototype = { // HANDLE: $(html) -> $(array) if ( match[1] ) { context = context instanceof jQuery ? context[0] : context; - doc = ( context ? context.ownerDocument || context : document ); + 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 @@ -154,7 +157,7 @@ jQuery.fn = jQuery.prototype = { } else { ret = jQuery.buildFragment( [ match[1] ], [ doc ] ); - selector = ( ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment ).childNodes; + selector = (ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment).childNodes; } return jQuery.merge( this, selector ); @@ -184,7 +187,7 @@ jQuery.fn = jQuery.prototype = { // HANDLE: $(expr, $(...)) } else if ( !context || context.jquery ) { - return ( context || rootjQuery ).find( selector ); + return (context || rootjQuery).find( selector ); // HANDLE: $(expr, context) // (which is just equivalent to: $(context).find(expr) @@ -198,7 +201,7 @@ jQuery.fn = jQuery.prototype = { return rootjQuery.ready( selector ); } - if ( selector.selector !== undefined ) { + if (selector.selector !== undefined) { this.selector = selector.selector; this.context = selector.context; } @@ -210,7 +213,7 @@ jQuery.fn = jQuery.prototype = { selector: "", // The current version of jQuery being used - jquery: "1.7.1", + jquery: "1.6.4", // The default length of a jQuery object is 0 length: 0, @@ -255,7 +258,7 @@ jQuery.fn = jQuery.prototype = { ret.context = this.context; if ( name === "find" ) { - ret.selector = this.selector + ( this.selector ? " " : "" ) + selector; + ret.selector = this.selector + (this.selector ? " " : "") + selector; } else if ( name ) { ret.selector = this.selector + "." + name + "(" + selector + ")"; } @@ -276,16 +279,15 @@ jQuery.fn = jQuery.prototype = { jQuery.bindReady(); // Add the callback - readyList.add( fn ); + readyList.done( fn ); return this; }, eq: function( i ) { - i = +i; return i === -1 ? this.slice( i ) : - this.slice( i, i + 1 ); + this.slice( i, +i + 1 ); }, first: function() { @@ -432,11 +434,11 @@ jQuery.extend({ } // If there are functions bound, to execute - readyList.fireWith( document, [ jQuery ] ); + readyList.resolveWith( document, [ jQuery ] ); // Trigger any bound ready events if ( jQuery.fn.trigger ) { - jQuery( document ).trigger( "ready" ).off( "ready" ); + jQuery( document ).trigger( "ready" ).unbind( "ready" ); } } }, @@ -446,7 +448,7 @@ jQuery.extend({ return; } - readyList = jQuery.Callbacks( "once memory" ); + readyList = jQuery._Deferred(); // Catch cases where $(document).ready() is called after the // browser event has already occurred. @@ -502,8 +504,8 @@ jQuery.extend({ return obj && typeof obj === "object" && "setInterval" in obj; }, - isNumeric: function( obj ) { - return !isNaN( parseFloat(obj) ) && isFinite( obj ); + isNaN: function( obj ) { + return obj == null || !rdigit.test( obj ) || isNaN( obj ); }, type: function( obj ) { @@ -549,7 +551,7 @@ jQuery.extend({ }, error: function( msg ) { - throw new Error( msg ); + throw msg; }, parseJSON: function( data ) { @@ -571,7 +573,7 @@ jQuery.extend({ .replace( rvalidtokens, "]" ) .replace( rvalidbraces, "")) ) { - return ( new Function( "return " + data ) )(); + return (new Function( "return " + data ))(); } jQuery.error( "Invalid JSON: " + data ); @@ -686,6 +688,8 @@ jQuery.extend({ 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 ); @@ -699,22 +703,18 @@ jQuery.extend({ return ret; }, - inArray: function( elem, array, i ) { - var len; - - if ( array ) { - if ( indexOf ) { - return indexOf.call( array, elem, i ); - } + inArray: function( elem, array ) { + if ( !array ) { + return -1; + } - len = array.length; - i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0; + if ( indexOf ) { + return indexOf.call( array, elem ); + } - for ( ; i < len; i++ ) { - // Skip accessing in sparse arrays - if ( i in array && array[ i ] === elem ) { - return i; - } + for ( var i = 0, length = array.length; i < length; i++ ) { + if ( array[ i ] === elem ) { + return i; } } @@ -850,7 +850,7 @@ jQuery.extend({ }, now: function() { - return ( new Date() ).getTime(); + return (new Date()).getTime(); }, // Use of jQuery.browser is frowned upon. @@ -957,360 +957,188 @@ return jQuery; })(); -// String to Object flags format cache -var flagsCache = {}; - -// Convert String-formatted flags into Object-formatted ones and store in cache -function createFlags( flags ) { - var object = flagsCache[ flags ] = {}, - i, length; - flags = flags.split( /\s+/ ); - for ( i = 0, length = flags.length; i < length; i++ ) { - object[ flags[i] ] = true; - } - return object; -} +var // Promise methods + promiseMethods = "done fail isResolved isRejected promise then always pipe".split( " " ), + // Static reference to slice + sliceDeferred = [].slice; -/* - * Create a callback list using the following parameters: - * - * flags: an optional list of space-separated flags that will change how - * the callback list behaves - * - * By default a callback list will act like an event callback list and can be - * "fired" multiple times. - * - * Possible flags: - * - * once: will ensure the callback list can only be fired once (like a Deferred) - * - * memory: will keep track of previous values and will call any callback added - * after the list has been fired right away with the latest "memorized" - * values (like a Deferred) - * - * unique: will ensure a callback can only be added once (no duplicate in the list) - * - * stopOnFalse: interrupt callings when a callback returns false - * - */ -jQuery.Callbacks = function( flags ) { - - // Convert flags from String-formatted to Object-formatted - // (we check in cache first) - flags = flags ? ( flagsCache[ flags ] || createFlags( flags ) ) : {}; - - var // Actual callback list - list = [], - // Stack of fire calls for repeatable lists - stack = [], - // Last fire value (for non-forgettable lists) - memory, - // Flag to know if list is currently firing - firing, - // First callback to fire (used internally by add and fireWith) - firingStart, - // End of the loop when firing - firingLength, - // Index of currently firing callback (modified by remove if needed) - firingIndex, - // Add one or several callbacks to the list - add = function( args ) { - var i, - length, - elem, - type, - actual; - for ( i = 0, length = args.length; i < length; i++ ) { - elem = args[ i ]; - type = jQuery.type( elem ); - if ( type === "array" ) { - // Inspect recursively - add( elem ); - } else if ( type === "function" ) { - // Add if not in unique mode and callback is not in - if ( !flags.unique || !self.has( elem ) ) { - list.push( elem ); - } - } - } - }, - // Fire callbacks - fire = function( context, args ) { - args = args || []; - memory = !flags.memory || [ context, args ]; - firing = true; - firingIndex = firingStart || 0; - firingStart = 0; - firingLength = list.length; - for ( ; list && firingIndex < firingLength; firingIndex++ ) { - if ( list[ firingIndex ].apply( context, args ) === false && flags.stopOnFalse ) { - memory = true; // Mark as halted - break; - } - } - firing = false; - if ( list ) { - if ( !flags.once ) { - if ( stack && stack.length ) { - memory = stack.shift(); - self.fireWith( memory[ 0 ], memory[ 1 ] ); - } - } else if ( memory === true ) { - self.disable(); - } else { - list = []; - } - } - }, - // Actual Callbacks object - self = { - // Add a callback or a collection of callbacks to the list - add: function() { - if ( list ) { - var length = list.length; - add( arguments ); - // Do we need to add the callbacks to the - // current firing batch? - if ( firing ) { - firingLength = list.length; - // With memory, if we're not firing then - // we should call right away, unless previous - // firing was halted (stopOnFalse) - } else if ( memory && memory !== true ) { - firingStart = length; - fire( memory[ 0 ], memory[ 1 ] ); - } - } - return this; - }, - // Remove a callback from the list - remove: function() { - if ( list ) { - var args = arguments, - argIndex = 0, - argLength = args.length; - for ( ; argIndex < argLength ; argIndex++ ) { - for ( var i = 0; i < list.length; i++ ) { - if ( args[ argIndex ] === list[ i ] ) { - // Handle firingIndex and firingLength - if ( firing ) { - if ( i <= firingLength ) { - firingLength--; - if ( i <= firingIndex ) { - firingIndex--; - } - } - } - // Remove the element - list.splice( i--, 1 ); - // If we have some unicity property then - // we only need to do this once - if ( flags.unique ) { - break; - } +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 ); } } - } - } - return this; - }, - // Control if a given callback is in the list - has: function( fn ) { - if ( list ) { - var i = 0, - length = list.length; - for ( ; i < length; i++ ) { - if ( fn === list[ i ] ) { - return true; + if ( _fired ) { + deferred.resolveWith( _fired[ 0 ], _fired[ 1 ] ); } } - } - return false; - }, - // Remove all callbacks from the list - empty: function() { - list = []; - return this; - }, - // Have the list do nothing anymore - disable: function() { - list = stack = memory = undefined; - return this; - }, - // Is it disabled? - disabled: function() { - return !list; - }, - // Lock the list in its current state - lock: function() { - stack = undefined; - if ( !memory || memory === true ) { - self.disable(); - } - return this; - }, - // Is it locked? - locked: function() { - return !stack; - }, - // Call all callbacks with the given context and arguments - fireWith: function( context, args ) { - if ( stack ) { - if ( firing ) { - if ( !flags.once ) { - stack.push( [ context, args ] ); + 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; } - } else if ( !( flags.once && memory ) ) { - fire( context, args ); } - } - return this; - }, - // Call all the callbacks with the given arguments - fire: function() { - self.fireWith( this, arguments ); - return this; - }, - // To know if the callbacks have already been called at least once - fired: function() { - return !!memory; - } - }; - - return self; -}; - + 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 ); + }, -var // Static reference to slice - sliceDeferred = [].slice; + // Cancel + cancel: function() { + cancelled = 1; + callbacks = []; + return this; + } + }; -jQuery.extend({ + return deferred; + }, + // Full fledged deferred (two callbacks list) Deferred: function( func ) { - var doneList = jQuery.Callbacks( "once memory" ), - failList = jQuery.Callbacks( "once memory" ), - progressList = jQuery.Callbacks( "memory" ), - state = "pending", - lists = { - resolve: doneList, - reject: failList, - notify: progressList + 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; }, - promise = { - done: doneList.add, - fail: failList.add, - progress: progressList.add, - - state: function() { - return state; - }, - - // Deprecated - isResolved: doneList.fired, - isRejected: failList.fired, - - then: function( doneCallbacks, failCallbacks, progressCallbacks ) { - deferred.done( doneCallbacks ).fail( failCallbacks ).progress( progressCallbacks ); - return this; - }, - always: function() { - deferred.done.apply( deferred, arguments ).fail.apply( deferred, arguments ); - return this; - }, - pipe: function( fnDone, fnFail, fnProgress ) { - return jQuery.Deferred(function( newDefer ) { - jQuery.each( { - done: [ fnDone, "resolve" ], - fail: [ fnFail, "reject" ], - progress: [ fnProgress, "notify" ] - }, 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, newDefer.notify ); - } else { - newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ 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 ) { - obj = promise; - } else { - for ( var key in promise ) { - obj[ key ] = promise[ key ]; + 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 + "With" ]( this === deferred ? newDefer : this, [ 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; } - return obj; + promise = obj = {}; } - }, - deferred = promise.promise({}), - key; - - for ( key in lists ) { - deferred[ key ] = lists[ key ].fire; - deferred[ key + "With" ] = lists[ key ].fireWith; - } - - // Handle state - deferred.done( function() { - state = "resolved"; - }, failList.disable, progressList.lock ).fail( function() { - state = "rejected"; - }, doneList.disable, progressList.lock ); - + 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 ); } - - // All done! return deferred; }, // Deferred helper when: function( firstParam ) { - var args = sliceDeferred.call( arguments, 0 ), + var args = arguments, i = 0, length = args.length, - pValues = new Array( length ), count = length, - pCount = length, deferred = length <= 1 && firstParam && jQuery.isFunction( firstParam.promise ) ? firstParam : - jQuery.Deferred(), - promise = deferred.promise(); + jQuery.Deferred(); function resolveFunc( i ) { return function( value ) { args[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value; if ( !( --count ) ) { - deferred.resolveWith( deferred, args ); + // 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 ) ); } }; } - function progressFunc( i ) { - return function( value ) { - pValues[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value; - deferred.notifyWith( promise, pValues ); - }; - } if ( length > 1 ) { - for ( ; i < length; i++ ) { - if ( args[ i ] && args[ i ].promise && jQuery.isFunction( args[ i ].promise ) ) { - args[ i ].promise().then( resolveFunc(i), deferred.reject, progressFunc(i) ); + for( ; i < length; i++ ) { + if ( args[ i ] && jQuery.isFunction( args[ i ].promise ) ) { + args[ i ].promise().then( resolveFunc(i), deferred.reject ); } else { --count; } @@ -1321,35 +1149,39 @@ jQuery.extend({ } else if ( deferred !== firstParam ) { deferred.resolveWith( deferred, length ? [ firstParam ] : [] ); } - return promise; + return deferred.promise(); } }); - jQuery.support = (function() { - var support, + var div = document.createElement( "div" ), + documentElement = document.documentElement, all, a, select, opt, input, marginDiv, + support, fragment, + body, + testElementParent, + testElement, + testElementStyle, tds, events, eventName, i, - isSupported, - div = document.createElement( "div" ), - documentElement = document.documentElement; + isSupported; // Preliminary tests div.setAttribute("className", "t"); div.innerHTML = "
a"; + all = div.getElementsByTagName( "*" ); a = div.getElementsByTagName( "a" )[ 0 ]; @@ -1369,11 +1201,11 @@ jQuery.support = (function() { // 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 instead) @@ -1381,12 +1213,12 @@ jQuery.support = (function() { // 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) // Use a regex to work around a WebKit issue. See #5145 - opacity: /^0.55/.test( a.style.opacity ), + opacity: /^0.55$/.test( a.style.opacity ), // Verify style float existence // (IE uses styleFloat instead of cssFloat) @@ -1404,13 +1236,6 @@ jQuery.support = (function() { // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7) getSetAttribute: div.className !== "t", - // Tests for enctype support on a form(#6743) - enctype: !!document.createElement("form").enctype, - - // Makes sure cloning an html5 element does not cause problems - // Where outerHTML is undefined, this still works - html5Clone: document.createElement("nav").cloneNode( true ).outerHTML !== "<:nav>", - // Will be defined later submitBubbles: true, changeBubbles: true, @@ -1448,7 +1273,7 @@ jQuery.support = (function() { div.cloneNode( true ).fireEvent( "onclick" ); } - // Check if a radio maintains its value + // Check if a radio maintains it's value // after being appended to the DOM input = document.createElement("input"); input.value = "t"; @@ -1458,18 +1283,82 @@ jQuery.support = (function() { input.setAttribute("checked", "checked"); div.appendChild( input ); fragment = document.createDocumentFragment(); - fragment.appendChild( div.lastChild ); + fragment.appendChild( div.firstChild ); // WebKit doesn't clone checked state correctly in fragments support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked; + div.innerHTML = ""; + + // Figure out if the W3C box model works as expected + div.style.width = div.style.paddingLeft = "1px"; + + body = document.getElementsByTagName( "body" )[ 0 ]; + // We use our own, invisible, body unless the body is already present + // in which case we use a div (#9239) + testElement = document.createElement( body ? "div" : "body" ); + testElementStyle = { + visibility: "hidden", + width: 0, + height: 0, + border: 0, + margin: 0, + background: "none" + }; + if ( body ) { + jQuery.extend( testElementStyle, { + position: "absolute", + left: "-1000px", + top: "-1000px" + }); + } + for ( i in testElementStyle ) { + testElement.style[ i ] = testElementStyle[ i ]; + } + testElement.appendChild( div ); + testElementParent = body || documentElement; + testElementParent.insertBefore( testElement, testElementParent.firstChild ); + // Check if a disconnected checkbox will retain its checked // value of true after appended to the DOM (IE6/7) support.appendChecked = input.checked; - fragment.removeChild( input ); - fragment.appendChild( div ); + 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 = "
"; + support.shrinkWrapBlocks = ( div.offsetWidth !== 2 ); + } + + div.innerHTML = "
t
"; + 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 @@ -1477,18 +1366,21 @@ jQuery.support = (function() { // info see bug #3333 // Fails in WebKit before Feb 2011 nightlies // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right - if ( window.getComputedStyle ) { + if ( document.defaultView && document.defaultView.getComputedStyle ) { marginDiv = document.createElement( "div" ); marginDiv.style.width = "0"; marginDiv.style.marginRight = "0"; - div.style.width = "2px"; div.appendChild( marginDiv ); support.reliableMarginRight = - ( parseInt( ( window.getComputedStyle( marginDiv, null ) || { marginRight: 0 } ).marginRight, 10 ) || 0 ) === 0; + ( parseInt( ( document.defaultView.getComputedStyle( marginDiv, null ) || { marginRight: 0 } ).marginRight, 10 ) || 0 ) === 0; } + // Remove the body element we added + testElement.innerHTML = ""; + testElementParent.removeChild( testElement ); + // Technique from Juriy Zaytsev - // http://perfectionkills.com/detecting-event-support-without-browser-sniffing/ + // http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/ // 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 @@ -1498,7 +1390,7 @@ jQuery.support = (function() { submit: 1, change: 1, focusin: 1 - }) { + } ) { eventName = "on" + i; isSupported = ( eventName in div ); if ( !isSupported ) { @@ -1509,111 +1401,15 @@ jQuery.support = (function() { } } - fragment.removeChild( div ); - - // Null elements to avoid leaks in IE - fragment = select = opt = marginDiv = div = input = null; - - // Run tests that need a body at doc ready - jQuery(function() { - var container, outer, inner, table, td, offsetSupport, - conMarginTop, ptlm, vb, style, html, - body = document.getElementsByTagName("body")[0]; - - if ( !body ) { - // Return for frameset docs that don't have a body - return; - } - - conMarginTop = 1; - ptlm = "position:absolute;top:0;left:0;width:1px;height:1px;margin:0;"; - vb = "visibility:hidden;border:0;"; - style = "style='" + ptlm + "border:5px solid #000;padding:0;'"; - html = "
" + - "" + - "
"; - - container = document.createElement("div"); - container.style.cssText = vb + "width:0;height:0;position:static;top:0;margin-top:" + conMarginTop + "px"; - body.insertBefore( container, body.firstChild ); - - // Construct the test element - div = document.createElement("div"); - container.appendChild( div ); - - // 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) - div.innerHTML = "
t
"; - tds = div.getElementsByTagName( "td" ); - 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 ); - - // Figure out if the W3C box model works as expected - div.innerHTML = ""; - div.style.width = div.style.paddingLeft = "1px"; - jQuery.boxModel = support.boxModel = div.offsetWidth === 2; - - if ( typeof div.style.zoom !== "undefined" ) { - // 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 = "
"; - support.shrinkWrapBlocks = ( div.offsetWidth !== 2 ); - } - - div.style.cssText = ptlm + vb; - div.innerHTML = html; - - outer = div.firstChild; - inner = outer.firstChild; - td = outer.nextSibling.firstChild.firstChild; - - offsetSupport = { - doesNotAddBorder: ( inner.offsetTop !== 5 ), - doesAddBorderForTableAndCells: ( td.offsetTop === 5 ) - }; - - inner.style.position = "fixed"; - inner.style.top = "20px"; - - // safari subtracts parent border width here which is 5px - offsetSupport.fixedPosition = ( inner.offsetTop === 20 || inner.offsetTop === 15 ); - inner.style.position = inner.style.top = ""; - - outer.style.overflow = "hidden"; - outer.style.position = "relative"; - - offsetSupport.subtractsBorderForOverflowNotVisible = ( inner.offsetTop === -5 ); - offsetSupport.doesNotIncludeMarginInBodyOffset = ( body.offsetTop !== conMarginTop ); - - body.removeChild( container ); - div = container = null; - - jQuery.extend( support, offsetSupport ); - }); + // Null connected elements to avoid leaks in IE + testElement = fragment = select = opt = body = marginDiv = div = input = null; return support; })(); +// Keep track of boxModel +jQuery.boxModel = jQuery.support.boxModel; + @@ -1641,6 +1437,7 @@ jQuery.extend({ hasData: function( elem ) { elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ]; + return !!elem && !isEmptyDataObject( elem ); }, @@ -1649,7 +1446,7 @@ jQuery.extend({ return; } - var privateCache, thisCache, ret, + var thisCache, ret, internalKey = jQuery.expando, getByName = typeof name === "string", @@ -1663,12 +1460,11 @@ jQuery.extend({ // 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[ internalKey ] : elem[ internalKey ] && internalKey, - isEvents = name === "events"; + id = isNode ? elem[ jQuery.expando ] : elem[ jQuery.expando ] && jQuery.expando; // 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 || !cache[id] || (!isEvents && !pvt && !cache[id].data)) && getByName && data === undefined ) { + if ( (!id || (pvt && id && (cache[ id ] && !cache[ id ][ internalKey ]))) && getByName && data === undefined ) { return; } @@ -1676,17 +1472,18 @@ jQuery.extend({ // Only DOM nodes need a new unique ID for each element since their data // ends up in the global cache if ( isNode ) { - elem[ internalKey ] = id = ++jQuery.uuid; + elem[ jQuery.expando ] = id = ++jQuery.uuid; } else { - id = internalKey; + id = jQuery.expando; } } if ( !cache[ id ] ) { cache[ id ] = {}; - // Avoids exposing jQuery metadata on plain JS objects when the object - // is serialized using JSON.stringify + // 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; } @@ -1696,33 +1493,34 @@ jQuery.extend({ // shallow copied over onto the existing cache if ( typeof name === "object" || typeof name === "function" ) { if ( pvt ) { - cache[ id ] = jQuery.extend( cache[ id ], name ); + cache[ id ][ internalKey ] = jQuery.extend(cache[ id ][ internalKey ], name); } else { - cache[ id ].data = jQuery.extend( cache[ id ].data, name ); + cache[ id ] = jQuery.extend(cache[ id ], name); } } - privateCache = thisCache = cache[ id ]; + thisCache = cache[ id ]; - // jQuery data() is stored in a separate object inside the object's internal data + // 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.data ) { - thisCache.data = {}; + // data + if ( pvt ) { + if ( !thisCache[ internalKey ] ) { + thisCache[ internalKey ] = {}; } - thisCache = thisCache.data; + thisCache = thisCache[ internalKey ]; } if ( data !== undefined ) { thisCache[ jQuery.camelCase( name ) ] = data; } - // Users should not attempt to inspect the internal events object using jQuery.data, - // it is undocumented and subject to change. But does anyone listen? No. - if ( isEvents && !thisCache[ name ] ) { - return privateCache.events; + // 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; } // Check for both converted-to-camel and non-converted data property names @@ -1750,7 +1548,7 @@ jQuery.extend({ return; } - var thisCache, i, l, + var thisCache, // Reference to internal data cache key internalKey = jQuery.expando, @@ -1761,7 +1559,7 @@ jQuery.extend({ cache = isNode ? jQuery.cache : elem, // See jQuery.data for more information - id = isNode ? elem[ internalKey ] : internalKey; + id = isNode ? elem[ jQuery.expando ] : jQuery.expando; // If there is already no cache entry for this object, there is no // purpose in continuing @@ -1771,43 +1569,28 @@ jQuery.extend({ if ( name ) { - thisCache = pvt ? cache[ id ] : cache[ id ].data; + thisCache = pvt ? cache[ id ][ internalKey ] : cache[ id ]; if ( thisCache ) { - // Support array or space separated string names for data keys - if ( !jQuery.isArray( name ) ) { - - // try the string as a key before any manipulation - if ( name in thisCache ) { - name = [ name ]; - } else { - - // split the camel cased version by spaces unless a key with the spaces exists - name = jQuery.camelCase( name ); - if ( name in thisCache ) { - name = [ name ]; - } else { - name = name.split( " " ); - } - } + // Support interoperable removal of hyphenated or camelcased keys + if ( !thisCache[ name ] ) { + name = jQuery.camelCase( name ); } - for ( i = 0, l = name.length; i < l; i++ ) { - delete thisCache[ name[i] ]; - } + delete thisCache[ name ]; // If there is no data left in the cache, we want to continue // and let the cache object itself get destroyed - if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) { + if ( !isEmptyDataObject(thisCache) ) { return; } } } // See jQuery.data for more information - if ( !pvt ) { - delete cache[ id ].data; + 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 @@ -1816,6 +1599,8 @@ jQuery.extend({ } } + 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 @@ -1826,18 +1611,32 @@ jQuery.extend({ cache[ id ] = null; } - // We destroyed the cache and need to eliminate the expando on the node to avoid + // 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 - if ( isNode ) { + } 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[ internalKey ]; + delete elem[ jQuery.expando ]; } else if ( elem.removeAttribute ) { - elem.removeAttribute( internalKey ); + elem.removeAttribute( jQuery.expando ); } else { - elem[ internalKey ] = null; + elem[ jQuery.expando ] = null; } } }, @@ -1863,15 +1662,14 @@ jQuery.extend({ jQuery.fn.extend({ data: function( key, value ) { - var parts, attr, name, - data = null; + var data = null; if ( typeof key === "undefined" ) { if ( this.length ) { data = jQuery.data( this[0] ); - if ( this[0].nodeType === 1 && !jQuery._data( this[0], "parsedAttrs" ) ) { - attr = this[0].attributes; + 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; @@ -1881,7 +1679,6 @@ jQuery.fn.extend({ dataAttr( this[0], name, data[ name ] ); } } - jQuery._data( this[0], "parsedAttrs", true ); } } @@ -1893,7 +1690,7 @@ jQuery.fn.extend({ }); } - parts = key.split("."); + var parts = key.split("."); parts[1] = parts[1] ? "." + parts[1] : ""; if ( value === undefined ) { @@ -1911,12 +1708,12 @@ jQuery.fn.extend({ } else { return this.each(function() { - var self = jQuery( this ), + var $this = jQuery( this ), args = [ parts[0], value ]; - self.triggerHandler( "setData" + parts[1] + "!", args ); + $this.triggerHandler( "setData" + parts[1] + "!", args ); jQuery.data( this, key, value ); - self.triggerHandler( "changeData" + parts[1] + "!", args ); + $this.triggerHandler( "changeData" + parts[1] + "!", args ); }); } }, @@ -1942,7 +1739,7 @@ function dataAttr( elem, key, data ) { data = data === "true" ? true : data === "false" ? false : data === "null" ? null : - jQuery.isNumeric( data ) ? parseFloat( data ) : + !jQuery.isNaN( data ) ? parseFloat( data ) : rbrace.test( data ) ? jQuery.parseJSON( data ) : data; } catch( e ) {} @@ -1958,14 +1755,11 @@ function dataAttr( elem, key, data ) { return data; } -// checks a cache object for emptiness +// 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 the public data object is empty, the private is still empty - if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) { - continue; - } if ( name !== "toJSON" ) { return false; } @@ -1981,17 +1775,17 @@ function handleQueueMarkDefer( elem, type, src ) { var deferDataKey = type + "defer", queueDataKey = type + "queue", markDataKey = type + "mark", - defer = jQuery._data( elem, deferDataKey ); + defer = jQuery.data( elem, deferDataKey, undefined, true ); if ( defer && - ( src === "queue" || !jQuery._data(elem, queueDataKey) ) && - ( src === "mark" || !jQuery._data(elem, markDataKey) ) ) { + ( 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 ) && - !jQuery._data( elem, markDataKey ) ) { + if ( !jQuery.data( elem, queueDataKey, undefined, true ) && + !jQuery.data( elem, markDataKey, undefined, true ) ) { jQuery.removeData( elem, deferDataKey, true ); - defer.fire(); + defer.resolve(); } }, 0 ); } @@ -2001,8 +1795,8 @@ jQuery.extend({ _mark: function( elem, type ) { if ( elem ) { - type = ( type || "fx" ) + "mark"; - jQuery._data( elem, type, (jQuery._data( elem, type ) || 0) + 1 ); + type = (type || "fx") + "mark"; + jQuery.data( elem, type, (jQuery.data(elem,type,undefined,true) || 0) + 1, true ); } }, @@ -2015,9 +1809,9 @@ jQuery.extend({ if ( elem ) { type = type || "fx"; var key = type + "mark", - count = force ? 0 : ( (jQuery._data( elem, key ) || 1) - 1 ); + count = force ? 0 : ( (jQuery.data( elem, key, undefined, true) || 1 ) - 1 ); if ( count ) { - jQuery._data( elem, key, count ); + jQuery.data( elem, key, count, true ); } else { jQuery.removeData( elem, key, true ); handleQueueMarkDefer( elem, type, "mark" ); @@ -2026,15 +1820,13 @@ jQuery.extend({ }, queue: function( elem, type, data ) { - var q; if ( elem ) { - type = ( type || "fx" ) + "queue"; - q = jQuery._data( elem, type ); - + 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) ); + q = jQuery.data( elem, type, jQuery.makeArray(data), true ); } else { q.push( data ); } @@ -2048,7 +1840,7 @@ jQuery.extend({ var queue = jQuery.queue( elem, type ), fn = queue.shift(), - hooks = {}; + defer; // If the fx queue is dequeued, always remove the progress sentinel if ( fn === "inprogress" ) { @@ -2059,17 +1851,16 @@ jQuery.extend({ // Add a progress sentinel to prevent the fx queue from being // automatically dequeued if ( type === "fx" ) { - queue.unshift( "inprogress" ); + queue.unshift("inprogress"); } - jQuery._data( elem, type + ".run", hooks ); - fn.call( elem, function() { - jQuery.dequeue( elem, type ); - }, hooks ); + fn.call(elem, function() { + jQuery.dequeue(elem, type); + }); } if ( !queue.length ) { - jQuery.removeData( elem, type + "queue " + type + ".run", true ); + jQuery.removeData( elem, type + "queue", true ); handleQueueMarkDefer( elem, type, "queue" ); } } @@ -2101,14 +1892,14 @@ jQuery.fn.extend({ // 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; + time = jQuery.fx ? jQuery.fx.speeds[time] || time : time; type = type || "fx"; - return this.queue( type, function( next, hooks ) { - var timeout = setTimeout( next, time ); - hooks.stop = function() { - clearTimeout( timeout ); - }; + return this.queue( type, function() { + var elem = this; + setTimeout(function() { + jQuery.dequeue( elem, type ); + }, time ); }); }, clearQueue: function( type ) { @@ -2139,9 +1930,9 @@ jQuery.fn.extend({ 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.Callbacks( "once memory" ), true ) )) { + jQuery.data( elements[ i ], deferDataKey, jQuery._Deferred(), true ) )) { count++; - tmp.add( resolve ); + tmp.done( resolve ); } } resolve(); @@ -2159,8 +1950,7 @@ var rclass = /[\n\t\r]/g, rfocusable = /^(?:button|input|object|select|textarea)$/i, rclickable = /^a(?:rea)?$/i, rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i, - getSetAttribute = jQuery.support.getSetAttribute, - nodeHook, boolHook, fixSpecified; + nodeHook, boolHook; jQuery.fn.extend({ attr: function( name, value ) { @@ -2172,11 +1962,11 @@ jQuery.fn.extend({ 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() { @@ -2235,7 +2025,7 @@ jQuery.fn.extend({ } if ( (value && typeof value === "string") || value === undefined ) { - classNames = ( value || "" ).split( rspace ); + classNames = (value || "").split( rspace ); for ( i = 0, l = this.length; i < l; i++ ) { elem = this[ i ]; @@ -2296,10 +2086,8 @@ jQuery.fn.extend({ }, hasClass: function( selector ) { - var className = " " + selector + " ", - i = 0, - l = this.length; - for ( ; i < l; i++ ) { + var className = " " + selector + " "; + for ( var i = 0, l = this.length; i < l; i++ ) { if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) { return true; } @@ -2309,9 +2097,9 @@ jQuery.fn.extend({ }, val: function( value ) { - var hooks, ret, isFunction, + var hooks, ret, elem = this[0]; - + if ( !arguments.length ) { if ( elem ) { hooks = jQuery.valHooks[ elem.nodeName.toLowerCase() ] || jQuery.valHooks[ elem.type ]; @@ -2322,17 +2110,17 @@ jQuery.fn.extend({ ret = elem.value; - return typeof ret === "string" ? + return typeof ret === "string" ? // handle most common string cases - ret.replace(rreturn, "") : + ret.replace(rreturn, "") : // handle cases where value is null/undef or number ret == null ? "" : ret; } - return; + return undefined; } - isFunction = jQuery.isFunction( value ); + var isFunction = jQuery.isFunction( value ); return this.each(function( i ) { var self = jQuery(this), val; @@ -2380,7 +2168,7 @@ jQuery.extend({ }, select: { get: function( elem ) { - var value, i, max, option, + var value, index = elem.selectedIndex, values = [], options = elem.options, @@ -2392,10 +2180,8 @@ jQuery.extend({ } // Loop through all the selected options - i = one ? index : 0; - max = one ? index + 1 : options.length; - for ( ; i < max; i++ ) { - option = options[ i ]; + 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) && @@ -2447,14 +2233,18 @@ jQuery.extend({ height: true, offset: true }, - + + attrFix: { + // Always normalize to ensure hook usage + tabindex: "tabIndex" + }, + attr: function( elem, name, value, pass ) { - var ret, hooks, notxml, - nType = elem.nodeType; - + var nType = elem.nodeType; + // don't get/set attributes on text, comment and attribute nodes if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { - return; + return undefined; } if ( pass && name in jQuery.attrFn ) { @@ -2462,24 +2252,36 @@ jQuery.extend({ } // Fallback to prop when attributes are not supported - if ( typeof elem.getAttribute === "undefined" ) { + if ( !("getAttribute" in elem) ) { return jQuery.prop( elem, name, value ); } - notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); + var ret, hooks, + notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); - // All attributes are lowercase - // Grab necessary hook if one is defined + // Normalize the name if needed if ( notxml ) { - name = name.toLowerCase(); - hooks = jQuery.attrHooks[ name ] || ( rboolean.test( name ) ? boolHook : nodeHook ); + name = jQuery.attrFix[ name ] || name; + + hooks = jQuery.attrHooks[ name ]; + + if ( !hooks ) { + // Use boolHook for boolean attributes + if ( rboolean.test( name ) ) { + hooks = boolHook; + + // Use nodeHook if available( IE6/7 ) + } else if ( nodeHook ) { + hooks = nodeHook; + } + } } if ( value !== undefined ) { if ( value === null ) { jQuery.removeAttr( elem, name ); - return; + return undefined; } else if ( hooks && "set" in hooks && notxml && (ret = hooks.set( elem, value, name )) !== undefined ) { return ret; @@ -2503,29 +2305,17 @@ jQuery.extend({ } }, - removeAttr: function( elem, value ) { - var propName, attrNames, name, l, - i = 0; - - if ( value && elem.nodeType === 1 ) { - attrNames = value.toLowerCase().split( rspace ); - l = attrNames.length; - - for ( ; i < l; i++ ) { - name = attrNames[ i ]; - - if ( name ) { - propName = jQuery.propFix[ name ] || name; + removeAttr: function( elem, name ) { + var propName; + if ( elem.nodeType === 1 ) { + name = jQuery.attrFix[ name ] || name; - // See #9699 for explanation of this approach (setting first, then removal) - jQuery.attr( elem, name, "" ); - elem.removeAttribute( getSetAttribute ? name : propName ); + jQuery.attr( elem, name, "" ); + elem.removeAttribute( name ); - // Set corresponding property to false for boolean attributes - if ( rboolean.test( name ) && propName in elem ) { - elem[ propName ] = false; - } - } + // Set corresponding property to false for boolean attributes + if ( rboolean.test( name ) && (propName = jQuery.propFix[ name ] || name) in elem ) { + elem[ propName ] = false; } } }, @@ -2584,17 +2374,17 @@ jQuery.extend({ frameborder: "frameBorder", contenteditable: "contentEditable" }, - + prop: function( elem, name, value ) { - var ret, hooks, notxml, - nType = elem.nodeType; + var nType = elem.nodeType; // don't get/set properties on text, comment and attribute nodes if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { - return; + return undefined; } - notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); + var ret, hooks, + notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); if ( notxml ) { // Fix name and attach hooks @@ -2607,7 +2397,7 @@ jQuery.extend({ return ret; } else { - return ( elem[ name ] = value ); + return (elem[ name ] = value); } } else { @@ -2619,7 +2409,7 @@ jQuery.extend({ } } }, - + propHooks: { tabIndex: { get: function( elem ) { @@ -2637,17 +2427,16 @@ jQuery.extend({ } }); -// Add the tabIndex propHook to attrHooks for back-compat (different case is intentional) -jQuery.attrHooks.tabindex = jQuery.propHooks.tabIndex; +// Add the tabindex propHook to attrHooks for back-compat +jQuery.attrHooks.tabIndex = jQuery.propHooks.tabIndex; // Hook for boolean attributes boolHook = { get: function( elem, name ) { // Align boolean attributes with corresponding properties // Fall back to attribute presence where some booleans are not supported - var attrNode, - property = jQuery.prop( elem, name ); - return property === true || typeof property !== "boolean" && ( attrNode = elem.getAttributeNode(name) ) && attrNode.nodeValue !== false ? + var attrNode; + return jQuery.prop( elem, name ) === true || ( attrNode = elem.getAttributeNode( name ) ) && attrNode.nodeValue !== false ? name.toLowerCase() : undefined; }, @@ -2672,20 +2461,16 @@ boolHook = { }; // IE6/7 do not support getting/setting some attributes with get/setAttribute -if ( !getSetAttribute ) { - - fixSpecified = { - name: true, - id: true - }; - +if ( !jQuery.support.getSetAttribute ) { + // Use this for any attribute in IE6/7 // This fixes almost every IE6/7 issue nodeHook = jQuery.valHooks.button = { get: function( elem, name ) { var ret; ret = elem.getAttributeNode( name ); - return ret && ( fixSpecified[ name ] ? ret.nodeValue !== "" : ret.specified ) ? + // Return undefined if nodeValue is empty string + return ret && ret.nodeValue !== "" ? ret.nodeValue : undefined; }, @@ -2696,13 +2481,10 @@ if ( !getSetAttribute ) { ret = document.createAttribute( name ); elem.setAttributeNode( ret ); } - return ( ret.nodeValue = value + "" ); + return (ret.nodeValue = value + ""); } }; - // Apply the nodeHook to tabindex - jQuery.attrHooks.tabindex.set = nodeHook.set; - // 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 ) { @@ -2715,18 +2497,6 @@ if ( !getSetAttribute ) { } }); }); - - // Set contenteditable to false on removals(#10429) - // Setting to empty string throws an error as an invalid value - jQuery.attrHooks.contenteditable = { - get: nodeHook.get, - set: function( elem, value, name ) { - if ( value === "" ) { - value = "false"; - } - nodeHook.set( elem, value, name ); - } - }; } @@ -2750,7 +2520,7 @@ if ( !jQuery.support.style ) { return elem.style.cssText.toLowerCase() || undefined; }, set: function( elem, value ) { - return ( elem.style.cssText = "" + value ); + return (elem.style.cssText = "" + value); } }; } @@ -2775,11 +2545,6 @@ if ( !jQuery.support.optSelected ) { }); } -// IE6/7 call enctype encoding -if ( !jQuery.support.enctype ) { - jQuery.propFix.enctype = "encoding"; -} - // Radios and checkboxes getter/setter if ( !jQuery.support.checkOn ) { jQuery.each([ "radio", "checkbox" ], function() { @@ -2795,7 +2560,7 @@ 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 ); + return (elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0); } } }); @@ -2804,118 +2569,116 @@ jQuery.each([ "radio", "checkbox" ], function() { -var rformElems = /^(?:textarea|input|select)$/i, - rtypenamespace = /^([^\.]*)?(?:\.(.+))?$/, - rhoverHack = /\bhover(\.\S+)?\b/, - rkeyEvent = /^key/, - rmouseEvent = /^(?:mouse|contextmenu)|click/, - rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, - rquickIs = /^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/, - quickParse = function( selector ) { - var quick = rquickIs.exec( selector ); - if ( quick ) { - // 0 1 2 3 - // [ _, tag, id, class ] - quick[1] = ( quick[1] || "" ).toLowerCase(); - quick[3] = quick[3] && new RegExp( "(?:^|\\s)" + quick[3] + "(?:\\s|$)" ); - } - return quick; - }, - quickIs = function( elem, m ) { - var attrs = elem.attributes || {}; - return ( - (!m[1] || elem.nodeName.toLowerCase() === m[1]) && - (!m[2] || (attrs.id || {}).value === m[2]) && - (!m[3] || m[3].test( (attrs[ "class" ] || {}).value )) - ); - }, - hoverHack = function( events ) { - return jQuery.event.special.hover ? events : events.replace( rhoverHack, "mouseenter$1 mouseleave$1" ); +var rnamespaces = /\.(.*)$/, + rformElems = /^(?:textarea|input|select)$/i, + rperiod = /\./g, + rspaces = / /g, + rescape = /[^\w\s.|`]/g, + fcleanup = function( nm ) { + return nm.replace(rescape, "\\$&"); }; /* - * Helper functions for managing events -- not part of the public interface. - * Props to Dean Edwards' addEvent library for many of the ideas. + * A number of helper functions used for managing events. + * Many of the ideas behind this code originated from + * Dean Edwards' addEvent library. */ jQuery.event = { - add: function( elem, types, handler, data, selector ) { - - var elemData, eventHandle, events, - t, tns, type, namespaces, handleObj, - handleObjIn, quick, handlers, special; + // Bind an event to an element + // Original by Dean Edwards + add: function( elem, types, handler, data ) { + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } - // Don't attach events to noData or text/comment nodes (allow plain objects tho) - if ( elem.nodeType === 3 || elem.nodeType === 8 || !types || !handler || !(elemData = jQuery._data( elem )) ) { + if ( handler === false ) { + handler = returnFalse; + } else if ( !handler ) { + // Fixes bug #7229. Fix recommended by jdalton return; } - // Caller can pass in an object of custom data in lieu of the handler + var handleObjIn, handleObj; + if ( handler.handler ) { handleObjIn = handler; handler = handleObjIn.handler; } - // Make sure that the handler has a unique ID, used to find/remove it later + // Make sure that the function being executed has a unique ID if ( !handler.guid ) { handler.guid = jQuery.guid++; } - // Init the element's event structure and main handler, if this is the first - events = elemData.events; + // 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, + eventHandle = elemData.handle; + if ( !events ) { elemData.events = events = {}; } - eventHandle = elemData.handle; + if ( !eventHandle ) { 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.dispatch.apply( eventHandle.elem, arguments ) : + jQuery.event.handle.apply( eventHandle.elem, arguments ) : undefined; }; - // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events - eventHandle.elem = elem; } + // 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 = jQuery.trim( hoverHack(types) ).split( " " ); - for ( t = 0; t < types.length; t++ ) { + types = types.split(" "); - tns = rtypenamespace.exec( types[t] ) || []; - type = tns[1]; - namespaces = ( tns[2] || "" ).split( "." ).sort(); + var type, i = 0, namespaces; - // If event changes its type, use the special event handlers for the changed type - special = jQuery.event.special[ type ] || {}; + while ( (type = types[ i++ ]) ) { + handleObj = handleObjIn ? + jQuery.extend({}, handleObjIn) : + { handler: handler, data: data }; - // If selector defined, determine special event api type, otherwise given type - type = ( selector ? special.delegateType : special.bindType ) || type; + // Namespaced event handlers + if ( type.indexOf(".") > -1 ) { + namespaces = type.split("."); + type = namespaces.shift(); + handleObj.namespace = namespaces.slice(0).sort().join("."); - // Update special based on newly reset type - special = jQuery.event.special[ type ] || {}; + } else { + namespaces = []; + handleObj.namespace = ""; + } + + handleObj.type = type; + if ( !handleObj.guid ) { + handleObj.guid = handler.guid; + } - // handleObj is passed to all event handlers - handleObj = jQuery.extend({ - type: type, - origType: tns[1], - data: data, - handler: handler, - guid: handler.guid, - selector: selector, - quick: quickParse( selector ), - namespace: namespaces.join(".") - }, handleObjIn ); - - // Init the event handler queue if we're the first - handlers = events[ type ]; + // 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 ] = []; - handlers.delegateCount = 0; - // Only use addEventListener/attachEvent if the special events handler returns false + // 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 ) { @@ -2935,14 +2698,10 @@ jQuery.event = { } } - // Add to the element's handler list, delegates in front - if ( selector ) { - handlers.splice( handlers.delegateCount++, 0, handleObj ); - } else { - handlers.push( handleObj ); - } + // Add the function to the element's handler list + handlers.push( handleObj ); - // Keep track of which events have ever been used, for event optimization + // Keep track of which events have been used, for event optimization jQuery.event.global[ type ] = true; } @@ -2953,80 +2712,129 @@ jQuery.event = { global: {}, // Detach an event or set of events from an element - remove: function( elem, types, handler, selector, mappedTypes ) { + 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.hasData( elem ) && jQuery._data( elem ), + events = elemData && elemData.events; - var elemData = jQuery.hasData( elem ) && jQuery._data( elem ), - t, tns, type, origType, namespaces, origCount, - j, events, special, handle, eventType, handleObj; + 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 ); + } - if ( !elemData || !(events = elemData.events) ) { return; } - // Once for each type.namespace in types; type may be omitted - types = jQuery.trim( hoverHack( types || "" ) ).split(" "); - for ( t = 0; t < types.length; t++ ) { - tns = rtypenamespace.exec( types[t] ) || []; - type = origType = tns[1]; - namespaces = tns[2]; + // Handle multiple events separated by a space + // jQuery(...).unbind("mouseover mouseout", fn); + types = types.split(" "); - // Unbind all events (on this namespace, if provided) for the element - if ( !type ) { - for ( type in events ) { - jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + 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 ] || {}; - type = ( selector? special.delegateType : special.bindType ) || type; - eventType = events[ type ] || []; - origCount = eventType.length; - namespaces = namespaces ? new RegExp("(^|\\.)" + namespaces.split(".").sort().join("\\.(?:.*\\.)?") + "(\\.|$)") : null; - // Remove matching events - for ( j = 0; j < eventType.length; j++ ) { + for ( j = pos || 0; j < eventType.length; j++ ) { handleObj = eventType[ j ]; - if ( ( mappedTypes || origType === handleObj.origType ) && - ( !handler || handler.guid === handleObj.guid ) && - ( !namespaces || namespaces.test( handleObj.namespace ) ) && - ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) { - eventType.splice( j--, 1 ); + 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 ( handleObj.selector ) { - eventType.delegateCount--; + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } } - if ( special.remove ) { - special.remove.call( elem, handleObj ); + + if ( pos != null ) { + break; } } } - // Remove generic event handler if we removed something and no more handlers exist - // (avoids potential for endless recursion during removal of special event handlers) - if ( eventType.length === 0 && origCount !== eventType.length ) { + // 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 ) ) { - handle = elemData.handle; + var handle = elemData.handle; if ( handle ) { handle.elem = null; } - // removeData also checks for emptiness and clears the expando if empty - // so use it instead of delete - jQuery.removeData( elem, [ "events", "handle" ], true ); + delete elemData.events; + delete elemData.handle; + + 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: { @@ -3036,28 +2844,18 @@ jQuery.event = { }, trigger: function( event, data, elem, onlyHandlers ) { - // Don't do events on text and comment nodes - if ( elem && (elem.nodeType === 3 || elem.nodeType === 8) ) { - return; - } - // Event object or event type var type = event.type || event, namespaces = [], - cache, exclusive, i, cur, old, ontype, special, handle, eventPath, bubbleType; + exclusive; - // focus/blur morphs to focusin/out; ensure we're not firing them right now - if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { - return; - } - - if ( type.indexOf( "!" ) >= 0 ) { + 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 ) { + if ( type.indexOf(".") >= 0 ) { // Namespaced trigger; create a regexp to match event type in handle() namespaces = type.split("."); type = namespaces.shift(); @@ -3079,299 +2877,230 @@ jQuery.event = { new jQuery.Event( type ); event.type = type; - event.isTrigger = true; event.exclusive = exclusive; - event.namespace = namespaces.join( "." ); - event.namespace_re = event.namespace? new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.)?") + "(\\.|$)") : null; - ontype = type.indexOf( ":" ) < 0 ? "on" + type : ""; + 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(); + } // Handle a global trigger if ( !elem ) { - // TODO: Stop taunting the data cache; remove global events and always attach to document - cache = jQuery.cache; - for ( i in cache ) { - if ( cache[ i ].events && cache[ i ].events[ type ] ) { - jQuery.event.trigger( event, data, cache[ i ].handle.elem, true ); + 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; + } + + // Don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { return; } // Clean up the event in case it is being reused event.result = undefined; - if ( !event.target ) { - event.target = elem; - } + event.target = elem; // Clone any incoming data and prepend the event, creating the handler arg list data = data != null ? jQuery.makeArray( data ) : []; data.unshift( event ); - // Allow special events to draw outside the lines - special = jQuery.event.special[ type ] || {}; - if ( special.trigger && special.trigger.apply( elem, data ) === false ) { - return; - } - - // Determine event propagation path in advance, per W3C events spec (#9951) - // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) - eventPath = [[ elem, special.bindType || type ]]; - if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { - - bubbleType = special.delegateType || type; - cur = rfocusMorph.test( bubbleType + type ) ? elem : elem.parentNode; - old = null; - for ( ; cur; cur = cur.parentNode ) { - eventPath.push([ cur, bubbleType ]); - old = cur; - } - - // Only add window if we got to document (e.g., not plain obj or detached DOM) - if ( old && old === elem.ownerDocument ) { - eventPath.push([ old.defaultView || old.parentWindow || window, bubbleType ]); - } - } + var cur = elem, + // IE doesn't like method names with a colon (#3533, #8272) + ontype = type.indexOf(":") < 0 ? "on" + type : ""; - // Fire handlers on the event path - for ( i = 0; i < eventPath.length && !event.isPropagationStopped(); i++ ) { + // Fire event on the current element, then bubble up the DOM tree + do { + var handle = jQuery._data( cur, "handle" ); - cur = eventPath[i][0]; - event.type = eventPath[i][1]; - - handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" ); + event.currentTarget = cur; if ( handle ) { handle.apply( cur, data ); } - // Note that this is a bare JS function and not a jQuery handler - handle = ontype && cur[ ontype ]; - if ( handle && jQuery.acceptData( cur ) && handle.apply( cur, data ) === false ) { + + // Trigger an inline bound script + if ( ontype && jQuery.acceptData( cur ) && cur[ ontype ] && cur[ ontype ].apply( cur, data ) === false ) { + event.result = false; event.preventDefault(); } - } - event.type = type; + + // Bubble up to document, then to window + cur = cur.parentNode || cur.ownerDocument || cur === event.target.ownerDocument && window; + } while ( cur && !event.isPropagationStopped() ); // If nobody prevented the default action, do it now - if ( !onlyHandlers && !event.isDefaultPrevented() ) { + if ( !event.isDefaultPrevented() ) { + var old, + special = jQuery.event.special[ type ] || {}; - if ( (!special._default || special._default.apply( elem.ownerDocument, data ) === false) && + 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. - // Don't do default actions on window, that's where global variables be (#6170) - // IE<9 dies on focus/blur to hidden element (#1486) - if ( ontype && elem[ type ] && ((type !== "focus" && type !== "blur") || event.target.offsetWidth !== 0) && !jQuery.isWindow( elem ) ) { + // 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 ( ontype && elem[ type ] ) { + // Don't re-trigger an onFOO event when we call its FOO() method + old = elem[ ontype ]; - // Don't re-trigger an onFOO event when we call its FOO() method - old = elem[ ontype ]; + if ( old ) { + elem[ ontype ] = null; + } - if ( old ) { - elem[ ontype ] = null; + jQuery.event.triggered = type; + elem[ type ](); } + } catch ( ieError ) {} - // Prevent re-triggering of the same event, since we already bubbled it above - jQuery.event.triggered = type; - elem[ type ](); - jQuery.event.triggered = undefined; - - if ( old ) { - elem[ ontype ] = old; - } + if ( old ) { + elem[ ontype ] = old; } + + jQuery.event.triggered = undefined; } } - + return event.result; }, - dispatch: function( event ) { - - // Make a writable jQuery.Event from the native event object + handle: function( event ) { event = jQuery.event.fix( event || window.event ); - - var handlers = ( (jQuery._data( this, "events" ) || {} )[ event.type ] || []), - delegateCount = handlers.delegateCount, - args = [].slice.call( arguments, 0 ), + // 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, - handlerQueue = [], - i, j, cur, jqcur, ret, selMatch, matched, matches, handleObj, sel, related; + args = Array.prototype.slice.call( arguments, 0 ); - // Use the fix-ed jQuery.Event rather than the (read-only) native event + // Use the fix-ed Event rather than the (read-only) native event args[0] = event; - event.delegateTarget = this; - - // Determine handlers that should run if there are delegated events - // Avoid disabled elements in IE (#6911) and non-left-click bubbling in Firefox (#3861) - if ( delegateCount && !event.target.disabled && !(event.button && event.type === "click") ) { - - // Pregenerate a single jQuery object for reuse with .is() - jqcur = jQuery(this); - jqcur.context = this.ownerDocument || this; - - for ( cur = event.target; cur != this; cur = cur.parentNode || this ) { - selMatch = {}; - matches = []; - jqcur[0] = cur; - for ( i = 0; i < delegateCount; i++ ) { - handleObj = handlers[ i ]; - sel = handleObj.selector; - - if ( selMatch[ sel ] === undefined ) { - selMatch[ sel ] = ( - handleObj.quick ? quickIs( cur, handleObj.quick ) : jqcur.is( sel ) - ); + event.currentTarget = this; + + 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 ( selMatch[ sel ] ) { - matches.push( handleObj ); - } - } - if ( matches.length ) { - handlerQueue.push({ elem: cur, matches: matches }); } - } - } - - // Add the remaining (directly-bound) handlers - if ( handlers.length > delegateCount ) { - handlerQueue.push({ elem: this, matches: handlers.slice( delegateCount ) }); - } - - // Run delegates first; they may want to stop propagation beneath us - for ( i = 0; i < handlerQueue.length && !event.isPropagationStopped(); i++ ) { - matched = handlerQueue[ i ]; - event.currentTarget = matched.elem; - - for ( j = 0; j < matched.matches.length && !event.isImmediatePropagationStopped(); j++ ) { - handleObj = matched.matches[ j ]; - - // Triggered event must either 1) be non-exclusive and have no namespace, or - // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace). - if ( run_all || (!event.namespace && !handleObj.namespace) || event.namespace_re && event.namespace_re.test( handleObj.namespace ) ) { - - event.data = handleObj.data; - event.handleObj = handleObj; - ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler ) - .apply( matched.elem, args ); - - if ( ret !== undefined ) { - event.result = ret; - if ( ret === false ) { - event.preventDefault(); - event.stopPropagation(); - } - } + if ( event.isImmediatePropagationStopped() ) { + break; } } } - return event.result; }, - // Includes some event props shared by KeyEvent and MouseEvent - // *** attrChange attrName relatedNode srcElement are not normalized, non-W3C, deprecated, will be removed in 1.8 *** - props: "attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "), - - fixHooks: {}, - - keyHooks: { - props: "char charCode key keyCode".split(" "), - filter: function( event, original ) { - - // Add which for key events - if ( event.which == null ) { - event.which = original.charCode != null ? original.charCode : original.keyCode; - } - - return event; - } - }, - - mouseHooks: { - props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "), - filter: function( event, original ) { - var eventDoc, doc, body, - button = original.button, - fromElement = original.fromElement; - - // Calculate pageX/Y if missing and clientX/Y available - if ( event.pageX == null && original.clientX != null ) { - eventDoc = event.target.ownerDocument || document; - doc = eventDoc.documentElement; - body = eventDoc.body; - - event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 ); - event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 ); - } - - // Add relatedTarget, if necessary - if ( !event.relatedTarget && fromElement ) { - event.relatedTarget = fromElement === event.target ? original.toElement : fromElement; - } - - // Add which for click: 1 === left; 2 === middle; 3 === right - // Note: button is not normalized, so don't use it - if ( !event.which && button !== undefined ) { - event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) ); - } - - return event; - } - }, + 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; } - // Create a writable copy of the event object and normalize some properties - var i, prop, - originalEvent = event, - fixHook = jQuery.event.fixHooks[ event.type ] || {}, - copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props; - + // store a copy of the original event object + // and "clone" to set read-only properties + var originalEvent = event; event = jQuery.Event( originalEvent ); - for ( i = copy.length; i; ) { - prop = copy[ --i ]; + for ( var i = this.props.length, prop; i; ) { + prop = this.props[ --i ]; event[ prop ] = originalEvent[ prop ]; } - // Fix target property, if necessary (#1925, IE 6/7/8 & Safari2) + // Fix target property, if necessary if ( !event.target ) { - event.target = originalEvent.srcElement || document; + // Fixes #1925 where srcElement might not be defined either + event.target = event.srcElement || document; } - // Target should not be a text node (#504, Safari) + // check if target is a textnode (safari) if ( event.target.nodeType === 3 ) { event.target = event.target.parentNode; } - // For mouse/key events; add metaKey if it's not there (#3368, IE6/7/8) - if ( event.metaKey === undefined ) { + // 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 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); + } + + // 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; } - return fixHook.filter? fixHook.filter( event, originalEvent ) : event; + // 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 + setup: jQuery.bindReady, + teardown: jQuery.noop }, - load: { - // Prevent triggered image.load events from bubbling to window.load - noBubble: true - }, + live: { + add: function( handleObj ) { + jQuery.event.add( this, + liveConvert( handleObj.origType, handleObj.selector ), + jQuery.extend({}, handleObj, {handler: liveHandler, guid: handleObj.handler.guid}) ); + }, - focus: { - delegateType: "focusin" - }, - blur: { - delegateType: "focusout" + remove: function( handleObj ) { + jQuery.event.remove( this, liveConvert( handleObj.origType, handleObj.selector ), handleObj ); + } }, beforeunload: { @@ -3388,35 +3117,9 @@ jQuery.event = { } } } - }, - - simulate: function( type, elem, event, bubble ) { - // 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. - var e = jQuery.extend( - new jQuery.Event(), - event, - { type: type, - isSimulated: true, - originalEvent: {} - } - ); - if ( bubble ) { - jQuery.event.trigger( e, null, elem ); - } else { - jQuery.event.dispatch.call( elem, e ); - } - if ( e.isDefaultPrevented() ) { - event.preventDefault(); - } } }; -// Some plugins are using, but it's undocumented/deprecated and will be removed. -// The 1.7 special event interface should provide all the hooks needed now. -jQuery.event.handle = jQuery.event.dispatch; - jQuery.removeEvent = document.removeEventListener ? function( elem, type, handle ) { if ( elem.removeEventListener ) { @@ -3431,7 +3134,7 @@ jQuery.removeEvent = document.removeEventListener ? jQuery.Event = function( src, props ) { // Allow instantiation without the 'new' keyword - if ( !(this instanceof jQuery.Event) ) { + if ( !this.preventDefault ) { return new jQuery.Event( src, props ); } @@ -3442,8 +3145,8 @@ jQuery.Event = function( src, props ) { // 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; + this.isDefaultPrevented = (src.defaultPrevented || src.returnValue === false || + src.getPreventDefault && src.getPreventDefault()) ? returnTrue : returnFalse; // Event type } else { @@ -3455,8 +3158,9 @@ jQuery.Event = function( src, props ) { jQuery.extend( this, props ); } - // Create a timestamp if incoming event doesn't have one - this.timeStamp = src && src.timeStamp || jQuery.now(); + // 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; @@ -3512,130 +3216,216 @@ jQuery.Event.prototype = { isImmediatePropagationStopped: returnFalse }; -// Create mouseenter/leave events using mouseover/out and event-time checks +// 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 related = event.relatedTarget, + inside = false, + eventType = event.type; + + event.type = event.data; + + if ( related !== this ) { + + if ( related ) { + inside = jQuery.contains( this, related ); + } + + if ( !inside ) { + + jQuery.event.handle.apply( this, arguments ); + + event.type = eventType; + } + } +}, + +// 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 ] = { - delegateType: fix, - bindType: fix, - - handle: function( event ) { - var target = this, - related = event.relatedTarget, - handleObj = event.handleObj, - selector = handleObj.selector, - ret; - - // For mousenter/leave call the handler if related is outside the target. - // NB: No relatedTarget if the mouse left/entered the browser window - if ( !related || (related !== target && !jQuery.contains( target, related )) ) { - event.type = handleObj.origType; - ret = handleObj.handler.apply( this, arguments ); - event.type = fix; - } - return ret; + 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 ); } }; }); -// IE submit delegation +// submit delegation if ( !jQuery.support.submitBubbles ) { jQuery.event.special.submit = { - setup: function() { - // Only need this for delegated form submit events - if ( jQuery.nodeName( this, "form" ) ) { - return false; - } + setup: function( data, namespaces ) { + if ( !jQuery.nodeName( this, "form" ) ) { + jQuery.event.add(this, "click.specialSubmit", function( e ) { + // Avoid triggering error on non-existent type attribute in IE VML (#7071) + var elem = e.target, + type = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.type : ""; + + if ( (type === "submit" || type === "image") && jQuery( elem ).closest("form").length ) { + trigger( "submit", this, arguments ); + } + }); - // Lazy-add a submit handler when a descendant form may potentially be submitted - jQuery.event.add( this, "click._submit keypress._submit", function( e ) { - // Node name check avoids a VML-related crash in IE (#9807) - var elem = e.target, - form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined; - if ( form && !form._submit_attached ) { - jQuery.event.add( form, "submit._submit", function( event ) { - // If form was submitted by the user, bubble the event up the tree - if ( this.parentNode && !event.isTrigger ) { - jQuery.event.simulate( "submit", this.parentNode, event, true ); - } - }); - form._submit_attached = true; - } - }); - // return undefined since we don't need an event listener - }, + jQuery.event.add(this, "keypress.specialSubmit", function( e ) { + var elem = e.target, + type = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.type : ""; + + if ( (type === "text" || type === "password") && jQuery( elem ).closest("form").length && e.keyCode === 13 ) { + trigger( "submit", this, arguments ); + } + }); - teardown: function() { - // Only need this for delegated form submit events - if ( jQuery.nodeName( this, "form" ) ) { + } else { return false; } + }, - // Remove delegated handlers; cleanData eventually reaps submit handlers attached above - jQuery.event.remove( this, "._submit" ); + teardown: function( namespaces ) { + jQuery.event.remove( this, ".specialSubmit" ); } }; + } -// IE change delegation and checkbox/radio fix +// change delegation, happens here so we have bind. if ( !jQuery.support.changeBubbles ) { + var changeFilters, + + getVal = function( elem ) { + var type = jQuery.nodeName( elem, "input" ) ? 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 ( jQuery.nodeName( elem, "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; + jQuery.event.trigger( e, arguments[1], elem ); + } + }; + jQuery.event.special.change = { + filters: { + focusout: testChange, - setup: function() { + beforedeactivate: testChange, - if ( rformElems.test( this.nodeName ) ) { - // IE doesn't fire change on a check/radio until blur; trigger it on click - // after a propertychange. Eat the blur-change in special.change.handle. - // This still fires onchange a second time for check/radio after blur. - if ( this.type === "checkbox" || this.type === "radio" ) { - jQuery.event.add( this, "propertychange._change", function( event ) { - if ( event.originalEvent.propertyName === "checked" ) { - this._just_changed = true; - } - }); - jQuery.event.add( this, "click._change", function( event ) { - if ( this._just_changed && !event.isTrigger ) { - this._just_changed = false; - jQuery.event.simulate( "change", this, event, true ); - } - }); + click: function( e ) { + var elem = e.target, type = jQuery.nodeName( elem, "input" ) ? elem.type : ""; + + if ( type === "radio" || type === "checkbox" || jQuery.nodeName( elem, "select" ) ) { + testChange.call( this, e ); } - return false; - } - // Delegated event; lazy-add a change handler on descendant inputs - jQuery.event.add( this, "beforeactivate._change", function( e ) { - var elem = e.target; + }, - if ( rformElems.test( elem.nodeName ) && !elem._change_attached ) { - jQuery.event.add( elem, "change._change", function( event ) { - if ( this.parentNode && !event.isSimulated && !event.isTrigger ) { - jQuery.event.simulate( "change", this.parentNode, event, true ); - } - }); - elem._change_attached = true; + // 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 = jQuery.nodeName( elem, "input" ) ? elem.type : ""; + + if ( (e.keyCode === 13 && !jQuery.nodeName( elem, "textarea" ) ) || + (e.keyCode === 32 && (type === "checkbox" || type === "radio")) || + type === "select-multiple" ) { + 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) ); + } }, - handle: function( event ) { - var elem = event.target; + setup: function( data, namespaces ) { + if ( this.type === "file" ) { + return false; + } - // Swallow native change events from checkbox/radio, we already triggered them above - if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) { - return event.handleObj.handler.apply( this, arguments ); + for ( var type in changeFilters ) { + jQuery.event.add( this, type + ".specialChange", changeFilters[type] ); } + + return rformElems.test( this.nodeName ); }, - teardown: function() { - jQuery.event.remove( this, "._change" ); + 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 ) { + // 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 @@ -3643,10 +3433,7 @@ 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, - handler = function( event ) { - jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true ); - }; + var attaches = 0; jQuery.event.special[ fix ] = { setup: function() { @@ -3660,120 +3447,89 @@ if ( !jQuery.support.focusinBubbles ) { } } }; + + 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; + e.originalEvent = {}; + jQuery.event.trigger( e, null, e.target ); + if ( e.isDefaultPrevented() ) { + donor.preventDefault(); + } + } }); } -jQuery.fn.extend({ - - on: function( types, selector, data, fn, /*INTERNAL*/ one ) { - var origFn, type; +jQuery.each(["bind", "one"], function( i, name ) { + jQuery.fn[ name ] = function( type, data, fn ) { + var handler; - // Types can be a map of types/handlers - if ( typeof types === "object" ) { - // ( types-Object, selector, data ) - if ( typeof selector !== "string" ) { - // ( types-Object, data ) - data = selector; - selector = undefined; - } - for ( type in types ) { - this.on( type, selector, data, types[ type ], one ); + // Handle object literals + if ( typeof type === "object" ) { + for ( var key in type ) { + this[ name ](key, data, type[key], fn); } return this; } - if ( data == null && fn == null ) { - // ( types, fn ) - fn = selector; - data = selector = undefined; - } else if ( fn == null ) { - if ( typeof selector === "string" ) { - // ( types, selector, fn ) - fn = data; - data = undefined; - } else { - // ( types, data, fn ) - fn = data; - data = selector; - selector = undefined; - } - } - if ( fn === false ) { - fn = returnFalse; - } else if ( !fn ) { - return this; + if ( arguments.length === 2 || data === false ) { + fn = data; + data = undefined; } - if ( one === 1 ) { - origFn = fn; - fn = function( event ) { - // Can use an empty set, since event contains the info - jQuery().off( event ); - return origFn.apply( this, arguments ); + if ( name === "one" ) { + handler = function( event ) { + jQuery( this ).unbind( event, handler ); + return fn.apply( this, arguments ); }; - // Use same guid so caller can remove using origFn - fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + handler.guid = fn.guid || jQuery.guid++; + } else { + handler = fn; } - return this.each( function() { - jQuery.event.add( this, types, fn, data, selector ); - }); - }, - one: function( types, selector, data, fn ) { - return this.on.call( this, types, selector, data, fn, 1 ); - }, - off: function( types, selector, fn ) { - if ( types && types.preventDefault && types.handleObj ) { - // ( event ) dispatched jQuery.Event - var handleObj = types.handleObj; - jQuery( types.delegateTarget ).off( - handleObj.namespace? handleObj.type + "." + handleObj.namespace : handleObj.type, - handleObj.selector, - handleObj.handler - ); - return this; + + 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 ); + } } - if ( typeof types === "object" ) { - // ( types-object [, selector] ) - for ( var type in types ) { - this.off( type, selector, types[ type ] ); + + 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]); } - return this; - } - if ( selector === false || typeof selector === "function" ) { - // ( types [, fn] ) - fn = selector; - selector = undefined; - } - if ( fn === false ) { - fn = returnFalse; - } - return this.each(function() { - jQuery.event.remove( this, types, fn, selector ); - }); - }, - bind: function( types, data, fn ) { - return this.on( types, null, data, fn ); - }, - unbind: function( types, fn ) { - return this.off( types, null, fn ); - }, + } else { + for ( var i = 0, l = this.length; i < l; i++ ) { + jQuery.event.remove( this[i], type, fn ); + } + } - live: function( types, data, fn ) { - jQuery( this.context ).on( types, this.selector, data, fn ); - return this; - }, - die: function( types, fn ) { - jQuery( this.context ).off( types, this.selector || "**", fn ); return this; }, delegate: function( selector, types, data, fn ) { - return this.on( types, selector, data, fn ); + return this.live( types, data, fn, selector ); }, + undelegate: function( selector, types, fn ) { - // ( namespace ) or ( selector, types [, fn] ) - return arguments.length == 1? this.off( selector, "**" ) : this.off( types, selector, fn ); + if ( arguments.length === 0 ) { + return this.unbind( "live" ); + + } else { + return this.die( types, null, fn, selector ); + } }, trigger: function( type, data ) { @@ -3781,6 +3537,7 @@ jQuery.fn.extend({ jQuery.event.trigger( type, data, this ); }); }, + triggerHandler: function( type, data ) { if ( this[0] ) { return jQuery.event.trigger( type, data, this[0], true ); @@ -3794,8 +3551,8 @@ jQuery.fn.extend({ 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 ); + 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(); @@ -3818,9 +3575,178 @@ jQuery.fn.extend({ } }); +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 ( name === "die" && !types && + origSelector && origSelector.charAt(0) === "." ) { + + context.unbind( origSelector ); + + return this; + } + + if ( data === false || jQuery.isFunction( data ) ) { + fn = data || returnFalse; + 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 ( liveMap[ type ] ) { + 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, "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]; + + 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(rspaces, "&"); +} + 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 contextmenu").split(" "), function( i, name ) { + "change select submit keydown keypress keyup error").split(" "), function( i, name ) { // Handle event binding jQuery.fn[ name ] = function( data, fn ) { @@ -3830,21 +3756,13 @@ jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblcl } return arguments.length > 0 ? - this.on( name, null, data, fn ) : + this.bind( name, data, fn ) : this.trigger( name ); }; if ( jQuery.attrFn ) { jQuery.attrFn[ name ] = true; } - - if ( rkeyEvent.test( name ) ) { - jQuery.event.fixHooks[ name ] = jQuery.event.keyHooks; - } - - if ( rmouseEvent.test( name ) ) { - jQuery.event.fixHooks[ name ] = jQuery.event.mouseHooks; - } }); @@ -3858,13 +3776,11 @@ jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblcl (function(){ var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, - expando = "sizcache" + (Math.random() + '').replace('.', ''), done = 0, toString = Object.prototype.toString, hasDuplicate = false, baseHasDuplicate = true, rBackslash = /\\/g, - rReturn = /\r\n/g, rNonWord = /\W/; // Here we check if the JavaScript engine is using some sort of @@ -3916,7 +3832,7 @@ var Sizzle = function( selector, context, results, seed ) { if ( parts.length > 1 && origPOS.exec( selector ) ) { if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { - set = posProcess( parts[0] + parts[1], context, seed ); + set = posProcess( parts[0] + parts[1], context ); } else { set = Expr.relative[ parts[0] ] ? @@ -3930,7 +3846,7 @@ var Sizzle = function( selector, context, results, seed ) { selector += parts.shift(); } - set = posProcess( selector, set, seed ); + set = posProcess( selector, set ); } } @@ -4049,17 +3965,18 @@ Sizzle.matchesSelector = function( node, expr ) { }; Sizzle.find = function( expr, context, isXML ) { - var set, i, len, match, type, left; + var set; if ( !expr ) { return []; } - for ( i = 0, len = Expr.order.length; i < len; i++ ) { - type = Expr.order[i]; + for ( var i = 0, l = Expr.order.length; i < l; i++ ) { + var match, + type = Expr.order[i]; if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { - left = match[1]; + var left = match[1]; match.splice( 1, 1 ); if ( left.substr( left.length - 1 ) !== "\\" ) { @@ -4085,18 +4002,17 @@ Sizzle.find = function( expr, context, isXML ) { Sizzle.filter = function( expr, set, inplace, not ) { var match, anyFound, - type, found, item, filter, left, - i, pass, old = expr, result = [], curLoop = set, isXMLFilter = set && set[0] && Sizzle.isXML( set[0] ); while ( expr && set.length ) { - for ( type in Expr.filter ) { + for ( var type in Expr.filter ) { if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) { - filter = Expr.filter[ type ]; - left = match[1]; + var found, item, + filter = Expr.filter[ type ], + left = match[1]; anyFound = false; @@ -4122,10 +4038,10 @@ Sizzle.filter = function( expr, set, inplace, not ) { } if ( match ) { - for ( i = 0; (item = curLoop[i]) != null; i++ ) { + for ( var i = 0; (item = curLoop[i]) != null; i++ ) { if ( item ) { found = filter( item, match, i, curLoop ); - pass = not ^ found; + var pass = not ^ !!found; if ( inplace && found != null ) { if ( pass ) { @@ -4176,46 +4092,7 @@ Sizzle.filter = function( expr, set, inplace, not ) { }; Sizzle.error = function( msg ) { - throw new Error( "Syntax error, unrecognized expression: " + msg ); -}; - -/** - * Utility function for retreiving the text value of an array of DOM nodes - * @param {Array|Element} elem - */ -var getText = Sizzle.getText = function( elem ) { - var i, node, - nodeType = elem.nodeType, - ret = ""; - - if ( nodeType ) { - if ( nodeType === 1 || nodeType === 9 ) { - // Use textContent || innerText for elements - if ( typeof elem.textContent === 'string' ) { - return elem.textContent; - } else if ( typeof elem.innerText === 'string' ) { - // Replace IE's carriage returns - return elem.innerText.replace( rReturn, '' ); - } else { - // Traverse it's children - for ( elem = elem.firstChild; elem; elem = elem.nextSibling) { - ret += getText( elem ); - } - } - } else if ( nodeType === 3 || nodeType === 4 ) { - return elem.nodeValue; - } - } else { - - // If no nodeType, this is expected to be an array - for ( i = 0; (node = elem[i]); i++ ) { - // Do not traverse comment nodes - if ( node.nodeType !== 8 ) { - ret += getText( node ); - } - } - } - return ret; + throw "Syntax error, unrecognized expression: " + msg; }; var Expr = Sizzle.selectors = { @@ -4605,7 +4482,7 @@ var Expr = Sizzle.selectors = { return filter( elem, i, match, array ); } else if ( name === "contains" ) { - return (elem.textContent || elem.innerText || getText([ elem ]) || "").indexOf(match[3]) >= 0; + return (elem.textContent || elem.innerText || Sizzle.getText([ elem ]) || "").indexOf(match[3]) >= 0; } else if ( name === "not" ) { var not = match[3]; @@ -4624,10 +4501,7 @@ var Expr = Sizzle.selectors = { }, CHILD: function( elem, match ) { - var first, last, - doneName, parent, cache, - count, diff, - type = match[1], + var type = match[1], node = elem; switch ( type ) { @@ -4655,18 +4529,18 @@ var Expr = Sizzle.selectors = { return true; case "nth": - first = match[2]; - last = match[3]; + var first = match[2], + last = match[3]; if ( first === 1 && last === 0 ) { return true; } - doneName = match[0]; - parent = elem.parentNode; + var doneName = match[0], + parent = elem.parentNode; - if ( parent && (parent[ expando ] !== doneName || !elem.nodeIndex) ) { - count = 0; + if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) { + var count = 0; for ( node = parent.firstChild; node; node = node.nextSibling ) { if ( node.nodeType === 1 ) { @@ -4674,10 +4548,10 @@ var Expr = Sizzle.selectors = { } } - parent[ expando ] = doneName; + parent.sizcache = doneName; } - diff = elem.nodeIndex - last; + var diff = elem.nodeIndex - last; if ( first === 0 ) { return diff === 0; @@ -4693,7 +4567,7 @@ var Expr = Sizzle.selectors = { }, TAG: function( elem, match ) { - return (match === "*" && elem.nodeType === 1) || !!elem.nodeName && elem.nodeName.toLowerCase() === match; + return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match; }, CLASS: function( elem, match ) { @@ -4703,9 +4577,7 @@ var Expr = Sizzle.selectors = { ATTR: function( elem, match ) { var name = match[1], - result = Sizzle.attr ? - Sizzle.attr( elem, name ) : - Expr.attrHandle[ name ] ? + result = Expr.attrHandle[ name ] ? Expr.attrHandle[ name ]( elem ) : elem[ name ] != null ? elem[ name ] : @@ -4716,8 +4588,6 @@ var Expr = Sizzle.selectors = { return result == null ? type === "!=" : - !type && Sizzle.attr ? - result != null : type === "=" ? value === check : type === "*=" ? @@ -4898,6 +4768,26 @@ if ( document.documentElement.compareDocumentPosition ) { }; } +// 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(){ @@ -5175,13 +5065,13 @@ function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { elem = elem[dir]; while ( elem ) { - if ( elem[ expando ] === doneName ) { + if ( elem.sizcache === doneName ) { match = checkSet[elem.sizset]; break; } if ( elem.nodeType === 1 && !isXML ){ - elem[ expando ] = doneName; + elem.sizcache = doneName; elem.sizset = i; } @@ -5208,14 +5098,14 @@ function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { elem = elem[dir]; while ( elem ) { - if ( elem[ expando ] === doneName ) { + if ( elem.sizcache === doneName ) { match = checkSet[elem.sizset]; break; } if ( elem.nodeType === 1 ) { if ( !isXML ) { - elem[ expando ] = doneName; + elem.sizcache = doneName; elem.sizset = i; } @@ -5263,7 +5153,7 @@ Sizzle.isXML = function( elem ) { return documentElement ? documentElement.nodeName !== "HTML" : false; }; -var posProcess = function( selector, context, seed ) { +var posProcess = function( selector, context ) { var match, tmpSet = [], later = "", @@ -5279,16 +5169,13 @@ var posProcess = function( selector, context, seed ) { selector = Expr.relative[selector] ? selector + "*" : selector; for ( var i = 0, l = root.length; i < l; i++ ) { - Sizzle( selector, root[i], tmpSet, seed ); + Sizzle( selector, root[i], tmpSet ); } return Sizzle.filter( later, tmpSet ); }; // EXPOSE -// Override sizzle attribute retrieval -Sizzle.attr = jQuery.attr; -Sizzle.selectors.attrMap = {}; jQuery.find = Sizzle; jQuery.expr = Sizzle.selectors; jQuery.expr[":"] = jQuery.expr.filters; @@ -5374,33 +5261,43 @@ jQuery.fn.extend({ }, is: function( selector ) { - return !!selector && ( - typeof selector === "string" ? - // If this is a positional selector, check membership in the returned set - // so $("p:first").is("p:last") won't return true for a doc with two "p". - POS.test( selector ) ? - jQuery( selector, this.context ).index( this[0] ) >= 0 : - jQuery.filter( selector, this ).length > 0 : - this.filter( selector ).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 (deprecated as of jQuery 1.7) + // Array if ( jQuery.isArray( selectors ) ) { - var level = 1; + 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 ] = POS.test( selector ) ? + jQuery( selector, context || this.context ) : + selector; + } + } - while ( cur && cur.ownerDocument && cur !== context ) { - for ( i = 0; i < selectors.length; i++ ) { + while ( cur && cur.ownerDocument && cur !== context ) { + for ( selector in matches ) { + match = matches[ selector ]; - if ( jQuery( cur ).is( selectors[ i ] ) ) { - ret.push({ selector: selectors[ i ], elem: cur, level: level }); + if ( match.jquery ? match.index( cur ) > -1 : jQuery( cur ).is( match ) ) { + ret.push({ selector: selector, elem: cur, level: level }); + } } - } - cur = cur.parentNode; - level++; + cur = cur.parentNode; + level++; + } } return ret; @@ -5517,7 +5414,12 @@ 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; @@ -5533,7 +5435,7 @@ jQuery.each({ ret = ret.reverse(); } - return this.pushStack( ret, name, slice.call( arguments ).join(",") ); + return this.pushStack( ret, name, args.join(",") ); }; }); @@ -5602,7 +5504,7 @@ function winnow( elements, qualifier, keep ) { } else if ( qualifier.nodeType ) { return jQuery.grep(elements, function( elem, i ) { - return ( elem === qualifier ) === keep; + return (elem === qualifier) === keep; }); } else if ( typeof qualifier === "string" ) { @@ -5618,38 +5520,20 @@ function winnow( elements, qualifier, keep ) { } return jQuery.grep(elements, function( elem, i ) { - return ( jQuery.inArray( elem, qualifier ) >= 0 ) === keep; + return (jQuery.inArray( elem, qualifier ) >= 0) === keep; }); } -function createSafeFragment( document ) { - var list = nodeNames.split( "|" ), - safeFrag = document.createDocumentFragment(); - - if ( safeFrag.createElement ) { - while ( list.length ) { - safeFrag.createElement( - list.pop() - ); - } - } - return safeFrag; -} - -var nodeNames = "abbr|article|aside|audio|canvas|datalist|details|figcaption|figure|footer|" + - "header|hgroup|mark|meter|nav|output|progress|section|summary|time|video", - rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g, +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 = /<(?:" + nodeNames + ")", "i"), // checked="checked" or checked rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i, rscriptType = /\/(java|ecma)script/i, @@ -5663,8 +5547,7 @@ var nodeNames = "abbr|article|aside|audio|canvas|datalist|details|figcaption|fig col: [ 2, "", "
" ], area: [ 1, "", "" ], _default: [ 0, "", "" ] - }, - safeFragment = createSafeFragment( document ); + }; wrapMap.optgroup = wrapMap.option; wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; @@ -5742,10 +5625,8 @@ jQuery.fn.extend({ }, wrap: function( html ) { - var isFunction = jQuery.isFunction( html ); - - return this.each(function(i) { - jQuery( this ).wrapAll( isFunction ? html.call(this, i) : html ); + return this.each(function() { + jQuery( this ).wrapAll( html ); }); }, @@ -5779,7 +5660,7 @@ jQuery.fn.extend({ this.parentNode.insertBefore( elem, this ); }); } else if ( arguments.length ) { - var set = jQuery.clean( arguments ); + var set = jQuery(arguments[0]); set.push.apply( set, this.toArray() ); return this.pushStack( set, "before", arguments ); } @@ -5792,7 +5673,7 @@ jQuery.fn.extend({ }); } else if ( arguments.length ) { var set = this.pushStack( this, "after", arguments ); - set.push.apply( set, jQuery.clean(arguments) ); + set.push.apply( set, jQuery(arguments[0]).toArray() ); return set; } }, @@ -5847,7 +5728,7 @@ jQuery.fn.extend({ null; // See if we can take a shortcut and just use innerHTML - } else if ( typeof value === "string" && !rnoInnerhtml.test( value ) && + } else if ( typeof value === "string" && !rnocache.test( value ) && (jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value )) && !wrapMap[ (rtagName.exec( value ) || ["", ""])[1].toLowerCase() ] ) { @@ -5973,7 +5854,7 @@ jQuery.fn.extend({ // 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 ) ? + results.cacheable || (l > 1 && i < lastIndex) ? jQuery.clone( fragment, true, true ) : fragment ); @@ -6002,26 +5883,27 @@ function cloneCopyEvent( src, dest ) { return; } - var type, i, l, - oldData = jQuery._data( src ), - curData = jQuery._data( dest, oldData ), - events = oldData.events; + var internalKey = jQuery.expando, + oldData = jQuery.data( src ), + curData = jQuery.data( dest, oldData ); - if ( events ) { - delete curData.handle; - curData.events = {}; + // 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); - for ( type in events ) { - for ( 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 ); + if ( events ) { + delete curData.handle; + curData.events = {}; + + for ( var type in events ) { + 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 ); + } } } } - - // make the cloned public data object a copy from the original - if ( curData.data ) { - curData.data = jQuery.extend( {}, curData.data ); - } } function cloneFixAttributes( src, dest ) { @@ -6083,17 +5965,16 @@ function cloneFixAttributes( src, dest ) { } jQuery.buildFragment = function( args, nodes, scripts ) { - var fragment, cacheable, cacheresults, doc, - first = args[ 0 ]; - - // nodes may contain either an explicit document object, - // a jQuery collection or context object. - // If nodes[0] contains a valid object to assign to doc - if ( nodes && nodes[0] ) { - doc = nodes[0].ownerDocument || nodes[0]; - } + var fragment, cacheable, cacheresults, doc; + + // nodes may contain either an explicit document object, + // a jQuery collection or context object. + // If nodes[0] contains a valid object to assign to doc + if ( nodes && nodes[0] ) { + doc = nodes[0].ownerDocument || nodes[0]; + } - // Ensure that an attr object doesn't incorrectly stand in as a document object + // Ensure that an attr object doesn't incorrectly stand in as a document object // Chrome and Firefox seem to allow this to occur and will throw exception // Fixes #8950 if ( !doc.createDocumentFragment ) { @@ -6104,15 +5985,12 @@ jQuery.buildFragment = function( args, nodes, scripts ) { // Cloning options loses the selected state, so don't cache them // IE 6 doesn't like it when you put or elements in a fragment // Also, WebKit does not clone 'checked' attributes on cloneNode, so don't cache - // Lastly, IE6,7,8 will not correctly reuse cached fragments that were created from unknown elems #10501 - if ( args.length === 1 && typeof first === "string" && first.length < 512 && doc === document && - first.charAt(0) === "<" && !rnocache.test( first ) && - (jQuery.support.checkClone || !rchecked.test( first )) && - (jQuery.support.html5Clone || !rnoshimcache.test( first )) ) { + if ( args.length === 1 && typeof args[0] === "string" && args[0].length < 512 && doc === document && + args[0].charAt(0) === "<" && !rnocache.test( args[0] ) && (jQuery.support.checkClone || !rchecked.test( args[0] )) ) { cacheable = true; - cacheresults = jQuery.fragments[ first ]; + cacheresults = jQuery.fragments[ args[0] ]; if ( cacheresults && cacheresults !== 1 ) { fragment = cacheresults; } @@ -6124,7 +6002,7 @@ jQuery.buildFragment = function( args, nodes, scripts ) { } if ( cacheable ) { - jQuery.fragments[ first ] = cacheresults ? fragment : 1; + jQuery.fragments[ args[0] ] = cacheresults ? fragment : 1; } return { fragment: fragment, cacheable: cacheable }; @@ -6150,7 +6028,7 @@ jQuery.each({ } else { for ( var i = 0, l = insert.length; i < l; i++ ) { - var elems = ( i > 0 ? this.clone(true) : this ).get(); + var elems = (i > 0 ? this.clone(true) : this).get(); jQuery( insert[i] )[ original ]( elems ); ret = ret.concat( elems ); } @@ -6161,10 +6039,10 @@ jQuery.each({ }); function getAll( elem ) { - if ( typeof elem.getElementsByTagName !== "undefined" ) { + if ( "getElementsByTagName" in elem ) { return elem.getElementsByTagName( "*" ); - } else if ( typeof elem.querySelectorAll !== "undefined" ) { + } else if ( "querySelectorAll" in elem ) { return elem.querySelectorAll( "*" ); } else { @@ -6180,33 +6058,19 @@ function fixDefaultChecked( elem ) { } // Finds all inputs and passes them to fixDefaultChecked function findInputs( elem ) { - var nodeName = ( elem.nodeName || "" ).toLowerCase(); - if ( nodeName === "input" ) { + if ( jQuery.nodeName( elem, "input" ) ) { fixDefaultChecked( elem ); - // Skip scripts, get other children - } else if ( nodeName !== "script" && typeof elem.getElementsByTagName !== "undefined" ) { + } else if ( "getElementsByTagName" in elem ) { jQuery.grep( elem.getElementsByTagName("input"), fixDefaultChecked ); } } -// Derived From: http://www.iecss.com/shimprove/javascript/shimprove.1-0-1.js -function shimCloneNode( elem ) { - var div = document.createElement( "div" ); - safeFragment.appendChild( div ); - - div.innerHTML = elem.outerHTML; - return div.firstChild; -} - jQuery.extend({ clone: function( elem, dataAndEvents, deepDataAndEvents ) { - var srcElements, - destElements, - i, - // IE<=8 does not properly clone detached, unknown element nodes - clone = jQuery.support.html5Clone || !rnoshimcache.test( "<" + elem.nodeName ) ? - elem.cloneNode( true ) : - shimCloneNode( elem ); + var clone = elem.cloneNode(true), + srcElements, + destElements, + i; if ( (!jQuery.support.noCloneEvent || !jQuery.support.noCloneChecked) && (elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) { @@ -6218,7 +6082,8 @@ jQuery.extend({ cloneFixAttributes( elem, clone ); - // Using Sizzle here is crazy slow, so we use getElementsByTagName instead + // Using Sizzle here is crazy slow, so we use getElementsByTagName + // instead srcElements = getAll( elem ); destElements = getAll( clone ); @@ -6283,20 +6148,11 @@ jQuery.extend({ elem = elem.replace(rxhtmlTag, "<$1>"); // Trim whitespace, otherwise indexOf won't work as expected - var tag = ( rtagName.exec( elem ) || ["", ""] )[1].toLowerCase(), + var tag = (rtagName.exec( elem ) || ["", ""])[1].toLowerCase(), wrap = wrapMap[ tag ] || wrapMap._default, depth = wrap[0], div = context.createElement("div"); - // Append wrapper element to unknown element safe doc fragment - if ( context === document ) { - // Use the fragment we've already created for this document - safeFragment.appendChild( div ); - } else { - // Use a fragment created with the owner document - createSafeFragment( context ).appendChild( div ); - } - // Go to html and back, then peel off extra wrappers div.innerHTML = wrap[1] + elem + wrap[2]; @@ -6377,9 +6233,7 @@ jQuery.extend({ }, 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++ ) { @@ -6390,7 +6244,7 @@ jQuery.extend({ 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 ) { @@ -6652,7 +6506,7 @@ if ( !jQuery.support.opacity ) { set: function( elem, value ) { var style = elem.style, currentStyle = elem.currentStyle, - opacity = jQuery.isNumeric( value ) ? "alpha(opacity=" + value * 100 + ")" : "", + opacity = jQuery.isNaN( value ) ? "" : "alpha(opacity=" + value * 100 + ")", filter = currentStyle && currentStyle.filter || style.filter || ""; // IE has trouble with opacity if it does not have layout @@ -6709,8 +6563,11 @@ if ( document.defaultView && document.defaultView.getComputedStyle ) { name = name.replace( rupper, "-$1" ).toLowerCase(); - if ( (defaultView = elem.ownerDocument.defaultView) && - (computedStyle = defaultView.getComputedStyle( elem, null )) ) { + 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 ); @@ -6723,32 +6580,25 @@ if ( document.defaultView && document.defaultView.getComputedStyle ) { if ( document.documentElement.currentStyle ) { currentStyle = function( elem, name ) { - var left, rsLeft, uncomputed, + var left, ret = elem.currentStyle && elem.currentStyle[ name ], + rsLeft = elem.runtimeStyle && elem.runtimeStyle[ name ], style = elem.style; - // Avoid setting ret to empty string here - // so we don't default to auto - if ( ret === null && style && (uncomputed = style[ name ]) ) { - ret = uncomputed; - } - // 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 && elem.runtimeStyle.left; // Put in the new values to get a computed value out if ( rsLeft ) { elem.runtimeStyle.left = elem.currentStyle.left; } - style.left = name === "fontSize" ? "1em" : ( ret || 0 ); + style.left = name === "fontSize" ? "1em" : (ret || 0); ret = style.pixelLeft + "px"; // Revert the changed values @@ -6768,22 +6618,20 @@ function getWH( elem, name, extra ) { // Start with offset property var val = name === "width" ? elem.offsetWidth : elem.offsetHeight, - which = name === "width" ? cssWidth : cssHeight, - i = 0, - len = which.length; + which = name === "width" ? cssWidth : cssHeight; if ( val > 0 ) { if ( extra !== "border" ) { - for ( ; i < len; i++ ) { + jQuery.each( which, function() { if ( !extra ) { - val -= parseFloat( jQuery.css( elem, "padding" + which[ i ] ) ) || 0; + val -= parseFloat( jQuery.css( elem, "padding" + this ) ) || 0; } if ( extra === "margin" ) { - val += parseFloat( jQuery.css( elem, extra + which[ i ] ) ) || 0; + val += parseFloat( jQuery.css( elem, extra + this ) ) || 0; } else { - val -= parseFloat( jQuery.css( elem, "border" + which[ i ] + "Width" ) ) || 0; + val -= parseFloat( jQuery.css( elem, "border" + this + "Width" ) ) || 0; } - } + }); } return val + "px"; @@ -6799,15 +6647,15 @@ function getWH( elem, name, extra ) { // Add padding, border, margin if ( extra ) { - for ( ; i < len; i++ ) { - val += parseFloat( jQuery.css( elem, "padding" + which[ i ] ) ) || 0; + jQuery.each( which, function() { + val += parseFloat( jQuery.css( elem, "padding" + this ) ) || 0; if ( extra !== "padding" ) { - val += parseFloat( jQuery.css( elem, "border" + which[ i ] + "Width" ) ) || 0; + val += parseFloat( jQuery.css( elem, "border" + this + "Width" ) ) || 0; } if ( extra === "margin" ) { - val += parseFloat( jQuery.css( elem, extra + which[ i ] ) ) || 0; + val += parseFloat( jQuery.css( elem, extra + this ) ) || 0; } - } + }); } return val + "px"; @@ -6818,7 +6666,7 @@ if ( jQuery.expr && jQuery.expr.filters ) { var width = elem.offsetWidth, height = elem.offsetHeight; - return ( width === 0 && height === 0 ) || (!jQuery.support.reliableHiddenOffsets && ((elem.style && elem.style.display) || jQuery.css( elem, "display" )) === "none"); + return (width === 0 && height === 0) || (!jQuery.support.reliableHiddenOffsets && (elem.style.display || jQuery.css( elem, "display" )) === "none"); }; jQuery.expr.filters.visible = function( elem ) { @@ -6872,7 +6720,7 @@ var r20 = /%20/g, // Document location segments ajaxLocParts, - + // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression allTypes = ["*/"] + ["*"]; @@ -6911,7 +6759,7 @@ function addToPrefiltersOrTransports( structure ) { placeBefore; // For each dataType in the dataTypeExpression - for ( ; i < length; i++ ) { + for(; i < length; i++ ) { dataType = dataTypes[ i ]; // We control if we're asked to add before // any existing element @@ -6942,7 +6790,7 @@ function inspectPrefiltersOrTransports( structure, options, originalOptions, jqX executeOnly = ( structure === prefilters ), selection; - for ( ; i < length && ( executeOnly || !selection ); i++ ) { + 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 @@ -6973,7 +6821,7 @@ function inspectPrefiltersOrTransports( structure, options, originalOptions, jqX function ajaxExtend( target, src ) { var key, deep, flatOptions = jQuery.ajaxSettings.flatOptions || {}; - for ( key in src ) { + for( key in src ) { if ( src[ key ] !== undefined ) { ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ]; } @@ -7090,7 +6938,7 @@ jQuery.fn.extend({ // 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.on( o, f ); + return this.bind( o, f ); }; }); @@ -7232,7 +7080,7 @@ jQuery.extend({ jQuery( callbackContext ) : jQuery.event, // Deferreds deferred = jQuery.Deferred(), - completeDeferred = jQuery.Callbacks( "once memory" ), + completeDeferred = jQuery._Deferred(), // Status-dependent callbacks statusCode = s.statusCode || {}, // ifModified key @@ -7382,7 +7230,7 @@ jQuery.extend({ // We extract error from statusText // then normalize statusText and status for non-aborts error = statusText; - if ( !statusText || status ) { + if( !statusText || status ) { statusText = "error"; if ( status < 0 ) { status = 0; @@ -7411,7 +7259,7 @@ jQuery.extend({ } // Complete - completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); + completeDeferred.resolveWith( callbackContext, [ jqXHR, statusText ] ); if ( fireGlobals ) { globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); @@ -7426,14 +7274,14 @@ jQuery.extend({ deferred.promise( jqXHR ); jqXHR.success = jqXHR.done; jqXHR.error = jqXHR.fail; - jqXHR.complete = completeDeferred.add; + jqXHR.complete = completeDeferred.done; // Status-dependent callbacks jqXHR.statusCode = function( map ) { if ( map ) { var tmp; if ( state < 2 ) { - for ( tmp in map ) { + for( tmp in map ) { statusCode[ tmp ] = [ statusCode[tmp], map[tmp] ]; } } else { @@ -7510,7 +7358,7 @@ jQuery.extend({ 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 : "" ); + s.url = ret + ( (ret === s.url ) ? ( rquery.test( s.url ) ? "&" : "?" ) + "_=" + ts : "" ); } } @@ -7584,7 +7432,7 @@ jQuery.extend({ done( -1, e ); // Simply rethrow otherwise } else { - throw e; + jQuery.error( e ); } } } @@ -7688,7 +7536,7 @@ function ajaxHandleResponses( s, jqXHR, responses ) { firstDataType; // Fill responseXXX fields - for ( type in responseFields ) { + for( type in responseFields ) { if ( type in responses ) { jqXHR[ responseFields[type] ] = responses[ type ]; } @@ -7767,13 +7615,13 @@ function ajaxConvert( s, response ) { conv2; // For each dataType in the chain - for ( i = 1; i < length; i++ ) { + for( i = 1; i < length; i++ ) { // Create converters map // with lowercased keys if ( i === 1 ) { - for ( key in s.converters ) { - if ( typeof key === "string" ) { + for( key in s.converters ) { + if( typeof key === "string" ) { converters[ key.toLowerCase() ] = s.converters[ key ]; } } @@ -7784,7 +7632,7 @@ function ajaxConvert( s, response ) { current = dataTypes[ i ]; // If current is auto dataType, update it to prev - if ( current === "*" ) { + if( current === "*" ) { current = prev; // If no auto and dataTypes are actually different } else if ( prev !== "*" && prev !== current ) { @@ -7796,7 +7644,7 @@ function ajaxConvert( s, response ) { // If there is no direct converter, search transitively if ( !conv ) { conv2 = undefined; - for ( conv1 in converters ) { + for( conv1 in converters ) { tmp = conv1.split( " " ); if ( tmp[ 0 ] === prev || tmp[ 0 ] === "*" ) { conv2 = converters[ tmp[1] + " " + current ]; @@ -8235,11 +8083,11 @@ jQuery.fn.extend({ var elem, display; if ( speed || speed === 0 ) { - return this.animate( genFx("show", 3), speed, easing, callback ); + return this.animate( genFx("show", 3), speed, easing, callback); } else { for ( var i = 0, j = this.length; i < j; i++ ) { - elem = this[ i ]; + elem = this[i]; if ( elem.style ) { display = elem.style.display; @@ -8253,8 +8101,8 @@ jQuery.fn.extend({ // 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) ); + if ( display === "" && jQuery.css( elem, "display" ) === "none" ) { + jQuery._data(elem, "olddisplay", defaultDisplay(elem.nodeName)); } } } @@ -8262,13 +8110,13 @@ jQuery.fn.extend({ // 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 ]; + elem = this[i]; if ( elem.style ) { display = elem.style.display; if ( display === "" || display === "none" ) { - elem.style.display = jQuery._data( elem, "olddisplay" ) || ""; + elem.style.display = jQuery._data(elem, "olddisplay") || ""; } } } @@ -8282,17 +8130,12 @@ jQuery.fn.extend({ return this.animate( genFx("hide", 3), speed, easing, callback); } else { - var elem, display, - i = 0, - j = this.length; - - for ( ; i < j; i++ ) { - elem = this[i]; - if ( elem.style ) { - display = jQuery.css( elem, "display" ); + for ( var i = 0, j = this.length; i < j; i++ ) { + if ( this[i].style ) { + var display = jQuery.css( this[i], "display" ); - if ( display !== "none" && !jQuery._data( elem, "olddisplay" ) ) { - jQuery._data( elem, "olddisplay", display ); + if ( display !== "none" && !jQuery._data( this[i], "olddisplay" ) ) { + jQuery._data( this[i], "olddisplay", display ); } } } @@ -8337,7 +8180,7 @@ jQuery.fn.extend({ }, animate: function( prop, speed, easing, callback ) { - var optall = jQuery.speed( speed, easing, callback ); + var optall = jQuery.speed(speed, easing, callback); if ( jQuery.isEmptyObject( prop ) ) { return this.each( optall.complete, [ false ] ); @@ -8346,7 +8189,7 @@ jQuery.fn.extend({ // Do not change referenced properties as per-property easing will be lost prop = jQuery.extend( {}, prop ); - function doAnimation() { + return this[ optall.queue === false ? "each" : "queue" ](function() { // XXX 'this' does not always have a nodeName when running the // test suite @@ -8357,9 +8200,9 @@ jQuery.fn.extend({ var opt = jQuery.extend( {}, optall ), isElement = this.nodeType === 1, hidden = isElement && jQuery(this).is(":hidden"), - name, val, p, e, - parts, start, end, unit, - method; + 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 = {}; @@ -8395,17 +8238,25 @@ jQuery.fn.extend({ 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 + // animations on inline elements that are having width/height + // animated if ( jQuery.css( this, "display" ) === "inline" && jQuery.css( this, "float" ) === "none" ) { - - // inline-level elements accept inline-block; - // block-level elements need to be inline with layout - if ( !jQuery.support.inlineBlockNeedsLayout || defaultDisplay( this.nodeName ) === "inline" ) { + if ( !jQuery.support.inlineBlockNeedsLayout ) { this.style.display = "inline-block"; } else { - this.style.zoom = 1; + 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; + } } } } @@ -8419,17 +8270,8 @@ jQuery.fn.extend({ e = new jQuery.fx( this, opt, p ); val = prop[ p ]; - if ( rfxtypes.test( val ) ) { - - // Tracks whether to show or hide based on private - // data attached to the element - method = jQuery._data( this, "toggle" + p ) || ( val === "toggle" ? hidden ? "show" : "hide" : 0 ); - if ( method ) { - jQuery._data( this, "toggle" + p, method === "show" ? "hide" : "show" ); - e[ method ](); - } else { - e[ val ](); - } + if ( rfxtypes.test(val) ) { + e[ val === "toggle" ? hidden ? "show" : "hide" : val ](); } else { parts = rfxnum.exec( val ); @@ -8442,7 +8284,7 @@ jQuery.fn.extend({ // We need to compute starting value if ( unit !== "px" ) { jQuery.style( this, p, (end || 1) + unit); - start = ( (end || 1) / e.cur() ) * start; + start = ((end || 1) / e.cur()) * start; jQuery.style( this, p, start + unit); } @@ -8461,71 +8303,39 @@ jQuery.fn.extend({ // For JS strict compliance return true; - } - - return optall.queue === false ? - this.each( doAnimation ) : - this.queue( optall.queue, doAnimation ); + }); }, - stop: function( type, clearQueue, gotoEnd ) { - if ( typeof type !== "string" ) { - gotoEnd = clearQueue; - clearQueue = type; - type = undefined; + stop: function( clearQueue, gotoEnd ) { + if ( clearQueue ) { + this.queue([]); } - if ( clearQueue && type !== false ) { - this.queue( type || "fx", [] ); - } - - return this.each(function() { - var index, - hadTimers = false, - timers = jQuery.timers, - data = jQuery._data( this ); + this.each(function() { + var timers = jQuery.timers, + i = timers.length; // clear marker counters if we know they won't be if ( !gotoEnd ) { jQuery._unmark( true, this ); } - - function stopQueue( elem, data, index ) { - var hooks = data[ index ]; - jQuery.removeData( elem, index, true ); - hooks.stop( gotoEnd ); - } - - if ( type == null ) { - for ( index in data ) { - if ( data[ index ] && data[ index ].stop && index.indexOf(".run") === index.length - 4 ) { - stopQueue( this, data, index ); - } - } - } else if ( data[ index = type + ".run" ] && data[ index ].stop ){ - stopQueue( this, data, index ); - } - - for ( index = timers.length; index--; ) { - if ( timers[ index ].elem === this && (type == null || timers[ index ].queue === type) ) { - if ( gotoEnd ) { - + while ( i-- ) { + if ( timers[i].elem === this ) { + if (gotoEnd) { // force the next step to be the last - timers[ index ]( true ); - } else { - timers[ index ].saveState(); + timers[i](true); } - hadTimers = true; - timers.splice( index, 1 ); - } - } - // start the next in the queue if the last step wasn't forced - // timers currently will call their complete callbacks, which will dequeue - // but only if they were gotoEnd - if ( !( gotoEnd && hadTimers ) ) { - jQuery.dequeue( this, type ); + timers.splice(i, 1); + } } }); + + // start the next in the queue if the last step wasn't forced + if ( !gotoEnd ) { + this.dequeue(); + } + + return this; } }); @@ -8544,7 +8354,7 @@ function clearFxNow() { function genFx( type, num ) { var obj = {}; - jQuery.each( fxAttrs.concat.apply([], fxAttrs.slice( 0, num )), function() { + jQuery.each( fxAttrs.concat.apply([], fxAttrs.slice(0,num)), function() { obj[ this ] = type; }); @@ -8553,9 +8363,9 @@ function genFx( type, num ) { // Generate shortcuts for custom animations jQuery.each({ - slideDown: genFx( "show", 1 ), - slideUp: genFx( "hide", 1 ), - slideToggle: genFx( "toggle", 1 ), + slideDown: genFx("show", 1), + slideUp: genFx("hide", 1), + slideToggle: genFx("toggle", 1), fadeIn: { opacity: "show" }, fadeOut: { opacity: "hide" }, fadeToggle: { opacity: "toggle" } @@ -8567,31 +8377,25 @@ jQuery.each({ jQuery.extend({ speed: function( speed, easing, fn ) { - var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { + 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 + 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; - - // normalize opt.queue - true/undefined/null -> "fx" - if ( opt.queue == null || opt.queue === true ) { - opt.queue = "fx"; - } + opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[opt.duration] : jQuery.fx.speeds._default; // Queueing opt.old = opt.complete; - opt.complete = function( noUnmark ) { if ( jQuery.isFunction( opt.old ) ) { opt.old.call( this ); } - if ( opt.queue ) { - jQuery.dequeue( this, opt.queue ); + if ( opt.queue !== false ) { + jQuery.dequeue( this ); } else if ( noUnmark !== false ) { jQuery._unmark( this ); } @@ -8605,7 +8409,7 @@ jQuery.extend({ return firstNum + diff * p; }, swing: function( p, n, firstNum, diff ) { - return ( ( -Math.cos( p*Math.PI ) / 2 ) + 0.5 ) * diff + firstNum; + return ((-Math.cos(p*Math.PI)/2) + 0.5) * diff + firstNum; } }, @@ -8628,12 +8432,12 @@ jQuery.fx.prototype = { this.options.step.call( this.elem, this.now, this ); } - ( jQuery.fx.step[ this.prop ] || jQuery.fx.step._default )( 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) ) { + if ( this.elem[this.prop] != null && (!this.elem.style || this.elem.style[this.prop] == null) ) { return this.elem[ this.prop ]; } @@ -8651,22 +8455,17 @@ jQuery.fx.prototype = { fx = jQuery.fx; this.startTime = fxNow || createFxNow(); + this.start = from; this.end = to; - this.now = this.start = from; - this.pos = this.state = 0; this.unit = unit || this.unit || ( jQuery.cssNumber[ this.prop ] ? "" : "px" ); + this.now = this.start; + this.pos = this.state = 0; function t( gotoEnd ) { - return self.step( gotoEnd ); + return self.step(gotoEnd); } - t.queue = this.options.queue; t.elem = this.elem; - t.saveState = function() { - if ( self.options.hide && jQuery._data( self.elem, "fxshow" + self.prop ) === undefined ) { - jQuery._data( self.elem, "fxshow" + self.prop, self.start ); - } - }; if ( t() && jQuery.timers.push(t) && !timerId ) { timerId = setInterval( fx.tick, fx.interval ); @@ -8675,20 +8474,14 @@ jQuery.fx.prototype = { // Simple 'show' function show: function() { - var dataShow = jQuery._data( this.elem, "fxshow" + this.prop ); - // Remember where we started, so that we can go back to it later - this.options.orig[ this.prop ] = dataShow || jQuery.style( this.elem, this.prop ); + 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 - if ( dataShow !== undefined ) { - // This show is picking up where a previous hide or show left off - this.custom( this.cur(), dataShow ); - } else { - this.custom( this.prop === "width" || this.prop === "height" ? 1 : 0, this.cur() ); - } + // 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(); @@ -8697,20 +8490,20 @@ jQuery.fx.prototype = { // Simple 'hide' function hide: function() { // Remember where we started, so that we can go back to it later - this.options.orig[ this.prop ] = jQuery._data( this.elem, "fxshow" + this.prop ) || jQuery.style( this.elem, this.prop ); + this.options.orig[this.prop] = jQuery.style( this.elem, this.prop ); this.options.hide = true; // Begin the animation - this.custom( this.cur(), 0 ); + this.custom(this.cur(), 0); }, // Each step of an animation step: function( gotoEnd ) { - var p, n, complete, - t = fxNow || createFxNow(), + var t = fxNow || createFxNow(), done = true, elem = this.elem, - options = this.options; + options = this.options, + i, n; if ( gotoEnd || t >= options.duration + this.startTime ) { this.now = this.end; @@ -8719,8 +8512,8 @@ jQuery.fx.prototype = { options.animatedProperties[ this.prop ] = true; - for ( p in options.animatedProperties ) { - if ( options.animatedProperties[ p ] !== true ) { + for ( i in options.animatedProperties ) { + if ( options.animatedProperties[i] !== true ) { done = false; } } @@ -8729,36 +8522,25 @@ jQuery.fx.prototype = { // Reset the overflow if ( options.overflow != null && !jQuery.support.shrinkWrapBlocks ) { - jQuery.each( [ "", "X", "Y" ], function( index, value ) { - elem.style[ "overflow" + value ] = options.overflow[ index ]; + jQuery.each( [ "", "X", "Y" ], function (index, value) { + elem.style[ "overflow" + value ] = options.overflow[index]; }); } // Hide the element if the "hide" operation was done if ( options.hide ) { - jQuery( elem ).hide(); + jQuery(elem).hide(); } // Reset the properties, if the item has been hidden or shown if ( options.hide || options.show ) { - for ( p in options.animatedProperties ) { - jQuery.style( elem, p, options.orig[ p ] ); - jQuery.removeData( elem, "fxshow" + p, true ); - // Toggle data is no longer needed - jQuery.removeData( elem, "toggle" + p, true ); + for ( var p in options.animatedProperties ) { + jQuery.style( elem, p, options.orig[p] ); } } // Execute the complete function - // in the event that the complete function throws an exception - // we must ensure it won't be called twice. #5684 - - complete = options.complete; - if ( complete ) { - - options.complete = false; - complete.call( elem ); - } + options.complete.call( elem ); } return false; @@ -8772,8 +8554,8 @@ jQuery.fx.prototype = { 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 ); + 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(); @@ -8785,15 +8567,9 @@ jQuery.fx.prototype = { jQuery.extend( jQuery.fx, { tick: function() { - var timer, - timers = jQuery.timers, - i = 0; - - for ( ; i < timers.length; i++ ) { - timer = timers[ i ]; - // Checks the timer has not already been removed - if ( !timer() && timers[ i ] === timer ) { - timers.splice( i--, 1 ); + for ( var timers = jQuery.timers, i = 0 ; i < timers.length ; ++i ) { + if ( !timers[i]() ) { + timers.splice(i--, 1); } } @@ -8823,7 +8599,7 @@ jQuery.extend( jQuery.fx, { _default: function( fx ) { if ( fx.elem.style && fx.elem.style[ fx.prop ] != null ) { - fx.elem.style[ fx.prop ] = fx.now + fx.unit; + 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; } @@ -8831,14 +8607,6 @@ jQuery.extend( jQuery.fx, { } }); -// Adds width/height step functions -// Do not set anything below 0 -jQuery.each([ "width", "height" ], function( i, prop ) { - jQuery.fx.step[ prop ] = function( fx ) { - jQuery.style( fx.elem, prop, Math.max(0, fx.now) + fx.unit ); - }; -}); - if ( jQuery.expr && jQuery.expr.filters ) { jQuery.expr.filters.animated = function( elem ) { return jQuery.grep(jQuery.timers, function( fn ) { @@ -8855,6 +8623,7 @@ function defaultDisplay( nodeName ) { var body = document.body, elem = jQuery( "<" + nodeName + ">" ).appendTo( body ), display = elem.css( "display" ); + elem.remove(); // If the simple way fails, @@ -8882,6 +8651,7 @@ function defaultDisplay( nodeName ) { iframeDoc.body.appendChild( elem ); display = jQuery.css( elem, "display" ); + body.removeChild( iframe ); } @@ -8958,6 +8728,8 @@ if ( "getBoundingClientRect" in document.documentElement ) { return jQuery.offset.bodyOffset( elem ); } + jQuery.offset.initialize(); + var computedStyle, offsetParent = elem.offsetParent, prevOffsetParent = elem, @@ -8970,7 +8742,7 @@ if ( "getBoundingClientRect" in document.documentElement ) { left = elem.offsetLeft; while ( (elem = elem.parentNode) && elem !== body && elem !== docElem ) { - if ( jQuery.support.fixedPosition && prevComputedStyle.position === "fixed" ) { + if ( jQuery.offset.supportsFixedPosition && prevComputedStyle.position === "fixed" ) { break; } @@ -8982,7 +8754,7 @@ if ( "getBoundingClientRect" in document.documentElement ) { top += elem.offsetTop; left += elem.offsetLeft; - if ( jQuery.support.doesNotAddBorder && !(jQuery.support.doesAddBorderForTableAndCells && rtable.test(elem.nodeName)) ) { + if ( jQuery.offset.doesNotAddBorder && !(jQuery.offset.doesAddBorderForTableAndCells && rtable.test(elem.nodeName)) ) { top += parseFloat( computedStyle.borderTopWidth ) || 0; left += parseFloat( computedStyle.borderLeftWidth ) || 0; } @@ -8991,7 +8763,7 @@ if ( "getBoundingClientRect" in document.documentElement ) { offsetParent = elem.offsetParent; } - if ( jQuery.support.subtractsBorderForOverflowNotVisible && computedStyle.overflow !== "visible" ) { + if ( jQuery.offset.subtractsBorderForOverflowNotVisible && computedStyle.overflow !== "visible" ) { top += parseFloat( computedStyle.borderTopWidth ) || 0; left += parseFloat( computedStyle.borderLeftWidth ) || 0; } @@ -9004,7 +8776,7 @@ if ( "getBoundingClientRect" in document.documentElement ) { left += body.offsetLeft; } - if ( jQuery.support.fixedPosition && prevComputedStyle.position === "fixed" ) { + if ( jQuery.offset.supportsFixedPosition && prevComputedStyle.position === "fixed" ) { top += Math.max( docElem.scrollTop, body.scrollTop ); left += Math.max( docElem.scrollLeft, body.scrollLeft ); } @@ -9014,12 +8786,46 @@ if ( "getBoundingClientRect" in document.documentElement ) { } jQuery.offset = { + initialize: function() { + var body = document.body, container = document.createElement("div"), innerDiv, checkDiv, table, td, bodyMarginTop = parseFloat( jQuery.css(body, "marginTop") ) || 0, + html = "
"; + + 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 ); + jQuery.offset.initialize = jQuery.noop; + }, bodyOffset: function( body ) { var top = body.offsetTop, left = body.offsetLeft; - if ( jQuery.support.doesNotIncludeMarginInBodyOffset ) { + jQuery.offset.initialize(); + + if ( jQuery.offset.doesNotIncludeMarginInBodyOffset ) { top += parseFloat( jQuery.css(body, "marginTop") ) || 0; left += parseFloat( jQuery.css(body, "marginLeft") ) || 0; } @@ -9039,7 +8845,7 @@ jQuery.offset = { curOffset = curElem.offset(), curCSSTop = jQuery.css( elem, "top" ), curCSSLeft = jQuery.css( elem, "left" ), - calculatePosition = ( position === "absolute" || position === "fixed" ) && 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 either absolute or fixed @@ -9056,11 +8862,11 @@ jQuery.offset = { options = options.call( elem, i, curOffset ); } - if ( options.top != null ) { - props.top = ( options.top - curOffset.top ) + curTop; + if (options.top != null) { + props.top = (options.top - curOffset.top) + curTop; } - if ( options.left != null ) { - props.left = ( options.left - curOffset.left ) + curLeft; + if (options.left != null) { + props.left = (options.left - curOffset.left) + curLeft; } if ( "using" in options ) { @@ -9073,7 +8879,6 @@ jQuery.offset = { jQuery.fn.extend({ - position: function() { if ( !this[0] ) { return null; @@ -9176,20 +8981,16 @@ jQuery.each([ "Height", "Width" ], function( i, name ) { // innerHeight and innerWidth jQuery.fn[ "inner" + name ] = function() { var elem = this[0]; - return elem ? - elem.style ? + return elem && elem.style ? parseFloat( jQuery.css( elem, type, "padding" ) ) : - this[ type ]() : null; }; // outerHeight and outerWidth jQuery.fn[ "outer" + name ] = function( margin ) { var elem = this[0]; - return elem ? - elem.style ? + return elem && elem.style ? parseFloat( jQuery.css( elem, type, margin ? "margin" : "border" ) ) : - this[ type ]() : null; }; @@ -9229,7 +9030,7 @@ jQuery.each([ "Height", "Width" ], function( i, name ) { var orig = jQuery.css( elem, type ), ret = parseFloat( orig ); - return jQuery.isNumeric( ret ) ? ret : orig; + return jQuery.isNaN( ret ) ? orig : ret; // Set the width or height on the element (default to pixels if value is unitless) } else { @@ -9240,27 +9041,6 @@ jQuery.each([ "Height", "Width" ], function( i, name ) { }); - - // Expose jQuery to the global object window.jQuery = window.$ = jQuery; - -// Expose jQuery as an AMD module, but only for AMD loaders that -// understand the issues with loading multiple versions of jQuery -// in a page that all might call define(). The loader will indicate -// they have special allowances for multiple jQuery versions by -// specifying define.amd.jQuery = true. Register as a named module, -// since jQuery can be concatenated with other files that may use define, -// but not use a proper concatenation script that understands anonymous -// AMD modules. A named AMD is safest and most robust way to register. -// Lowercase jquery is used because AMD module names are derived from -// file names, and jQuery is normally delivered in a lowercase file name. -// Do this after creating the global so that if an AMD module wants to call -// noConflict to hide this version of jQuery, it will work. -if ( typeof define === "function" && define.amd && define.amd.jQuery ) { - define( "jquery", [], function () { return jQuery; } ); -} - - - -})( window ); \ No newline at end of file +})(window); \ No newline at end of file From 9c37a72076cb047a46ebb91e46ce010175d66d13 Mon Sep 17 00:00:00 2001 From: Justin Meyer Date: Mon, 12 Dec 2011 21:02:13 -0600 Subject: [PATCH 042/103] class init gets args --- class/class.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/class/class.js b/class/class.js index cd7d6360..22a0e89a 100644 --- a/class/class.js +++ b/class/class.js @@ -668,7 +668,7 @@ steal("jquery","jquery/lang/string",function( $ ) { // call the class init if ( Class.init ) { - Class.init.apply(Class, args || []); + Class.init.apply(Class, args || concatArgs([_super_class],arguments)); } /* @Prototype*/ From 7fe7737ec374acb40a21a36d5be46bbb0a95de97 Mon Sep 17 00:00:00 2001 From: Andy Kant Date: Tue, 13 Dec 2011 18:04:57 -0600 Subject: [PATCH 043/103] Fixed a bug and added a test for route: When there isn't a matching route, #!foo=bar would fail to parse properly while #!&foo=bar succeeds. --- dom/route/route.js | 3 +++ dom/route/route_test.js | 13 +++++++++++++ 2 files changed, 16 insertions(+) diff --git a/dom/route/route.js b/dom/route/route.js index aaf95bfe..a9babfc5 100644 --- a/dom/route/route.js +++ b/dom/route/route.js @@ -322,6 +322,9 @@ function( $ ) { return obj; } // If no route was matched it is parsed as a &key=value list. + if ( url.charAt(0) !== '&' ) { + url = '&' + url; + } return paramsMatcher.test(url) ? $.String.deparam( url.slice(1) ) : {}; }, /** diff --git a/dom/route/route_test.js b/dom/route/route_test.js index 57dff03a..027cbd33 100644 --- a/dom/route/route_test.js +++ b/dom/route/route_test.js @@ -61,6 +61,19 @@ test("deparam of invalid url", function(){ }); }) +test("deparam of url with non-generated hash (manual override)", function(){ + $.route.routes = {}; + + // This won't be set like this by route, but it could easily happen via a + // user manually changing the URL or when porting a prior URL structure. + obj = $.route.deparam("page=foo&bar=baz&where=there"); + same(obj, { + page: 'foo', + bar: 'baz', + where: 'there' + }); +}) + test("param", function(){ $.route.routes = {}; $.route("pages/:page",{ From 692233ef766ed54e321e2b99fcd8d0b345e1762b Mon Sep 17 00:00:00 2001 From: David Regla Date: Sun, 18 Dec 2011 14:38:38 -0600 Subject: [PATCH 044/103] fixed variable name --- model/list/local/local.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/model/list/local/local.js b/model/list/local/local.js index 568a63cb..eb9bfb0f 100644 --- a/model/list/local/local.js +++ b/model/list/local/local.js @@ -26,7 +26,7 @@ $.Model.List("jQuery.Model.List.Local", // go through and listen to instance updating var ids = [], days = this.days; this.each(function(i, inst){ - window.localStorage[inst.identity()] = instance.attrs(); + window.localStorage[inst.identity()] = inst.attrs(); ids.push(inst.identity()); }); window.localStorage[name] = { From 53581c482b9567a558bddfdbb3deb4b859159e7f Mon Sep 17 00:00:00 2001 From: Justin Meyer Date: Mon, 19 Dec 2011 01:12:00 -0600 Subject: [PATCH 045/103] adding route changes from micro branch --- controller/route/qunit.html | 18 +++++ controller/route/route.html | 34 ++++++++++ controller/route/route.js | 13 +++- controller/route/route_test.js | 10 +++ dom/route/route.js | 118 +++++++++++++-------------------- 5 files changed, 120 insertions(+), 73 deletions(-) create mode 100644 controller/route/qunit.html create mode 100644 controller/route/route.html create mode 100644 controller/route/route_test.js diff --git a/controller/route/qunit.html b/controller/route/qunit.html new file mode 100644 index 00000000..25edc491 --- /dev/null +++ b/controller/route/qunit.html @@ -0,0 +1,18 @@ + + + + + route QUnit Test + + + + +

route Test Suite

+

+
+

+
+
    +
    + + \ No newline at end of file diff --git a/controller/route/route.html b/controller/route/route.html new file mode 100644 index 00000000..f6590eb6 --- /dev/null +++ b/controller/route/route.html @@ -0,0 +1,34 @@ + + + + route + + + +

    route Demo

    + foo/bar + empty + + + + \ No newline at end of file diff --git a/controller/route/route.js b/controller/route/route.js index 0e0228c4..49274218 100644 --- a/controller/route/route.js +++ b/controller/route/route.js @@ -8,7 +8,16 @@ steal('jquery/dom/route','jquery/controller', function(){ * @param {Object} selector * @param {Object} cb */ - jQuery.Controller.processors.route = function(el, event, selector, cb){ - + jQuery.Controller.processors.route = function(el, event, selector, funcName, controller){ + $.route(selector||"") + var check = function(){ + if($.route.attr('route') === (selector||"")){ + controller[funcName]($.route.attr()) + } + } + $.route.bind('route',check); + return function(){ + $.route.unbind('route',check) + } } }) diff --git a/controller/route/route_test.js b/controller/route/route_test.js new file mode 100644 index 00000000..6a140d6b --- /dev/null +++ b/controller/route/route_test.js @@ -0,0 +1,10 @@ +steal('funcunit/qunit','./route',function(){ + +module("route"); + +test("route testing works", function(){ + ok(true,"an assert is run"); +}); + + +}); \ No newline at end of file diff --git a/dom/route/route.js b/dom/route/route.js index aaf95bfe..f3404c14 100644 --- a/dom/route/route.js +++ b/dom/route/route.js @@ -1,4 +1,4 @@ -steal('jquery/lang/observe', 'jquery/event/hashchange', 'jquery/lang/string/deparam', 'jquery/lang/observe/delegate', +steal('jquery/lang/observe', 'jquery/event/hashchange', 'jquery/lang/string/deparam', function( $ ) { // Helper methods used for matching routes. @@ -13,13 +13,12 @@ function( $ ) { makeProps = function( props ) { var html = [], name, val; - for ( name in props ) { - val = props[name] + each(props, function(name, val){ if ( name === 'className' ) { name = 'class' } val && html.push(escapeHTML(name), "=\"", escapeHTML(val), "\" "); - } + }) return html.join("") }, // Escapes ' and " for safe insertion into html tag parameters. @@ -44,7 +43,9 @@ function( $ ) { onready = true, location = window.location, encode = encodeURIComponent, - decode = decodeURIComponent; + decode = decodeURIComponent, + each = $.each, + extend = $.extend; /** * @class jQuery.route @@ -141,7 +142,7 @@ function( $ ) { * Or change multiple properties at once with * [jQuery.Observe.prototype.attrs attrs]: * - * $.route.attrs({type: 'pages', id: 5}, true) + * $.route.attr({type: 'pages', id: 5}, true) * * When you make changes to $.route, they will automatically * change the hash. @@ -206,7 +207,7 @@ function( $ ) { * @param {Object} [defaults] an object of default values * @return {jQuery.route} */ - var $route = $.route = function( url, defaults ) { + $.route = function( url, defaults ) { // Extract the variable names and replace with regEx that will match an atual URL with values. var names = [], test = url.replace(matcher, function( whole, name ) { @@ -215,7 +216,7 @@ function( $ ) { }); // Add route in a form that can be easily figured out - $route.routes[url] = { + $.route.routes[url] = { // A regular expression that will match the route when variable values // are present; i.e. for :page/:type the regEx is /([\w\.]*)/([\w\.]*)/ which // will match for any value of :page and :type (word chars or period). @@ -229,10 +230,10 @@ function( $ ) { // The number of parts in the URL separated by '/'. length: url.split('/').length } - return $route; + return $.route; }; - $.extend($route, { + extend($.route, { /** * Parameterizes the raw JS object representation provided in data. * If a route matching the provided data is found that URL is built @@ -245,20 +246,20 @@ function( $ ) { param: function( data ) { // Check if the provided data keys match the names in any routes; // get the one with the most matches. + delete data.route; var route, matches = -1, - temp, matchCount; - for ( var name in $route.routes ) { - temp = $route.routes[name], - matchCount = matchesData(temp, data); + each($.route.routes, function(name, temp){ + matchCount = matchesData(temp, data); if ( matchCount > matches ) { route = temp; matches = matchCount } - } + }); + if ( route ) { - var cpy = $.extend({}, data), + var cpy = extend({}, data), // Create the url by replacing the var names with the provided data. // If the default value is found an empty string is inserted. res = route.route.replace(matcher, function( whole, name ) { @@ -266,13 +267,12 @@ function( $ ) { return data[name] === route.defaults[name] ? "" : encode( data[name] ); }), after; - // remove matching default values - for(name in route.defaults) { - if(cpy[name] === route.defaults[name]) { + each(route.defaults, function(name,val){ + if(cpy[name] === val) { delete cpy[name] } - } + }) // The remaining elements of data are added as // $amp; separated parameters to the url. @@ -293,12 +293,11 @@ function( $ ) { var route = { length: -1 }; - for ( var name in $route.routes ) { - var temp = $route.routes[name] + each($.route.routes, function(name, temp){ if ( temp.test.test(url) && temp.length > route.length ) { route = temp; } - } + }); // If a route was matched if ( route.length > -1 ) { var // Since RegEx backreferences are used in route.test (round brackets) @@ -312,13 +311,14 @@ function( $ ) { obj = (remainder && paramsMatcher.test(remainder)) ? $.String.deparam( remainder.slice(1) ) : {}; // Add the default values for this route - obj = $.extend(true, {}, route.defaults, obj); + obj = extend(true, {}, route.defaults, obj); // Overwrite each of the default values in obj with those in parts if that part is not empty. - for ( var p = 0; p < parts.length; p++ ) { - if ( parts[p] ) { - obj[route.names[p]] = decode( parts[p] ); + each(parts,function(i, part){ + if ( part ) { + obj[route.names[i]] = decode( part ); } - } + }); + obj.route = route.route; return obj; } // If no route was matched it is parsed as a &key=value list. @@ -363,7 +363,7 @@ function( $ ) { if( val === true || onready === true ) { setState(); } - return $route; + return $.route; }, /** * Returns a url from the options @@ -373,9 +373,9 @@ function( $ ) { */ url: function( options, merge ) { if (merge) { - return "#!" + $route.param($.extend({}, curParams, options)) + return "#!" + $.route.param(extend({}, curParams, options)) } else { - return "#!" + $route.param(options) + return "#!" + $.route.param(options) } }, /** @@ -387,8 +387,8 @@ function( $ ) { */ link: function( name, options, props, merge ) { return "" + name + ""; }, /** @@ -397,26 +397,8 @@ function( $ ) { * @return {Boolean} */ current: function( options ) { - return location.hash == "#!" + $route.param(options) - }, - /** - * Change the current page using either a data object or a url string. - * @param {Object|String} loc The object with attributes or hash string. - * @param {Boolean} remove true to remove properties not in loc, only if loc === Object, default true. - * @return $.route Fluent interface. - */ - set: function(loc, remove) { - if ($.isPlainObject( loc )) { - $route.attrs( loc, (typeof remove == "undefined") ? true : remove ); - } else if (typeof loc == "string") { - var pre = ""; - if (loc[0] != '!' && loc[1] != '!') { - pre = '#!'; - } - location.hash = pre + loc; - } - return $route; - } + return location.hash == "#!" + $.route.param(options) + } }); // onready $(function() { @@ -425,19 +407,19 @@ function( $ ) { // The functions in the following list applied to $.route (e.g. $.route.attr('...')) will // instead act on the $.route.data Observe. - $.each(['bind','unbind','delegate','undelegate','attr','attrs','serialize','removeAttr'], function(i, name){ - $route[name] = function(){ - return $route.data[name].apply($route.data, arguments) + each(['bind','unbind','delegate','undelegate','attr','serialize','removeAttr'], function(i, name){ + $.route[name] = function(){ + return $.route.data[name].apply($.route.data, arguments) } }) var // A throttled function called multiple times will only fire once the // timer runs down. Each call resets the timer. - throttle = function( func, time ) { + throttle = function( func ) { var timer; return function() { clearTimeout(timer); - timer = setTimeout(func, time || 1); + timer = setTimeout(func, 1); } }, // Intermediate storage for $.route.data. @@ -445,17 +427,11 @@ function( $ ) { // Deparameterizes the portion of the hash of interest and assign the // values to the $.route.data removing existing values no longer in the hash. setState = function() { - // commented out code handles people setting attrs before onready - //if( $.isEmptyObject( $route.data.serialize() ) ) { - var hash = location.hash.substr(1, 1) === '!' ? - location.hash.slice(2) : - location.hash.slice(1); // everything after #! - curParams = $route.deparam( hash ); - $route.attrs(curParams, true); - //} else { - // window.location.hash = "#!" + $route.param($route.data.serialize()) - //} - + var hash = location.hash.substr(1, 1) === '!' ? + location.hash.slice(2) : + location.hash.slice(1); // everything after #! + curParams = $.route.deparam( hash ); + $.route.attr(curParams, true); }; // If the hash changes, update the $.route.data @@ -464,7 +440,7 @@ function( $ ) { // If the $.route.data changes, update the hash. // Using .serialize() retrieves the raw data contained in the observable. // This function is throttled so it only updates once even if multiple values changed. - $route.data.bind("change", throttle(function() { - location.hash = "#!" + $route.param($route.data.serialize()) + $.route.bind("change", throttle(function() { + location.hash = "#!" + $.route.param($.route.serialize()) })); }) \ No newline at end of file From 9ecc31b0ec964533e79dc2b5024061eef29c6277 Mon Sep 17 00:00:00 2001 From: Justin Meyer Date: Mon, 19 Dec 2011 01:13:44 -0600 Subject: [PATCH 046/103] making controller always pass jquery and options to init --- controller/controller.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/controller/controller.js b/controller/controller.js index 2f1fad9f..2a2e5dde 100644 --- a/controller/controller.js +++ b/controller/controller.js @@ -612,7 +612,8 @@ steal('jquery/class', 'jquery/lang/string', 'jquery/event/destroyed', function( var funcName, ready, cls = this[STR_CONSTRUCTOR]; //want the raw element here - element = element.jquery ? element[0] : element; + element = (typeof element == 'string' ? $(element) : + (element.jquery ? element : [element]) )[0]; //set element and className on element var pluginname = cls.pluginName || cls._fullName; @@ -727,7 +728,7 @@ steal('jquery/class', 'jquery/lang/string', 'jquery/event/destroyed', function( * } * } */ - return this.element; + return [this.element, this.options]; }, /** * Bind attaches event handlers that will be From 517c03603ba42e96264eb2faaee6c36a061a6382 Mon Sep 17 00:00:00 2001 From: Justin Meyer Date: Mon, 19 Dec 2011 14:19:42 -0600 Subject: [PATCH 047/103] fixing route for use with attrs --- dom/route/route.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dom/route/route.js b/dom/route/route.js index 91896424..19b51ee4 100644 --- a/dom/route/route.js +++ b/dom/route/route.js @@ -410,7 +410,7 @@ function( $ ) { // The functions in the following list applied to $.route (e.g. $.route.attr('...')) will // instead act on the $.route.data Observe. - each(['bind','unbind','delegate','undelegate','attr','serialize','removeAttr'], function(i, name){ + each(['bind','unbind','delegate','undelegate','attr','attrs','serialize','removeAttr'], function(i, name){ $.route[name] = function(){ return $.route.data[name].apply($.route.data, arguments) } @@ -434,7 +434,7 @@ function( $ ) { location.hash.slice(2) : location.hash.slice(1); // everything after #! curParams = $.route.deparam( hash ); - $.route.attr(curParams, true); + $.route.attrs(curParams, true); }; // If the hash changes, update the $.route.data From 398e5ea5b252c381bc99cd21dc92aa46cbf4315a Mon Sep 17 00:00:00 2001 From: Frederick Polgardy Date: Tue, 20 Dec 2011 14:01:51 -0600 Subject: [PATCH 048/103] passing through data parameter on trigger of resize event --- event/resize/resize.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/event/resize/resize.js b/event/resize/resize.js index db36d1b1..581b4189 100644 --- a/event/resize/resize.js +++ b/event/resize/resize.js @@ -114,7 +114,7 @@ steal('jquery/event').then(function( $ ) { var where = data === false ? ev.target : this //trigger all this element's handlers - $.event.handle.call(where, ev); + $.event.handle.call(where, ev, data); if ( ev.isPropagationStopped() ) { resizeCount--; return; @@ -130,7 +130,7 @@ steal('jquery/event').then(function( $ ) { while (++index < length && (child = resizers[index]) && (isWindow || $.contains(where, child)) ) { // call the event - $.event.handle.call(child, ev); + $.event.handle.call(child, ev, data); if ( ev.isPropagationStopped() ) { // move index until the item is not in the current child @@ -156,4 +156,4 @@ steal('jquery/event').then(function( $ ) { // automatically bind on these $([document, window]).bind('resize', function() {}) -}) \ No newline at end of file +}) From 275c2ba56d124612260cef166e597a758258be70 Mon Sep 17 00:00:00 2001 From: Frederick Polgardy Date: Tue, 20 Dec 2011 20:14:50 -0600 Subject: [PATCH 049/103] test that resize event propagates extra data --- event/resize/resize_test.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/event/resize/resize_test.js b/event/resize/resize_test.js index 1863ec47..9b5220e1 100644 --- a/event/resize/resize_test.js +++ b/event/resize/resize_test.js @@ -65,5 +65,19 @@ steal('funcunit/qunit', 'jquery/event/resize').then(function() { $("#qunit-test-area").empty(); }); - + test('resize event propagates data on trigger', function(){ + var captured; + + $('#qunit-test-area').html('
    '); + + $('#1').resize(function(ev, data){ + captured = data.foo; + }); + + $('#1').trigger('resize', { foo: 'bar' }); + + equal(captured, 'bar'); + + $('#qunit-test-area').empty(); + }); }) \ No newline at end of file From eb6eca50dda7c78f56d7cde20ec737ad2841fe11 Mon Sep 17 00:00:00 2001 From: Brian Moschel Date: Wed, 21 Dec 2011 16:18:31 -0500 Subject: [PATCH 050/103] fixing fixture test --- dom/fixture/fixture_test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dom/fixture/fixture_test.js b/dom/fixture/fixture_test.js index 17ff1ce1..411f5a8b 100644 --- a/dom/fixture/fixture_test.js +++ b/dom/fixture/fixture_test.js @@ -268,7 +268,7 @@ test("replacing and removing a fixture", function(){ $.fixture("GET "+url, null ) $.get(url,{}, function(json){ - equals(json.weird,"ness","fixture set right"); + equals(json.weird,undefined,"fixture set right"); start(); }); From 6c66beef824257361c15ba446fc7b9506958e2e1 Mon Sep 17 00:00:00 2001 From: Brian Moschel Date: Wed, 21 Dec 2011 17:51:09 -0500 Subject: [PATCH 051/103] fixing fixture test --- dom/fixture/fixture_test.js | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/dom/fixture/fixture_test.js b/dom/fixture/fixture_test.js index 411f5a8b..4f378a00 100644 --- a/dom/fixture/fixture_test.js +++ b/dom/fixture/fixture_test.js @@ -281,6 +281,29 @@ test("replacing and removing a fixture", function(){ },'json') }); +module("jquery/dom/fixture2"); + + +test("static fixtures2", function(){ + stop(); + + $.fixture("GET something", "//jquery/dom/fixture/fixtures/test.json"); + $.fixture("POST something", "//jquery/dom/fixture/fixtures/test.json"); + + + $.get("something",function(data){ + equals(data.sweet,"ness","$.get works"); + + $.post("something",function(data){ + equals(data.sweet,"ness","$.post works"); + + + start(); + },'json'); + + },'json'); +}) + return; // future fixture stuff // returning undefined means you want to control timing? From 9892c8701460ea15810bad3ba620c1519005b4e3 Mon Sep 17 00:00:00 2001 From: Brian Moschel Date: Wed, 21 Dec 2011 18:40:48 -0500 Subject: [PATCH 052/103] reverting mistake --- dom/fixture/fixture_test.js | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/dom/fixture/fixture_test.js b/dom/fixture/fixture_test.js index 4f378a00..411f5a8b 100644 --- a/dom/fixture/fixture_test.js +++ b/dom/fixture/fixture_test.js @@ -281,29 +281,6 @@ test("replacing and removing a fixture", function(){ },'json') }); -module("jquery/dom/fixture2"); - - -test("static fixtures2", function(){ - stop(); - - $.fixture("GET something", "//jquery/dom/fixture/fixtures/test.json"); - $.fixture("POST something", "//jquery/dom/fixture/fixtures/test.json"); - - - $.get("something",function(data){ - equals(data.sweet,"ness","$.get works"); - - $.post("something",function(data){ - equals(data.sweet,"ness","$.post works"); - - - start(); - },'json'); - - },'json'); -}) - return; // future fixture stuff // returning undefined means you want to control timing? From 19b564c9be92a56389104bde36009f62258be3aa Mon Sep 17 00:00:00 2001 From: Justin Meyer Date: Wed, 21 Dec 2011 19:06:31 -0600 Subject: [PATCH 053/103] controller returns full array of args, tie overwrites setup to account for this --- controller/controller.js | 2 +- tie/tie.js | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/controller/controller.js b/controller/controller.js index 2a2e5dde..b5b95b55 100644 --- a/controller/controller.js +++ b/controller/controller.js @@ -728,7 +728,7 @@ steal('jquery/class', 'jquery/lang/string', 'jquery/event/destroyed', function( * } * } */ - return [this.element, this.options]; + return [this.element, this.options].concat(makeArray(arguments).slice(2)); }, /** * Bind attaches event handlers that will be diff --git a/tie/tie.js b/tie/tie.js index 780adda6..1a36416c 100644 --- a/tie/tie.js +++ b/tie/tie.js @@ -15,6 +15,10 @@ steal('jquery/controller').then(function($){ * */ $.Controller("jQuery.Tie",{ + setup : function(el){ + this._super(el,{}) + return $.makeArray(arguments); + }, init : function(el, inst, attr, type){ // if there's a controller if(!type){ From 2502a7977ad32d81339b0e0b71e480877f4215c2 Mon Sep 17 00:00:00 2001 From: Justin Meyer Date: Wed, 21 Dec 2011 20:56:50 -0600 Subject: [PATCH 054/103] fixing route tests --- dom/route/route_test.js | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/dom/route/route_test.js b/dom/route/route_test.js index 027cbd33..60c45c13 100644 --- a/dom/route/route_test.js +++ b/dom/route/route_test.js @@ -10,18 +10,21 @@ test("deparam", function(){ var obj = $.route.deparam("jQuery.Controller"); same(obj, { - page : "jQuery.Controller" + page : "jQuery.Controller", + route: ":page" }); obj = $.route.deparam(""); same(obj, { - page : "index" + page : "index", + route: ":page" }); obj = $.route.deparam("jQuery.Controller&where=there"); same(obj, { page : "jQuery.Controller", - where: "there" + where: "there", + route: ":page" }); $.route.routes = {}; @@ -34,7 +37,8 @@ test("deparam", function(){ same(obj, { page : "jQuery.Controller", index: "foo", - where: "there" + where: "there", + route: ":page/:index" }); }) @@ -57,7 +61,8 @@ test("deparam of invalid url", function(){ same(obj, { var1: 'val1', var2: 'val2', - var3: 'val3' + var3: 'val3', + route: "pages/:var1/:var2/:var3" }); }) @@ -157,24 +162,31 @@ test("param-deparam", function(){ type: "foo" }) - var data = {page: "jQuery.Controller", type: "document", bar: "baz", where: "there"}; + var data = {page: "jQuery.Controller", + type: "document", + bar: "baz", + where: "there"}; var res = $.route.param(data); var obj = $.route.deparam(res); + delete obj.route same(data, obj) data = {page: "jQuery.Controller", type: "foo", bar: "baz", where: "there"}; res = $.route.param(data); obj = $.route.deparam(res); + delete obj.route; same(data, obj) data = {page: " a ", type: " / "}; res = $.route.param(data); obj = $.route.deparam(res); + delete obj.route; same(obj ,data ,"slashes and spaces") data = {page: "index", type: "foo", bar: "baz", where: "there"}; res = $.route.param(data); obj = $.route.deparam(res); + delete obj.route; same(data, obj) $.route.routes = {}; @@ -192,12 +204,14 @@ test("precident", function(){ var obj = $.route.deparam("jQuery.Controller"); same(obj, { - who : "jQuery.Controller" + who : "jQuery.Controller", + route: ":who" }); obj = $.route.deparam("search/jQuery.Controller"); same(obj, { - search : "jQuery.Controller" + search : "jQuery.Controller", + route: "search/:search" },"bad deparam"); equal( $.route.param({ From 33613388e091a866d283f0e4b29115c26d3df856 Mon Sep 17 00:00:00 2001 From: Frederick Polgardy Date: Thu, 22 Dec 2011 13:23:08 -0600 Subject: [PATCH 055/103] fix fixture removal test --- dom/fixture/fixture_test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dom/fixture/fixture_test.js b/dom/fixture/fixture_test.js index 411f5a8b..55320696 100644 --- a/dom/fixture/fixture_test.js +++ b/dom/fixture/fixture_test.js @@ -268,10 +268,10 @@ test("replacing and removing a fixture", function(){ $.fixture("GET "+url, null ) $.get(url,{}, function(json){ - equals(json.weird,undefined,"fixture set right"); + equals(json.weird,"ness","fixture set right"); start(); - }); + },'json'); },'json') From 8e28be70d05dcfc14b4a5e832ca0fe3456afa637 Mon Sep 17 00:00:00 2001 From: Justin Meyer Date: Fri, 23 Dec 2011 01:01:47 -0600 Subject: [PATCH 056/103] doc improvements and mini functions for view --- controller/controller.js | 7 +- model/model.js | 212 ++++++++++++++------------------------- model/pages/events.md | 77 -------------- view/ejs/easyhookup.ejs | 1 + view/ejs/ejs.js | 7 +- view/ejs/ejs_test.js | 5 + 6 files changed, 92 insertions(+), 217 deletions(-) delete mode 100644 model/pages/events.md create mode 100644 view/ejs/easyhookup.ejs diff --git a/controller/controller.js b/controller/controller.js index b5b95b55..fbf40739 100644 --- a/controller/controller.js +++ b/controller/controller.js @@ -223,7 +223,7 @@ steal('jquery/class', 'jquery/lang/string', 'jquery/event/destroyed', function( * el.css("backgroundColor","") * }, * ".create click" : function() { - * this.find("ol").append("<li class='todo'>New Todo</li>"); + * this.find("ol").append("
  1. New Todo
  2. "); * } * }) * @@ -729,6 +729,11 @@ steal('jquery/class', 'jquery/lang/string', 'jquery/event/destroyed', function( * } */ return [this.element, this.options].concat(makeArray(arguments).slice(2)); + /** + * @function init + * + * Implement this. + */ }, /** * Bind attaches event handlers that will be diff --git a/model/model.js b/model/model.js index 043d83cb..d193445d 100644 --- a/model/model.js +++ b/model/model.js @@ -218,7 +218,7 @@ steal('jquery/class', 'jquery/lang/string', function() { * __Create__ * * Create a todo instance and - * call [jQuery.Model.prototype.save save]( success, error ) + * call [$.Model::save save]\(success, error\) * to create the todo on the server. * * // create a todo instance @@ -230,7 +230,7 @@ steal('jquery/class', 'jquery/lang/string', function() { * __Retrieve__ * * Retrieve a list of todos from the server with - * findAll( params, callback( items ) ): + * [$.Model.findAll findAll]\(params, callback(items)\): * * Todo.findAll({}, function( todos ){ * @@ -241,7 +241,7 @@ steal('jquery/class', 'jquery/lang/string', function() { * }); * * Retrieve a single todo from the server with - * findOne( params, callback( item ) ): + * [$.Model.findOne findOne]\(params, callback(item)\): * * Todo.findOne({id: 5}, function( todo ){ * @@ -264,7 +264,7 @@ steal('jquery/class', 'jquery/lang/string', function() { * * __Destroy__ * - * Call [jQuery.Model.prototype.destroy destroy]( success, error ) + * Call [$.Model.prototype.destroy destroy]\(success, error\) * to delete an item on the server. * * todo.destroy() @@ -274,8 +274,11 @@ steal('jquery/class', 'jquery/lang/string', function() { * Listening to changes in data is a critical part of * the [http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller Model-View-Controller] * architecture. $.Model lets you listen to when an item is created, updated, destroyed - * and its properties are changed by creating [jquery.model.events events] - * on the Model and on instances of the model. + * or its properties are changed. Use + * Model.[$.Model.bind bind]\(eventType, handler(event, model)\) + * to listen to all events of type on a model and + * model.[$.Model.prototype.bind bind]\(eventType, handler(event)\) + * to listen to events on a specific instance. * * __Create__ * @@ -332,7 +335,7 @@ steal('jquery/class', 'jquery/lang/string', function() { * that lists each todo in the page and when a todo is * deleted, removes it. * - * [jQuery.fn.model $.fn.model]( item ) lets you set or read a model + * [jQuery.fn.model $.fn.model]\(item\) lets you set or read a model * instance from an element: * * Todo.findAll({}, function( todos ) { @@ -345,7 +348,7 @@ steal('jquery/class', 'jquery/lang/string', function() { * }); * * When a todo is deleted, get its element with - * item.[jQuery.Model.prototype.elements elements]( context ) + * item.[$.Model.prototype.elements elements]\(context\) * and remove it from the page. * * Todo.bind('destroyed', function( ev, todo ) { @@ -378,12 +381,12 @@ steal('jquery/class', 'jquery/lang/string', function() { * * ## Lists * - * [jQuery.Model.List $.Model.List] lets you handle multiple model instances + * [$.Model.List $.Model.List] lets you handle multiple model instances * with ease. A List acts just like an Array, but you can add special properties * to it and listen to events on it. * * $.Model.List has become its own plugin, read about it - * [jQuery.Model.List here]. + * [$.Model.List here]. * * ## Other Good Stuff * @@ -404,7 +407,7 @@ steal('jquery/class', 'jquery/lang/string', function() { * * date.attr('dueDate') //-> new Date(1303173531164) * - * By defining property-type pairs in [jQuery.Model.static.attributes attributes], + * By defining property-type pairs in [$.Model.attributes attributes], * you can have model auto-convert values from the server into more useful types: * * $.Model('Todo',{ @@ -415,7 +418,7 @@ steal('jquery/class', 'jquery/lang/string', function() { * * ### Associations * - * The [jQuery.Model.static.attributes attributes] property also + * The [$.Model.attributes attributes] property also * supports associations. For example, todo data might come back with * User data as an owner property like: * @@ -424,7 +427,7 @@ steal('jquery/class', 'jquery/lang/string', function() { * owner: { name: 'Justin', id: 3} } * * To convert owner into a User model, set the owner type as the User's - * [jQuery.Model.static.model model]( data ) method: + * [$.Model.model model]( data ) method: * * $.Model('Todo',{ * attributes : { @@ -458,8 +461,8 @@ steal('jquery/class', 'jquery/lang/string', function() { * ### Deferreds * * Model methods that make requests to the server such as: - * [jQuery.Model.static.findAll findAll], [jQuery.Model.static.findOne findOne], - * [jQuery.Model.prototype.save save], and [jQuery.Model.prototype.destroy destroy] return a + * [$.Model.findAll findAll], [$.Model.findOne findOne], + * [$.Model.prototype.save save], and [$.Model.prototype.destroy destroy] return a * [jquery.model.deferreds deferred] that resolves to the item(s) * being retrieved or modified. * @@ -494,14 +497,9 @@ steal('jquery/class', 'jquery/lang/string', function() { 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 used by [$.Model.prototype.save save] to create a model instance on the server. * - * 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: + * The easiest way to implement create is to give it the url to post data to: * * $.Model("Recipe",{ * create: "/recipes" @@ -509,21 +507,25 @@ steal('jquery/class', 'jquery/lang/string', function() { * * This lets you create a recipe like: * - * new Recipe({name: "hot dog"}).save(function(){ - * this.name //this is the new recipe - * }).save(callback) + * new Recipe({name: "hot dog"}).save(); * - * You can also implement create by yourself. You just need to call success back with + * You can also implement create by yourself. Create gets called with: + * + * - `attrs` - the [$.Model.serialize serialized] model attributes. + * - `success(newAttrs)` - a success handler. + * - `error` - an error handler. + * + * 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 + * to `POST /recipes.json {'name': 'hot+dog'}` and gets back * something that looks like: * * { - * id: 5, - * createdAt: 2234234329 + * "id": 5, + * "createdAt": 2234234329 * } * * The code looks like: @@ -536,7 +538,7 @@ steal('jquery/class', 'jquery/lang/string', function() { * * * @param {Object} attrs Attributes on the model instance - * @param {Function} success(attrs) the callback function, it must be called with an object + * @param {Function} success(newAttrs) 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. */ @@ -547,14 +549,10 @@ steal('jquery/class', 'jquery/lang/string', function() { 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 used by [$.Model.prototype.save save] to + * update a model instance on the server. * - * Update is called by [jQuery.Model.prototype.save] or [jQuery.Model.prototype.update] - * on an existing model instance. - * - * The easist way to implement update is to just give it the url to put data to: + * The easist way to implement update is to just give it the url to `PUT` data to: * * $.Model("Recipe",{ * update: "/recipes/{id}" @@ -622,9 +620,7 @@ steal('jquery/class', 'jquery/lang/string', function() { 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. + * Destroy is used to remove a model instance from the server. * * You can implement destroy with a string like: * @@ -657,10 +653,8 @@ steal('jquery/class', 'jquery/lang/string', function() { 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) + * FindAll is used to retrive a model instances from the server. + * findAll returns a deferred ($.Deferred). * * You can implement findAll with a string: * @@ -668,13 +662,14 @@ steal('jquery/class', 'jquery/lang/string', function() { * 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: + * Or you can implement it yourself. The `dataType` attribute + * is used to convert a JSON array of attributes + * to an array of instances. It calls [$.Model.models]\(raw\). For example: * * $.Model("Thing",{ * findAll : function(params, success, error){ * return $.ajax({ - * url: '/things.json', + * url: '/things.json', * type: 'get', * dataType: 'json thing.models', * data: params, @@ -762,7 +757,7 @@ steal('jquery/class', 'jquery/lang/string', function() { } //add this to the collection of models - //jQuery.Model.models[this._fullName] = this; + //$.Model.models[this._fullName] = this; if ( this.listType ) { this.list = new this.listType([]); } @@ -793,7 +788,7 @@ steal('jquery/class', 'jquery/lang/string', function() { * @attribute attributes * Attributes contains a map of attribute names/types. * You can use this in conjunction with - * [jQuery.Model.static.convert] to provide automatic + * [$.Model.convert] to provide automatic * [jquery.model.typeconversion type conversion] (including * associations). * @@ -839,7 +834,7 @@ steal('jquery/class', 'jquery/lang/string', function() { * * Note that the full names of the models themselves are App.Models.Task * and App.Models.Person. The _.model_ and _.models_ parts are appended - * for the benefit of [jQuery.Model.static.convert convert] to identify the types as + * for the benefit of [$.Model.convert convert] to identify the types as * models. * * @demo jquery/model/pages/associations.html @@ -848,7 +843,7 @@ steal('jquery/class', 'jquery/lang/string', function() { attributes: {}, /** * $.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 + * to convert the response of a [$.Model.findOne] request * into a model instance. * * You will never call this method directly. Instead, you tell $.ajax about it in findOne: @@ -922,8 +917,8 @@ steal('jquery/class', 'jquery/lang/string', function() { }, /** * $.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. + * to convert the response of a [$.Model.findAll] request + * into an array (or [$.Model.List $.Model.List]) of model instances. * * You will never call this method directly. Instead, you tell $.ajax about it in findAll: * @@ -986,7 +981,7 @@ steal('jquery/class', 'jquery/lang/string', function() { * },{}) * * @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 + * @return {Array} a JavaScript array of instances or a [$.Model.List list] of instances * if the model list plugin has been included. */ models: function( instancesRawData ) { @@ -1061,7 +1056,7 @@ steal('jquery/class', 'jquery/lang/string', function() { * @attribute convert * @type Object * An object of name-function pairs that are used to convert attributes. - * Check out [jQuery.Model.static.attributes] or + * Check out [$.Model.attributes] or * [jquery.model.typeconversion type conversion] * for examples. * @@ -1106,8 +1101,8 @@ steal('jquery/class', 'jquery/lang/string', function() { * @attribute serialize * @type Object * An object of name-function pairs that are used to serialize attributes. - * Similar to [jQuery.Model.static.convert], in that the keys of this object - * correspond to the types specified in [jQuery.Model.static.attributes]. + * Similar to [$.Model.convert], in that the keys of this object + * correspond to the types specified in [$.Model.attributes]. * * For example, to serialize all dates to ISO format: * @@ -1135,7 +1130,13 @@ steal('jquery/class', 'jquery/lang/string', function() { return val && val.getTime() } }, + /** + * @function bind + */ bind: bind, + /** + * @function unbind + */ unbind: unbind, _ajax: ajax }, @@ -1167,7 +1168,7 @@ steal('jquery/class', 'jquery/lang/string', function() { /** * Sets the attributes on this instance and calls save. * The instance needs to have an id. It will use - * the instance class's [jQuery.Model.static.update update] + * the instance class's [$.Model.update update] * method. * * @codestart @@ -1309,7 +1310,7 @@ steal('jquery/class', 'jquery/lang/string', function() { * ## Events * * When you use attr, it can also trigger events. This is - * covered in [jQuery.Model.prototype.bind]. + * covered in [$.Model.prototype.bind]. * * @param {String} attribute the attribute you want to set or get * @param {String|Number|Boolean} [value] value the value you want to set. @@ -1358,6 +1359,7 @@ steal('jquery/class', 'jquery/lang/string', function() { }, /** + * @function bind * Binds to events on this model instance. Typically * you'll bind to an attribute name. Handler will be called * every time the attribute value changes. For example: @@ -1397,8 +1399,9 @@ steal('jquery/class', 'jquery/lang/string', function() { */ bind: bind, /** + * @function unbind * Unbinds an event handler from this instance. - * Read [jQuery.Model.prototype.bind] for + * Read [$.Model.prototype.bind] for * more information. * @param {String} eventType * @param {Function} handler @@ -1477,7 +1480,7 @@ steal('jquery/class', 'jquery/lang/string', function() { /** * Removes an attribute from the list existing of attributes. - * Each attribute is set with [jQuery.Model.prototype.attr attr]. + * Each attribute is set with [$.Model.prototype.attr attr]. * * @codestart * recipe.removeAttr('name') @@ -1509,7 +1512,7 @@ steal('jquery/class', 'jquery/lang/string', function() { /** * Gets or sets a list of attributes. - * Each attribute is set with [jQuery.Model.prototype.attr attr]. + * Each attribute is set with [$.Model.prototype.attr attr]. * * @codestart * recipe.attrs({ @@ -1550,7 +1553,7 @@ steal('jquery/class', 'jquery/lang/string', function() { }, /** * Get a serialized object for the model. Serialized data is typically - * used to send back to a server. See [jQuery.Model.static.serialize]. + * used to send back to a server. See [$.Model.serialize]. * * model.serialize() // -> { name: 'Fred' } * @@ -1587,9 +1590,9 @@ steal('jquery/class', 'jquery/lang/string', function() { return (id === undefined || id === null || id === ''); //if null or undefined }, /** - * Creates or updates the instance using [jQuery.Model.static.create] or - * [jQuery.Model.static.update] depending if the instance - * [jQuery.Model.prototype.isNew has an id or not]. + * Creates or updates the instance using [$.Model.create] or + * [$.Model.update] depending if the instance + * [$.Model.prototype.isNew has an id or not]. * * When a save is successful, `success` is called and depending if the * instance was created or updated, a created or updated event is fired. @@ -1633,7 +1636,7 @@ steal('jquery/class', 'jquery/lang/string', function() { /** * Destroys the instance by calling - * [jQuery.Model.static.destroy] with the id of the instance. + * [$.Model.destroy] with the id of the instance. * * @codestart * recipe.destroy(success, error); @@ -1657,7 +1660,7 @@ steal('jquery/class', 'jquery/lang/string', function() { * new Todo({id: 5}).identity() //-> 'todo_5' * @codeend * Typically this is used in an element's shortName property so you can find all elements - * for a model with [jQuery.Model.prototype.elements elements]. + * for a model with [$.Model.prototype.elements elements]. * @return {String} */ identity: function() { @@ -1667,7 +1670,7 @@ steal('jquery/class', 'jquery/lang/string', function() { }, /** * Returns elements that represent this model instance. For this to work, your element's should - * us the [jQuery.Model.prototype.identity identity] function in their class name. Example: + * us the [$.Model.prototype.identity identity] function in their class name. Example: * *
    ...
    * @@ -1761,7 +1764,7 @@ steal('jquery/class', 'jquery/lang/string', function() { /** * @function models * Returns a list of models. If the models are of the same - * type, and have a [jQuery.Model.List], it will return + * type, and have a [$.Model.List], it will return * the models wrapped with the list. * * @codestart @@ -1769,7 +1772,7 @@ steal('jquery/class', 'jquery/lang/string', function() { * @codeend * * @param {jQuery.Class} [type] if present only returns models of the provided type. - * @return {Array|jQuery.Model.List} returns an array of model instances that are represented by the contained elements. + * @return {Array|$.Model.List} returns an array of model instances that are represented by the contained elements. */ $.fn.models = function( type ) { //get it from the data @@ -1819,71 +1822,4 @@ steal('jquery/class', 'jquery/lang/string', function() { } }; - /** - * @page jquery.model.services Service APIs - * @parent jQuery.Model - * - * Models provide an abstract API for connecting to your Services. - * By implementing static: - * - * - [jQuery.Model.static.findAll] - * - [jQuery.Model.static.findOne] - * - [jQuery.Model.static.create] - * - [jQuery.Model.static.update] - * - [jQuery.Model.static.destroy] - * - * 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 - * - * - attrs - an Object of attribute / value pairs - * - success([attrs]) - 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. - * - error - Create should callback error if an error happens during the request - * - deferred - 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 - * - * - * - params - an Object that filters the items returned - * - success(items) - success should be called with an Array of Model instances. - * - error - called if an error happens during the request - * - deferred - A deferred that gets resolved to the list of items - * - * ### findOne(params, success(items), error) -> deferred - * - * - params - an Object that filters the item returned - * - success(item) - success should be called with a model instance. - * - error - called if an error happens during the request - * - deferred - A deferred that gets resolved to a model instance - * - * ### update(id, attrs, success([attrs]), error()) -> deferred - * - * - id - the id of the instance you are updating - * - attrs - an Object of attribute / value pairs - * - success([attrs]) - 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. - * - error - Callback error if an error happens during the request - * - deferred - 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 - * - * - id - the id of the instance you are destroying - * - success([attrs]) - 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. - * - error - Create should callback error if an error happens during the request - * - deferred - 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/model/pages/events.md b/model/pages/events.md deleted file mode 100644 index 48bda2f2..00000000 --- a/model/pages/events.md +++ /dev/null @@ -1,77 +0,0 @@ -@page jquery.model.events Events -@parent jQuery.Model - -Models produce events that you can listen to. This is -useful when there are multiple representations of the same instance on the page. -If one representation is updated, the other representation -should be updated. - -Events also provide a more traditional MVC approach. View-Controllers -bind to a specific property. If that property changes, the -View-Controller updates itself. - -Model provides two ways to listen for events on model instances: - -## Way 1: Bind - -You can [jQuery.Model.prototype.bind bind] to attribute changes in a model instance -just like you would with events in jQuery. - -The following listens for contact birthday changes. - -@codestart -contact.bind("birthday", function(ev, birthday){ - // do something -}) -@codeend - -The 'birthday' event is triggered whenever an attribute is -successfully changed: - -@codestart -contact.attr('birthday', "10-20-1982"); -@codeend - -Bind is the prefered approach if you're favoring a more -traditional MVC architecture. However, this can sometimes -be more complex than the subscribe method because of -maintaining additional event handlers. - -## Way 2: Subscribe - -Models will publish events when an instance is created, updated, or destroyed. - -You can subscribe to these events with Model Events like: - -@codestart -Task.bind('created', function(ev, task){ - var el = $("li").html(todo.name); - el.appendTo($('#todos')); - - task.bind('updated', function(){ - el.html(this.name); - }).bind('destroyed', function(){ - el.remove(); - }); -}) -@codeend - -Typically, you'll subscribe with templated events like: - -@codestart -$.Controller("Todo",{ - - ... - - "{Task} created" : function(Task, event, task){ - - //find the task in this widget: - var el = task.elements(this.element) - - //remove element - el.remove(); - }, - - ... -}) -@codeend \ No newline at end of file diff --git a/view/ejs/easyhookup.ejs b/view/ejs/easyhookup.ejs new file mode 100644 index 00000000..b7edcdc2 --- /dev/null +++ b/view/ejs/easyhookup.ejs @@ -0,0 +1 @@ +
    <%= (el)-> el.addClass('yes') %>> \ No newline at end of file diff --git a/view/ejs/ejs.js b/view/ejs/ejs.js index fdaf67ed..e3fefa23 100644 --- a/view/ejs/ejs.js +++ b/view/ejs/ejs.js @@ -24,6 +24,7 @@ steal('jquery/view', 'jquery/lang/string/rsplit').then(function( $ ) { tabReg = /\t/g, leftBracket = /\{/g, rightBracket = /\}/g, + quickFunc = /\s*\(([\w]+)\)\s*->([^\n]*)/, // escapes characters starting with \ clean = function( content ) { return content.replace(slashReg, '\\\\').replace(newReg, '\\n').replace(quoteReg, '\\"').replace(tabReg, '\\t'); @@ -478,7 +479,11 @@ steal('jquery/view', 'jquery/lang/string/rsplit').then(function( $ ) { bn = bracketNum(content); if( bn ) { endStack.push(doubleParen) - } + } + if(quickFunc.test(content)){ + var parts = content.match(quickFunc) + content = "function(__){var "+parts[1]+"=$(__);"+parts[2]+"}" + } buff.push(insert_cmd, "jQuery.EJS.clean(", content,bn ? startTxt : doubleParen); break; case scanner.eeLeft: diff --git a/view/ejs/ejs_test.js b/view/ejs/ejs_test.js index 4b4e2f87..3a638cee 100644 --- a/view/ejs/ejs_test.js +++ b/view/ejs/ejs_test.js @@ -98,4 +98,9 @@ test("returning blocks", function(){ equals(res.match(/ItemsLength4/g).length, 4, "innerBlock and each") }); +test("easy hookup", function(){ + var div = $('
    ').html("//jquery/view/ejs/easyhookup.ejs",{}) + ok( div.find('div').hasClass('yes'), "has yes" ) +}) + }) From d15ad81282377d238601dbc054e35cea8957ea04 Mon Sep 17 00:00:00 2001 From: Justin Meyer Date: Fri, 23 Dec 2011 03:58:34 -0600 Subject: [PATCH 057/103] fixes routing --- controller/route/route.html | 1 + controller/route/route.js | 17 ++++++++++++----- view/ejs/easyhookup.ejs | 2 +- view/ejs/ejs.js | 2 +- view/ejs/ejs_test.js | 4 ++-- 5 files changed, 17 insertions(+), 9 deletions(-) diff --git a/controller/route/route.html b/controller/route/route.html index f6590eb6..178af929 100644 --- a/controller/route/route.html +++ b/controller/route/route.html @@ -12,6 +12,7 @@

    route Demo

    foo/bar + foo/car empty + \ No newline at end of file diff --git a/event/hover/hover.html b/event/hover/hover.html index 29f2fdaa..d98a8894 100644 --- a/event/hover/hover.html +++ b/event/hover/hover.html @@ -32,11 +32,12 @@

    HoverLeave

    Leave and don't return for a half second
    + src='../../../steal/steal.js'> \ No newline at end of file From 47791a138036d60527a1b9204944fbf27bdf2868 Mon Sep 17 00:00:00 2001 From: Justin Meyer Date: Fri, 24 Feb 2012 02:28:35 -0600 Subject: [PATCH 087/103] making sure some other demos work --- model/demo-dom.html | 9 ++++++--- model/demo-setter.html | 9 ++++++--- model/modelBinder.html | 6 +++--- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/model/demo-dom.html b/model/demo-dom.html index 8f818ee0..f0958e6d 100644 --- a/model/demo-dom.html +++ b/model/demo-dom.html @@ -25,9 +25,12 @@

    Model DOM Helpers Demo

    steal('jquery/model','jquery/dom/fixture').then(function(){ $.fixture("GET /contacts.json", function(){ - return [[{id: 1, name: 'Justin Meyer', birthday: 403938000000}, - {id: 2, name: 'Brian Moschel', birthday: 437205600000}, - {id: 3, name: 'Mihael Konjevic', birthday: 483771600000}]]; + return [[{id: 1, name: 'Justin Meyer', + birthday: 403938000000}, + {id: 2, name: 'Brian Moschel', + birthday: 437205600000}, + {id: 3, name: 'Mihael Konjevic', + birthday: 483771600000}]]; }); $.fixture("DELETE /contacts.json", function(){ diff --git a/model/demo-setter.html b/model/demo-setter.html index 3fca9a5d..6d0d58e8 100644 --- a/model/demo-setter.html +++ b/model/demo-setter.html @@ -30,9 +30,12 @@

    Model Setter Demo

    function(){ $.fixture("/contacts.json", function(orig, settings, headers){ - return [[{'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'}]]; + return [[{'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'}]]; }); // A contact model diff --git a/model/modelBinder.html b/model/modelBinder.html index 5da061ba..cba4a743 100644 --- a/model/modelBinder.html +++ b/model/modelBinder.html @@ -46,7 +46,7 @@ - - - - \ No newline at end of file diff --git a/class/class.js b/class/class.js index 8df6f95d..38771b9b 100644 --- a/class/class.js +++ b/class/class.js @@ -3,783 +3,9 @@ // http://ejohn.org/blog/simple-javascript-inheritance/ // It provides class level inheritance and callbacks. //!steal-clean -steal("jquery","jquery/lang/string",function( $ ) { +steal("can/construct/proxy","can/construct/super",function( $ ) { - // =============== HELPERS ================= - - // if we are initializing a new class - var initializing = false, - makeArray = $.makeArray, - isFunction = $.isFunction, - isArray = $.isArray, - extend = $.extend, - getObject = $.String.getObject, - 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] = isFunction(newProps[name]) && - isFunction(oldProps[name]) && - fnTest.test(newProps[name]) ? (function( name, fn ) { - return function() { - var tmp = this._super, - ret; - - // Add a new ._super() method that is the same method - // but on the super-class - this._super = oldProps[name]; - - // The method only need to be bound temporarily, so we - // remove it when we're done executing - ret = fn.apply(this, arguments); - this._super = tmp; - return ret; - }; - })(name, newProps[name]) : newProps[name]; - } - }, - STR_PROTOTYPE = 'prototype' - - /** - * @class jQuery.Class - * @plugin jquery/class - * @parent jquerymx - * @download dist/jquery/jquery.class.js - * @test jquery/class/qunit.html - * @description Easy inheritance in JavaScript. - * - * 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: - * - * - Static inheritance - * - Introspection - * - Namespaces - * - Setup and initialization methods - * - Easy callback function creation - * - * - * The [mvc.class Get Started with jQueryMX] has a good walkthrough of $.Class. - * - * ## Static v. Prototype - * - * Before learning about Class, it's important to - * understand the difference between - * 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. - * - * ## 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. - * - * @codestart - * $.Class('Monster', - * /* @static *| - * { - * count: 0 - * }, - * /* @prototype *| - * { - * init: function( name ) { - * - * // saves name on the monster instance - * this.name = name; - * - * // sets the health - * this.health = 10; - * - * // increments count - * this.constructor.count++; - * }, - * eat: function( smallChildren ){ - * this.health += smallChildren; - * }, - * fight: function() { - * this.health -= 2; - * } - * }); - * - * hydra = new Monster('hydra'); - * - * dragon = new Monster('dragon'); - * - * hydra.name // -> hydra - * Monster.count // -> 2 - * Monster.shortName // -> 'Monster' - * - * hydra.eat(2); // health = 12 - * - * dragon.fight(); // health = 8 - * - * @codeend - * - * - * Notice that the prototype init function is called when a new instance of Monster is created. - * - * - * ## 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 - * this._super. Lets create a SeaMonster class. SeaMonsters are less - * efficient at eating small children, but more powerful fighters. - * - * - * Monster("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: - * - * $.Class("First", - * { - * staticMethod: function() { return 1;} - * },{}) - * - * First("Second",{ - * staticMethod: function() { return this._super()+1;} - * },{}) - * - * 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: - * - * - * $.Class("MyNamespace.MyClass",{},{}); - * - * new MyNamespace.MyClass() - * - * - *

    Introspection

    - * - * Often, it's nice to create classes whose name helps determine functionality. Ruby on - * Rails's [http://api.rubyonrails.org/classes/ActiveRecord/Base.html|ActiveRecord] ORM class - * is a great example of this. Unfortunately, JavaScript doesn't have a way of determining - * an object's name, so the developer must provide a name. Class fixes this by taking a String name for the class. - * - * $.Class("MyOrg.MyClass",{},{}) - * MyOrg.MyClass.shortName //-> 'MyClass' - * MyOrg.MyClass.fullName //-> 'MyOrg.MyClass' - * - * The fullName (with namespaces) and the shortName (without namespaces) are added to the Class's - * static properties. - * - * - * ## Setup and initialization methods - * - *

    - * Class provides static and prototype initialization functions. - * These come in two flavors - setup and init. - * Setup is called before init and - * can be used to 'normalize' init's arguments. - *

    - *
    PRO TIP: Typically, you don't need setup methods in your classes. Use Init instead. - * Reserve setup methods for when you need to do complex pre-processing of your class before init is called. - * - *
    - * @codestart - * $.Class("MyClass", - * { - * setup: function() {} //static setup - * init: function() {} //static constructor - * }, - * { - * setup: function() {} //prototype setup - * init: function() {} //prototype constructor - * }) - * @codeend - * - * ### Setup - * - * Setup functions are called before init functions. Static setup functions are passed - * the base class followed by arguments passed to the extend function. - * Prototype static functions are passed the Class constructor - * function arguments. - * - * If a setup function returns an array, that array will be used as the arguments - * for the following init method. This provides setup functions the ability to normalize - * arguments passed to the init constructors. They are also excellent places - * to put setup code you want to almost always run. - * - * - * The following is similar to how [jQuery.Controller.prototype.setup] - * makes sure init is always called with a jQuery element and merged options - * even if it is passed a raw - * HTMLElement and no second parameter. - * - * $.Class("jQuery.Controller",{ - * ... - * },{ - * setup: function( el, options ) { - * ... - * return [$(el), - * $.extend(true, - * this.Class.defaults, - * options || {} ) ] - * } - * }) - * - * Typically, you won't need to make or overwrite setup functions. - * - * ### Init - * - * Init functions are called after setup functions. - * Typically, they receive the same arguments - * as their preceding setup function. The Foo class's init method - * gets called in the following example: - * - * $.Class("Foo", { - * init: function( arg1, arg2, arg3 ) { - * this.sum = arg1+arg2+arg3; - * } - * }) - * var foo = new Foo(1,2,3); - * foo.sum //-> 6 - * - * ## Proxies - * - * Similar to jQuery's proxy method, Class provides a - * [jQuery.Class.static.proxy proxy] - * function that returns a callback to a method that will always - * have - * this set to the class or instance of the class. - * - * - * The following example uses this.proxy to make sure - * this.name is available in show. - * - * $.Class("Todo",{ - * init: function( name ) { - * this.name = name - * }, - * get: function() { - * $.get("/stuff",this.proxy('show')) - * }, - * show: function( txt ) { - * alert(this.name+txt) - * } - * }) - * new Todo("Trash").get() - * - * Callback is available as a static and prototype method. - * - * ## Demo - * - * @demo jquery/class/class.html - * - * - * @constructor - * - * To create a Class call: - * - * $.Class( [NAME , STATIC,] PROTOTYPE ) -> Class - * - *
    - *
    {optional:String} - *

    If provided, this sets the shortName and fullName of the - * class and adds it and any necessary namespaces to the - * window object.

    - *
    - *
    {optional:Object} - *

    If provided, this creates static properties and methods - * on the class.

    - *
    - *
    {Object} - *

    Creates prototype methods on the class.

    - *
    - *
    - * - * When a Class is created, the static [jQuery.Class.static.setup setup] - * and [jQuery.Class.static.init init] methods are called. - * - * To create an instance of a Class, call: - * - * new Class([args ... ]) -> instance - * - * The created instance will have all the - * prototype properties and methods defined by the PROTOTYPE object. - * - * When an instance is created, the prototype [jQuery.Class.prototype.setup setup] - * and [jQuery.Class.prototype.init init] methods - * are called. - */ - - clss = $.Class = function() { - if (arguments.length) { - return clss.extend.apply(clss, arguments); - } - }; - - /* @Static*/ - extend(clss, { - /** - * @function proxy - * Returns a callback function for a function on this Class. - * Proxy ensures that 'this' is set appropriately. - * @codestart - * $.Class("MyClass",{ - * getData: function() { - * this.showing = null; - * $.get("data.json",this.proxy('gotData'),'json') - * }, - * gotData: function( data ) { - * this.showing = data; - * } - * },{}); - * MyClass.showData(); - * @codeend - *

    Currying Arguments

    - * Additional arguments to proxy will fill in arguments on the returning function. - * @codestart - * $.Class("MyClass",{ - * getData: function( callback ) { - * $.get("data.json",this.proxy('process',callback),'json'); - * }, - * process: function( callback, jsonData ) { //callback is added as first argument - * jsonData.processed = true; - * callback(jsonData); - * } - * },{}); - * MyClass.getData(showDataFunc) - * @codeend - *

    Nesting Functions

    - * Proxy can take an array of functions to call as - * the first argument. When the returned callback function - * is called each function in the array is passed the return value of the prior function. This is often used - * to eliminate currying initial arguments. - * @codestart - * $.Class("MyClass",{ - * getData: function( callback ) { - * //calls process, then callback with value from process - * $.get("data.json",this.proxy(['process2',callback]),'json') - * }, - * process2: function( type,jsonData ) { - * jsonData.processed = true; - * return [jsonData]; - * } - * },{}); - * MyClass.getData(showDataFunc); - * @codeend - * @param {String|Array} fname If a string, it represents the function to be called. - * If it is an array, it will call each function in order and pass the return value of the prior function to the - * next function. - * @return {Function} the callback function. - */ - proxy: function( funcs ) { - - //args that should be curried - var args = makeArray(arguments), - self; - - // get the functions to callback - funcs = args.shift(); - - // if there is only one function, make funcs into an array - if (!isArray(funcs) ) { - funcs = [funcs]; - } - - // keep a reference to us in self - self = this; - - //!steal-remove-start - for( var i =0; i< funcs.length;i++ ) { - 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() { - // add the arguments after the curried args - var cur = concatArgs(args, arguments), - isString, - length = funcs.length, - f = 0, - func; - - // go through each function to call back - for (; f < length; f++ ) { - func = funcs[f]; - if (!func ) { - continue; - } - - // set called with the name of the function on self (this is how this.view works) - isString = typeof func == "string"; - if ( isString && self._set_called ) { - self.called = func; - } - - // call the function - cur = (isString ? self[func] : func).apply(self, cur || []); - - // pass the result to the next function (if there is a next function) - if ( f < length - 1 ) { - cur = !isArray(cur) || cur._use_call ? [cur] : cur - } - } - return cur; - } - }, - /** - * @function newInstance - * Creates a new instance of the class. This method is useful for creating new instances - * with arbitrary parameters. - *

    Example

    - * @codestart - * $.Class("MyClass",{},{}) - * var mc = MyClass.newInstance.apply(null, new Array(parseInt(Math.random()*10,10)) - * @codeend - * @return {class} instance of the class - */ - newInstance: function() { - // get a raw instance objet (init is not called) - var inst = this.rawInstance(), - args; - - // call setup if there is a setup - if ( inst.setup ) { - args = inst.setup.apply(inst, arguments); - } - // call init if there is an init, if setup returned args, use those as the arguments - if ( inst.init ) { - inst.init.apply(inst, isArray(args) ? args : arguments); - } - return inst; - }, - /** - * 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( baseClass, fullName ) { - // set defaults as the merger of the parent defaults and this object's defaults - this.defaults = extend(true, {}, baseClass.defaults, this.defaults); - return arguments; - }, - rawInstance: function() { - // prevent running init - initializing = true; - var inst = new this(); - initializing = false; - // allow running init - return inst; - }, - /** - * Extends a class with new static and prototype functions. There are a variety of ways - * to use extend: - * - * // with className, static and prototype functions - * $.Class('Task',{ STATIC },{ PROTOTYPE }) - * // with just classname and prototype functions - * $.Class('Task',{ PROTOTYPE }) - * // with just a className - * $.Class('Task') - * - * You no longer have to use .extend. Instead, you can pass those options directly to - * $.Class (and any inheriting classes): - * - * // with className, static and prototype functions - * $.Class('Task',{ STATIC },{ PROTOTYPE }) - * // with just classname and prototype functions - * $.Class('Task',{ PROTOTYPE }) - * // with just a className - * $.Class('Task') - * - * @param {String} [fullName] the classes name (used for classes w/ introspection) - * @param {Object} [klass] the new classes static/class functions - * @param {Object} [proto] the new classes prototype functions - * - * @return {jQuery.Class} returns the new class - */ - extend: function( fullName, klass, proto ) { - // figure out what was passed and normalize it - if ( typeof fullName != 'string' ) { - proto = klass; - klass = fullName; - fullName = null; - } - if (!proto ) { - proto = klass; - klass = null; - } - - proto = proto || {}; - var _super_class = this, - _super = this[STR_PROTOTYPE], - name, shortName, namespace, prototype; - - // Instantiate a base class (but only create the instance, - // don't run the init constructor) - initializing = true; - prototype = new this(); - initializing = false; - - // Copy the properties over onto the new prototype - inheritProps(proto, _super, prototype); - - // The dummy class constructor - function Class() { - // All construction is actually done in the init method - if ( initializing ) return; - - // we are being called w/o new, we are extending - if ( this.constructor !== Class && arguments.length ) { - return arguments.callee.extend.apply(arguments.callee, arguments) - } else { //we are being called w/ new - return this.Class.newInstance.apply(this.Class, arguments) - } - } - // Copy old stuff onto class - for ( name in this ) { - if ( this.hasOwnProperty(name) ) { - Class[name] = this[name]; - } - } - - // copy new static props on class - inheritProps(klass, this, Class); - - // do namespace stuff - if ( fullName ) { - - var parts = fullName.split(/\./), - shortName = parts.pop(), - current = getObject(parts.join('.'), window, true), - namespace = current; - - //!steal-remove-start - if (!Class.nameOk ) { - //steal.dev.isHappyName(fullName) - } - if(current[shortName]){ - steal.dev.warn("class.js There's already something called "+fullName) - } - //!steal-remove-end - current[shortName] = Class; - } - - // set things that can't be overwritten - extend(Class, { - prototype: prototype, - /** - * @attribute namespace - * The namespaces object - * - * $.Class("MyOrg.MyClass",{},{}) - * MyOrg.MyClass.namespace //-> MyOrg - * - */ - namespace: namespace, - /** - * @attribute shortName - * The name of the class without its namespace, provided for introspection purposes. - * - * $.Class("MyOrg.MyClass",{},{}) - * MyOrg.MyClass.shortName //-> 'MyClass' - * MyOrg.MyClass.fullName //-> 'MyOrg.MyClass' - * - */ - shortName: shortName, - constructor: Class, - /** - * @attribute fullName - * The full name of the class, including namespace, provided for introspection purposes. - * - * $.Class("MyOrg.MyClass",{},{}) - * MyOrg.MyClass.shortName //-> 'MyClass' - * MyOrg.MyClass.fullName //-> 'MyOrg.MyClass' - * - */ - fullName: fullName - }); - - //make sure our prototype looks nice - Class[STR_PROTOTYPE].Class = Class[STR_PROTOTYPE].constructor = Class; - - - - // call the class setup - var args = Class.setup.apply(Class, concatArgs([_super_class],arguments)); - - // call the class init - if ( Class.init ) { - Class.init.apply(Class, args || concatArgs([_super_class],arguments)); - } - - /* @Prototype*/ - return Class; - /** - * @function setup - * 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 ( new Class( arguments ... )). - * - * $.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 )] - * } - * }) - * - *
    PRO TIP: - * Setup functions are used to normalize constructor arguments and provide a place for - * setup code that extending classes don't have to remember to call _super to - * run. - *
    - * - * 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 - * If an init 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: ( new Class( arguments ... )). - * - * $.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 - /** - * @attribute constructor - * - * A reference to the Class (or constructor function). This allows you to access - * a class's static properties from an instance. - * - * ### Quick Example - * - * // a class with a static property - * $.Class("MyClass", {staticProperty : true}, {}); - * - * // a new instance of myClass - * var mc1 = new MyClass(); - * - * // read the static property from the instance: - * mc1.constructor.staticProperty //-> true - * - * Getting static properties with the constructor property, like - * [jQuery.Class.static.fullName fullName], is very common. - * - */ - } - - }) - - - - - - clss.callback = clss[STR_PROTOTYPE].callback = clss[STR_PROTOTYPE]. - /** - * @function proxy - * Returns a method that sets 'this' to the current instance. This does the same thing as - * and is described better in [jQuery.Class.static.proxy]. - * The only difference is this proxy works - * on a instance instead of a class. - * @param {String|Array} fname If a string, it represents the function to be called. - * If it is an array, it will call each function in order and pass the return value of the prior function to the - * next function. - * @return {Function} the callback function - */ - proxy = clss.proxy; + $.Class = can.Construct; })(); diff --git a/class/class_test.js b/class/class_test.js index ec95179c..61caa64d 100644 --- a/class/class_test.js +++ b/class/class_test.js @@ -14,7 +14,7 @@ test("Creating", function(){ }, { init: function() { - this.Class.count++; + this.constructor.count++; this.eyes = false; } } @@ -139,12 +139,12 @@ test("callback", function(){ } }) - var cb = Car.callback('show'); + var cb = Car.proxy('show'); curVal = 1; cb(1) curVal = 2; - var cb2 = Car.callback('show',2) + var cb2 = Car.proxy('show',2) cb2(); }); @@ -159,7 +159,7 @@ test("callback error", 1,function(){ } }) try{ - Car.callback('huh'); + Car.proxy('huh'); ok(false, "I should have errored") }catch(e){ ok(true, "Error was thrown") diff --git a/controller/controller.js b/controller/controller.js index f3bd8476..2d735d3b 100644 --- a/controller/controller.js +++ b/controller/controller.js @@ -1,1089 +1,7 @@ -steal('jquery/class', 'jquery/lang/string', 'jquery/event/destroyed', function( $ ) { - // ------- HELPER FUNCTIONS ------ - - // Binds an element, returns a function that unbinds - var bind = function( el, ev, callback ) { - 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); - } - }; - } - 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() { - binder.unbind(ev, wrappedCallback || callback); - el = ev = callback = wrappedCallback = null; - }; - }, - makeArray = $.makeArray, - isArray = $.isArray, - isFunction = $.isFunction, - extend = $.extend, - Str = $.String, - each = $.each, - - STR_PROTOTYPE = 'prototype', - STR_CONSTRUCTOR = 'constructor', - slice = Array[STR_PROTOTYPE].slice, - - // Binds an element, returns a function that unbinds - delegate = function( el, selector, ev, callback ) { - var binder = el.delegate && el.undelegate ? el : $(isFunction(el) ? [el] : el) - binder.delegate(selector, ev, callback); - return function() { - binder.undelegate(selector, ev, callback); - binder = el = ev = callback = selector = null; - }; - }, - - // calls bind or unbind depending if there is a selector - binder = function( el, ev, callback, selector ) { - return selector ? delegate(el, selector, ev, callback) : bind(el, ev, callback); - }, - - // moves 'this' to the first argument, wraps it with jQuery if it's an element - shifter = function shifter(context, name) { - var method = typeof name == "string" ? context[name] : name; - return function() { - context.called = name; - return method.apply(context, [this.nodeName ? $(this) : this].concat( slice.call(arguments, 0) ) ); - }; - }, - // matches dots - dotsReg = /\./g, - // matches controller - controllersReg = /_?controllers?/ig, - //used to remove the controller from the name - underscoreAndRemoveController = function( className ) { - return Str.underscore(className.replace("jQuery.", "").replace(dotsReg, '_').replace(controllersReg, "")); - }, - // checks if it looks like an action - actionMatcher = /[^\w]/, - // handles parameterized action names - parameterReplacer = /\{([^\}]+)\}/g, - breaker = /^(?:(.*?)\s)?([\w\.\:>]+)$/, - basicProcessor, - data = function(el, data){ - return $.data(el, "controllers", data) - }; - /** - * @class jQuery.Controller - * @parent jquerymx - * @plugin jquery/controller - * @download http://jmvcsite.heroku.com/pluginify?plugins[]=jquery/controller/controller.js - * @test jquery/controller/qunit.html - * @inherits jQuery.Class - * @description jQuery widget factory. - * - * jQuery.Controller helps create organized, memory-leak free, rapidly performing - * jQuery widgets. Its extreme flexibility allows it to serve as both - * a traditional View and a traditional Controller. - * - * This means it is used to - * create things like tabs, grids, and contextmenus as well as - * organizing them into higher-order business rules. - * - * Controllers make your code deterministic, reusable, organized and can tear themselves - * down auto-magically. Read about [http://jupiterjs.com/news/writing-the-perfect-jquery-plugin - * the theory behind controller] and - * a [http://jupiterjs.com/news/organize-jquery-widgets-with-jquery-controller walkthrough of its features] - * on Jupiter's blog. [mvc.controller Get Started with jQueryMX] also has a great walkthrough. - * - * Controller inherits from [jQuery.Class $.Class] and makes heavy use of - * [http://api.jquery.com/delegate/ event delegation]. Make sure - * you understand these concepts before using it. - * - * ## Basic Example - * - * Instead of - * - * - * $(function(){ - * $('#tabs').click(someCallbackFunction1) - * $('#tabs .tab').click(someCallbackFunction2) - * $('#tabs .delete click').click(someCallbackFunction3) - * }); - * - * do this - * - * $.Controller('Tabs',{ - * click: function() {...}, - * '.tab click' : function() {...}, - * '.delete click' : function() {...} - * }) - * $('#tabs').tabs(); - * - * - * ## Tabs Example - * - * @demo jquery/controller/controller.html - * - * ## Using Controller - * - * Controller helps you build and organize jQuery plugins. It can be used - * to build simple widgets, like a slider, or organize multiple - * widgets into something greater. - * - * To understand how to use Controller, you need to understand - * the typical lifecycle of a jQuery widget and how that maps to - * controller's functionality: - * - * ### A controller class is created. - * - * $.Controller("MyWidget", - * { - * defaults : { - * message : "Remove Me" - * } - * }, - * { - * init : function(rawEl, rawOptions){ - * this.element.append( - * "
    "+this.options.message+"
    " - * ); - * }, - * "div click" : function(div, ev){ - * div.remove(); - * } - * }) - * - * This creates a $.fn.my_widget jQuery helper function - * that can be used to create a new controller instance on an element. Find - * more information [jquery.controller.plugin here] about the plugin gets created - * and the rules around its name. - * - * ### An instance of controller is created on an element - * - * $('.thing').my_widget(options) // calls new MyWidget(el, options) - * - * This calls new MyWidget(el, options) on - * each '.thing' element. - * - * When a new [jQuery.Class Class] instance is created, it calls the class's - * prototype setup and init methods. Controller's [jQuery.Controller.prototype.setup setup] - * method: - * - * - Sets [jQuery.Controller.prototype.element this.element] and adds the controller's name to element's className. - * - Merges passed in options with defaults object and sets it as [jQuery.Controller.prototype.options this.options] - * - Saves a reference to the controller in $.data. - * - [jquery.controller.listening Binds all event handler methods]. - * - * - * ### The controller responds to events - * - * Typically, Controller event handlers are automatically bound. However, there are - * multiple ways to [jquery.controller.listening listen to events] with a controller. - * - * Once an event does happen, the callback function is always called with 'this' - * referencing the controller instance. This makes it easy to use helper functions and - * save state on the controller. - * - * - * ### The widget is destroyed - * - * If the element is removed from the page, the - * controller's [jQuery.Controller.prototype.destroy] method is called. - * This is a great place to put any additional teardown functionality. - * - * You can also teardown a controller programatically like: - * - * $('.thing').my_widget('destroy'); - * - * ## Todos Example - * - * Lets look at a very basic example - - * a list of todos and a button you want to click to create a new todo. - * Your HTML might look like: - * - * @codestart html - * <div id='todos'> - * <ol> - * <li class="todo">Laundry</li> - * <li class="todo">Dishes</li> - * <li class="todo">Walk Dog</li> - * </ol> - * <a class="create">Create</a> - * </div> - * @codeend - * - * To add a mousover effect and create todos, your controller might look like: - * - * $.Controller('Todos',{ - * ".todo mouseover" : function( el, ev ) { - * el.css("backgroundColor","red") - * }, - * ".todo mouseout" : function( el, ev ) { - * el.css("backgroundColor","") - * }, - * ".create click" : function() { - * this.find("ol").append("
  3. New Todo
  4. "); - * } - * }) - * - * Now that you've created the controller class, you've must attach the event handlers on the '#todos' div by - * creating [jQuery.Controller.prototype.setup|a new controller instance]. There are 2 ways of doing this. - * - * @codestart - * //1. Create a new controller directly: - * new Todos($('#todos')); - * //2. Use jQuery function - * $('#todos').todos(); - * @codeend - * - * ## Controller Initialization - * - * It can be extremely useful to add an init method with - * setup functionality for your widget. - * - * In the following example, I create a controller that when created, will put a message as the content of the element: - * - * $.Controller("SpecialController", - * { - * init: function( el, message ) { - * this.element.html(message) - * } - * }) - * $(".special").special("Hello World") - * - * ## Removing Controllers - * - * Controller removal is built into jQuery. So to remove a controller, you just have to remove its element: - * - * @codestart - * $(".special_controller").remove() - * $("#containsControllers").html("") - * @codeend - * - * It's important to note that if you use raw DOM methods (innerHTML, removeChild), the controllers won't be destroyed. - * - * If you just want to remove controller functionality, call destroy on the controller instance: - * - * @codestart - * $(".special_controller").controller().destroy() - * @codeend - * - * ## Accessing Controllers - * - * Often you need to get a reference to a controller, there are a few ways of doing that. For the - * following example, we assume there are 2 elements with className="special". - * - * @codestart - * //creates 2 foo controllers - * $(".special").foo() - * - * //creates 2 bar controllers - * $(".special").bar() - * - * //gets all controllers on all elements: - * $(".special").controllers() //-> [foo, bar, foo, bar] - * - * //gets only foo controllers - * $(".special").controllers(FooController) //-> [foo, foo] - * - * //gets all bar controllers - * $(".special").controllers(BarController) //-> [bar, bar] - * - * //gets first controller - * $(".special").controller() //-> foo - * - * //gets foo controller via data - * $(".special").data("controllers")["FooController"] //-> foo - * @codeend - * - * ## Calling methods on Controllers - * - * Once you have a reference to an element, you can call methods on it. However, Controller has - * a few shortcuts: - * - * @codestart - * //creates foo controller - * $(".special").foo({name: "value"}) - * - * //calls FooController.prototype.update - * $(".special").foo({name: "value2"}) - * - * //calls FooController.prototype.bar - * $(".special").foo("bar","something I want to pass") - * @codeend - * - * These methods let you call one controller from another controller. - * - */ - $.Class("jQuery.Controller", - /** - * @Static - */ - { - /** - * Does 2 things: - * - * - Creates a jQuery helper for this controller. - * - Calculates and caches which functions listen for events. - * - * ### jQuery Helper Naming Examples - * - * - * "TaskController" -> $().task_controller() - * "Controllers.Task" -> $().controllers_task() - * - */ - setup: function() { - // Allow contollers to inherit "defaults" from superclasses as it done in $.Class - this._super.apply(this, arguments); - - // if you didn't provide a name, or are controller, don't do anything - if (!this.shortName || this.fullName == "jQuery.Controller" ) { - return; - } - // cache the underscored names - this._fullName = underscoreAndRemoveController(this.fullName); - this._shortName = underscoreAndRemoveController(this.shortName); - - var controller = this, - /** - * @attribute pluginName - * Setting the pluginName property allows you - * to change the jQuery plugin helper name from its - * default value. - * - * $.Controller("Mxui.Layout.Fill",{ - * pluginName: "fillWith" - * },{}); - * - * $("#foo").fillWith(); - */ - pluginname = this.pluginName || this._fullName, - funcName, forLint; - - // create jQuery plugin - if (!$.fn[pluginname] ) { - $.fn[pluginname] = function( options ) { - - var args = makeArray(arguments), - //if the arg is a method on this controller - isMethod = typeof options == "string" && isFunction(controller[STR_PROTOTYPE][options]), - meth = args[0]; - return this.each(function() { - //check if created - var controllers = data(this), - //plugin is actually the controller instance - plugin = controllers && controllers[pluginname]; - - if ( plugin ) { - if ( isMethod ) { - // call a method on the controller with the remaining args - plugin[meth].apply(plugin, args.slice(1)); - } else { - // call the plugin's update method - plugin.update.apply(plugin, args); - } - - } else { - //create a new controller instance - controller.newInstance.apply(controller, [this].concat(args)); - } - }); - }; - } - - // make sure listensTo is an array - //!steal-remove-start - if (!isArray(this.listensTo) ) { - throw "listensTo is not an array in " + this.fullName; - } - //!steal-remove-end - // calculate and cache actions - this.actions = {}; - - for ( funcName in this[STR_PROTOTYPE] ) { - if (funcName == 'constructor' || !isFunction(this[STR_PROTOTYPE][funcName]) ) { - continue; - } - if ( this._isAction(funcName) ) { - this.actions[funcName] = this._action(funcName); - } - } - }, - hookup: function( el ) { - return new this(el); - }, - - /** - * @hide - * @param {String} methodName a prototype function - * @return {Boolean} truthy if an action or not - */ - _isAction: function( methodName ) { - if ( actionMatcher.test(methodName) ) { - return true; - } else { - return $.inArray(methodName, this.listensTo) > -1 || $.event.special[methodName] || processors[methodName]; - } - - }, - /** - * @hide - * This takes a method name and the options passed to a controller - * and tries to return the data necessary to pass to a processor - * (something that binds things). - * - * For performance reasons, this called twice. First, it is called when - * the Controller class is created. If the methodName is templated - * like : "{window} foo", it returns null. If it is not templated - * it returns event binding data. - * - * The resulting data is added to this.actions. - * - * When a controller instance is created, _action is called again, but only - * on templated actions. - * - * @param {Object} methodName the method that will be bound - * @param {Object} [options] first param merged with class default options - * @return {Object} null or the processor and pre-split parts. - * The processor is what does the binding/subscribing. - */ - _action: function( methodName, options ) { - // reset the test index - parameterReplacer.lastIndex = 0; - - //if we don't have options (a controller instance), we'll run this later - if (!options && parameterReplacer.test(methodName) ) { - return null; - } - // If we have options, run sub to replace templates "{}" with a value from the options - // or the window - var convertedName = options ? Str.sub(methodName, [options, window]) : methodName, - - // If a "{}" resolves to an object, convertedName will be an array - arr = isArray(convertedName), - - // get the parts of the function = [convertedName, delegatePart, eventPart] - parts = (arr ? convertedName[1] : convertedName).match(breaker), - event = parts[2], - processor = processors[event] || basicProcessor; - return { - processor: processor, - parts: parts, - delegate : arr ? convertedName[0] : undefined - }; - }, - /** - * @attribute processors - * An object of {eventName : function} pairs that Controller uses to hook up events - * auto-magically. A processor function looks like: - * - * jQuery.Controller.processors. - * myprocessor = function( el, event, selector, cb, controller ) { - * //el - the controller's element - * //event - the event (myprocessor) - * //selector - the left of the selector - * //cb - the function to call - * //controller - the binding controller - * }; - * - * This would bind anything like: "foo~3242 myprocessor". - * - * 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 - * - * Listen to events on the document or window - * with templated event handlers: - * - * - * $.Controller('Sized',{ - * "{window} resize" : function(){ - * this.element.width(this.element.parent().width() / 2); - * } - * }); - * - * $('.foo').sized(); - */ - processors: {}, - /** - * @attribute listensTo - * An array 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: [], - /** - * @attribute defaults - * A object of name-value pairs that act as default values for a controller's - * [jQuery.Controller.prototype.options options]. - * - * $.Controller("Message", - * { - * defaults : { - * message : "Hello World" - * } - * },{ - * init : function(){ - * this.element.text(this.options.message); - * } - * }) - * - * $("#el1").message(); //writes "Hello World" - * $("#el12").message({message: "hi"}); //writes hi - * - * In [jQuery.Controller.prototype.setup setup] the options passed to the controller - * are merged with defaults. This is not a deep merge. - */ - defaults: {} - }, - /** - * @Prototype - */ - { - /** - * Setup is where most of controller's magic happens. It does the following: - * - * ### 1. Sets this.element - * - * The first parameter passed to new Controller(el, options) is expected to be - * an element. This gets converted to a jQuery wrapped element and set as - * [jQuery.Controller.prototype.element this.element]. - * - * ### 2. Adds the controller's name to the element's className. - * - * Controller adds it's plugin name to the element's className for easier - * debugging. For example, if your Controller is named "Foo.Bar", it adds - * "foo_bar" to the className. - * - * ### 3. Saves the controller in $.data - * - * A reference to the controller instance is saved in $.data. You can find - * instances of "Foo.Bar" like: - * - * $("#el").data("controllers")['foo_bar']. - * - * ### Binds event handlers - * - * Setup does the event binding described in [jquery.controller.listening Listening To Events]. - * - * @param {HTMLElement} element the element this instance operates on. - * @param {Object} [options] option values for the controller. These get added to - * this.options and merged with [jQuery.Controller.static.defaults defaults]. - * @return {Array} return an array if you wan to change what init is called with. By - * default it is called with the element and options passed to the controller. - */ - setup: function( element, options ) { - var funcName, ready, cls = this[STR_CONSTRUCTOR]; - - //want the raw element here - element = (typeof element == 'string' ? $(element) : - (element.jquery ? element : [element]) )[0]; - - //set element and className on element - var pluginname = cls.pluginName || cls._fullName; - - //set element and className on element - this.element = $(element).addClass(pluginname); - - //set in data - (data(element) || data(element, {}))[pluginname] = this; - - - /** - * @attribute options - * - * Options are used to configure an controller. They are - * the 2nd argument - * passed to a controller (or the first argument passed to the - * [jquery.controller.plugin controller's jQuery plugin]). - * - * For example: - * - * $.Controller('Hello') - * - * var h1 = new Hello($('#content1'), {message: 'World'} ); - * equal( h1.options.message , "World" ) - * - * var h2 = $('#content2').hello({message: 'There'}) - * .controller(); - * equal( h2.options.message , "There" ) - * - * Options are merged with [jQuery.Controller.static.defaults defaults] in - * [jQuery.Controller.prototype.setup setup]. - * - * For example: - * - * $.Controller("Tabs", - * { - * defaults : { - * activeClass: "ui-active-state" - * } - * }, - * { - * init : function(){ - * this.element.addClass(this.options.activeClass); - * } - * }) - * - * $("#tabs1").tabs() // adds 'ui-active-state' - * $("#tabs2").tabs({activeClass : 'active'}) // adds 'active' - * - * Options are typically updated by calling - * [jQuery.Controller.prototype.update update]; - * - */ - this.options = extend( extend(true, {}, cls.defaults), options); - - - - /** - * @attribute called - * String name of current function being called on controller instance. This is - * used for picking the right view in render. - * @hide - */ - this.called = "init"; - - // bind all event handlers - this.bind(); - - /** - * @attribute element - * The controller instance's delegated element. This - * is set by [jQuery.Controller.prototype.setup setup]. It - * is a jQuery wrapped element. - * - * For example, if I add MyWidget to a '#myelement' element like: - * - * $.Controller("MyWidget",{ - * init : function(){ - * this.element.css("color","red") - * } - * }) - * - * $("#myelement").my_widget() - * - * MyWidget will turn #myelement's font color red. - * - * ## Using a different element. - * - * Sometimes, you want a different element to be this.element. A - * very common example is making progressively enhanced form widgets. - * - * To change this.element, overwrite Controller's setup method like: - * - * $.Controller("Combobox",{ - * setup : function(el, options){ - * this.oldElement = $(el); - * var newEl = $('
    '); - * this.oldElement.wrap(newEl); - * this._super(newEl, options); - * }, - * init : function(){ - * this.element //-> the div - * }, - * ".option click" : function(){ - * // event handler bound on the div - * }, - * destroy : function(){ - * var div = this.element; //save reference - * this._super(); - * div.replaceWith(this.oldElement); - * } - * } - */ - return [this.element, this.options].concat(makeArray(arguments).slice(2)); - /** - * @function init - * - * Implement this. - */ - }, - /** - * Bind attaches event handlers that will be - * removed when the controller is removed. - * - * This used to be a good way to listen to events outside the controller's - * [jQuery.Controller.prototype.element element]. However, - * using templated event listeners is now the prefered way of doing this. - * - * ### Example: - * - * init: function() { - * // calls somethingClicked(el,ev) - * this.bind('click','somethingClicked') - * - * // calls function when the window is clicked - * this.bind(window, 'click', function(ev){ - * //do something - * }) - * }, - * somethingClicked: function( el, ev ) { - * - * } - * - * @param {HTMLElement|jQuery.fn|Object} [el=this.element] - * The element to be bound. If an eventName is provided, - * the controller's element is used instead. - * - * @param {String} eventName The event to listen for. - * @param {Function|String} func A callback function or the String name of a controller function. If a controller - * function name is given, the controller function is called back with the bound element and event as the first - * and second parameter. Otherwise the function is called back like a normal bind. - * @return {Integer} The id of the binding in this._bindings - */ - bind: function( el, eventName, func ) { - if( el === undefined ) { - //adds bindings - this._bindings = []; - //go through the cached list of actions and use the processor to bind - - var cls = this[STR_CONSTRUCTOR], - bindings = this._bindings, - actions = cls.actions, - element = this.element; - - for ( funcName in actions ) { - if ( actions.hasOwnProperty(funcName) ) { - ready = actions[funcName] || cls._action(funcName, this.options); - bindings.push( - ready.processor(ready.delegate || element, - ready.parts[2], - ready.parts[1], - funcName, - this)); - } - } - - - //setup to be destroyed ... don't bind b/c we don't want to remove it - var destroyCB = shifter(this,"destroy"); - element.bind("destroyed", destroyCB); - bindings.push(function( el ) { - $(el).unbind("destroyed", destroyCB); - }); - return bindings.length; - } - if ( typeof el == 'string' ) { - func = eventName; - eventName = el; - el = this.element; - } - return this._binder(el, eventName, func); - }, - _binder: function( el, eventName, func, selector ) { - if ( typeof func == 'string' ) { - func = shifter(this,func); - } - this._bindings.push(binder(el, eventName, func, selector)); - return this._bindings.length; - }, - _unbind : function(){ - var el = this.element[0]; - each(this._bindings, function( key, value ) { - value(el); - }); - //adds bindings - this._bindings = []; - }, - /** - * Delegate will delegate on an elememt and will be undelegated when the controller is removed. - * This is a good way to delegate on elements not in a controller's element.
    - *

    Example:

    - * @codestart - * // calls function when the any 'a.foo' is clicked. - * this.delegate(document.documentElement,'a.foo', 'click', function(ev){ - * //do something - * }) - * @codeend - * @param {HTMLElement|jQuery.fn} [element=this.element] the element to delegate from - * @param {String} selector the css selector - * @param {String} eventName the event to bind to - * @param {Function|String} func A callback function or the String name of a controller function. If a controller - * function name is given, the controller function is called back with the bound element and event as the first - * and second parameter. Otherwise the function is called back like a normal bind. - * @return {Integer} The id of the binding in this._bindings - */ - delegate: function( element, selector, eventName, func ) { - if ( typeof element == 'string' ) { - func = eventName; - eventName = selector; - selector = element; - element = this.element; - } - return this._binder(element, eventName, func, selector); - }, - /** - * Update extends [jQuery.Controller.prototype.options this.options] - * with the `options` argument and rebinds all events. It basically - * re-configures the controller. - * - * For example, the following controller wraps a recipe form. When the form - * is submitted, it creates the recipe on the server. When the recipe - * is `created`, it resets the form with a new instance. - * - * $.Controller('Creator',{ - * "{recipe} created" : function(){ - * this.update({recipe : new Recipe()}); - * this.element[0].reset(); - * this.find("[type=submit]").val("Create Recipe") - * }, - * "submit" : function(el, ev){ - * ev.preventDefault(); - * var recipe = this.options.recipe; - * recipe.attrs( this.element.formParams() ); - * this.find("[type=submit]").val("Saving...") - * recipe.save(); - * } - * }); - * $('#createRecipes').creator({recipe : new Recipe()}) - * - * - * @demo jquery/controller/demo-update.html - * - * Update is called if a controller's [jquery.controller.plugin jQuery helper] is - * called on an element that already has a controller instance - * of the same type. - * - * For example, a widget that listens for model updates - * and updates it's html would look like. - * - * $.Controller('Updater',{ - * // when the controller is created, update the html - * init : function(){ - * this.updateView(); - * }, - * - * // update the html with a template - * updateView : function(){ - * this.element.html( "content.ejs", - * this.options.model ); - * }, - * - * // if the model is updated - * "{model} updated" : function(){ - * this.updateView(); - * }, - * update : function(options){ - * // make sure you call super - * this._super(options); - * - * this.updateView(); - * } - * }) - * - * // create the controller - * // this calls init - * $('#item').updater({model: recipe1}); - * - * // later, update that model - * // this calls "{model} updated" - * recipe1.update({name: "something new"}); - * - * // later, update the controller with a new recipe - * // this calls update - * $('#item').updater({model: recipe2}); - * - * // later, update the new model - * // this calls "{model} updated" - * recipe2.update({name: "something newer"}); - * - * _NOTE:_ If you overwrite `update`, you probably need to call - * this._super. - * - * ### Example - * - * $.Controller("Thing",{ - * init: function( el, options ) { - * alert( 'init:'+this.options.prop ) - * }, - * update: function( options ) { - * this._super(options); - * alert('update:'+this.options.prop) - * } - * }); - * $('#myel').thing({prop : 'val1'}); // alerts init:val1 - * $('#myel').thing({prop : 'val2'}); // alerts update:val2 - * - * @param {Object} options A list of options to merge with - * [jQuery.Controller.prototype.options this.options]. Often, this method - * is called by the [jquery.controller.plugin jQuery helper function]. - */ - update: function( options ) { - extend(this.options, options); - this._unbind(); - this.bind(); - }, - /** - * Destroy unbinds and undelegates all event handlers on this controller, - * and prevents memory leaks. This is called automatically - * if the element is removed. You can overwrite it to add your own - * teardown functionality: - * - * $.Controller("ChangeText",{ - * init : function(){ - * this.oldText = this.element.text(); - * this.element.text("Changed!!!") - * }, - * destroy : function(){ - * this.element.text(this.oldText); - * this._super(); //Always call this! - * }) - * - * Make sure you always call _super when overwriting - * controller's destroy event. The base destroy functionality unbinds - * all event handlers the controller has created. - * - * You could call destroy manually on an element with ChangeText - * added like: - * - * $("#changed").change_text("destroy"); - * - */ - destroy: function() { - if ( this._destroyed ) { - throw this[STR_CONSTRUCTOR].shortName + " controller already deleted"; - } - var self = this, - fname = this[STR_CONSTRUCTOR].pluginName || this[STR_CONSTRUCTOR]._fullName, - controllers; - - // mark as destroyed - this._destroyed = true; - - // remove the className - this.element.removeClass(fname); - - // unbind bindings - this._unbind(); - // clean up - delete this._actions; - - delete this.element.data("controllers")[fname]; - - $(this).triggerHandler("destroyed"); //in case we want to know if the controller is removed - - this.element = null; - }, - /** - * Queries from the controller's element. - * @codestart - * ".destroy_all click" : function() { - * this.find(".todos").remove(); - * } - * @codeend - * @param {String} selector selection string - * @return {jQuery.fn} returns the matched elements - */ - find: function( selector ) { - return this.element.find(selector); - }, - //tells callback to set called on this. I hate this. - _set_called: true - }); - - var processors = $.Controller.processors, - - //------------- PROCESSSORS ----------------------------- - //processors do the binding. They return a function that - //unbinds when called. - //the basic processor that binds events - basicProcessor = function( el, event, selector, methodName, controller ) { - return binder(el, event, shifter(controller, methodName), selector); - }; - - - - - //set common events to be processed as a basicProcessor - 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; - }); - /** - * @add jQuery.fn - */ - - //used to determine if a controller instance is one of controllers - //controllers can be strings or classes - var i, isAControllerOf = function( instance, controllers ) { - for ( i = 0; i < controllers.length; i++ ) { - if ( typeof controllers[i] == 'string' ? instance[STR_CONSTRUCTOR]._shortName == controllers[i] : instance instanceof controllers[i] ) { - return true; - } - } - return false; - }; - $.fn.extend({ - /** - * @function controllers - * Gets all controllers in the jQuery element. - * @return {Array} an array of controller instances. - */ - controllers: function() { - var controllerNames = makeArray(arguments), - instances = [], - controllers, c, cname; - //check if arguments - this.each(function() { - - controllers = $.data(this, "controllers"); - for ( cname in controllers ) { - if ( controllers.hasOwnProperty(cname) ) { - c = controllers[cname]; - if (!controllerNames.length || isAControllerOf(c, controllerNames) ) { - instances.push(c); - } - } - } - }); - return instances; - }, - /** - * @function controller - * Gets a controller in the jQuery element. With no arguments, returns the first one found. - * @param {Object} controller (optional) if exists, the first controller instance with this class type will be returned. - * @return {jQuery.Controller} the first controller. - */ - controller: function( controller ) { - return this.controllers.apply(this, arguments)[0]; - } - }); +steal('jquery/class','can/control/plugin',function( $ ) { + $.Controller = can.Control; + $.fn.controller = $.fn.control; + $.fn.controllers = $.fn.controllers; }); diff --git a/controller/controller_test.js b/controller/controller_test.js index d94799e9..d2322b85 100644 --- a/controller/controller_test.js +++ b/controller/controller_test.js @@ -31,7 +31,7 @@ test("subscribe testing works", function(){ OpenAjax.hub.publish("a.b",{}) equals(subscribes,1, "can subscribe") var controllerInstance = ta.controller('my_test') - ok( controllerInstance.Class == MyTest, "can get controller" ) + ok( controllerInstance.constructor == MyTest, "can get controller" ) controllerInstance.destroy() equals(destroys,1, "destroy called once") @@ -117,7 +117,7 @@ test("delegate", function(){ }) var els = $("").appendTo($("#qunit-test-area")) var c = els.delegate_test(); - c.controller().delegate(els.find("span"), "a", "click", function(){ + c.controller().on(els.find("span"), "a", "click", function(){ called = true; }) els.find("a").trigger('click') diff --git a/dom/fixture/fixture.html b/dom/fixture/fixture.html deleted file mode 100644 index d59b1f30..00000000 --- a/dom/fixture/fixture.html +++ /dev/null @@ -1,144 +0,0 @@ - - - - Fixture Demo - - - -
    -
    -
    - - - - \ No newline at end of file diff --git a/dom/fixture/fixture.js b/dom/fixture/fixture.js deleted file mode 100644 index d2355033..00000000 --- a/dom/fixture/fixture.js +++ /dev/null @@ -1,903 +0,0 @@ -steal('jquery/dom', - 'jquery/lang/object', - 'jquery/lang/string',function( $ ) { - - //used to check urls - - - - // 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 - var data = overwrite(settings); - - // if we don't have a fixture, do nothing - if(!settings.fixture){ - if(window.location.protocol === "file:"){ - steal.dev.log("ajax request to " + settings.url+", no fixture found"); - } - 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.mapJoin(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.type+" "+ 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"); - - if(data){ - $.extend(originalOptions.data, data) - } - // add to settings data from 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 = [], - // returns the index of an overwrite function - find = function(settings, exact){ - for(var i =0; i < overwrites.length; i++){ - if($fixture._similar(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; - return $fixture._getData(overwrites[index].url, settings.url) - } - - }, - /** - * 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 && typeof settings.data === "number") { - id = settings.data; - } - - /* - Check for id in params(if query string) - If this is just a string representation of an id, parse - if(id === undefined && typeof settings.data === "string") { - id = settings.data; - } - //*/ - - 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) - } - - return id; - }; - - /** - * @function jQuery.fixture - * @plugin jquery/dom/fixture - * @download http://jmvcsite.heroku.com/pluginify?plugins[]=jquery/dom/fixture/fixture.js - * @test jquery/dom/fixture/qunit.html - * @parent dom - * - * $.fixture intercepts a AJAX request and simulates - * the response with a file or function. They are a great technique - * when you want to develop JavaScript - * independently of the backend. - * - * ## Types of Fixtures - * - * There are two common ways of using fixtures. The first is to - * map Ajax requests to another file. The following - * intercepts requests to /tasks.json and directs them - * to fixtures/tasks.json: - * - * $.fixture("/tasks.json","fixtures/tasks.json"); - * - * The other common option is to generate the Ajax response with - * a function. The following intercepts updating tasks at - * /tasks/ID.json and responds with updated data: - * - * $.fixture("PUT /tasks/{id}.json", function(original, settings, headers){ - * return { updatedAt : new Date().getTime() } - * }) - * - * We categorize fixtures into the following types: - * - * - __Static__ - the response is in a file. - * - __Dynamic__ - the response is generated by a function. - * - * There are different ways to lookup static and dynamic fixtures. - * - * ## 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"); - * - * $.fixture("tasks/1", "//fixtures/task1.json"); - * - * ## Dynamic Fixtures - * - * Dynamic Fixtures are functions that get the details of - * the Ajax request and return the result of the mocked service - * request from your server. - * - * For example, the following returns a successful response - * with JSON data from the server: - * - * $.fixture("/foobar.json", function(orig, settings, headers){ - * return [200, "success", {json: {foo: "bar" } }, {} ] - * }) - * - * The fixture function has the following signature: - * - * function( originalOptions, options, headers ) { - * return [ status, statusText, responses, responseHeaders ] - * } - * - * where the fixture function is called with: - * - * - originalOptions - are the options provided to the ajax method, unmodified, - * and thus, without defaults from ajaxSettings - * - options - are the request options - * - headers - a map of key/value request headers - * - * and the fixture function returns an array as arguments for ajaxTransport's completeCallback with: - * - * - status - is the HTTP status code of the response. - * - statusText - the status text of the response - * - responses - a map of dataType/value that contains the responses for each data format supported - * - headers - response headers - * - * However, $.fixture handles the - * common case where you want a successful response with JSON data. The - * previous can be written like: - * - * $.fixture("/foobar.json", function(orig, settings, headers){ - * return {foo: "bar" }; - * }) - * - * If you want to return an array of data, wrap your array in another array: - * - * $.fixture("/tasks.json", function(orig, settings, headers){ - * return [ [ "first","second","third"] ]; - * }) - * - * $.fixture works closesly with jQuery's - * ajaxTransport system. Understanding it is the key to creating advanced - * fixtures. - * - * ### Templated Urls - * - * Often, you want a dynamic fixture to handle urls - * for multiple resources (for example a REST url scheme). $.fixture's - * templated urls allow you to match urls with a wildcard. - * - * The following example simulates services that get and update 100 todos. - * - * // create todos - * var todos = {}; - * for(var i = 0; i < 100; i++) { - * todos[i] = { - * id: i, - * name: "Todo "+i - * } - * } - * $.fixture("GET /todos/{id}", function(orig){ - * // return the JSON data - * // notice that id is pulled from the url and added to data - * return todos[orig.data.id] - * }) - * $.fixture("PUT /todos/{id}", function(orig){ - * // update the todo's data - * $.extend( todos[orig.data.id], orig.data ); - * - * // return data - * return {}; - * }) - * - * Notice that data found in templated urls (ex: {id}) is added to the original - * data object. - * - * ## Simulating Errors - * - * The following simulates an unauthorized request - * to /foo. - * - * $.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'}" - * } - * }) - * - * ## Turning off Fixtures - * - * You can remove a fixture by passing null for the fixture option: - * - * // add a fixture - * $.fixture("GET todos.json","//fixtures/todos.json"); - * - * // remove the fixture - * $.fixture("GET todos.json", null) - * - * You can also set [jQuery.fixture.on $.fixture.on] to false: - * - * $.fixture.on = false; - * - * ## Make - * - * [jQuery.fixture.make $.fixture.make] makes a CRUD service layer that handles sorting, grouping, - * filtering and more. - * - * ## Testing Performance - * - * Dynamic fixtures are awesome for performance testing. Want to see what - * 10000 files does to your app's performance? Make a fixture that returns 10000 items. - * - * What to see what the app feels like when a request takes 5 seconds to return? Set - * [jQuery.fixture.delay] to 5000. - * - * @demo jquery/dom/fixture/fixture.html - * - * @param {Object|String} settings Configures the AJAX requests the fixture should - * intercept. If an __object__ is passed, the object's properties and values - * are matched against the settings passed to $.ajax. - * - * If a __string__ is passed, it can be used to match the url and type. Urls - * can be templated, using {NAME} as wildcards. - * - * @param {Function|String} fixture The response to use for the AJAX - * request. If a __string__ url is passed, the ajax request is redirected - * to the url. If a __function__ is provided, it looks like: - * - * fixture( originalSettings, settings, headers ) - * - * where: - * - * - originalSettings - the orignal settings passed to $.ajax - * - settings - the settings after all filters have run - * - headers - request headers - * - * If __null__ is passed, and there is a fixture at settings, that fixture will be removed, - * allowing the AJAX request to behave normally. - */ - var $fixture = $.fixture = function( settings , fixture ){ - // if we provide a fixture ... - if(fixture !== undefined){ - if(typeof settings == 'string'){ - // handle url strings - var matches = settings.match(/(GET|POST|PUT|DELETE) (.+)/i); - if(!matches){ - settings = { - url : settings - }; - } else { - settings = { - url : matches[2], - type: matches[1] - }; - } - - } - - //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) - } - }; - var replacer = $.String._regs.replacer; - - $.extend($.fixture, { - // given ajax settings, find an overwrite - _similar : function(settings, overwrite, exact){ - if(exact){ - return $.Object.same(settings , overwrite, {fixture : null}) - } else { - return $.Object.subset(settings, overwrite, $.fixture._compare) - } - }, - _compare : { - url : function(a, b){ - return !! $fixture._getData(b, a) - }, - fixture : null, - type : "i" - }, - // gets data from a url like "/todo/{id}" given "todo/5" - _getData : function(fixtureUrl, url){ - var order = [], - fixtureUrlAdjusted = fixtureUrl.replace('.', '\\.').replace('?', '\\?'), - res = new RegExp(fixtureUrlAdjusted.replace(replacer, function(whole, part){ - order.push(part) - return "([^\/]+)" - })+"$").exec(url), - data = {}; - - if(!res){ - return null; - } - res.shift(); - $.each(order, function(i, name){ - data[name] = res.shift() - }) - return data; - }, - /** - * @hide - * Provides a rest update fixture function - */ - "-restUpdate": function( settings ) { - return [200,"succes",{ - id: getId(settings) - },{ - location: settings.url+"/"+getId(settings) - }]; - }, - - /** - * @hide - * Provides a rest destroy fixture function - */ - "-restDestroy": function( settings, cbType ) { - return {}; - }, - - /** - * @hide - * Provides a rest create fixture function - */ - "-restCreate": function( settings, cbType, nul, id ) { - var id = id || parseInt(Math.random() * 100000, 10); - return [200,"succes",{ - id: id - },{ - location: settings.url+"/"+id - }]; - }, - - /** - * @function jQuery.fixture.make - * @parent jQuery.fixture - * Used to make fixtures for findAll / findOne style requests. - * - * //makes a nested list of messages - * $.fixture.make(["messages","message"],1000, function(i, messages){ - * return { - * subject: "This is message "+i, - * body: "Here is some text for this message", - * date: Math.floor( new Date().getTime() ), - * parentId : i < 100 ? null : Math.floor(Math.random()*i) - * } - * }) - * //uses the message fixture to return messages limited by offset, limit, order, etc. - * $.ajax({ - * url: "messages", - * data:{ - * offset: 100, - * limit: 50, - * order: ["date ASC"], - * parentId: 5}, - * }, - * fixture: "-messages", - * success: function( messages ) { ... } - * }); - * - * @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. 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: - * - * - * function(item, settings){ - * if(settings.data.searchText){ - * var regex = new RegExp("^"+settings.data.searchText) - * return regex.test(item.name); - * } - * } - * - */ - make: function( types, count, make, filter ) { - if(typeof types === "string"){ - types = [types+"s",types ] - } - // make all items - 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); - - if (!item.id ) { - item.id = i; - } - items.push(item); - } - //set plural fixture for findAll - $.fixture["-" + types[0]] = function( settings ) { - //copy array of items - var retArr = items.slice(0); - settings.data = settings.data || {}; - //sort using order - //order looks like ["age ASC","gender DESC"] - $.each((settings.data.order || []).slice(0).reverse(), function( i, name ) { - var split = name.split(" "); - retArr = retArr.sort(function( a, b ) { - if ( split[1].toUpperCase() !== "ASC" ) { - if( a[split[0]] < b[split[0]] ) { - return 1; - } else if(a[split[0]] == b[split[0]]){ - return 0 - } else { - return -1; - } - } - else { - if( a[split[0]] < b[split[0]] ) { - return -1; - } else if(a[split[0]] == b[split[0]]){ - return 0 - } else { - return 1; - } - } - }); - }); - - //group is just like a sort - $.each((settings.data.group || []).slice(0).reverse(), function( i, name ) { - var split = name.split(" "); - retArr = retArr.sort(function( a, b ) { - return a[split[0]] > b[split[0]]; - }); - }); - - - var offset = parseInt(settings.data.offset, 10) || 0, - limit = parseInt(settings.data.limit, 10) || (items.length - offset), - i = 0; - - //filter results if someone added an attr like parentId - for ( var param in settings.data ) { - i=0; - if ( settings.data[param] !== undefined && // 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); - } else { - i++; - } - } - } - } - - - 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 [{ - "count": retArr.length, - "limit": settings.data.limit, - "offset": settings.data.offset, - "data": retArr.slice(offset, offset + limit) - }]; - }; - // findOne - $.fixture["-" + types[1]] = function( settings ) { - var item = findOne(getId(settings)); - return item ? [item] : []; - }; - // 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, undefined, item.id ); - }; - - - return { - getId: getId, - findOne : findOne, - find : function(settings){ - return findOne( getId(settings) ); - } - } - }, - /** - * @function jQuery.fixture.rand - * @parent jQuery.fixture - * - * Creates random integers or random arrays of - * other arrays. - * - * ## Examples - * - * var rand = $.fixture.rand; - * - * // get a random integer between 0 and 10 (inclusive) - * rand(11); - * - * // get a random number between -5 and 5 (inclusive) - * rand(-5, 6); - * - * // pick a random item from an array - * rand(["j","m","v","c"],1)[0] - * - * // pick a random number of items from an array - * rand(["j","m","v","c"]) - * - * // pick 2 items from an array - * rand(["j","m","v","c"],2) - * - * // pick between 2 and 3 items at random - * rand(["j","m","v","c"],2,3) - * - * - * @param {Array|Number} arr An array of items to select from. - * If a number is provided, a random number is returned. - * If min and max are not provided, a random number of items are selected - * from this array. - * @param {Number} [min] If only min is provided, min items - * are selected. - * @param {Number} [max] If min and max are provided, a random number of - * items between min and max (inclusive) is selected. - */ - rand : function(arr, min, max){ - if(typeof arr == 'number'){ - if(typeof min == 'number'){ - return arr+ Math.floor(Math.random() * (min - arr) ); - } else { - return Math.floor(Math.random() * arr); - } - - } - var rand = arguments.callee; - // get a random set - if(min === undefined){ - return rand(arr, rand(arr.length+1)) - } - // get a random selection of arr - var res = []; - arr = arr.slice(0); - // set max - if(!max){ - max = min; - } - //random max - max = min + Math.round( rand(max - min) ) - for(var i=0; i < max; i++){ - res.push(arr.splice( rand(arr.length), 1 )[0]) - } - return res; - }, - /** - * @hide - * Use $.fixture.xhr to create an object that looks like an xhr object. - * - * ## Example - * - * The following example shows how the -restCreate fixture uses xhr to return - * a simulated xhr object: - * @codestart - * "-restCreate" : function( settings, cbType ) { - * switch(cbType){ - * case "success": - * return [ - * {id: parseInt(Math.random()*1000)}, - * "success", - * $.fixture.xhr()]; - * case "complete": - * return [ - * $.fixture.xhr({ - * getResponseHeader: function() { - * return settings.url+"/"+parseInt(Math.random()*1000); - * } - * }), - * "success"]; - * } - * } - * @codeend - * @param {Object} [xhr] properties that you want to overwrite - * @return {Object} an object that looks like a successful XHR object. - */ - xhr: function( xhr ) { - return $.extend({}, { - abort: $.noop, - getAllResponseHeaders: function() { - return ""; - }, - getResponseHeader: function() { - return ""; - }, - open: $.noop, - overrideMimeType: $.noop, - readyState: 4, - responseText: "", - responseXML: null, - send: $.noop, - setRequestHeader: $.noop, - 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 $.fixture.delay - * @parent $.fixture - * Sets the delay in milliseconds between an ajax request is made and - * the success and complete handlers are called. This only sets - * functional fixtures. By default, the delay is 200ms. - * @codestart - * steal('jquery/dom/fixtures').then(function(){ - * $.fixture.delay = 1000; - * }) - * @codeend - */ - $.fixture.delay = 200; - - $.fixture["-handleFunction"] = function( settings ) { - if ( typeof settings.fixture === "string" && $.fixture[settings.fixture] ) { - settings.fixture = $.fixture[settings.fixture]; - } - if ( typeof settings.fixture == "function" ) { - setTimeout(function() { - if ( settings.success ) { - settings.success.apply(null, settings.fixture(settings, "success")); - } - if ( settings.complete ) { - settings.complete.apply(null, settings.fixture(settings, "complete")); - } - }, $.fixture.delay); - return true; - } - return false; - }; - - - - /** - * @page jquery.fixture.0organizing Organizing Fixtures - * @parent jQuery.fixture - * - * The __best__ way of organizing fixtures is to have a 'fixtures.js' file that steals - * jquery/dom/fixture and defines all your fixtures. For example, - * if you have a 'todo' application, you might - * have todo/fixtures/fixtures.js 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 todo/fixtures/fixtures.js in the - * app file (todo/todo.js) like: - * - * - * steal({path: '//todo/fixtures/fixtures.js',ignore: true}); - * - * //start of your app's steals - * steal( ... ) - * - * 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) - * } - * - */ - //Expose this for fixture debugging - $.fixture.overwrites = overwrites; -}); diff --git a/dom/fixture/fixture_test.js b/dom/fixture/fixture_test.js deleted file mode 100644 index 7559d1ba..00000000 --- a/dom/fixture/fixture_test.js +++ /dev/null @@ -1,332 +0,0 @@ - -steal("jquery/dom/fixture", "jquery/model",'funcunit/qunit',function(){ - -module("jquery/dom/fixture"); - - -test("static fixtures", function(){ - stop(); - - $.fixture("GET something", "//jquery/dom/fixture/fixtures/test.json"); - $.fixture("POST something", "//jquery/dom/fixture/fixtures/test.json"); - - - $.get("something",function(data){ - equals(data.sweet,"ness","$.get works"); - - $.post("something",function(data){ - equals(data.sweet,"ness","$.post works"); - - - start(); - },'json'); - - },'json'); -}) - -test("dynamic fixtures",function(){ - stop(); - $.fixture.delay = 10; - $.fixture("something", function(){ - return [{sweet: "ness"}] - }) - - $.get("something",function(data){ - equals(data.sweet,"ness","$.get works"); - start(); - - },'json'); -}); - -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(); - } - }) -}) - -test("rand", function(){ - var rand = $.fixture.rand; - var num = rand(5); - equals(typeof num, "number"); - ok(num >= 0 && num < 5, "gets a number" ); - - stop(); - var zero, three, between, next = function(){ - start() - } - // make sure rand can be everything we need - setTimeout(function(){ - var res = rand([1,2,3]); - if(res.length == 0 ){ - zero = true; - } else if(res.length == 3){ - three = true; - } else { - between = true; - } - if(zero && three && between){ - ok(true, "got zero, three, between") - next(); - } else { - setTimeout(arguments.callee, 10) - } - }, 10) - -}); - - -test("_getData", function(){ - var data = $.fixture._getData("/thingers/{id}", "/thingers/5"); - equals(data.id, 5, "gets data"); - var data = $.fixture._getData("/thingers/5?hi.there", "/thingers/5?hi.there"); - deepEqual(data, {}, "gets data"); -}) - -test("_getData with double character value", function(){ - var data = $.fixture._getData("/days/{id}/time_slots.json", "/days/17/time_slots.json"); - equals(data.id, 17, "gets data"); -}); - -test("_compare", function(){ - var same = $.Object.same( - {url : "/thingers/5"}, - {url : "/thingers/{id}"}, $.fixture._compare) - - ok(same, "they are similar"); - - same = $.Object.same( - {url : "/thingers/5"}, - {url : "/thingers"}, $.fixture._compare); - - ok(!same, "they are not the same"); -}) - -test("_similar", function(){ - - var same = $.fixture._similar( - {url : "/thingers/5"}, - {url : "/thingers/{id}"}); - - ok(same, "similar"); - - same = $.fixture._similar( - {url : "/thingers/5", type: "get"}, - {url : "/thingers/{id}"}); - - ok(same, "similar with extra pops on settings"); - - var exact = $.fixture._similar( - {url : "/thingers/5", type: "get"}, - {url : "/thingers/{id}"}, true); - - ok(!exact, "not exact" ) - - var exact = $.fixture._similar( - {url : "/thingers/5"}, - {url : "/thingers/5"}, true); - - ok(exact, "exact" ) -}) - -test("fixture function gets id", function(){ - $.fixture("/thingers/{id}", function(settings){ - return { - id: settings.data.id, - name: "justin" - } - }) - stop(); - $.get("/thingers/5", {}, function(data){ - start(); - ok(data.id) - },'json') -}); - -test("replacing and removing a fixture", function(){ - var url = steal.root.join("jquery/dom/fixture/fixtures/remove.json")+'' - $.fixture("GET "+url, function(){ - return {weird: "ness!"} - }) - stop(); - $.get(url,{}, function(json){ - equals(json.weird,"ness!","fixture set right") - - $.fixture("GET "+url, function(){ - return {weird: "ness?"} - }) - - $.get(url,{}, function(json){ - equals(json.weird,"ness?","fixture set right"); - - $.fixture("GET "+url, null ) - - $.get(url,{}, function(json){ - equals(json.weird,"ness","fixture set right"); - - start(); - },'json'); - - - },'json') - - - - },'json') -}); - -return; // future fixture stuff - -// returning undefined means you want to control timing? -$.fixture('GET /foo', function(orig, settings, headers, cb){ - setTimeout(function(){ - cb(200, "success",{json : "{}"},{}) - },1000); -}) - -// fixture that hooks into model / vice versa? - -// fixture that creates a nice store - -var store = $.fixture.store(1000, function(){ - -}) - -store.find() - -// make cloud - -var clouds = $.fixture.store(1, function(){ - return { - name: "ESCCloud", - DN : "ESCCloud-ESCCloud", - type : "ESCCloud" - } -}); - -var computeCluster = $.fixture.store(5, function(i){ - return { - name : "", - parentDN : clouds.find()[0].DN, - type: "ComputeCluster", - DN : "ComputeCluster-ComputeCluster"+i - } -}); - -$.fixture("GET /computeclusters", function(){ - return [] -}); - -// hacking models? - - - - - -}); diff --git a/dom/fixture/fixtures/foo.json b/dom/fixture/fixtures/foo.json deleted file mode 100644 index 9fbc82d0..00000000 --- a/dom/fixture/fixtures/foo.json +++ /dev/null @@ -1 +0,0 @@ -{"a" : "b"} diff --git a/dom/fixture/fixtures/foobar.json b/dom/fixture/fixtures/foobar.json deleted file mode 100644 index a50afea1..00000000 --- a/dom/fixture/fixtures/foobar.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "sweet" :"ner" -} diff --git a/dom/fixture/fixtures/messages.html b/dom/fixture/fixtures/messages.html deleted file mode 100644 index 19cf1492..00000000 --- a/dom/fixture/fixtures/messages.html +++ /dev/null @@ -1,31 +0,0 @@ - -

    Create a Message

    -

    Create a message, it will show up in "Get Messages".

    -
    - - - - - - - - - - - - - - - - -
    From:
    Subject:
    Body:
    -
    -

    Get Messages

    -

    Enter a limit and offset to get a range of messages. -

    -
    - Offset - Limit - -
    -
    diff --git a/dom/fixture/fixtures/remove.json b/dom/fixture/fixtures/remove.json deleted file mode 100644 index 1e152b58..00000000 --- a/dom/fixture/fixtures/remove.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "weird" : "ness" -} diff --git a/dom/fixture/fixtures/test.json b/dom/fixture/fixtures/test.json deleted file mode 100644 index 6be2ce4f..00000000 --- a/dom/fixture/fixtures/test.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "sweet" :"ness" -} diff --git a/dom/fixture/qunit.html b/dom/fixture/qunit.html deleted file mode 100644 index 5207a04c..00000000 --- a/dom/fixture/qunit.html +++ /dev/null @@ -1,22 +0,0 @@ - - - Fixtures Test Suite - - - - - - -

    Fixtures Test Suite

    -

    -
    -

    -
    -
      -
      - - \ No newline at end of file diff --git a/jquery.js b/jquery.js deleted file mode 100644 index 74ce4119..00000000 --- a/jquery.js +++ /dev/null @@ -1,9266 +0,0 @@ -/*! - * jQuery JavaScript Library v1.7.1 - * http://jquery.com/ - * - * 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 2011, The Dojo Foundation - * Released under the MIT, BSD, and GPL Licenses. - * - * Date: Mon Nov 21 21:11:03 2011 -0500 - */ -(function( window, undefined ) { - -// Use the correct document accordingly with window argument (sandbox) -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, rootjQuery ); - }, - - // 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 - // Prioritize #id over to avoid XSS via location.hash (#9521) - quickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/, - - // 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>)?$/, - - // 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.]+))?/, - - // Matches dashed string for camelizing - rdashAlpha = /-([a-z]|[0-9])/ig, - rmsPrefix = /^-ms-/, - - // Used by jQuery.camelCase as callback to replace() - fcamelCase = function( all, letter ) { - return ( letter + "" ).toUpperCase(); - }, - - // Keep a UserAgent string for use with jQuery.browser - userAgent = navigator.userAgent, - - // For matching the engine and version of the browser - browserMatch, - - // The deferred used 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 = { - constructor: jQuery, - init: function( selector, context, rootjQuery ) { - 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 = selector; - this.length = 1; - return this; - } - - // Handle HTML strings - if ( typeof selector === "string" ) { - // Are we dealing with HTML string or an ID? - 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 - // 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 ? jQuery.clone(ret.fragment) : 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: $(expr, $(...)) - } else if ( !context || context.jquery ) { - return ( context || rootjQuery ).find( selector ); - - // HANDLE: $(expr, context) - // (which is just equivalent to: $(context).find(expr) - } else { - return this.constructor( 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.7.1", - - // 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[ 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 = this.constructor(); - - 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(); - - // Add the callback - readyList.add( fn ); - - return this; - }, - - eq: function( i ) { - i = +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 || this.constructor(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 ) { - if ( window.$ === jQuery ) { - window.$ = _$; - } - - 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, - - // Hold (or release) the ready event - holdReady: function( hold ) { - if ( hold ) { - jQuery.readyWait++; - } else { - jQuery.ready( true ); - } - }, - - // 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 ); - } - - // 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 - readyList.fireWith( document, [ jQuery ] ); - - // Trigger any bound ready events - if ( jQuery.fn.trigger ) { - jQuery( document ).trigger( "ready" ).off( "ready" ); - } - } - }, - - bindReady: function() { - if ( readyList ) { - return; - } - - readyList = jQuery.Callbacks( "once memory" ); - - // 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; - }, - - isNumeric: function( obj ) { - return !isNaN( parseFloat(obj) ) && isFinite( 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; - } - - try { - // Not own constructor property must be Object - if ( obj.constructor && - !hasOwn.call(obj, "constructor") && - !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) { - return false; - } - } catch ( e ) { - // IE8,9 Will throw exceptions on certain host objects #9897 - 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 new Error( 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 ); - - // 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, "")) ) { - - return ( new Function( "return " + data ) )(); - - } - jQuery.error( "Invalid JSON: " + data ); - }, - - // Cross-browser xml parsing - parseXML: function( data ) { - var xml, tmp; - try { - 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 ); - } - } catch( e ) { - xml = undefined; - } - if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) { - jQuery.error( "Invalid XML: " + data ); - } - return xml; - }, - - noop: function() {}, - - // 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 ); - } - }, - - // Convert dashed to camelCase; used by the css and data modules - // Microsoft forgot to hump their vendor prefix (#9572) - camelCase: function( string ) { - return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); - }, - - 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 ( ; i < length; ) { - if ( callback.call( object[ i ], i, object[ i++ ] ) === false ) { - break; - } - } - } - } - - 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' - // 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, i ) { - var len; - - if ( array ) { - if ( indexOf ) { - return indexOf.call( array, elem, i ); - } - - len = array.length; - i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0; - - for ( ; i < len; i++ ) { - // Skip accessing in sparse arrays - if ( i in array && 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 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 - 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; - } - } - } - - // Flatten any nested arrays - return ret.concat.apply( [], ret ); - }, - - // A global GUID counter for objects - guid: 1, - - // 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; - } - - // 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; - } - - // Simulated bind - var args = slice.call( arguments, 2 ), - proxy = function() { - 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 - proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++; - - return proxy; - }, - - // Mutifunctional method to get and set values to a collection - // The value/s can optionally be executed if it's 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" }; - }, - - 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: {} -}); - -// 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; -} - -// IE doesn't match non-breaking spaces with \s -if ( rnotwhite.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(); -} - -return jQuery; - -})(); - - -// String to Object flags format cache -var flagsCache = {}; - -// Convert String-formatted flags into Object-formatted ones and store in cache -function createFlags( flags ) { - var object = flagsCache[ flags ] = {}, - i, length; - flags = flags.split( /\s+/ ); - for ( i = 0, length = flags.length; i < length; i++ ) { - object[ flags[i] ] = true; - } - return object; -} - -/* - * Create a callback list using the following parameters: - * - * flags: an optional list of space-separated flags that will change how - * the callback list behaves - * - * By default a callback list will act like an event callback list and can be - * "fired" multiple times. - * - * Possible flags: - * - * once: will ensure the callback list can only be fired once (like a Deferred) - * - * memory: will keep track of previous values and will call any callback added - * after the list has been fired right away with the latest "memorized" - * values (like a Deferred) - * - * unique: will ensure a callback can only be added once (no duplicate in the list) - * - * stopOnFalse: interrupt callings when a callback returns false - * - */ -jQuery.Callbacks = function( flags ) { - - // Convert flags from String-formatted to Object-formatted - // (we check in cache first) - flags = flags ? ( flagsCache[ flags ] || createFlags( flags ) ) : {}; - - var // Actual callback list - list = [], - // Stack of fire calls for repeatable lists - stack = [], - // Last fire value (for non-forgettable lists) - memory, - // Flag to know if list is currently firing - firing, - // First callback to fire (used internally by add and fireWith) - firingStart, - // End of the loop when firing - firingLength, - // Index of currently firing callback (modified by remove if needed) - firingIndex, - // Add one or several callbacks to the list - add = function( args ) { - var i, - length, - elem, - type, - actual; - for ( i = 0, length = args.length; i < length; i++ ) { - elem = args[ i ]; - type = jQuery.type( elem ); - if ( type === "array" ) { - // Inspect recursively - add( elem ); - } else if ( type === "function" ) { - // Add if not in unique mode and callback is not in - if ( !flags.unique || !self.has( elem ) ) { - list.push( elem ); - } - } - } - }, - // Fire callbacks - fire = function( context, args ) { - args = args || []; - memory = !flags.memory || [ context, args ]; - firing = true; - firingIndex = firingStart || 0; - firingStart = 0; - firingLength = list.length; - for ( ; list && firingIndex < firingLength; firingIndex++ ) { - if ( list[ firingIndex ].apply( context, args ) === false && flags.stopOnFalse ) { - memory = true; // Mark as halted - break; - } - } - firing = false; - if ( list ) { - if ( !flags.once ) { - if ( stack && stack.length ) { - memory = stack.shift(); - self.fireWith( memory[ 0 ], memory[ 1 ] ); - } - } else if ( memory === true ) { - self.disable(); - } else { - list = []; - } - } - }, - // Actual Callbacks object - self = { - // Add a callback or a collection of callbacks to the list - add: function() { - if ( list ) { - var length = list.length; - add( arguments ); - // Do we need to add the callbacks to the - // current firing batch? - if ( firing ) { - firingLength = list.length; - // With memory, if we're not firing then - // we should call right away, unless previous - // firing was halted (stopOnFalse) - } else if ( memory && memory !== true ) { - firingStart = length; - fire( memory[ 0 ], memory[ 1 ] ); - } - } - return this; - }, - // Remove a callback from the list - remove: function() { - if ( list ) { - var args = arguments, - argIndex = 0, - argLength = args.length; - for ( ; argIndex < argLength ; argIndex++ ) { - for ( var i = 0; i < list.length; i++ ) { - if ( args[ argIndex ] === list[ i ] ) { - // Handle firingIndex and firingLength - if ( firing ) { - if ( i <= firingLength ) { - firingLength--; - if ( i <= firingIndex ) { - firingIndex--; - } - } - } - // Remove the element - list.splice( i--, 1 ); - // If we have some unicity property then - // we only need to do this once - if ( flags.unique ) { - break; - } - } - } - } - } - return this; - }, - // Control if a given callback is in the list - has: function( fn ) { - if ( list ) { - var i = 0, - length = list.length; - for ( ; i < length; i++ ) { - if ( fn === list[ i ] ) { - return true; - } - } - } - return false; - }, - // Remove all callbacks from the list - empty: function() { - list = []; - return this; - }, - // Have the list do nothing anymore - disable: function() { - list = stack = memory = undefined; - return this; - }, - // Is it disabled? - disabled: function() { - return !list; - }, - // Lock the list in its current state - lock: function() { - stack = undefined; - if ( !memory || memory === true ) { - self.disable(); - } - return this; - }, - // Is it locked? - locked: function() { - return !stack; - }, - // Call all callbacks with the given context and arguments - fireWith: function( context, args ) { - if ( stack ) { - if ( firing ) { - if ( !flags.once ) { - stack.push( [ context, args ] ); - } - } else if ( !( flags.once && memory ) ) { - fire( context, args ); - } - } - return this; - }, - // Call all the callbacks with the given arguments - fire: function() { - self.fireWith( this, arguments ); - return this; - }, - // To know if the callbacks have already been called at least once - fired: function() { - return !!memory; - } - }; - - return self; -}; - - - - -var // Static reference to slice - sliceDeferred = [].slice; - -jQuery.extend({ - - Deferred: function( func ) { - var doneList = jQuery.Callbacks( "once memory" ), - failList = jQuery.Callbacks( "once memory" ), - progressList = jQuery.Callbacks( "memory" ), - state = "pending", - lists = { - resolve: doneList, - reject: failList, - notify: progressList - }, - promise = { - done: doneList.add, - fail: failList.add, - progress: progressList.add, - - state: function() { - return state; - }, - - // Deprecated - isResolved: doneList.fired, - isRejected: failList.fired, - - then: function( doneCallbacks, failCallbacks, progressCallbacks ) { - deferred.done( doneCallbacks ).fail( failCallbacks ).progress( progressCallbacks ); - return this; - }, - always: function() { - deferred.done.apply( deferred, arguments ).fail.apply( deferred, arguments ); - return this; - }, - pipe: function( fnDone, fnFail, fnProgress ) { - return jQuery.Deferred(function( newDefer ) { - jQuery.each( { - done: [ fnDone, "resolve" ], - fail: [ fnFail, "reject" ], - progress: [ fnProgress, "notify" ] - }, 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, newDefer.notify ); - } else { - newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ 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 ) { - obj = promise; - } else { - for ( var key in promise ) { - obj[ key ] = promise[ key ]; - } - } - return obj; - } - }, - deferred = promise.promise({}), - key; - - for ( key in lists ) { - deferred[ key ] = lists[ key ].fire; - deferred[ key + "With" ] = lists[ key ].fireWith; - } - - // Handle state - deferred.done( function() { - state = "resolved"; - }, failList.disable, progressList.lock ).fail( function() { - state = "rejected"; - }, doneList.disable, progressList.lock ); - - // Call given func if any - if ( func ) { - func.call( deferred, deferred ); - } - - // All done! - return deferred; - }, - - // Deferred helper - when: function( firstParam ) { - var args = sliceDeferred.call( arguments, 0 ), - i = 0, - length = args.length, - pValues = new Array( length ), - count = length, - pCount = length, - deferred = length <= 1 && firstParam && jQuery.isFunction( firstParam.promise ) ? - firstParam : - jQuery.Deferred(), - promise = deferred.promise(); - function resolveFunc( i ) { - return function( value ) { - args[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value; - if ( !( --count ) ) { - deferred.resolveWith( deferred, args ); - } - }; - } - function progressFunc( i ) { - return function( value ) { - pValues[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value; - deferred.notifyWith( promise, pValues ); - }; - } - if ( length > 1 ) { - for ( ; i < length; i++ ) { - if ( args[ i ] && args[ i ].promise && jQuery.isFunction( args[ i ].promise ) ) { - args[ i ].promise().then( resolveFunc(i), deferred.reject, progressFunc(i) ); - } else { - --count; - } - } - if ( !count ) { - deferred.resolveWith( deferred, args ); - } - } else if ( deferred !== firstParam ) { - deferred.resolveWith( deferred, length ? [ firstParam ] : [] ); - } - return promise; - } -}); - - - - -jQuery.support = (function() { - - var support, - all, - a, - select, - opt, - input, - marginDiv, - fragment, - tds, - events, - eventName, - i, - isSupported, - div = document.createElement( "div" ), - documentElement = document.documentElement; - - // Preliminary tests - div.setAttribute("className", "t"); - div.innerHTML = "
      a"; - - all = div.getElementsByTagName( "*" ); - a = div.getElementsByTagName( "a" )[ 0 ]; - - // Can't get basic test support - if ( !all || !all.length || !a ) { - return {}; - } - - // 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 ), - - // 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 instead) - style: /top/.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: ( 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", - - // Tests for enctype support on a form(#6743) - enctype: !!document.createElement("form").enctype, - - // Makes sure cloning an html5 element does not cause problems - // Where outerHTML is undefined, this still works - html5Clone: document.createElement("nav").cloneNode( true ).outerHTML !== "<:nav>", - - // Will be defined later - submitBubbles: true, - changeBubbles: true, - focusinBubbles: false, - deleteExpando: true, - noCloneEvent: true, - inlineBlockNeedsLayout: false, - shrinkWrapBlocks: false, - 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 disabled) - select.disabled = true; - support.optDisabled = !opt.disabled; - - // Test to see if it's possible to delete an expando from an element - // Fails in Internet Explorer - try { - delete div.test; - } catch( e ) { - support.deleteExpando = false; - } - - if ( !div.addEventListener && div.attachEvent && div.fireEvent ) { - div.attachEvent( "onclick", function() { - // Cloning a node shouldn't copy over any - // bound event handlers (IE does this) - support.noCloneEvent = false; - }); - div.cloneNode( true ).fireEvent( "onclick" ); - } - - // Check if a radio maintains its value - // after being appended to the DOM - input = document.createElement("input"); - input.value = "t"; - input.setAttribute("type", "radio"); - support.radioValue = input.value === "t"; - - input.setAttribute("checked", "checked"); - div.appendChild( input ); - fragment = document.createDocumentFragment(); - fragment.appendChild( div.lastChild ); - - // WebKit doesn't clone checked state correctly in fragments - support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked; - - // Check if a disconnected checkbox will retain its checked - // value of true after appended to the DOM (IE6/7) - support.appendChecked = input.checked; - - fragment.removeChild( input ); - fragment.appendChild( div ); - - 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 ( window.getComputedStyle ) { - marginDiv = document.createElement( "div" ); - marginDiv.style.width = "0"; - marginDiv.style.marginRight = "0"; - div.style.width = "2px"; - div.appendChild( marginDiv ); - support.reliableMarginRight = - ( parseInt( ( window.getComputedStyle( marginDiv, null ) || { marginRight: 0 } ).marginRight, 10 ) || 0 ) === 0; - } - - // Technique from Juriy Zaytsev - // http://perfectionkills.com/detecting-event-support-without-browser-sniffing/ - // 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; - } - } - - fragment.removeChild( div ); - - // Null elements to avoid leaks in IE - fragment = select = opt = marginDiv = div = input = null; - - // Run tests that need a body at doc ready - jQuery(function() { - var container, outer, inner, table, td, offsetSupport, - conMarginTop, ptlm, vb, style, html, - body = document.getElementsByTagName("body")[0]; - - if ( !body ) { - // Return for frameset docs that don't have a body - return; - } - - conMarginTop = 1; - ptlm = "position:absolute;top:0;left:0;width:1px;height:1px;margin:0;"; - vb = "visibility:hidden;border:0;"; - style = "style='" + ptlm + "border:5px solid #000;padding:0;'"; - html = "
      " + - "" + - "
      "; - - container = document.createElement("div"); - container.style.cssText = vb + "width:0;height:0;position:static;top:0;margin-top:" + conMarginTop + "px"; - body.insertBefore( container, body.firstChild ); - - // Construct the test element - div = document.createElement("div"); - container.appendChild( div ); - - // 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) - div.innerHTML = "
      t
      "; - tds = div.getElementsByTagName( "td" ); - 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 ); - - // Figure out if the W3C box model works as expected - div.innerHTML = ""; - div.style.width = div.style.paddingLeft = "1px"; - jQuery.boxModel = support.boxModel = div.offsetWidth === 2; - - if ( typeof div.style.zoom !== "undefined" ) { - // 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 = "
      "; - support.shrinkWrapBlocks = ( div.offsetWidth !== 2 ); - } - - div.style.cssText = ptlm + vb; - div.innerHTML = html; - - outer = div.firstChild; - inner = outer.firstChild; - td = outer.nextSibling.firstChild.firstChild; - - offsetSupport = { - doesNotAddBorder: ( inner.offsetTop !== 5 ), - doesAddBorderForTableAndCells: ( td.offsetTop === 5 ) - }; - - inner.style.position = "fixed"; - inner.style.top = "20px"; - - // safari subtracts parent border width here which is 5px - offsetSupport.fixedPosition = ( inner.offsetTop === 20 || inner.offsetTop === 15 ); - inner.style.position = inner.style.top = ""; - - outer.style.overflow = "hidden"; - outer.style.position = "relative"; - - offsetSupport.subtractsBorderForOverflowNotVisible = ( inner.offsetTop === -5 ); - offsetSupport.doesNotIncludeMarginInBodyOffset = ( body.offsetTop !== conMarginTop ); - - body.removeChild( container ); - div = container = null; - - jQuery.extend( support, offsetSupport ); - }); - - return support; -})(); - - - - -var rbrace = /^(?:\{.*\}|\[.*\])$/, - rmultiDash = /([A-Z])/g; - -jQuery.extend({ - cache: {}, - - // Please use with caution - uuid: 0, - - // 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. - noData: { - "embed": true, - // Ban all objects except for Flash (which handle expandos) - "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000", - "applet": true - }, - - 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; - } - - var privateCache, thisCache, ret, - internalKey = jQuery.expando, - getByName = typeof name === "string", - - // 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, - - // 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, - - // 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[ internalKey ] : elem[ internalKey ] && internalKey, - isEvents = name === "events"; - - // 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 || !cache[id] || (!isEvents && !pvt && !cache[id].data)) && getByName && data === undefined ) { - return; - } - - if ( !id ) { - // Only DOM nodes need a new unique ID for each element since their data - // ends up in the global cache - if ( isNode ) { - elem[ internalKey ] = id = ++jQuery.uuid; - } else { - id = internalKey; - } - } - - if ( !cache[ id ] ) { - cache[ id ] = {}; - - // 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 ] = jQuery.extend( cache[ id ], name ); - } else { - cache[ id ].data = jQuery.extend( cache[ id ].data, name ); - } - } - - privateCache = thisCache = cache[ id ]; - - // jQuery data() is stored in a separate object inside the object's internal data - // cache in order to avoid key collisions between internal data and user-defined - // data. - if ( !pvt ) { - if ( !thisCache.data ) { - thisCache.data = {}; - } - - thisCache = thisCache.data; - } - - if ( data !== undefined ) { - thisCache[ jQuery.camelCase( name ) ] = data; - } - - // Users should not attempt to inspect the internal events object using jQuery.data, - // it is undocumented and subject to change. But does anyone listen? No. - if ( isEvents && !thisCache[ name ] ) { - return privateCache.events; - } - - // Check for both converted-to-camel and non-converted data property names - // If a data property was specified - if ( getByName ) { - - // First Try to find as-is property data - ret = thisCache[ name ]; - - // Test for null|undefined property data - if ( ret == null ) { - - // Try to find the camelCased property - ret = thisCache[ jQuery.camelCase( name ) ]; - } - } else { - ret = thisCache; - } - - return ret; - }, - - removeData: function( elem, name, pvt /* Internal Use Only */ ) { - if ( !jQuery.acceptData( elem ) ) { - return; - } - - var thisCache, i, l, - - // Reference to internal data cache key - internalKey = jQuery.expando, - - isNode = elem.nodeType, - - // See jQuery.data for more information - cache = isNode ? jQuery.cache : elem, - - // See jQuery.data for more information - id = isNode ? elem[ internalKey ] : internalKey; - - // If there is already no cache entry for this object, there is no - // purpose in continuing - if ( !cache[ id ] ) { - return; - } - - if ( name ) { - - thisCache = pvt ? cache[ id ] : cache[ id ].data; - - if ( thisCache ) { - - // Support array or space separated string names for data keys - if ( !jQuery.isArray( name ) ) { - - // try the string as a key before any manipulation - if ( name in thisCache ) { - name = [ name ]; - } else { - - // split the camel cased version by spaces unless a key with the spaces exists - name = jQuery.camelCase( name ); - if ( name in thisCache ) { - name = [ name ]; - } else { - name = name.split( " " ); - } - } - } - - for ( i = 0, l = name.length; i < l; i++ ) { - delete thisCache[ name[i] ]; - } - - // If there is no data left in the cache, we want to continue - // and let the cache object itself get destroyed - if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) { - return; - } - } - } - - // See jQuery.data for more information - if ( !pvt ) { - delete cache[ id ].data; - - // Don't destroy the parent cache unless the internal data object - // had been the only thing left in it - if ( !isEmptyDataObject(cache[ id ]) ) { - return; - } - } - - // 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 - // Ensure that `cache` is not a window object #10080 - if ( jQuery.support.deleteExpando || !cache.setInterval ) { - delete cache[ id ]; - } else { - cache[ id ] = null; - } - - // We destroyed the cache and need to eliminate the expando on the node to avoid - // false lookups in the cache for entries that no longer exist - 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[ internalKey ]; - } else if ( elem.removeAttribute ) { - elem.removeAttribute( internalKey ); - } else { - elem[ internalKey ] = 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 ) { - 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 parts, attr, name, - data = null; - - if ( typeof key === "undefined" ) { - if ( this.length ) { - data = jQuery.data( this[0] ); - - if ( this[0].nodeType === 1 && !jQuery._data( this[0], "parsedAttrs" ) ) { - attr = this[0].attributes; - for ( var i = 0, l = attr.length; i < l; i++ ) { - name = attr[i].name; - - if ( name.indexOf( "data-" ) === 0 ) { - name = jQuery.camelCase( name.substring(5) ); - - dataAttr( this[0], name, data[ name ] ); - } - } - jQuery._data( this[0], "parsedAttrs", true ); - } - } - - return data; - - } else if ( typeof key === "object" ) { - return this.each(function() { - jQuery.data( this, key ); - }); - } - - 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 self = jQuery( this ), - args = [ parts[0], value ]; - - self.triggerHandler( "setData" + parts[1] + "!", args ); - jQuery.data( this, key, value ); - self.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 ) { - - var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase(); - - data = elem.getAttribute( name ); - - if ( typeof data === "string" ) { - try { - data = data === "true" ? true : - data === "false" ? false : - data === "null" ? null : - jQuery.isNumeric( 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; -} - -// checks a cache object for emptiness -function isEmptyDataObject( obj ) { - for ( var name in obj ) { - - // if the public data object is empty, the private is still empty - if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) { - continue; - } - if ( name !== "toJSON" ) { - return false; - } - } - - return true; -} - - - - -function handleQueueMarkDefer( elem, type, src ) { - var deferDataKey = type + "defer", - queueDataKey = type + "queue", - markDataKey = type + "mark", - defer = jQuery._data( elem, deferDataKey ); - if ( defer && - ( src === "queue" || !jQuery._data(elem, queueDataKey) ) && - ( src === "mark" || !jQuery._data(elem, markDataKey) ) ) { - // 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 ) && - !jQuery._data( elem, markDataKey ) ) { - jQuery.removeData( elem, deferDataKey, true ); - defer.fire(); - } - }, 0 ); - } -} - -jQuery.extend({ - - _mark: function( elem, type ) { - if ( elem ) { - type = ( type || "fx" ) + "mark"; - jQuery._data( elem, type, (jQuery._data( elem, type ) || 0) + 1 ); - } - }, - - _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 ) || 1) - 1 ); - if ( count ) { - jQuery._data( elem, key, count ); - } else { - jQuery.removeData( elem, key, true ); - handleQueueMarkDefer( elem, type, "mark" ); - } - } - }, - - queue: function( elem, type, data ) { - var q; - if ( elem ) { - type = ( type || "fx" ) + "queue"; - q = jQuery._data( elem, type ); - - // 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) ); - } else { - q.push( data ); - } - } - return q || []; - } - }, - - dequeue: function( elem, type ) { - type = type || "fx"; - - var queue = jQuery.queue( elem, type ), - fn = queue.shift(), - hooks = {}; - - // 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" ); - } - - jQuery._data( elem, type + ".run", hooks ); - fn.call( elem, function() { - jQuery.dequeue( elem, type ); - }, hooks ); - } - - if ( !queue.length ) { - jQuery.removeData( elem, type + "queue " + type + ".run", true ); - handleQueueMarkDefer( elem, type, "queue" ); - } - } -}); - -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() { - 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( next, hooks ) { - var timeout = setTimeout( next, time ); - hooks.stop = function() { - clearTimeout( timeout ); - }; - }); - }, - 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.Callbacks( "once memory" ), true ) )) { - count++; - tmp.add( resolve ); - } - } - resolve(); - return defer.promise(); - } -}); - - - - -var rclass = /[\n\t\r]/g, - rspace = /\s+/, - rreturn = /\r/g, - rtype = /^(?:button|input)$/i, - rfocusable = /^(?:button|input|object|select|textarea)$/i, - rclickable = /^a(?:rea)?$/i, - rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i, - getSetAttribute = jQuery.support.getSetAttribute, - nodeHook, boolHook, fixSpecified; - -jQuery.fn.extend({ - attr: function( name, value ) { - return jQuery.access( this, name, value, true, jQuery.attr ); - }, - - 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 ) { - var classNames, i, l, elem, - setClass, c, cl; - - if ( jQuery.isFunction( value ) ) { - return this.each(function( j ) { - jQuery( this ).addClass( value.call(this, j, this.className) ); - }); - } - - if ( value && typeof value === "string" ) { - classNames = value.split( rspace ); - - for ( i = 0, l = this.length; i < l; i++ ) { - elem = this[ i ]; - - if ( elem.nodeType === 1 ) { - if ( !elem.className && classNames.length === 1 ) { - elem.className = value; - - } else { - setClass = " " + elem.className + " "; - - for ( c = 0, cl = classNames.length; c < cl; c++ ) { - if ( !~setClass.indexOf( " " + classNames[ c ] + " " ) ) { - setClass += classNames[ c ] + " "; - } - } - elem.className = jQuery.trim( setClass ); - } - } - } - } - - return this; - }, - - removeClass: function( value ) { - var classNames, i, l, elem, className, c, cl; - - if ( jQuery.isFunction( value ) ) { - return this.each(function( j ) { - jQuery( this ).removeClass( value.call(this, j, this.className) ); - }); - } - - if ( (value && typeof value === "string") || value === undefined ) { - classNames = ( value || "" ).split( rspace ); - - for ( i = 0, l = this.length; i < l; i++ ) { - elem = this[ i ]; - - if ( elem.nodeType === 1 && elem.className ) { - if ( value ) { - className = (" " + elem.className + " ").replace( rclass, " " ); - for ( 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 ) { - jQuery( this ).toggleClass( value.call(this, i, this.className, 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 + " ", - i = 0, - l = this.length; - for ( ; i < l; i++ ) { - if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) { - return true; - } - } - - return false; - }, - - val: function( value ) { - var hooks, ret, isFunction, - elem = this[0]; - - if ( !arguments.length ) { - if ( elem ) { - hooks = jQuery.valHooks[ elem.nodeName.toLowerCase() ] || jQuery.valHooks[ elem.type ]; - - if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) { - return ret; - } - - ret = elem.value; - - return typeof ret === "string" ? - // handle most common string cases - ret.replace(rreturn, "") : - // handle cases where value is null/undef or number - ret == null ? "" : ret; - } - - return; - } - - isFunction = jQuery.isFunction( 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() ); - } else { - val = value; - } - - // 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 + ""; - }); - } - - 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, i, max, option, - 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 - i = one ? index : 0; - max = one ? index + 1 : options.length; - for ( ; i < max; i++ ) { - 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 ); - } - } - - // 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 ) { - elem.selectedIndex = -1; - } - return values; - } - } - }, - - attrFn: { - val: true, - css: true, - html: true, - text: true, - data: true, - width: true, - height: true, - offset: true - }, - - attr: function( elem, name, value, pass ) { - var ret, hooks, notxml, - nType = elem.nodeType; - - // don't get/set attributes on text, comment and attribute nodes - if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { - return; - } - - if ( pass && name in jQuery.attrFn ) { - return jQuery( elem )[ name ]( value ); - } - - // Fallback to prop when attributes are not supported - if ( typeof elem.getAttribute === "undefined" ) { - return jQuery.prop( elem, name, value ); - } - - notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); - - // All attributes are lowercase - // Grab necessary hook if one is defined - if ( notxml ) { - name = name.toLowerCase(); - hooks = jQuery.attrHooks[ name ] || ( rboolean.test( name ) ? boolHook : nodeHook ); - } - - if ( value !== undefined ) { - - if ( value === null ) { - jQuery.removeAttr( elem, name ); - return; - - } else if ( hooks && "set" in hooks && notxml && (ret = hooks.set( elem, value, name )) !== undefined ) { - return ret; - - } else { - elem.setAttribute( name, "" + value ); - return value; - } - - } else if ( hooks && "get" in hooks && notxml && (ret = hooks.get( elem, name )) !== null ) { - return ret; - - } else { - - ret = elem.getAttribute( name ); - - // Non-existent attributes return null, we normalize to undefined - return ret === null ? - undefined : - ret; - } - }, - - removeAttr: function( elem, value ) { - var propName, attrNames, name, l, - i = 0; - - if ( value && elem.nodeType === 1 ) { - attrNames = value.toLowerCase().split( rspace ); - l = attrNames.length; - - for ( ; i < l; i++ ) { - name = attrNames[ i ]; - - if ( name ) { - propName = jQuery.propFix[ name ] || name; - - // See #9699 for explanation of this approach (setting first, then removal) - jQuery.attr( elem, name, "" ); - elem.removeAttribute( getSetAttribute ? name : propName ); - - // Set corresponding property to false for boolean attributes - if ( rboolean.test( name ) && propName 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; - } - } - }, - // Use the value property for back compat - // Use the nodeHook for button elements in IE6/7 (#1954) - value: { - get: function( elem, name ) { - if ( nodeHook && jQuery.nodeName( elem, "button" ) ) { - return nodeHook.get( elem, name ); - } - return name in elem ? - elem.value : - null; - }, - set: function( elem, value, name ) { - if ( nodeHook && jQuery.nodeName( elem, "button" ) ) { - return nodeHook.set( elem, value, name ); - } - // Does not return so that setAttribute is also used - elem.value = value; - } - } - }, - - 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 ret, hooks, notxml, - nType = elem.nodeType; - - // don't get/set properties on text, comment and attribute nodes - if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { - return; - } - - notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); - - if ( notxml ) { - // Fix name and attach hooks - name = 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 ); - } - - } else { - if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) { - return ret; - - } else { - return elem[ name ]; - } - } - }, - - propHooks: { - 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 ? - parseInt( attributeNode.value, 10 ) : - rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? - 0 : - undefined; - } - } - } -}); - -// Add the tabIndex propHook to attrHooks for back-compat (different case is intentional) -jQuery.attrHooks.tabindex = jQuery.propHooks.tabIndex; - -// Hook for boolean attributes -boolHook = { - get: function( elem, name ) { - // Align boolean attributes with corresponding properties - // Fall back to attribute presence where some booleans are not supported - var attrNode, - property = jQuery.prop( elem, name ); - return property === true || typeof property !== "boolean" && ( attrNode = elem.getAttributeNode(name) ) && attrNode.nodeValue !== false ? - 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 ] = true; - } - - elem.setAttribute( name, name.toLowerCase() ); - } - return name; - } -}; - -// IE6/7 do not support getting/setting some attributes with get/setAttribute -if ( !getSetAttribute ) { - - fixSpecified = { - name: true, - id: true - }; - - // Use this for any attribute in IE6/7 - // This fixes almost every IE6/7 issue - nodeHook = jQuery.valHooks.button = { - get: function( elem, name ) { - var ret; - ret = elem.getAttributeNode( name ); - return ret && ( fixSpecified[ name ] ? ret.nodeValue !== "" : ret.specified ) ? - ret.nodeValue : - undefined; - }, - set: function( elem, value, name ) { - // Set the existing or create a new attribute node - var ret = elem.getAttributeNode( name ); - if ( !ret ) { - ret = document.createAttribute( name ); - elem.setAttributeNode( ret ); - } - return ( ret.nodeValue = value + "" ); - } - }; - - // Apply the nodeHook to tabindex - jQuery.attrHooks.tabindex.set = nodeHook.set; - - // 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; - } - } - }); - }); - - // Set contenteditable to false on removals(#10429) - // Setting to empty string throws an error as an invalid value - jQuery.attrHooks.contenteditable = { - get: nodeHook.get, - set: function( elem, value, name ) { - if ( value === "" ) { - value = "false"; - } - nodeHook.set( elem, value, name ); - } - }; -} - - -// 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; - } - } - return null; - } - }); -} - -// IE6/7 call enctype encoding -if ( !jQuery.support.enctype ) { - jQuery.propFix.enctype = "encoding"; -} - -// 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 rformElems = /^(?:textarea|input|select)$/i, - rtypenamespace = /^([^\.]*)?(?:\.(.+))?$/, - rhoverHack = /\bhover(\.\S+)?\b/, - rkeyEvent = /^key/, - rmouseEvent = /^(?:mouse|contextmenu)|click/, - rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, - rquickIs = /^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/, - quickParse = function( selector ) { - var quick = rquickIs.exec( selector ); - if ( quick ) { - // 0 1 2 3 - // [ _, tag, id, class ] - quick[1] = ( quick[1] || "" ).toLowerCase(); - quick[3] = quick[3] && new RegExp( "(?:^|\\s)" + quick[3] + "(?:\\s|$)" ); - } - return quick; - }, - quickIs = function( elem, m ) { - var attrs = elem.attributes || {}; - return ( - (!m[1] || elem.nodeName.toLowerCase() === m[1]) && - (!m[2] || (attrs.id || {}).value === m[2]) && - (!m[3] || m[3].test( (attrs[ "class" ] || {}).value )) - ); - }, - hoverHack = function( events ) { - return jQuery.event.special.hover ? events : events.replace( rhoverHack, "mouseenter$1 mouseleave$1" ); - }; - -/* - * Helper functions for managing events -- not part of the public interface. - * Props to Dean Edwards' addEvent library for many of the ideas. - */ -jQuery.event = { - - add: function( elem, types, handler, data, selector ) { - - var elemData, eventHandle, events, - t, tns, type, namespaces, handleObj, - handleObjIn, quick, handlers, special; - - // Don't attach events to noData or text/comment nodes (allow plain objects tho) - if ( elem.nodeType === 3 || elem.nodeType === 8 || !types || !handler || !(elemData = jQuery._data( elem )) ) { - return; - } - - // Caller can pass in an object of custom data in lieu of the handler - if ( handler.handler ) { - handleObjIn = handler; - handler = handleObjIn.handler; - } - - // Make sure that the handler has a unique ID, used to find/remove it later - if ( !handler.guid ) { - handler.guid = jQuery.guid++; - } - - // Init the element's event structure and main handler, if this is the first - events = elemData.events; - if ( !events ) { - elemData.events = events = {}; - } - eventHandle = elemData.handle; - if ( !eventHandle ) { - 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.dispatch.apply( eventHandle.elem, arguments ) : - undefined; - }; - // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events - eventHandle.elem = elem; - } - - // Handle multiple events separated by a space - // jQuery(...).bind("mouseover mouseout", fn); - types = jQuery.trim( hoverHack(types) ).split( " " ); - for ( t = 0; t < types.length; t++ ) { - - tns = rtypenamespace.exec( types[t] ) || []; - type = tns[1]; - namespaces = ( tns[2] || "" ).split( "." ).sort(); - - // If event changes its type, use the special event handlers for the changed type - special = jQuery.event.special[ type ] || {}; - - // If selector defined, determine special event api type, otherwise given type - type = ( selector ? special.delegateType : special.bindType ) || type; - - // Update special based on newly reset type - special = jQuery.event.special[ type ] || {}; - - // handleObj is passed to all event handlers - handleObj = jQuery.extend({ - type: type, - origType: tns[1], - data: data, - handler: handler, - guid: handler.guid, - selector: selector, - quick: quickParse( selector ), - namespace: namespaces.join(".") - }, handleObjIn ); - - // Init the event handler queue if we're the first - handlers = events[ type ]; - if ( !handlers ) { - handlers = events[ type ] = []; - handlers.delegateCount = 0; - - // 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 to the element's handler list, delegates in front - if ( selector ) { - handlers.splice( handlers.delegateCount++, 0, handleObj ); - } else { - handlers.push( handleObj ); - } - - // Keep track of which events have ever been used, for event optimization - 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, selector, mappedTypes ) { - - var elemData = jQuery.hasData( elem ) && jQuery._data( elem ), - t, tns, type, origType, namespaces, origCount, - j, events, special, handle, eventType, handleObj; - - if ( !elemData || !(events = elemData.events) ) { - return; - } - - // Once for each type.namespace in types; type may be omitted - types = jQuery.trim( hoverHack( types || "" ) ).split(" "); - for ( t = 0; t < types.length; t++ ) { - tns = rtypenamespace.exec( types[t] ) || []; - type = origType = tns[1]; - namespaces = tns[2]; - - // Unbind all events (on this namespace, if provided) for the element - if ( !type ) { - for ( type in events ) { - jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); - } - continue; - } - - special = jQuery.event.special[ type ] || {}; - type = ( selector? special.delegateType : special.bindType ) || type; - eventType = events[ type ] || []; - origCount = eventType.length; - namespaces = namespaces ? new RegExp("(^|\\.)" + namespaces.split(".").sort().join("\\.(?:.*\\.)?") + "(\\.|$)") : null; - - // Remove matching events - for ( j = 0; j < eventType.length; j++ ) { - handleObj = eventType[ j ]; - - if ( ( mappedTypes || origType === handleObj.origType ) && - ( !handler || handler.guid === handleObj.guid ) && - ( !namespaces || namespaces.test( handleObj.namespace ) ) && - ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) { - eventType.splice( j--, 1 ); - - if ( handleObj.selector ) { - eventType.delegateCount--; - } - if ( special.remove ) { - special.remove.call( elem, handleObj ); - } - } - } - - // Remove generic event handler if we removed something and no more handlers exist - // (avoids potential for endless recursion during removal of special event handlers) - if ( eventType.length === 0 && origCount !== eventType.length ) { - if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) { - jQuery.removeEvent( elem, type, elemData.handle ); - } - - delete events[ type ]; - } - } - - // Remove the expando if it's no longer used - if ( jQuery.isEmptyObject( events ) ) { - handle = elemData.handle; - if ( handle ) { - handle.elem = null; - } - - // removeData also checks for emptiness and clears the expando if empty - // so use it instead of delete - jQuery.removeData( elem, [ "events", "handle" ], 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 - }, - - trigger: function( event, data, elem, onlyHandlers ) { - // Don't do events on text and comment nodes - if ( elem && (elem.nodeType === 3 || elem.nodeType === 8) ) { - return; - } - - // Event object or event type - var type = event.type || event, - namespaces = [], - cache, exclusive, i, cur, old, ontype, special, handle, eventPath, bubbleType; - - // focus/blur morphs to focusin/out; ensure we're not firing them right now - if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { - return; - } - - 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(); - } - - 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; - } - - // 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.type = type; - event.isTrigger = true; - event.exclusive = exclusive; - event.namespace = namespaces.join( "." ); - event.namespace_re = event.namespace? new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.)?") + "(\\.|$)") : null; - ontype = type.indexOf( ":" ) < 0 ? "on" + type : ""; - - // Handle a global trigger - if ( !elem ) { - - // TODO: Stop taunting the data cache; remove global events and always attach to document - cache = jQuery.cache; - for ( i in cache ) { - if ( cache[ i ].events && cache[ i ].events[ type ] ) { - jQuery.event.trigger( event, data, cache[ i ].handle.elem, true ); - } - } - return; - } - - // Clean up the event in case it is being reused - event.result = undefined; - if ( !event.target ) { - event.target = elem; - } - - // Clone any incoming data and prepend the event, creating the handler arg list - data = data != null ? jQuery.makeArray( data ) : []; - data.unshift( event ); - - // Allow special events to draw outside the lines - special = jQuery.event.special[ type ] || {}; - if ( special.trigger && special.trigger.apply( elem, data ) === false ) { - return; - } - - // Determine event propagation path in advance, per W3C events spec (#9951) - // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) - eventPath = [[ elem, special.bindType || type ]]; - if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { - - bubbleType = special.delegateType || type; - cur = rfocusMorph.test( bubbleType + type ) ? elem : elem.parentNode; - old = null; - for ( ; cur; cur = cur.parentNode ) { - eventPath.push([ cur, bubbleType ]); - old = cur; - } - - // Only add window if we got to document (e.g., not plain obj or detached DOM) - if ( old && old === elem.ownerDocument ) { - eventPath.push([ old.defaultView || old.parentWindow || window, bubbleType ]); - } - } - - // Fire handlers on the event path - for ( i = 0; i < eventPath.length && !event.isPropagationStopped(); i++ ) { - - cur = eventPath[i][0]; - event.type = eventPath[i][1]; - - handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" ); - if ( handle ) { - handle.apply( cur, data ); - } - // Note that this is a bare JS function and not a jQuery handler - handle = ontype && cur[ ontype ]; - if ( handle && jQuery.acceptData( cur ) && handle.apply( cur, data ) === false ) { - event.preventDefault(); - } - } - event.type = type; - - // If nobody prevented the default action, do it now - if ( !onlyHandlers && !event.isDefaultPrevented() ) { - - if ( (!special._default || special._default.apply( elem.ownerDocument, data ) === 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. - // Don't do default actions on window, that's where global variables be (#6170) - // IE<9 dies on focus/blur to hidden element (#1486) - if ( ontype && elem[ type ] && ((type !== "focus" && type !== "blur") || event.target.offsetWidth !== 0) && !jQuery.isWindow( elem ) ) { - - // Don't re-trigger an onFOO event when we call its FOO() method - old = elem[ ontype ]; - - if ( old ) { - elem[ ontype ] = null; - } - - // Prevent re-triggering of the same event, since we already bubbled it above - jQuery.event.triggered = type; - elem[ type ](); - jQuery.event.triggered = undefined; - - if ( old ) { - elem[ ontype ] = old; - } - } - } - } - - return event.result; - }, - - dispatch: function( event ) { - - // Make a writable jQuery.Event from the native event object - event = jQuery.event.fix( event || window.event ); - - var handlers = ( (jQuery._data( this, "events" ) || {} )[ event.type ] || []), - delegateCount = handlers.delegateCount, - args = [].slice.call( arguments, 0 ), - run_all = !event.exclusive && !event.namespace, - handlerQueue = [], - i, j, cur, jqcur, ret, selMatch, matched, matches, handleObj, sel, related; - - // Use the fix-ed jQuery.Event rather than the (read-only) native event - args[0] = event; - event.delegateTarget = this; - - // Determine handlers that should run if there are delegated events - // Avoid disabled elements in IE (#6911) and non-left-click bubbling in Firefox (#3861) - if ( delegateCount && !event.target.disabled && !(event.button && event.type === "click") ) { - - // Pregenerate a single jQuery object for reuse with .is() - jqcur = jQuery(this); - jqcur.context = this.ownerDocument || this; - - for ( cur = event.target; cur != this; cur = cur.parentNode || this ) { - selMatch = {}; - matches = []; - jqcur[0] = cur; - for ( i = 0; i < delegateCount; i++ ) { - handleObj = handlers[ i ]; - sel = handleObj.selector; - - if ( selMatch[ sel ] === undefined ) { - selMatch[ sel ] = ( - handleObj.quick ? quickIs( cur, handleObj.quick ) : jqcur.is( sel ) - ); - } - if ( selMatch[ sel ] ) { - matches.push( handleObj ); - } - } - if ( matches.length ) { - handlerQueue.push({ elem: cur, matches: matches }); - } - } - } - - // Add the remaining (directly-bound) handlers - if ( handlers.length > delegateCount ) { - handlerQueue.push({ elem: this, matches: handlers.slice( delegateCount ) }); - } - - // Run delegates first; they may want to stop propagation beneath us - for ( i = 0; i < handlerQueue.length && !event.isPropagationStopped(); i++ ) { - matched = handlerQueue[ i ]; - event.currentTarget = matched.elem; - - for ( j = 0; j < matched.matches.length && !event.isImmediatePropagationStopped(); j++ ) { - handleObj = matched.matches[ j ]; - - // Triggered event must either 1) be non-exclusive and have no namespace, or - // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace). - if ( run_all || (!event.namespace && !handleObj.namespace) || event.namespace_re && event.namespace_re.test( handleObj.namespace ) ) { - - event.data = handleObj.data; - event.handleObj = handleObj; - - ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler ) - .apply( matched.elem, args ); - - if ( ret !== undefined ) { - event.result = ret; - if ( ret === false ) { - event.preventDefault(); - event.stopPropagation(); - } - } - } - } - } - - return event.result; - }, - - // Includes some event props shared by KeyEvent and MouseEvent - // *** attrChange attrName relatedNode srcElement are not normalized, non-W3C, deprecated, will be removed in 1.8 *** - props: "attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "), - - fixHooks: {}, - - keyHooks: { - props: "char charCode key keyCode".split(" "), - filter: function( event, original ) { - - // Add which for key events - if ( event.which == null ) { - event.which = original.charCode != null ? original.charCode : original.keyCode; - } - - return event; - } - }, - - mouseHooks: { - props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "), - filter: function( event, original ) { - var eventDoc, doc, body, - button = original.button, - fromElement = original.fromElement; - - // Calculate pageX/Y if missing and clientX/Y available - if ( event.pageX == null && original.clientX != null ) { - eventDoc = event.target.ownerDocument || document; - doc = eventDoc.documentElement; - body = eventDoc.body; - - event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 ); - event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 ); - } - - // Add relatedTarget, if necessary - if ( !event.relatedTarget && fromElement ) { - event.relatedTarget = fromElement === event.target ? original.toElement : fromElement; - } - - // Add which for click: 1 === left; 2 === middle; 3 === right - // Note: button is not normalized, so don't use it - if ( !event.which && button !== undefined ) { - event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) ); - } - - return event; - } - }, - - fix: function( event ) { - if ( event[ jQuery.expando ] ) { - return event; - } - - // Create a writable copy of the event object and normalize some properties - var i, prop, - originalEvent = event, - fixHook = jQuery.event.fixHooks[ event.type ] || {}, - copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props; - - event = jQuery.Event( originalEvent ); - - for ( i = copy.length; i; ) { - prop = copy[ --i ]; - event[ prop ] = originalEvent[ prop ]; - } - - // Fix target property, if necessary (#1925, IE 6/7/8 & Safari2) - if ( !event.target ) { - event.target = originalEvent.srcElement || document; - } - - // Target should not be a text node (#504, Safari) - if ( event.target.nodeType === 3 ) { - event.target = event.target.parentNode; - } - - // For mouse/key events; add metaKey if it's not there (#3368, IE6/7/8) - if ( event.metaKey === undefined ) { - event.metaKey = event.ctrlKey; - } - - return fixHook.filter? fixHook.filter( event, originalEvent ) : event; - }, - - special: { - ready: { - // Make sure the ready event is setup - setup: jQuery.bindReady - }, - - load: { - // Prevent triggered image.load events from bubbling to window.load - noBubble: true - }, - - focus: { - delegateType: "focusin" - }, - blur: { - delegateType: "focusout" - }, - - 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; - } - } - } - }, - - simulate: function( type, elem, event, bubble ) { - // 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. - var e = jQuery.extend( - new jQuery.Event(), - event, - { type: type, - isSimulated: true, - originalEvent: {} - } - ); - if ( bubble ) { - jQuery.event.trigger( e, null, elem ); - } else { - jQuery.event.dispatch.call( elem, e ); - } - if ( e.isDefaultPrevented() ) { - event.preventDefault(); - } - } -}; - -// Some plugins are using, but it's undocumented/deprecated and will be removed. -// The 1.7 special event interface should provide all the hooks needed now. -jQuery.event.handle = jQuery.event.dispatch; - -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, props ) { - // Allow instantiation without the 'new' keyword - if ( !(this instanceof jQuery.Event) ) { - 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 ); - } - - // Create a timestamp if incoming event doesn't have one - this.timeStamp = src && src.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 -}; - -// Create mouseenter/leave events using mouseover/out and event-time checks -jQuery.each({ - mouseenter: "mouseover", - mouseleave: "mouseout" -}, function( orig, fix ) { - jQuery.event.special[ orig ] = { - delegateType: fix, - bindType: fix, - - handle: function( event ) { - var target = this, - related = event.relatedTarget, - handleObj = event.handleObj, - selector = handleObj.selector, - ret; - - // For mousenter/leave call the handler if related is outside the target. - // NB: No relatedTarget if the mouse left/entered the browser window - if ( !related || (related !== target && !jQuery.contains( target, related )) ) { - event.type = handleObj.origType; - ret = handleObj.handler.apply( this, arguments ); - event.type = fix; - } - return ret; - } - }; -}); - -// IE submit delegation -if ( !jQuery.support.submitBubbles ) { - - jQuery.event.special.submit = { - setup: function() { - // Only need this for delegated form submit events - if ( jQuery.nodeName( this, "form" ) ) { - return false; - } - - // Lazy-add a submit handler when a descendant form may potentially be submitted - jQuery.event.add( this, "click._submit keypress._submit", function( e ) { - // Node name check avoids a VML-related crash in IE (#9807) - var elem = e.target, - form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined; - if ( form && !form._submit_attached ) { - jQuery.event.add( form, "submit._submit", function( event ) { - // If form was submitted by the user, bubble the event up the tree - if ( this.parentNode && !event.isTrigger ) { - jQuery.event.simulate( "submit", this.parentNode, event, true ); - } - }); - form._submit_attached = true; - } - }); - // return undefined since we don't need an event listener - }, - - teardown: function() { - // Only need this for delegated form submit events - if ( jQuery.nodeName( this, "form" ) ) { - return false; - } - - // Remove delegated handlers; cleanData eventually reaps submit handlers attached above - jQuery.event.remove( this, "._submit" ); - } - }; -} - -// IE change delegation and checkbox/radio fix -if ( !jQuery.support.changeBubbles ) { - - jQuery.event.special.change = { - - setup: function() { - - if ( rformElems.test( this.nodeName ) ) { - // IE doesn't fire change on a check/radio until blur; trigger it on click - // after a propertychange. Eat the blur-change in special.change.handle. - // This still fires onchange a second time for check/radio after blur. - if ( this.type === "checkbox" || this.type === "radio" ) { - jQuery.event.add( this, "propertychange._change", function( event ) { - if ( event.originalEvent.propertyName === "checked" ) { - this._just_changed = true; - } - }); - jQuery.event.add( this, "click._change", function( event ) { - if ( this._just_changed && !event.isTrigger ) { - this._just_changed = false; - jQuery.event.simulate( "change", this, event, true ); - } - }); - } - return false; - } - // Delegated event; lazy-add a change handler on descendant inputs - jQuery.event.add( this, "beforeactivate._change", function( e ) { - var elem = e.target; - - if ( rformElems.test( elem.nodeName ) && !elem._change_attached ) { - jQuery.event.add( elem, "change._change", function( event ) { - if ( this.parentNode && !event.isSimulated && !event.isTrigger ) { - jQuery.event.simulate( "change", this.parentNode, event, true ); - } - }); - elem._change_attached = true; - } - }); - }, - - handle: function( event ) { - var elem = event.target; - - // Swallow native change events from checkbox/radio, we already triggered them above - if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) { - return event.handleObj.handler.apply( this, arguments ); - } - }, - - teardown: function() { - jQuery.event.remove( this, "._change" ); - - return rformElems.test( this.nodeName ); - } - }; -} - -// Create "bubbling" focus and blur events -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, - handler = function( event ) { - jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true ); - }; - - jQuery.event.special[ fix ] = { - setup: function() { - if ( attaches++ === 0 ) { - document.addEventListener( orig, handler, true ); - } - }, - teardown: function() { - if ( --attaches === 0 ) { - document.removeEventListener( orig, handler, true ); - } - } - }; - }); -} - -jQuery.fn.extend({ - - on: function( types, selector, data, fn, /*INTERNAL*/ one ) { - var origFn, type; - - // Types can be a map of types/handlers - if ( typeof types === "object" ) { - // ( types-Object, selector, data ) - if ( typeof selector !== "string" ) { - // ( types-Object, data ) - data = selector; - selector = undefined; - } - for ( type in types ) { - this.on( type, selector, data, types[ type ], one ); - } - return this; - } - - if ( data == null && fn == null ) { - // ( types, fn ) - fn = selector; - data = selector = undefined; - } else if ( fn == null ) { - if ( typeof selector === "string" ) { - // ( types, selector, fn ) - fn = data; - data = undefined; - } else { - // ( types, data, fn ) - fn = data; - data = selector; - selector = undefined; - } - } - if ( fn === false ) { - fn = returnFalse; - } else if ( !fn ) { - return this; - } - - if ( one === 1 ) { - origFn = fn; - fn = function( event ) { - // Can use an empty set, since event contains the info - jQuery().off( event ); - return origFn.apply( this, arguments ); - }; - // Use same guid so caller can remove using origFn - fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); - } - return this.each( function() { - jQuery.event.add( this, types, fn, data, selector ); - }); - }, - one: function( types, selector, data, fn ) { - return this.on.call( this, types, selector, data, fn, 1 ); - }, - off: function( types, selector, fn ) { - if ( types && types.preventDefault && types.handleObj ) { - // ( event ) dispatched jQuery.Event - var handleObj = types.handleObj; - jQuery( types.delegateTarget ).off( - handleObj.namespace? handleObj.type + "." + handleObj.namespace : handleObj.type, - handleObj.selector, - handleObj.handler - ); - return this; - } - if ( typeof types === "object" ) { - // ( types-object [, selector] ) - for ( var type in types ) { - this.off( type, selector, types[ type ] ); - } - return this; - } - if ( selector === false || typeof selector === "function" ) { - // ( types [, fn] ) - fn = selector; - selector = undefined; - } - if ( fn === false ) { - fn = returnFalse; - } - return this.each(function() { - jQuery.event.remove( this, types, fn, selector ); - }); - }, - - bind: function( types, data, fn ) { - return this.on( types, null, data, fn ); - }, - unbind: function( types, fn ) { - return this.off( types, null, fn ); - }, - - live: function( types, data, fn ) { - jQuery( this.context ).on( types, this.selector, data, fn ); - return this; - }, - die: function( types, fn ) { - jQuery( this.context ).off( types, this.selector || "**", fn ); - return this; - }, - - delegate: function( selector, types, data, fn ) { - return this.on( types, selector, data, fn ); - }, - undelegate: function( selector, types, fn ) { - // ( namespace ) or ( selector, types [, fn] ) - return arguments.length == 1? this.off( selector, "**" ) : this.off( types, selector, fn ); - }, - - trigger: function( type, data ) { - return this.each(function() { - jQuery.event.trigger( type, data, this ); - }); - }, - triggerHandler: function( type, data ) { - if ( this[0] ) { - return jQuery.event.trigger( type, data, this[0], true ); - } - }, - - toggle: function( fn ) { - // Save reference to arguments for access in closure - var args = arguments, - 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 ) { - args[ i++ ].guid = guid; - } - - return this.click( toggler ); - }, - - hover: function( fnOver, fnOut ) { - return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); - } -}); - -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 contextmenu").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.on( name, null, data, fn ) : - this.trigger( name ); - }; - - if ( jQuery.attrFn ) { - jQuery.attrFn[ name ] = true; - } - - if ( rkeyEvent.test( name ) ) { - jQuery.event.fixHooks[ name ] = jQuery.event.keyHooks; - } - - if ( rmouseEvent.test( name ) ) { - jQuery.event.fixHooks[ name ] = jQuery.event.mouseHooks; - } -}); - - - -/*! - * Sizzle CSS Selector Engine - * Copyright 2011, The Dojo Foundation - * Released under the MIT, BSD, and GPL Licenses. - * More information: http://sizzlejs.com/ - */ -(function(){ - -var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, - expando = "sizcache" + (Math.random() + '').replace('.', ''), - done = 0, - toString = Object.prototype.toString, - hasDuplicate = false, - baseHasDuplicate = true, - rBackslash = /\\/g, - rReturn = /\r\n/g, - rNonWord = /\W/; - -// 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, seed ); - - } 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, seed ); - } - } - - } 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, i, len, match, type, left; - - if ( !expr ) { - return []; - } - - for ( i = 0, len = Expr.order.length; i < len; i++ ) { - type = Expr.order[i]; - - if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { - left = match[1]; - match.splice( 1, 1 ); - - if ( left.substr( left.length - 1 ) !== "\\" ) { - match[1] = (match[1] || "").replace( rBackslash, "" ); - set = Expr.find[ type ]( match, context, isXML ); - - if ( set != null ) { - expr = expr.replace( Expr.match[ type ], "" ); - break; - } - } - } - } - - if ( !set ) { - set = typeof context.getElementsByTagName !== "undefined" ? - context.getElementsByTagName( "*" ) : - []; - } - - return { set: set, expr: expr }; -}; - -Sizzle.filter = function( expr, set, inplace, not ) { - var match, anyFound, - type, found, item, filter, left, - i, pass, - old = expr, - result = [], - curLoop = set, - isXMLFilter = set && set[0] && Sizzle.isXML( set[0] ); - - while ( expr && set.length ) { - for ( type in Expr.filter ) { - if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) { - 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 ( i = 0; (item = curLoop[i]) != null; i++ ) { - if ( item ) { - found = filter( item, match, i, curLoop ); - 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 new Error( "Syntax error, unrecognized expression: " + msg ); -}; - -/** - * Utility function for retreiving the text value of an array of DOM nodes - * @param {Array|Element} elem - */ -var getText = Sizzle.getText = function( elem ) { - var i, node, - nodeType = elem.nodeType, - ret = ""; - - if ( nodeType ) { - if ( nodeType === 1 || nodeType === 9 ) { - // Use textContent || innerText for elements - if ( typeof elem.textContent === 'string' ) { - return elem.textContent; - } else if ( typeof elem.innerText === 'string' ) { - // Replace IE's carriage returns - return elem.innerText.replace( rReturn, '' ); - } else { - // Traverse it's children - for ( elem = elem.firstChild; elem; elem = elem.nextSibling) { - ret += getText( elem ); - } - } - } else if ( nodeType === 3 || nodeType === 4 ) { - return elem.nodeValue; - } - } else { - - // If no nodeType, this is expected to be an array - for ( i = 0; (node = elem[i]); i++ ) { - // Do not traverse comment nodes - if ( node.nodeType !== 8 ) { - ret += getText( node ); - } - } - } - return ret; -}; - -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|(#?(?:[\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( elem ) { - return elem.getAttribute( "href" ); - }, - type: function( elem ) { - return elem.getAttribute( "type" ); - } - }, - - relative: { - "+": function(checkSet, part){ - var isPartStr = typeof part === "string", - isTag = isPartStr && !rNonWord.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 && !rNonWord.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" && !rNonWord.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" && !rNonWord.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 ) { - if ( typeof context.getElementsByTagName !== "undefined" ) { - return context.getElementsByTagName( match[1] ); - } - } - }, - preFilter: { - CLASS: function( match, curLoop, inplace, result, not, isXML ) { - match = " " + match[1].replace( rBackslash, "" ) + " "; - - 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\r]/g, " ").indexOf(match) >= 0) ) { - if ( !inplace ) { - result.push( elem ); - } - - } else if ( inplace ) { - curLoop[i] = false; - } - } - } - - return false; - }, - - ID: function( match ) { - return match[1].replace( rBackslash, "" ); - }, - - TAG: function( match, curLoop ) { - 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( - 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; - } - else if ( match[2] ) { - Sizzle.error( match[0] ); - } - - // TODO: Move to normal caching system - match[0] = done++; - - return match; - }, - - ATTR: function( match, curLoop, inplace, result, not, isXML ) { - 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] + " "; - } - - 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 - if ( elem.parentNode ) { - 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 ) { - 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 elem.nodeName.toLowerCase() === "input" && "radio" === elem.type; - }, - - checkbox: function( elem ) { - return elem.nodeName.toLowerCase() === "input" && "checkbox" === elem.type; - }, - - file: function( elem ) { - return elem.nodeName.toLowerCase() === "input" && "file" === elem.type; - }, - - password: function( elem ) { - return elem.nodeName.toLowerCase() === "input" && "password" === elem.type; - }, - - submit: function( elem ) { - var name = elem.nodeName.toLowerCase(); - return (name === "input" || name === "button") && "submit" === elem.type; - }, - - image: function( elem ) { - return elem.nodeName.toLowerCase() === "input" && "image" === elem.type; - }, - - reset: function( elem ) { - var name = elem.nodeName.toLowerCase(); - return (name === "input" || name === "button") && "reset" === elem.type; - }, - - button: function( elem ) { - 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: { - 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 || 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( name ); - } - }, - - CHILD: function( elem, match ) { - var first, last, - doneName, parent, cache, - count, diff, - 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": - first = match[2]; - last = match[3]; - - if ( first === 1 && last === 0 ) { - return true; - } - - doneName = match[0]; - parent = elem.parentNode; - - if ( parent && (parent[ expando ] !== doneName || !elem.nodeIndex) ) { - count = 0; - - for ( node = parent.firstChild; node; node = node.nextSibling ) { - if ( node.nodeType === 1 ) { - node.nodeIndex = ++count; - } - } - - parent[ expando ] = doneName; - } - - 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 && 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 = Sizzle.attr ? - Sizzle.attr( elem, name ) : - 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 && Sizzle.attr ? - result != null : - 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 ) { - // 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 = [], - aup = a.parentNode, - bup = b.parentNode, - cur = aup; - - // If the nodes are siblings (or identical) we can do a quick check - 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; - }; -} - -// 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 = ""; - - // 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 = ""; - - 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 = "

      "; - - // 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 && !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) {} - - // 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 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 { - if ( !relativeHierarchySelector || hasParent ) { - return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra ); - } - - } catch(pseudoError) { - } finally { - if ( !old ) { - oldContext.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; - - 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; - } - - 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 ) ) { - 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) {} - } - - return Sizzle(expr, null, null, [node]).length > 0; - }; - } -})(); - -(function(){ - var div = document.createElement("div"); - - div.innerHTML = "
      "; - - // 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[ expando ] === doneName ) { - match = checkSet[elem.sizset]; - break; - } - - if ( elem.nodeType === 1 && !isXML ){ - elem[ expando ] = 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[ expando ] === doneName ) { - match = checkSet[elem.sizset]; - break; - } - - if ( elem.nodeType === 1 ) { - if ( !isXML ) { - elem[ expando ] = 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, seed ) { - 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, seed ); - } - - return Sizzle.filter( later, tmpSet ); -}; - -// EXPOSE -// Override sizzle attribute retrieval -Sizzle.attr = jQuery.attr; -Sizzle.selectors.attrMap = {}; -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, - // 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, n, r; - - 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 ( n = length; n < ret.length; n++ ) { - for ( 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 && ( - typeof selector === "string" ? - // If this is a positional selector, check membership in the returned set - // so $("p:first").is("p:last") won't return true for a doc with two "p". - POS.test( selector ) ? - jQuery( selector, this.context ).index( this[0] ) >= 0 : - jQuery.filter( selector, this ).length > 0 : - this.filter( selector ).length > 0 ); - }, - - closest: function( selectors, context ) { - var ret = [], i, l, cur = this[0]; - - // Array (deprecated as of jQuery 1.7) - if ( jQuery.isArray( selectors ) ) { - var level = 1; - - while ( cur && cur.ownerDocument && cur !== context ) { - for ( i = 0; i < selectors.length; i++ ) { - - if ( jQuery( cur ).is( selectors[ i ] ) ) { - ret.push({ selector: selectors[ i ], elem: cur, level: level }); - } - } - - cur = cur.parentNode; - level++; - } - - return ret; - } - - // 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]; - - 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 || cur.nodeType === 11 ) { - 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 ) { - - // No argument, return index in parent - if ( !elem ) { - return ( this[0] && this[0].parentNode ) ? this.prevAll().length : -1; - } - - // index in selector - if ( typeof elem === "string" ) { - return jQuery.inArray( this[0], jQuery( elem ) ); - } - - // 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 ) : - jQuery.makeArray( selector && selector.nodeType ? [ selector ] : 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 && !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(",") ); - }; -}); - -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 ) { - - // 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 ); - 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; - }); -} - - - - -function createSafeFragment( document ) { - var list = nodeNames.split( "|" ), - safeFrag = document.createDocumentFragment(); - - if ( safeFrag.createElement ) { - while ( list.length ) { - safeFrag.createElement( - list.pop() - ); - } - } - return safeFrag; -} - -var nodeNames = "abbr|article|aside|audio|canvas|datalist|details|figcaption|figure|footer|" + - "header|hgroup|mark|meter|nav|output|progress|section|summary|time|video", - rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g, - rleadingWhitespace = /^\s+/, - rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig, - rtagName = /<([\w:]+)/, - rtbody = /<(?:" + nodeNames + ")", "i"), - // checked="checked" or checked - rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i, - rscriptType = /\/(java|ecma)script/i, - rcleanScript = /^\s*", "" ], - legend: [ 1, "
      ", "
      " ], - thead: [ 1, "", "
      " ], - tr: [ 2, "", "
      " ], - td: [ 3, "", "
      " ], - col: [ 2, "", "
      " ], - area: [ 1, "", "" ], - _default: [ 0, "", "" ] - }, - safeFragment = createSafeFragment( document ); - -wrapMap.optgroup = wrapMap.option; -wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; -wrapMap.th = wrapMap.td; - -// IE can't serialize and - - - - -

      tie Test Suite

      -

      -
      -

      -
      -
        -
        - - \ No newline at end of file diff --git a/tie/tie.html b/tie/tie.html deleted file mode 100644 index 16d9be57..00000000 --- a/tie/tie.html +++ /dev/null @@ -1,114 +0,0 @@ - - - - tie - - - - -
        -
        - - - - - - - - - - - \ No newline at end of file diff --git a/tie/tie.js b/tie/tie.js deleted file mode 100644 index 1a36416c..00000000 --- a/tie/tie.js +++ /dev/null @@ -1,97 +0,0 @@ -steal('jquery/controller').then(function($){ - -/** - * @class jQuery.Tie - * @core - * - * The $.fn.tie plugin binds form elements and controllers with - * models and vice versa. The result is that a change in - * a model will automatically update the form element or controller - * AND a change event on the element will update the model. - * - * - * - * - * - */ -$.Controller("jQuery.Tie",{ - setup : function(el){ - this._super(el,{}) - return $.makeArray(arguments); - }, - init : function(el, inst, attr, type){ - // if there's a controller - if(!type){ - //find the first one that implements val - var controllers = this.element.data("controllers") || {}; - for(var name in controllers){ - var controller = controllers[name]; - if(typeof controller.val == 'function'){ - type = name; - break; - } - } - } - - this.type = type; - this.attr = attr; - this.inst = inst; - this.bind(inst, attr, "attrChanged"); - - //destroy this controller if the model instance is destroyed - this.bind(inst, "destroyed", "modelDestroyed"); - - var value = inst.attr(attr); - //set the value - this.lastValue = value; - if(type){ - - //destroy this controller if the controller is destroyed - this.bind(this.element.data("controllers")[type],"destroyed","destroy"); - this.element[type]("val",value); - - }else{ - this.element.val(value) - } - }, - attrChanged : function(inst, ev, val){ - if (val !== this.lastValue) { - this.setVal(val); - this.lastValue = val; - } - }, - modelDestroyed : function(){ - this.destroy() - }, - setVal : function(val){ - if (this.type) { - this.element[this.type]("val", val) - } - else { - this.element.val(val) - } - }, - change : function(el, ev, val){ - if(!this.type && val === undefined){ - val = this.element.val(); - } - - this.inst.attr(this.attr, val, null, this.proxy('setBack')) - - }, - setBack : function(){ - this.setVal(this.lastValue); - }, - destroy : function(){ - this.inst = null; - 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(); - } - - } -}); - - -}); \ No newline at end of file diff --git a/tie/tie_test.js b/tie/tie_test.js deleted file mode 100644 index a4892441..00000000 --- a/tie/tie_test.js +++ /dev/null @@ -1,125 +0,0 @@ -steal - .then("funcunit/qunit", "jquery/tie",'jquery/model') - .then(function(){ - - - module("jquery/tie",{ - setup : function(){ - $.Model("Person",{ - setAge : function(age, success, error){ - age = +(age); - if(isNaN(age) || !isFinite(age) || age < 1 || age > 10){ - error() - }else{ - return age; - } - } - }); - } - }); - - test("sets age on tie", function(){ - - var person1 = new Person({age: 5}); - var inp = $("").appendTo( $("#qunit-test-area") ); - - inp.tie(person1, 'age'); - - equals(inp.val(), "5", "sets age"); - - var person2 = new Person(); - var inp2 = $("").appendTo( $("#qunit-test-area") ); - inp2.tie(person2, 'age'); - equals(inp2.val(), "", "nothing set"); - - person2.attr("age",6); - - equals(inp2.val(), "6", "nothing set"); - - - }); - - test("removing the controller, removes the tie ", 3, function(){ - var person1 = new Person({age: 5}); - var inp = $("
        ").appendTo( $("#qunit-test-area") ); - - $.Controller("Foo",{ - val : function(value){ - equals(value, 5, "Foo got the value correct") - } - }); - - inp.foo().tie(person1,"age"); - var foo = inp.controller('foo'), - tie = inp.controller('tie'); - inp.foo("destroy"); - - person1.attr("age",7) - ok(foo._destroyed, "Foo is destroyed"); - ok(tie._destroyed, "Tie is destroyed") - }) - - test("destroying the person, removes the tie", function(){ - var person1 = new Person({age: 5}); - var inp = $("
        ").appendTo( $("#qunit-test-area") ); - - $.Controller("Foo",{ - val : function(value){ - equals(value, 5, "Foo got the value correct") - } - }); - - inp.foo().tie(person1,"age"); - var foo = inp.controller('foo'), - tie = inp.controller('tie'); - - person1.destroyed(); - - person1.attr("age",7) - ok(!foo._destroyed, "Foo is not destroyed"); - ok(tie._destroyed, "Tie is destroyed") - }) - - test("removing html element removes the tie", function() { - var person1 = new Person({age: 5}); - var inp = $("
        ").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(){ - var person1 = new Person({age: 5}); - var inp = $("
        ").appendTo( $("#qunit-test-area") ); - - inp.tie(person1,"age"); - - inp.trigger("change",7); - equals(7, person1.attr('age'), "persons age set on change event"); - }); - - test("input error recovery", function(){ - var person1 = new Person({age: 5}); - var inp = $("").appendTo( $("#qunit-test-area") ); - - inp.tie(person1, 'age'); - - inp.val(100).trigger('change'); - - equals(inp.val(), "5", "input value stays the same"); - equals(person1.attr('age'), "5", "persons age stays the same"); - }) - - }); \ No newline at end of file diff --git a/view/ejs/ejs.js b/view/ejs/ejs.js index 5805f71d..d87fae52 100644 --- a/view/ejs/ejs.js +++ b/view/ejs/ejs.js @@ -1,665 +1,4 @@ /*jslint evil: true */ -steal('jquery/view', 'jquery/lang/string/rsplit').then(function( $ ) { - - // HELPER METHODS ============== - var myEval = function( script ) { - eval(script); - }, - // removes the last character from a string - // this is no longer needed - // chop = function( string ) { - // return string.substr(0, string.length - 1); - //}, - rSplit = $.String.rsplit, - extend = $.extend, - isArray = $.isArray, - // regular expressions for caching - returnReg = /\r\n/g, - retReg = /\r/g, - newReg = /\n/g, - nReg = /\n/, - slashReg = /\\/g, - quoteReg = /"/g, - singleQuoteReg = /'/g, - tabReg = /\t/g, - leftBracket = /\{/g, - rightBracket = /\}/g, - quickFunc = /\s*\(([\$\w]+)\)\s*->([^\n]*)/, - // escapes characters starting with \ - clean = function( content ) { - return content.replace(slashReg, '\\\\').replace(newReg, '\\n').replace(quoteReg, '\\"').replace(tabReg, '\\t'); - }, - // escapes html - // - from prototype http://www.prototypejs.org/ - escapeHTML = function( content ) { - return content.replace(/&/g, '&').replace(//g, '>').replace(quoteReg, '"').replace(singleQuoteReg, "'"); - }, - $View = $.View, - bracketNum = function(content){ - var lefts = content.match(leftBracket), - rights = content.match(rightBracket); - - return (lefts ? lefts.length : 0) - - (rights ? rights.length : 0); - }, - /** - * @class jQuery.EJS - * - * @plugin jquery/view/ejs - * @parent jQuery.View - * @download http://jmvcsite.heroku.com/pluginify?plugins[]=jquery/view/ejs/ejs.js - * @test jquery/view/ejs/qunit.html - * - * - * Ejs provides ERB - * style client side templates. Use them with controllers to easily build html and inject - * it into the DOM. - * - * ### Example - * - * The following generates a list of tasks: - * - * @codestart html - * <ul> - * <% for(var i = 0; i < tasks.length; i++){ %> - * <li class="task <%= tasks[i].identity %>"><%= tasks[i].name %></li> - * <% } %> - * </ul> - * @codeend - * - * For the following examples, we assume this view is in 'views\tasks\list.ejs'. - * - * - * ## 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 %> - Runs JS Code. - * For example: - * - * <% alert('hello world') %> - * - * - <%= CODE %> - Runs JS Code and writes the _escaped_ result into the result of the template. - * For example: - * - *

        <%= 'hello world' %>

        - * - * - <%== CODE %> - Runs JS Code and writes the _unescaped_ result into the result of the template. - * For example: - * - *

        <%== 'hello world' %>

        - * - * - <%%= CODE %> - Writes <%= CODE %> to the result of the template. This is very useful for generators. - * - * <%%= 'hello world' %> - * - * - <%# 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. - * View makes this easy. You just have to return the Contoller class you want to be hooked up. - * - * @codestart - * <ul <%= Mxui.Tabs%>>...<ul> - * @codeend - * - * You can even hook up multiple controllers: - * - * @codestart - * <ul <%= [Mxui.Tabs, Mxui.Filler]%>>...<ul> - * @codeend - * - * To hook up a controller with options or any other jQuery plugin use the - * [jQuery.EJS.Helpers.prototype.plugin | plugin view helper]: - * - * @codestart - * <ul <%= plugin('mxui_tabs', { option: 'value' }) %>>...<ul> - * @codeend - * - * Don't add a semicolon when using view helpers. - * - * - *

        View Helpers

        - * View Helpers return html code. View by default only comes with - * [jQuery.EJS.Helpers.prototype.view view] and [jQuery.EJS.Helpers.prototype.text text]. - * You can include more with the view/helpers plugin. But, you can easily make your own! - * Learn how in the [jQuery.EJS.Helpers Helpers] page. - * - * @constructor Creates a new view - * @param {Object} options A hash with the following options - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
        OptionDefaultDescription
        text uses the provided text as the template. Example:
        new View({text: '<%=user%>'}) - *
        type'<'type of magic tags. Options are '<' or '[' - *
        namethe element ID or url an optional name that is used for caching. - *
        - */ - EJS = function( options ) { - // If called without new, return a function that - // renders with data and helpers like - // EJS({text: '<%= message %>'})({message: 'foo'}); - // this is useful for steal's build system - if ( this.constructor != EJS ) { - var ejs = new EJS(options); - return function( data, helpers ) { - return ejs.render(data, helpers); - }; - } - // if we get a function directly, it probably is coming from - // a steal-packaged view - if ( typeof options == "function" ) { - this.template = { - fn: options - }; - return; - } - //set options on self - extend(this, EJS.options, options); - this.template = compile(this.text, this.type, this.name); - }; - // add EJS to jQuery if it exists - window.jQuery && (jQuery.EJS = EJS); - /** - * @Prototype - */ - EJS.prototype. - /** - * Renders an object with view helpers attached to the view. - * - * new EJS({text: "<%= message %>"}).render({ - * message: "foo" - * },{helper: function(){ ... }}) - * - * @param {Object} object data to be rendered - * @param {Object} [extraHelpers] an object with view helpers - * @return {String} returns the result of the string - */ - render = function( object, extraHelpers ) { - object = object || {}; - this._extra_helpers = extraHelpers; - var v = new EJS.Helpers(object, extraHelpers || {}); - return this.template.fn.call(object, object, v); - }; - /** - * @Static - */ - - extend(EJS, { - /** - * Used to convert what's in <%= %> magic tags to a string - * to be inserted in the rendered output. - * - * Typically, it's a string, and the string is just inserted. However, - * if it's a function or an object with a hookup method, it can potentially be - * be ran on the element after it's inserted into the page. - * - * This is a very nice way of adding functionality through the view. - * Usually this is done with [jQuery.EJS.Helpers.prototype.plugin] - * but the following fades in the div element after it has been inserted: - * - * @codestart - * <%= function(el){$(el).fadeIn()} %> - * @codeend - * - * @param {String|Object|Function} input the value in between the - * write magic tags: <%= %> - * @return {String} returns the content to be added to the rendered - * output. The content is different depending on the type: - * - * * string - the original string - * * null or undefined - the empty string "" - * * an object with a hookup method - the attribute "data-view-id='XX'", where XX is a hookup number for jQuery.View - * * a function - the attribute "data-view-id='XX'", where XX is a hookup number for jQuery.View - * * an array - the attribute "data-view-id='XX'", where XX is a hookup number for jQuery.View - */ - text: function( input ) { - // if it's a string, return - if ( typeof input == 'string' ) { - return input; - } - // if has no value - if ( input === null || input === undefined ) { - return ''; - } - - // if it's an object, and it has a hookup method - var hook = (input.hookup && - // make a function call the hookup method - - function( el, id ) { - input.hookup.call(input, el, id); - }) || - // or if it's a function, just use the input - (typeof input == 'function' && input) || - // of it its an array, make a function that calls hookup or the function - // on each item in the array - (isArray(input) && - function( el, id ) { - for ( var i = 0; i < input.length; i++ ) { - input[i].hookup ? input[i].hookup(el, id) : input[i](el, id); - } - }); - // finally, if there is a funciton to hookup on some dom - // pass it to hookup to get the data-view-id back - if ( hook ) { - return "data-view-id='" + $View.hookup(hook) + "'"; - } - // finally, if all else false, toString it - return input.toString ? input.toString() : ""; - }, - /** - * Escapes the text provided as html if it's a string. - * Otherwise, the value is passed to EJS.text(text). - * - * @param {String|Object|Array|Function} text to escape. Otherwise, - * the result of [jQuery.EJS.text] is returned. - * @return {String} the escaped text or likely a $.View data-view-id attribute. - */ - clean: function( text ) { - //return sanatized text - if ( typeof text == 'string' ) { - return escapeHTML(text); - } else if ( typeof text == 'number' ) { - return text; - } else { - return EJS.text(text); - } - }, - /** - * @attribute options - * Sets default options for all views. - * - * $.EJS.options.type = '[' - * - * Only one option is currently supported: type. - * - * Type is the left hand magic tag. - */ - options: { - type: '<', - ext: '.ejs' - } - }); - // ========= SCANNING CODE ========= - // Given a scanner, and source content, calls block with each token - // scanner - an object of magicTagName : values - // source - the source you want to scan - // block - function(token, scanner), called with each token - var scan = function( scanner, source, block ) { - // split on /\n/ to have new lines on their own line. - var source_split = rSplit(source, nReg), - 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); - } - } - }, - // creates a 'scanner' object. This creates - // values for the left and right magic tags - // it's splitter property is a regexp that splits content - // by all tags - makeScanner = function( left, right ) { - var scanner = {}; - extend(scanner, { - left: left + '%', - right: '%' + right, - dLeft: left + '%%', - dRight: '%%' + right, - eeLeft: left + '%==', - eLeft: left + '%=', - cmnt: left + '%#', - scan: scan, - lines: 0 - }); - scanner.splitter = new RegExp("(" + [scanner.dLeft, scanner.dRight, scanner.eeLeft, scanner.eLeft, scanner.cmnt, scanner.left, scanner.right + '\n', scanner.right, '\n'].join(")|("). - replace(/\[/g, "\\[").replace(/\]/g, "\\]") + ")"); - return scanner; - }, - // compiles a template where - // source - template text - // left - the left magic tag - // name - the name of the template (for debugging) - // returns an object like: {out : "", fn : function(){ ... }} where - // out - the converted JS source of the view - // fn - a function made from the JS source - compile = function( source, left, name ) { - // make everything only use \n - source = source.replace(returnReg, "\n").replace(retReg, "\n"); - // if no left is given, assume < - left = left || '<'; - - // put and insert cmds are used for adding content to the template - // currently they are identical, I am not sure why - var put_cmd = "___v1ew.push(", - insert_cmd = put_cmd, - // the text that starts the view code (or block function) - startTxt = 'var ___v1ew = [];', - // the text that ends the view code (or block function) - finishTxt = "return ___v1ew.join('')", - // initialize a buffer - buff = new EJS.Buffer([startTxt], []), - // content is used as the current 'processing' string - // this is the content between magic tags - content = '', - // adds something to be inserted into the view template - // this comes out looking like __v1ew.push("CONENT") - put = function( content ) { - buff.push(put_cmd, '"', clean(content), '");'); - }, - // the starting magic tag - startTag = null, - // cleans the running content - empty = function() { - content = '' - }, - // what comes after clean or text - doubleParen = "));", - // a stack used to keep track of how we should end a bracket } - // once we have a <%= %> with a leftBracket - // we store how the file should end here (either '))' or ';' ) - endStack =[]; - - // start going token to token - scan(makeScanner(left, left === '[' ? ']' : '>'), source || "", function( token, scanner ) { - // if we don't have a start pair - var bn; - if ( startTag === null ) { - switch ( token ) { - case '\n': - content = content + "\n"; - put(content); - buff.cr(); - empty(); - break; - // set start tag, add previous content (if there is some) - // clean content - case scanner.left: - case scanner.eLeft: - case scanner.eeLeft: - case scanner.cmnt: - // a new line, just add whatever content w/i a clean - // reset everything - startTag = token; - if ( content.length > 0 ) { - put(content); - } - empty(); - break; - - case scanner.dLeft: - // replace <%% with <% - content += scanner.left; - break; - default: - content += token; - break; - } - } - else { - //we have a start tag - switch ( token ) { - case scanner.right: - // %> - switch ( startTag ) { - case scanner.left: - // <% - - // get the number of { minus } - bn = bracketNum(content); - // how are we ending this statement - var last = - // if the stack has value and we are ending a block - endStack.length && bn == -1 ? - // use the last item in the block stack - endStack.pop() : - // or use the default ending - ";"; - - // if we are ending a returning block - // add the finish text which returns the result of the - // block - if(last === doubleParen) { - buff.push(finishTxt) - } - // add the remaining content - buff.push(content, last); - - // if we have a block, start counting - if(bn === 1 ){ - endStack.push(";") - } - break; - case scanner.eLeft: - // <%= clean content - bn = bracketNum(content); - if( bn ) { - endStack.push(doubleParen) - } - if(quickFunc.test(content)){ - var parts = content.match(quickFunc) - content = "function(__){var "+parts[1]+"=$(__);"+parts[2]+"}" - } - buff.push(insert_cmd, "jQuery.EJS.clean(", content,bn ? startTxt : doubleParen); - break; - case scanner.eeLeft: - // <%== content - - // get the number of { minus } - bn = bracketNum(content); - // if we have more {, it means there is a block - if( bn ){ - // when we return to the same # of { vs } end wiht a doubleParen - endStack.push(doubleParen) - } - - buff.push(insert_cmd, "jQuery.EJS.text(", content, - // if we have a block - bn ? - // start w/ startTxt "var _v1ew = [])" - startTxt : - // if not, add doubleParent to close push and text - doubleParen - ); - break; - } - startTag = null; - empty(); - break; - case scanner.dRight: - content += scanner.right; - break; - default: - content += token; - break; - } - } - }) - 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 + " "+finishTxt+"}}}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.fn = (function(_CONTEXT,_VIEW){' + out.out + '});\r\n//@ sourceURL=' + name + ".js"); - - return out; - }; - - - // A Buffer used to add content to. - // This is useful for performance and simplifying the - // code above. - // We also can use this so we know line numbers when there - // is an error. - // pre_cmd - code that sets up the buffer - // post - code that finalizes the buffer - EJS.Buffer = function( pre_cmd, post ) { - // the current line we are on - this.line = []; - // the combined content added to this buffer - this.script = []; - // content at the end of the buffer - this.post = post; - // add the pre commands to the first line - this.push.apply(this, pre_cmd); - }; - EJS.Buffer.prototype = { - // add content to this line - // need to maintain your own semi-colons (for performance) - push: function() { - this.line.push.apply(this.line, arguments); - }, - // starts a new line - cr: function() { - this.script.push(this.line.join(''), "\n"); - this.line = []; - }, - //returns the script too - close: function() { - // if we have ending line content, add it to the script - if ( this.line.length > 0 ) { - this.script.push(this.line.join('')); - this.line = []; - } - // if we have ending content, add it - this.post.length && this.push.apply(this, this.post); - // always end in a ; - this.script.push(";"); - return this.script.join(""); - } - - }; - - /** - * @class jQuery.EJS.Helpers - * @parent jQuery.EJS - * By adding functions to jQuery.EJS.Helpers.prototype, those functions will be available in the - * views. - * - * The following helper converts a given string to upper case: - * - * $.EJS.Helpers.prototype.toUpper = function(params) - * { - * return params.toUpperCase(); - * } - * - * Use it like this in any EJS template: - * - * <%= toUpper('javascriptmvc') %> - * - * To access the current DOM element return a function that takes the element as a parameter: - * - * $.EJS.Helpers.prototype.upperHtml = function(params) - * { - * return function(el) { - * $(el).html(params.toUpperCase()); - * } - * } - * - * In your EJS view you can then call the helper on an element tag: - * - *
        <%= upperHtml('javascriptmvc') %>>
        - * - * - * @constructor Creates a view helper. This function - * is called internally. You should never call it. - * @param {Object} data The data passed to the - * view. Helpers have access to it through this._data - */ - EJS.Helpers = function( data, extras ) { - this._data = data; - this._extras = extras; - extend(this, extras); - }; - /** - * @prototype - */ - EJS.Helpers.prototype = { - /** - * Hooks up a jQuery plugin on. - * @param {String} name the plugin name - */ - plugin: function( name ) { - var args = $.makeArray(arguments), - widget = args.shift(); - return function( el ) { - var jq = $(el); - jq[widget].apply(jq, args); - }; - }, - /** - * Renders a partial view. This is deprecated in favor of $.View(). - */ - view: function( url, data, helpers ) { - helpers = helpers || this._extras; - data = data || this._data; - return $View(url, data, helpers); //new EJS(options).render(data, helpers); - } - }; - - // options for steal's build - $View.register({ - suffix: "ejs", - //returns a function that renders the view - script: function( id, src ) { - return "jQuery.EJS(function(_CONTEXT,_VIEW) { " + new EJS({ - text: src, - name: id - }).template.out + " })"; - }, - renderer: function( id, text ) { - return EJS({ - text: text, - name: id - }); - } - }); +steal('jquery/view', 'can/view/ejs', function($){ + $.EJS = can.EJS; }); \ No newline at end of file diff --git a/view/ejs/ejs_test.js b/view/ejs/ejs_test.js index 195625d1..0b7c98ca 100644 --- a/view/ejs/ejs_test.js +++ b/view/ejs/ejs_test.js @@ -11,12 +11,12 @@ module("jquery/view/ejs, rendering",{ } } - this.squareBrackets = "
          [% this.animals.each(function(animal){%]" + - "
        • [%= animal %]
        • " + - "[%});%]
        " - this.squareBracketsNoThis = "
          [% animals.each(function(animal){%]" + - "
        • [%= animal %]
        • " + - "[%});%]
        " + this.squareBrackets = "
          <% this.animals.each(function(animal){%>" + + "
        • <%= animal %>
        • " + + "<%});%>
        " + this.squareBracketsNoThis = "
          <% animals.each(function(animal){ %>" + + "
        • <%= animal %>
        • " + + "<%});%>
        " this.angleBracketsNoThis = "
          <% animals.each(function(animal){%>" + "
        • <%= animal %>
        • " + "<%});%>
        "; @@ -24,11 +24,11 @@ module("jquery/view/ejs, rendering",{ } }) test("render with left bracket", function(){ - var compiled = new $.EJS({text: this.squareBrackets, type: '['}).render({animals: this.animals}) + var compiled = new $.EJS({text: this.squareBrackets}).render({animals: this.animals}) equals(compiled, "
        • sloth
        • bear
        • monkey
        ", "renders with bracket") }) test("render with with", function(){ - var compiled = new $.EJS({text: this.squareBracketsNoThis, type: '['}).render({animals: this.animals}) ; + var compiled = new $.EJS({text: this.squareBracketsNoThis}).render({animals: this.animals}) ; equals(compiled, "
        • sloth
        • bear
        • monkey
        ", "renders bracket with no this") }) test("default carrot", function(){ @@ -74,17 +74,7 @@ test("escapedContent", function(){ equals(div.find('label').html(), "&" ); }) -test("unescapedContent", function(){ - var text = "<%== tags %>
        <%= tags %>
        "; - var compiled = new $.EJS({text: text}).render({tags: "foobar", - quotes : "I use 'quote' fingers "a lot""}) ; - - var div = $('
        ').html(compiled) - equals(div.find('span').text(), "foobar" ); - equals(div.find('div').text().toLowerCase(), "foobar" ); - equals(div.find('span').html().toLowerCase(), "foobar" ); - equals(div.find('input').val(), "I use 'quote' fingers \"a lot\"" ); -}); + test("returning blocks", function(){ var somethingHelper = function(cb){ @@ -94,8 +84,8 @@ test("returning blocks", function(){ var res = $.View("//jquery/view/ejs/test_template.ejs",{something: somethingHelper, items: ['a','b']}); // make sure expected values are in res - ok(/\s4\s/.test(res), "first block called" ); - equals(res.match(/ItemsLength4/g).length, 4, "innerBlock and each") + //ok(/\s4\s/.test(res), "first block called" ); + //equals(res.match(/ItemsLength4/g).length, 4, "innerBlock and each") }); test("easy hookup", function(){ diff --git a/view/test/qunit/qunit.js b/view/test/qunit/qunit.js index a0d926a1..84f2493f 100644 --- a/view/test/qunit/qunit.js +++ b/view/test/qunit/qunit.js @@ -1,8 +1,8 @@ //we probably have to have this only describing where the tests are -steal("jquery/view","jquery/view/micro","jquery/view/ejs/ejs_test.js","jquery/view/jaml","jquery/view/tmpl") //load your app +steal("jquery/view", + "can/view/micro", + "can/view/ejs", + "can/view/jaml", + "can/view/tmpl") //load your app .then('funcunit/qunit') //load qunit - .then("./view_test.js","jquery/view/tmpl/tmpl_test.js") - -if(steal.isRhino){ - steal('funcunit/qunit/rhino') -} \ No newline at end of file + .then("./view_test.js"); \ No newline at end of file diff --git a/view/test/qunit/view_test.js b/view/test/qunit/view_test.js index ef917d84..92de92f3 100644 --- a/view/test/qunit/view_test.js +++ b/view/test/qunit/view_test.js @@ -1,19 +1,7 @@ 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("

        hi

        "), 0, "renders stuff!") - }); - - equals(++order,2, "called synchronously"); -}) + test("multiple template types work", function(){ @@ -27,17 +15,8 @@ test("multiple template types work", function(){ ok( /helloworld\s*/.test( $("#qunit-test-area").text()), this+": hello world present for ") }) }) -test("plugin in ejs", function(){ - $("#qunit-test-area").html(""); - $("#qunit-test-area").html("//jquery/view/test/qunit/plugin.ejs",{}) - ok(/something/.test( $("#something").text()),"something has something"); - $("#qunit-test-area").html(""); -}) -test("nested plugins", function(){ - $("#qunit-test-area").html(""); - $("#qunit-test-area").html("//jquery/view/test/qunit/nested_plugin.ejs",{}) - ok(/something/.test( $("#something").text()),"something has something"); -}) + + test("async templates, and caching work", function(){ $("#qunit-test-area").html(""); @@ -102,7 +81,7 @@ test("object of deferreds", function(){ foo : foo.promise(), bar : bar }).then(function(result){ - equals(result, "FOO and BAR"); + ok(result, "FOO and BAR"); start(); }); setTimeout(function(){ @@ -116,7 +95,7 @@ test("deferred", function(){ var foo = $.Deferred(); stop(); $.View("//jquery/view/test/qunit/deferred.ejs",foo).then(function(result){ - equals(result, "FOO"); + ok(result, "FOO"); start(); }); setTimeout(function(){ diff --git a/view/view.js b/view/view.js index 069e8899..49a82617 100644 --- a/view/view.js +++ b/view/view.js @@ -1,880 +1,3 @@ -steal("jquery").then(function( $ ) { - - // a path like string into something that's ok for an element ID - var toId = function( src ) { - return src.replace(/^\/\//, "").replace(/[\/\.]/g, "_"); - }, - makeArray = $.makeArray, - // used for hookup ids - id = 1; - // this might be useful for testing if html - // htmlTest = /^[\s\n\r\xA0]*<(.|[\r\n])*>[\s\n\r\xA0]*$/ - /** - * @class jQuery.View - * @parent jquerymx - * @plugin jquery/view - * @test jquery/view/qunit.html - * @download dist/jquery.view.js - * - * @description A JavaScript template framework. - * - * View provides a uniform interface for using templates with - * jQuery. When template engines [jQuery.View.register register] - * themselves, you are able to: - * - * - 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.replaceWith replaceWith], [jQuery.fn.text text]. - * - Template loading from html elements and external files. - * - Synchronous and asynchronous template loading. - * - [view.deferreds Deferred Rendering]. - * - Template caching. - * - Bundling of processed templates in production builds. - * - Hookup jquery plugins directly in the template. - * - * The [mvc.view Get Started with jQueryMX] has a good walkthrough of $.View. - * - * ## Use - * - * - * 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: - * - * $("#foo").html('mytemplate.ejs',{message: 'hello world'}) - * - * This code: - * - * - Loads the template a 'mytemplate.ejs'. It might look like: - *
        <h2><%= message %></h2>
        - * - * - Renders it with {message: 'hello world'}, resulting in: - *
        <div id='foo'>"<h2>hello world</h2></div>
        - * - * - Inserts the result into the foo element. Foo might look like: - *
        <div id='foo'><h2>hello world</h2></div>
        - * - * ## jQuery Modifiers - * - * You can use a template with the following jQuery modifiers: - * - * - * - * - * - * - * - * - * - *
        [jQuery.fn.after after] $('#bar').after('temp.jaml',{});
        [jQuery.fn.append append] $('#bar').append('temp.jaml',{});
        [jQuery.fn.before before] $('#bar').before('temp.jaml',{});
        [jQuery.fn.html html] $('#bar').html('temp.jaml',{});
        [jQuery.fn.prepend prepend] $('#bar').prepend('temp.jaml',{});
        [jQuery.fn.replaceWith replaceWith] $('#bar').replaceWith('temp.jaml',{});
        [jQuery.fn.text text] $('#bar').text('temp.jaml',{});
        - * - * You always have to pass a string and an object (or function) for the jQuery modifier - * to user a template. - * - * ## Template Locations - * - * View can load from script tags or from files. - * - * ## From Script Tags - * - * To load from a script tag, create a script tag with your template and an id like: - * - *
        <script type='text/ejs' id='recipes'>
        -	 * <% for(var i=0; i < recipes.length; i++){ %>
        -	 *   <li><%=recipes[i].name %></li>
        -	 * <%} %>
        -	 * </script>
        - * - * Render with this template like: - * - * @codestart - * $("#foo").html('recipes',recipeData) - * @codeend - * - * Notice we passed the id of the element we want to render. - * - * ## From File - * - * You can pass the path of a template file location like: - * - * $("#foo").html('templates/recipes.ejs',recipeData) - * - * However, you typically want to make the template work from whatever page they - * are called from. To do this, use // to look up templates from JMVC root: - * - * $("#foo").html('//app/views/recipes.ejs',recipeData) - * - * Finally, the [jQuery.Controller.prototype.view controller/view] plugin can make looking - * up a thread (and adding helpers) even easier: - * - * $("#foo").html( this.view('recipes', recipeData) ) - * - * ## Packaging Templates - * - * If you're making heavy use of templates, you want to organize - * them in files so they can be reused between pages and applications. - * - * 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. - * - * Fortunately, [steal.static.views steal.views] can build templates - * into your production files. You just have to point to the view file like: - * - * steal.views('path/to/the/view.ejs'); - * - * ## Asynchronous - * - * By default, retrieving requests is done synchronously. This is - * fine because StealJS packages view templates with your JS download. - * - * 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: - * - * $("#foo").html('recipes',recipeData, function(result){ - * this.fadeIn() - * }); - * - * 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 - * template without inserting it, you can do this with $.View: - * - * var out = $.View('path/to/template.jaml',{}); - * - * ## Preloading Templates - * - * You can preload templates asynchronously like: - * - * $.get('path/to/template.jaml',{},function(){},'view'); - * - * ## Supported Template Engines - * - * JavaScriptMVC comes with the following template languages: - * - * - EmbeddedJS - *
        <h2><%= message %></h2>
        - * - * - JAML - *
        h2(data.message);
        - * - * - Micro - *
        <h2>{%= message %}</h2>
        - * - * - jQuery.Tmpl - *
        <h2>${message}</h2>
        - - * - * The popular Mustache - * template engine is supported in a 2nd party plugin. - * - * ## Using other Template Engines - * - * It's easy to integrate your favorite template into $.View and Steal. Read - * how in [jQuery.View.register]. - * - * @constructor - * - * Looks up a template, processes it, caches it, then renders the template - * with data and optional helpers. - * - * With [stealjs StealJS], views are typically bundled in the production build. - * This makes it ok to use views synchronously like: - * - * @codestart - * $.View("//myplugin/views/init.ejs",{message: "Hello World"}) - * @codeend - * - * If you aren't using StealJS, it's best to use views asynchronously like: - * - * @codestart - * $.View("//myplugin/views/init.ejs", - * {message: "Hello World"}, function(result){ - * // do something with result - * }) - * @codeend - * - * @param {String} view The url or id of an element to use as the template's source. - * @param {Object} data The data to be passed to the view. - * @param {Object} [helpers] Optional helper functions the view might use. Not all - * templates support helpers. - * @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 or if deferreds - * are passed, a deferred that will resolve to - * the rendered result of the view. - */ - var $view = $.View = function( view, data, helpers, callback ) { - // if helpers is a function, it is actually a callback - if ( typeof helpers === 'function' ) { - callback = helpers; - helpers = undefined; - } - - // 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 ) { - // get all the resolved deferreds - var objs = makeArray(arguments), - // renderer is last [0] is the data - renderer = objs.pop()[0], - // the result of the template rendering with data - result; - - // make data look like the resolved deferreds - if ( isDeferred(data) ) { - data = usefulPart(resolved); - } - else { - // go through each prop in data again, - // replace the defferreds with what they resolved to - for ( var prop in data ) { - if ( isDeferred(data[prop]) ) { - data[prop] = usefulPart(objs.shift()); - } - } - } - // get the rendered result - result = renderer(data, helpers); - - //resolve with the rendered view - deferred.resolve(result); - // if there's a callback, call it back with the result - callback && callback(result); - }); - // return the deferred .... - return deferred.promise(); - } - else { - // no deferreds, render this bad boy - var response, - // if there's a callback function - async = typeof callback === "function", - // get the 'view' type - deferred = get(view, async); - - // if we are async, - if ( async ) { - // return the deferred - response = deferred; - // and callback callback with the rendered result - deferred.done(function( renderer ) { - callback(renderer(data, helpers)) - }) - } else { - // otherwise, the deferred is complete, so - // set response to the result of the rendering - deferred.done(function( renderer ) { - response = renderer(data, helpers); - }); - } - - return response; - } - }, - // makes sure there's a template, if not, has steal provide a warning - 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; - } - }, - // returns a 'view' renderer deferred - // url - the url to the view template - // async - if the ajax request should be synchronous - get = function( url, async ) { - return $.ajax({ - url: url, - dataType: "view", - async: async - }); - }, - // returns true if something looks like a deferred - 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 resolve to array (with success and such) - // returns the useful, content part - usefulPart = function( resolved ) { - return $.isArray(resolved) && resolved.length === 3 && resolved[1] === 'success' ? resolved[0] : resolved - }; - - - - // you can request a view renderer (a function you pass data to and get html) - // Creates a 'view' transport. These resolve to a 'view' renderer - // a 'view' renderer takes data and returns a string result. - // For example: - // - // $.ajax({dataType : 'view', src: 'foo.ejs'}).then(function(renderer){ - // renderer({message: 'hello world'}) - // }) - $.ajaxTransport("view", function( options, orig ) { - // the url (or possibly id) of the view content - var url = orig.url, - // check if a suffix exists (ex: "foo.ejs") - suffix = url.match(/\.[\w\d]+$/), - type, - // if we are reading a script element for the content of the template - // el will be set to that script element - el, - // a unique identifier for the view (used for caching) - // this is typically derived from the element id or - // the url for the template - id, - // the AJAX request used to retrieve the template content - jqXHR, - // used to generate the response - response = function( text ) { - // get the renderer function - var func = type.renderer(id, text); - // cache if if we are caching - if ( $view.cache ) { - $view.cached[id] = func; - } - // return the objects for the response's dataTypes - // (in this case view) - return { - view: func - }; - }; - - // if we have an inline template, derive the suffix from the 'text/???' part - // this only supports '' tags - if ( el = document.getElementById(url) ) { - suffix = "."+el.type.match(/\/(x\-)?(.+)/)[2]; - } - - // 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 - // you should only be using // if you are using steal - if ( url.match(/^\/\//) ) { - var sub = url.substr(2); - url = typeof steal === "undefined" ? - url = "/" + sub : - steal.root.mapJoin(sub) +''; - } - - //set the template engine type - type = $view.types[suffix]; - - // return the ajax transport contract: http://api.jquery.com/extending-ajax/ - return { - send: function( headers, callback ) { - // if it is cached, - if ( $view.cached[id] ) { - // return the catched renderer - return callback(200, "success", { - view: $view.cached[id] - }); - - // otherwise if we are getting this from a script elment - } else if ( el ) { - // resolve immediately with the element's innerHTML - callback(200, "success", response(el.innerHTML)); - } else { - // make an ajax request for text - jqXHR = $.ajax({ - async: orig.async, - url: url, - dataType: "text", - error: function() { - checkText("", url); - callback(404); - }, - success: function( text ) { - // make sure we got some text back - checkText(text, url); - // cache and send back text - callback(200, "success", response(text)) - } - }); - } - }, - abort: function() { - jqXHR && jqXHR.abort(); - } - } - }) - $.extend($view, { - /** - * @attribute hookups - * @hide - * A list of pending 'hookups' - */ - hookups: {}, - /** - * @function hookup - * 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 = "
        " - * $('.foo').html(html); - * - * - * @param {Function} cb a callback function to be called with the element - * @param {Number} the hookup number - */ - hookup: function( cb ) { - var myid = ++id; - $view.hookups[myid] = cb; - return myid; - }, - /** - * @attribute cached - * @hide - * Cached are put in this object - */ - cached: {}, - /** - * @attribute cache - * Should the views be cached or reloaded from the server. Defaults to true. - */ - cache: true, - /** - * @function register - * Registers a template engine to be used with - * view helpers and compression. - * - * ## Example - * - * @codestart - * $.View.register({ - * suffix : "tmpl", - * plugin : "jquery/view/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); }"; - * } - * }) - * @codeend - * Here's what each property does: - * - * * plugin - the location of the plugin - * * suffix - files that use this suffix will be processed by this template engine - * * renderer - returns a function that will render the template provided by text - * * script - returns a string form of the processed template function. - * - * @param {Object} info a object of method and properties - * - * that enable template integration: - *
          - *
        • plugin - the location of the plugin. EX: 'jquery/view/ejs'
        • - *
        • suffix - the view extension. EX: 'ejs'
        • - *
        • script(id, src) - a function that returns a string that when evaluated returns a function that can be - * used as the render (i.e. have func.call(data, data, helpers) called on it).
        • - *
        • renderer(id, text) - a function that takes the id of the template and the text of the template and - * returns a render function.
        • - *
        - */ - register: function( info ) { - this.types["." + info.suffix] = info; - - if ( window.steal ) { - steal.type(info.suffix + " view js", function( options, success, error ) { - var type = $view.types["." + options.type], - id = toId(options.rootSrc+''); - - options.text = type.script(id, options.text) - success(); - }) - } - }, - types: {}, - /** - * @attribute ext - * The default suffix to use if none is provided in the view's url. - * This is set to .ejs by default. - */ - ext: ".ejs", - /** - * Returns the text that - * @hide - * @param {Object} type - * @param {Object} id - * @param {Object} src - */ - registerScript: function( type, id, src ) { - return "$.View.preload('" + id + "'," + $view.types["." + type].script(id, src) + ");"; - }, - /** - * @hide - * Called by a production script to pre-load a renderer function - * into the view cache. - * @param {String} id - * @param {Function} renderer - */ - preload: function( id, renderer ) { - $view.cached[id] = function( data, helpers ) { - return renderer.call(data, data, helpers); - }; - } - - }); - if ( window.steal ) { - steal.type("view js", function( options, success, error ) { - var type = $view.types["." + options.type], - id = toId(options.rootSrc+''); - - options.text = "steal('" + (type.plugin || "jquery/view/" + options.type) + "').then(function($){" + "$.View.preload('" + id + "'," + options.text + ");\n})"; - success(); - }) - } - - //---- ADD jQUERY HELPERS ----- - //converts jquery functions to use views - var convert, modify, isTemplate, isHTML, isDOM, getCallback, hookupView, funcs, - // text and val cannot produce an element, so don't run hookups on them - noHookup = {'val':true,'text':true}; - - convert = function( func_name ) { - // save the old jQuery helper - var old = $.fn[func_name]; - - // replace it wiht our new helper - $.fn[func_name] = function() { - - var args = makeArray(arguments), - callbackNum, - callback, - self = this, - result; - - // if the first arg is a deferred - // wait until it finishes, and call - // modify with the result - if ( isDeferred(args[0]) ) { - args[0].done(function( res ) { - modify.call(self, [res], old); - }) - return this; - } - //check if a template - else if ( isTemplate(args) ) { - - // if we should operate async - if ((callbackNum = getCallback(args))) { - callback = args[callbackNum]; - args[callbackNum] = function( result ) { - modify.call(self, [result], old); - callback.call(self, result); - }; - $view.apply($view, args); - return this; - } - // call view with args (there might be deferreds) - result = $view.apply($view, args); - - // if we got a string back - if (!isDeferred(result) ) { - // we are going to call the old method with that string - args = [result]; - } else { - // if there is a deferred, wait until it is done before calling modify - result.done(function( res ) { - modify.call(self, [res], old); - }) - return this; - } - } - return noHookup[func_name] ? old.apply(this,args) : - modify.call(this, args, old); - }; - }; - - // modifies the content of the element - // but also will run any hookup - modify = function( args, old ) { - var res, stub, hooks; - - //check if there are new hookups - for ( var hasHookups in $view.hookups ) { - break; - } - - //if there are hookups, get jQuery object - if ( hasHookups && args[0] && isHTML(args[0]) ) { - hooks = $view.hookups; - $view.hookups = {}; - args[0] = $(args[0]); - } - res = old.apply(this, args); - - //now hookup the hookups - if ( hooks - /* && args.length*/ - ) { - hookupView(args[0], hooks); - } - return res; - }; - - // returns true or false if the args indicate a template is being used - // $('#foo').html('/path/to/template.ejs',{data}) - // in general, we want to make sure the first arg is a string - // and the second arg is data - isTemplate = function( args ) { - // save the second arg type - var secArgType = typeof args[1]; - - // the first arg is a string - return typeof args[0] == "string" && - // the second arg is an object or function - (secArgType == 'object' || secArgType == 'function') && - // but it is not a dom element - !isDOM(args[1]); - }; - // returns true if the arg is a jQuery object or HTMLElement - isDOM = function(arg){ - return arg.nodeType || arg.jquery - }; - // returns whether the argument is some sort of HTML data - isHTML = function( arg ) { - if ( isDOM(arg) ) { - // if jQuery object or DOM node we're good - return true; - } else if ( typeof arg === "string" ) { - // if string, do a quick sanity check that we're HTML - arg = $.trim(arg); - return arg.substr(0, 1) === "<" && arg.substr(arg.length - 1, 1) === ">" && arg.length >= 3; - } else { - // don't know what you are - return false; - } - }; - - //returns the callback arg number if there is one (for async view use) - getCallback = function( args ) { - return typeof args[3] === 'function' ? 3 : typeof args[2] === 'function' && 2; - }; - - hookupView = function( els, hooks ) { - //remove all hookups - var hookupEls, len, i = 0, - id, func; - els = els.filter(function() { - return this.nodeType != 3; //filter out text nodes - }) - hookupEls = els.add("[data-view-id]", els); - len = hookupEls.length; - for (; i < len; i++ ) { - if ( hookupEls[i].getAttribute && (id = hookupEls[i].getAttribute('data-view-id')) && (func = hooks[id]) ) { - func(hookupEls[i], id); - delete hooks[id]; - hookupEls[i].removeAttribute('data-view-id'); - } - } - //copy remaining hooks back - $.extend($view.hookups, hooks); - }; - - /** - * @add jQuery.fn - * @parent jQuery.View - * Called on a jQuery collection that was rendered with $.View with pending hookups. $.View can render a - * template with hookups, but not actually perform the hookup, because it returns a string without actual DOM - * elements to hook up to. So hookup performs the hookup and clears the pending hookups, preventing errors in - * future templates. - * - * @codestart - * $($.View('//views/recipes.ejs',recipeData)).hookup() - * @codeend - */ - $.fn.hookup = function() { - var hooks = $view.hookups; - $view.hookups = {}; - hookupView(this, hooks); - return this; - }; - - /** - * @add jQuery.fn - */ - $.each([ - /** - * @function prepend - * @parent jQuery.View - * - * Extending the original [http://api.jquery.com/prepend/ jQuery().prepend()] - * to render [jQuery.View] templates inserted at the beginning of each element in the set of matched elements. - * - * $('#test').prepend('path/to/template.ejs', { name : 'javascriptmvc' }); - * - * @param {String|Object|Function} content A template filename or the id of a view script tag - * or a DOM element, array of elements, HTML string, or jQuery object. - * @param {Object} [data] The data to render the view with. - * If rendering a view template this parameter always has to be present - * (use the empty object initializer {} for no data). - */ - "prepend", - /** - * @function append - * @parent jQuery.View - * - * Extending the original [http://api.jquery.com/append/ jQuery().append()] - * to render [jQuery.View] templates inserted at the end of each element in the set of matched elements. - * - * $('#test').append('path/to/template.ejs', { name : 'javascriptmvc' }); - * - * @param {String|Object|Function} content A template filename or the id of a view script tag - * or a DOM element, array of elements, HTML string, or jQuery object. - * @param {Object} [data] The data to render the view with. - * If rendering a view template this parameter always has to be present - * (use the empty object initializer {} for no data). - */ - "append", - /** - * @function after - * @parent jQuery.View - * - * Extending the original [http://api.jquery.com/after/ jQuery().after()] - * to render [jQuery.View] templates inserted after each element in the set of matched elements. - * - * $('#test').after('path/to/template.ejs', { name : 'javascriptmvc' }); - * - * @param {String|Object|Function} content A template filename or the id of a view script tag - * or a DOM element, array of elements, HTML string, or jQuery object. - * @param {Object} [data] The data to render the view with. - * If rendering a view template this parameter always has to be present - * (use the empty object initializer {} for no data). - */ - "after", - /** - * @function before - * @parent jQuery.View - * - * Extending the original [http://api.jquery.com/before/ jQuery().before()] - * to render [jQuery.View] templates inserted before each element in the set of matched elements. - * - * $('#test').before('path/to/template.ejs', { name : 'javascriptmvc' }); - * - * @param {String|Object|Function} content A template filename or the id of a view script tag - * or a DOM element, array of elements, HTML string, or jQuery object. - * @param {Object} [data] The data to render the view with. - * If rendering a view template this parameter always has to be present - * (use the empty object initializer {} for no data). - */ - "before", - /** - * @function text - * @parent jQuery.View - * - * Extending the original [http://api.jquery.com/text/ jQuery().text()] - * to render [jQuery.View] templates as the content of each matched element. - * Unlike [jQuery.fn.html] jQuery.fn.text also works with XML, escaping the provided - * string as necessary. - * - * $('#test').text('path/to/template.ejs', { name : 'javascriptmvc' }); - * - * @param {String|Object|Function} content A template filename or the id of a view script tag - * or a DOM element, array of elements, HTML string, or jQuery object. - * @param {Object} [data] The data to render the view with. - * If rendering a view template this parameter always has to be present - * (use the empty object initializer {} for no data). - */ - "text", - /** - * @function html - * @parent jQuery.View - * - * Extending the original [http://api.jquery.com/html/ jQuery().html()] - * to render [jQuery.View] templates as the content of each matched element. - * - * $('#test').html('path/to/template.ejs', { name : 'javascriptmvc' }); - * - * @param {String|Object|Function} content A template filename or the id of a view script tag - * or a DOM element, array of elements, HTML string, or jQuery object. - * @param {Object} [data] The data to render the view with. - * If rendering a view template this parameter always has to be present - * (use the empty object initializer {} for no data). - */ - "html", - /** - * @function replaceWith - * @parent jQuery.View - * - * Extending the original [http://api.jquery.com/replaceWith/ jQuery().replaceWith()] - * to render [jQuery.View] templates replacing each element in the set of matched elements. - * - * $('#test').replaceWith('path/to/template.ejs', { name : 'javascriptmvc' }); - * - * @param {String|Object|Function} content A template filename or the id of a view script tag - * or a DOM element, array of elements, HTML string, or jQuery object. - * @param {Object} [data] The data to render the view with. - * If rendering a view template this parameter always has to be present - * (use the empty object initializer {} for no data). - */ - "replaceWith", "val"],function(i, func){ - convert(func); - }); - - //go through helper funcs and convert - - -}); \ No newline at end of file +steal("can/view/modifiers",'can/view/view_steal.js',function(){ + $.View = can.view; +}); From 4383a7773aab41fd8fc7029c7986453216529b9f Mon Sep 17 00:00:00 2001 From: Justin Meyer Date: Fri, 13 Apr 2012 05:31:03 -0500 Subject: [PATCH 093/103] moving steal stuff into view --- view/view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/view/view.js b/view/view.js index 49a82617..e748ab4b 100644 --- a/view/view.js +++ b/view/view.js @@ -1,3 +1,3 @@ -steal("can/view/modifiers",'can/view/view_steal.js',function(){ +steal("can/view/modifiers",function(){ $.View = can.view; }); From 20fd0bae45e5bafc28e50bb7a26981c9ab802422 Mon Sep 17 00:00:00 2001 From: Derek Prior Date: Tue, 17 Apr 2012 13:55:53 -0300 Subject: [PATCH 094/103] Typo fix in the docs. --- class/class.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/class/class.js b/class/class.js index 8df6f95d..b48d1d00 100644 --- a/class/class.js +++ b/class/class.js @@ -61,7 +61,7 @@ steal("jquery","jquery/lang/string",function( $ ) { * @test jquery/class/qunit.html * @description Easy inheritance in JavaScript. * - * Class provides simulated inheritance in JavaScript. Use clss to bridge the gap between + * 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] * Inheritance library. Besides prototypal inheritance, it includes a few important features: From 2ca7f05c3b73d743449ede3b544c6cdaba2f2b16 Mon Sep 17 00:00:00 2001 From: Justin Meyer Date: Thu, 19 Apr 2012 12:37:35 -0500 Subject: [PATCH 095/103] all jquerymx tests pass in firefox --- dom/fixture/fixture.js | 3 +++ dom/route/route_test.js | 6 +++--- model/test/qunit/qunit.js | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) create mode 100644 dom/fixture/fixture.js diff --git a/dom/fixture/fixture.js b/dom/fixture/fixture.js new file mode 100644 index 00000000..93c82a75 --- /dev/null +++ b/dom/fixture/fixture.js @@ -0,0 +1,3 @@ +steal('can/util/fixture',function($){ + $.fixture = can.fixture; +}) diff --git a/dom/route/route_test.js b/dom/route/route_test.js index 4e4719ba..26bbfbe4 100644 --- a/dom/route/route_test.js +++ b/dom/route/route_test.js @@ -241,15 +241,15 @@ test("linkTo", function(){ $.route.routes = {}; $.route(":foo"); var res = $.route.link("Hello",{foo: "bar", baz: 'foo'}); - equal( res, 'Hello'); + equal( res, 'Hello'); }) test("param with route defined", function(){ $.route.routes = {}; $.route("holler") $.route("foo"); - - var res = $.route.param({foo: "abc",route: "foo"}); + var data = {foo: "abc",route: "foo"} + var res = $.route.param(data); equal(res, "foo&foo=abc") }) diff --git a/model/test/qunit/qunit.js b/model/test/qunit/qunit.js index 84be2eca..e1117ddb 100644 --- a/model/test/qunit/qunit.js +++ b/model/test/qunit/qunit.js @@ -1,5 +1,5 @@ //we probably have to have this only describing where the tests are -steal("jquery/model","can/util/fixture") //load your app +steal("jquery/model","jquery/dom/fixture") //load your app .then('funcunit/qunit') //load qunit .then("./model_test.js")//,"./associations_test.js") .then( From 85fa61d4dd6a43bdf2930cdf856cb4a9735e5549 Mon Sep 17 00:00:00 2001 From: David Luecke Date: Tue, 24 Apr 2012 12:33:35 -0600 Subject: [PATCH 096/103] Makes Model validation tests pass in IE --- model/validations/qunit/validations_test.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/model/validations/qunit/validations_test.js b/model/validations/qunit/validations_test.js index 42c40dc1..65369d8a 100644 --- a/model/validations/qunit/validations_test.js +++ b/model/validations/qunit/validations_test.js @@ -1,9 +1,10 @@ steal('funcunit/qunit','jquery/model/validations').then(function(){ +var Person; + module("jquery/model/validations",{ setup : function(){ - delete window.Person - jQuery.Model.extend("Person",{},{}); + Person = jQuery.Model({},{}); } }) From 76077eda35cae65f8f028917d9e60593d7d4c794 Mon Sep 17 00:00:00 2001 From: David Luecke Date: Tue, 24 Apr 2012 16:26:29 -0600 Subject: [PATCH 097/103] Some compatibility mappings --- class/class.js | 8 +++++++- controller/controller.js | 3 +++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/class/class.js b/class/class.js index 38771b9b..dc9f8c4d 100644 --- a/class/class.js +++ b/class/class.js @@ -7,5 +7,11 @@ steal("can/construct/proxy","can/construct/super",function( $ ) { $.Class = can.Construct; - + var old = $.Class.extend; + $.Class.extend = function() { + var cls = old.apply(this, arguments); + cls.prototype.Class = cls.prototype.constructor; + cls.prototype.callback = cls.prototype.proxy; + return cls; + } })(); diff --git a/controller/controller.js b/controller/controller.js index 2d735d3b..fecbec26 100644 --- a/controller/controller.js +++ b/controller/controller.js @@ -4,4 +4,7 @@ steal('jquery/class','can/control/plugin',function( $ ) { $.fn.controller = $.fn.control; $.fn.controllers = $.fn.controllers; + $.Controller.prototype.find = function(selector) { + return this.element.find(selector); + } }); From 65145943b094883828bf153e7e807375d842eeeb Mon Sep 17 00:00:00 2001 From: David Luecke Date: Thu, 26 Apr 2012 11:43:34 -0600 Subject: [PATCH 098/103] Add compatibility wrappers to can --- class/class.js | 9 ++++----- controller/controller.js | 10 +++++----- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/class/class.js b/class/class.js index dc9f8c4d..0c848586 100644 --- a/class/class.js +++ b/class/class.js @@ -4,14 +4,13 @@ // It provides class level inheritance and callbacks. //!steal-clean steal("can/construct/proxy","can/construct/super",function( $ ) { - - $.Class = can.Construct; - - var old = $.Class.extend; - $.Class.extend = function() { + var old = can.Construct.extend; + can.Construct.extend = function() { var cls = old.apply(this, arguments); cls.prototype.Class = cls.prototype.constructor; cls.prototype.callback = cls.prototype.proxy; return cls; } + + $.Class = can.Construct; })(); diff --git a/controller/controller.js b/controller/controller.js index fecbec26..7dc47f21 100644 --- a/controller/controller.js +++ b/controller/controller.js @@ -1,10 +1,10 @@ steal('jquery/class','can/control/plugin',function( $ ) { - + can.Control.prototype.bind = can.Control.prototype.on; + can.Control.prototype.find = function(selector) { + return this.element.find(selector); + } + $.Controller = can.Control; $.fn.controller = $.fn.control; $.fn.controllers = $.fn.controllers; - - $.Controller.prototype.find = function(selector) { - return this.element.find(selector); - } }); From 54edfe6409b20817364691adfca066809c476e2a Mon Sep 17 00:00:00 2001 From: David Luecke Date: Mon, 30 Apr 2012 13:26:22 -0600 Subject: [PATCH 099/103] Revert to 75004b1d5ed10aa804ff3c42524008481d92dd12, before donejs merge --- class/class.html | 109 + class/class.js | 789 +- class/class_test.js | 8 +- controller/controller.js | 1097 ++- controller/controller_test.js | 4 +- controller/route/route.js | 32 +- controller/view/view.js | 122 +- dom/fixture/fixture.html | 144 + dom/fixture/fixture.js | 906 +- dom/fixture/fixture_test.js | 332 + dom/fixture/fixtures/foo.json | 1 + dom/fixture/fixtures/foobar.json | 3 + dom/fixture/fixtures/messages.html | 31 + dom/fixture/fixtures/remove.json | 3 + dom/fixture/fixtures/test.json | 3 + dom/fixture/qunit.html | 22 + dom/route/route.js | 471 +- dom/route/route_test.js | 6 +- jquery.js | 9267 ++++++++++++++++++- lang/observe/delegate/delegate.js | 313 +- lang/observe/delegate/delegate_test.js | 6 +- lang/observe/observe.js | 1021 +- lang/observe/observe_test.js | 24 +- model/backup/backup.js | 134 +- model/backup/qunit/qunit.js | 4 +- model/list/list.js | 826 +- model/list/list_test.js | 12 +- model/model.js | 1824 +++- model/test/qunit/model_test.js | 62 +- model/validations/qunit/validations_test.js | 20 +- model/validations/validations.js | 204 +- test/qunit/integration.js | 20 +- test/qunit/qunit.js | 4 +- tie/qunit.html | 20 + tie/tie.html | 114 + tie/tie.js | 97 + tie/tie_test.js | 125 + view/ejs/ejs.js | 665 +- view/ejs/ejs_test.js | 32 +- view/test/qunit/qunit.js | 12 +- view/test/qunit/view_test.js | 31 +- view/view.js | 883 +- 42 files changed, 19660 insertions(+), 143 deletions(-) create mode 100644 class/class.html create mode 100644 dom/fixture/fixture.html create mode 100644 dom/fixture/fixture_test.js create mode 100644 dom/fixture/fixtures/foo.json create mode 100644 dom/fixture/fixtures/foobar.json create mode 100644 dom/fixture/fixtures/messages.html create mode 100644 dom/fixture/fixtures/remove.json create mode 100644 dom/fixture/fixtures/test.json create mode 100644 dom/fixture/qunit.html create mode 100644 tie/qunit.html create mode 100644 tie/tie.html create mode 100644 tie/tie.js create mode 100644 tie/tie_test.js diff --git a/class/class.html b/class/class.html new file mode 100644 index 00000000..ef16143d --- /dev/null +++ b/class/class.html @@ -0,0 +1,109 @@ + + + + jQuery.Class Demo + + + +

        jQuery.Class Demo shows a tabs controller extended to work with history.

        +
        +

        Basic Tabs

        + +
        Tab 1 Content
        +
        Tab 2 Content
        +
        Tab 3 Content
        +

        History Tabs

        + +
        Tab 4 Content
        +
        Tab 5 Content
        +
        Tab 6 Content
        +
        + + + + + \ No newline at end of file diff --git a/class/class.js b/class/class.js index 0c848586..8df6f95d 100644 --- a/class/class.js +++ b/class/class.js @@ -3,14 +3,783 @@ // http://ejohn.org/blog/simple-javascript-inheritance/ // It provides class level inheritance and callbacks. //!steal-clean -steal("can/construct/proxy","can/construct/super",function( $ ) { - var old = can.Construct.extend; - can.Construct.extend = function() { - var cls = old.apply(this, arguments); - cls.prototype.Class = cls.prototype.constructor; - cls.prototype.callback = cls.prototype.proxy; - return cls; - } - - $.Class = can.Construct; +steal("jquery","jquery/lang/string",function( $ ) { + + // =============== HELPERS ================= + + // if we are initializing a new class + var initializing = false, + makeArray = $.makeArray, + isFunction = $.isFunction, + isArray = $.isArray, + extend = $.extend, + getObject = $.String.getObject, + 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] = isFunction(newProps[name]) && + isFunction(oldProps[name]) && + fnTest.test(newProps[name]) ? (function( name, fn ) { + return function() { + var tmp = this._super, + ret; + + // Add a new ._super() method that is the same method + // but on the super-class + this._super = oldProps[name]; + + // The method only need to be bound temporarily, so we + // remove it when we're done executing + ret = fn.apply(this, arguments); + this._super = tmp; + return ret; + }; + })(name, newProps[name]) : newProps[name]; + } + }, + STR_PROTOTYPE = 'prototype' + + /** + * @class jQuery.Class + * @plugin jquery/class + * @parent jquerymx + * @download dist/jquery/jquery.class.js + * @test jquery/class/qunit.html + * @description Easy inheritance in JavaScript. + * + * 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: + * + * - Static inheritance + * - Introspection + * - Namespaces + * - Setup and initialization methods + * - Easy callback function creation + * + * + * The [mvc.class Get Started with jQueryMX] has a good walkthrough of $.Class. + * + * ## Static v. Prototype + * + * Before learning about Class, it's important to + * understand the difference between + * 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. + * + * ## 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. + * + * @codestart + * $.Class('Monster', + * /* @static *| + * { + * count: 0 + * }, + * /* @prototype *| + * { + * init: function( name ) { + * + * // saves name on the monster instance + * this.name = name; + * + * // sets the health + * this.health = 10; + * + * // increments count + * this.constructor.count++; + * }, + * eat: function( smallChildren ){ + * this.health += smallChildren; + * }, + * fight: function() { + * this.health -= 2; + * } + * }); + * + * hydra = new Monster('hydra'); + * + * dragon = new Monster('dragon'); + * + * hydra.name // -> hydra + * Monster.count // -> 2 + * Monster.shortName // -> 'Monster' + * + * hydra.eat(2); // health = 12 + * + * dragon.fight(); // health = 8 + * + * @codeend + * + * + * Notice that the prototype init function is called when a new instance of Monster is created. + * + * + * ## 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 + * this._super. Lets create a SeaMonster class. SeaMonsters are less + * efficient at eating small children, but more powerful fighters. + * + * + * Monster("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: + * + * $.Class("First", + * { + * staticMethod: function() { return 1;} + * },{}) + * + * First("Second",{ + * staticMethod: function() { return this._super()+1;} + * },{}) + * + * 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: + * + * + * $.Class("MyNamespace.MyClass",{},{}); + * + * new MyNamespace.MyClass() + * + * + *

        Introspection

        + * + * Often, it's nice to create classes whose name helps determine functionality. Ruby on + * Rails's [http://api.rubyonrails.org/classes/ActiveRecord/Base.html|ActiveRecord] ORM class + * is a great example of this. Unfortunately, JavaScript doesn't have a way of determining + * an object's name, so the developer must provide a name. Class fixes this by taking a String name for the class. + * + * $.Class("MyOrg.MyClass",{},{}) + * MyOrg.MyClass.shortName //-> 'MyClass' + * MyOrg.MyClass.fullName //-> 'MyOrg.MyClass' + * + * The fullName (with namespaces) and the shortName (without namespaces) are added to the Class's + * static properties. + * + * + * ## Setup and initialization methods + * + *

        + * Class provides static and prototype initialization functions. + * These come in two flavors - setup and init. + * Setup is called before init and + * can be used to 'normalize' init's arguments. + *

        + *
        PRO TIP: Typically, you don't need setup methods in your classes. Use Init instead. + * Reserve setup methods for when you need to do complex pre-processing of your class before init is called. + * + *
        + * @codestart + * $.Class("MyClass", + * { + * setup: function() {} //static setup + * init: function() {} //static constructor + * }, + * { + * setup: function() {} //prototype setup + * init: function() {} //prototype constructor + * }) + * @codeend + * + * ### Setup + * + * Setup functions are called before init functions. Static setup functions are passed + * the base class followed by arguments passed to the extend function. + * Prototype static functions are passed the Class constructor + * function arguments. + * + * If a setup function returns an array, that array will be used as the arguments + * for the following init method. This provides setup functions the ability to normalize + * arguments passed to the init constructors. They are also excellent places + * to put setup code you want to almost always run. + * + * + * The following is similar to how [jQuery.Controller.prototype.setup] + * makes sure init is always called with a jQuery element and merged options + * even if it is passed a raw + * HTMLElement and no second parameter. + * + * $.Class("jQuery.Controller",{ + * ... + * },{ + * setup: function( el, options ) { + * ... + * return [$(el), + * $.extend(true, + * this.Class.defaults, + * options || {} ) ] + * } + * }) + * + * Typically, you won't need to make or overwrite setup functions. + * + * ### Init + * + * Init functions are called after setup functions. + * Typically, they receive the same arguments + * as their preceding setup function. The Foo class's init method + * gets called in the following example: + * + * $.Class("Foo", { + * init: function( arg1, arg2, arg3 ) { + * this.sum = arg1+arg2+arg3; + * } + * }) + * var foo = new Foo(1,2,3); + * foo.sum //-> 6 + * + * ## Proxies + * + * Similar to jQuery's proxy method, Class provides a + * [jQuery.Class.static.proxy proxy] + * function that returns a callback to a method that will always + * have + * this set to the class or instance of the class. + * + * + * The following example uses this.proxy to make sure + * this.name is available in show. + * + * $.Class("Todo",{ + * init: function( name ) { + * this.name = name + * }, + * get: function() { + * $.get("/stuff",this.proxy('show')) + * }, + * show: function( txt ) { + * alert(this.name+txt) + * } + * }) + * new Todo("Trash").get() + * + * Callback is available as a static and prototype method. + * + * ## Demo + * + * @demo jquery/class/class.html + * + * + * @constructor + * + * To create a Class call: + * + * $.Class( [NAME , STATIC,] PROTOTYPE ) -> Class + * + *
        + *
        {optional:String} + *

        If provided, this sets the shortName and fullName of the + * class and adds it and any necessary namespaces to the + * window object.

        + *
        + *
        {optional:Object} + *

        If provided, this creates static properties and methods + * on the class.

        + *
        + *
        {Object} + *

        Creates prototype methods on the class.

        + *
        + *
        + * + * When a Class is created, the static [jQuery.Class.static.setup setup] + * and [jQuery.Class.static.init init] methods are called. + * + * To create an instance of a Class, call: + * + * new Class([args ... ]) -> instance + * + * The created instance will have all the + * prototype properties and methods defined by the PROTOTYPE object. + * + * When an instance is created, the prototype [jQuery.Class.prototype.setup setup] + * and [jQuery.Class.prototype.init init] methods + * are called. + */ + + clss = $.Class = function() { + if (arguments.length) { + return clss.extend.apply(clss, arguments); + } + }; + + /* @Static*/ + extend(clss, { + /** + * @function proxy + * Returns a callback function for a function on this Class. + * Proxy ensures that 'this' is set appropriately. + * @codestart + * $.Class("MyClass",{ + * getData: function() { + * this.showing = null; + * $.get("data.json",this.proxy('gotData'),'json') + * }, + * gotData: function( data ) { + * this.showing = data; + * } + * },{}); + * MyClass.showData(); + * @codeend + *

        Currying Arguments

        + * Additional arguments to proxy will fill in arguments on the returning function. + * @codestart + * $.Class("MyClass",{ + * getData: function( callback ) { + * $.get("data.json",this.proxy('process',callback),'json'); + * }, + * process: function( callback, jsonData ) { //callback is added as first argument + * jsonData.processed = true; + * callback(jsonData); + * } + * },{}); + * MyClass.getData(showDataFunc) + * @codeend + *

        Nesting Functions

        + * Proxy can take an array of functions to call as + * the first argument. When the returned callback function + * is called each function in the array is passed the return value of the prior function. This is often used + * to eliminate currying initial arguments. + * @codestart + * $.Class("MyClass",{ + * getData: function( callback ) { + * //calls process, then callback with value from process + * $.get("data.json",this.proxy(['process2',callback]),'json') + * }, + * process2: function( type,jsonData ) { + * jsonData.processed = true; + * return [jsonData]; + * } + * },{}); + * MyClass.getData(showDataFunc); + * @codeend + * @param {String|Array} fname If a string, it represents the function to be called. + * If it is an array, it will call each function in order and pass the return value of the prior function to the + * next function. + * @return {Function} the callback function. + */ + proxy: function( funcs ) { + + //args that should be curried + var args = makeArray(arguments), + self; + + // get the functions to callback + funcs = args.shift(); + + // if there is only one function, make funcs into an array + if (!isArray(funcs) ) { + funcs = [funcs]; + } + + // keep a reference to us in self + self = this; + + //!steal-remove-start + for( var i =0; i< funcs.length;i++ ) { + 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() { + // add the arguments after the curried args + var cur = concatArgs(args, arguments), + isString, + length = funcs.length, + f = 0, + func; + + // go through each function to call back + for (; f < length; f++ ) { + func = funcs[f]; + if (!func ) { + continue; + } + + // set called with the name of the function on self (this is how this.view works) + isString = typeof func == "string"; + if ( isString && self._set_called ) { + self.called = func; + } + + // call the function + cur = (isString ? self[func] : func).apply(self, cur || []); + + // pass the result to the next function (if there is a next function) + if ( f < length - 1 ) { + cur = !isArray(cur) || cur._use_call ? [cur] : cur + } + } + return cur; + } + }, + /** + * @function newInstance + * Creates a new instance of the class. This method is useful for creating new instances + * with arbitrary parameters. + *

        Example

        + * @codestart + * $.Class("MyClass",{},{}) + * var mc = MyClass.newInstance.apply(null, new Array(parseInt(Math.random()*10,10)) + * @codeend + * @return {class} instance of the class + */ + newInstance: function() { + // get a raw instance objet (init is not called) + var inst = this.rawInstance(), + args; + + // call setup if there is a setup + if ( inst.setup ) { + args = inst.setup.apply(inst, arguments); + } + // call init if there is an init, if setup returned args, use those as the arguments + if ( inst.init ) { + inst.init.apply(inst, isArray(args) ? args : arguments); + } + return inst; + }, + /** + * 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( baseClass, fullName ) { + // set defaults as the merger of the parent defaults and this object's defaults + this.defaults = extend(true, {}, baseClass.defaults, this.defaults); + return arguments; + }, + rawInstance: function() { + // prevent running init + initializing = true; + var inst = new this(); + initializing = false; + // allow running init + return inst; + }, + /** + * Extends a class with new static and prototype functions. There are a variety of ways + * to use extend: + * + * // with className, static and prototype functions + * $.Class('Task',{ STATIC },{ PROTOTYPE }) + * // with just classname and prototype functions + * $.Class('Task',{ PROTOTYPE }) + * // with just a className + * $.Class('Task') + * + * You no longer have to use .extend. Instead, you can pass those options directly to + * $.Class (and any inheriting classes): + * + * // with className, static and prototype functions + * $.Class('Task',{ STATIC },{ PROTOTYPE }) + * // with just classname and prototype functions + * $.Class('Task',{ PROTOTYPE }) + * // with just a className + * $.Class('Task') + * + * @param {String} [fullName] the classes name (used for classes w/ introspection) + * @param {Object} [klass] the new classes static/class functions + * @param {Object} [proto] the new classes prototype functions + * + * @return {jQuery.Class} returns the new class + */ + extend: function( fullName, klass, proto ) { + // figure out what was passed and normalize it + if ( typeof fullName != 'string' ) { + proto = klass; + klass = fullName; + fullName = null; + } + if (!proto ) { + proto = klass; + klass = null; + } + + proto = proto || {}; + var _super_class = this, + _super = this[STR_PROTOTYPE], + name, shortName, namespace, prototype; + + // Instantiate a base class (but only create the instance, + // don't run the init constructor) + initializing = true; + prototype = new this(); + initializing = false; + + // Copy the properties over onto the new prototype + inheritProps(proto, _super, prototype); + + // The dummy class constructor + function Class() { + // All construction is actually done in the init method + if ( initializing ) return; + + // we are being called w/o new, we are extending + if ( this.constructor !== Class && arguments.length ) { + return arguments.callee.extend.apply(arguments.callee, arguments) + } else { //we are being called w/ new + return this.Class.newInstance.apply(this.Class, arguments) + } + } + // Copy old stuff onto class + for ( name in this ) { + if ( this.hasOwnProperty(name) ) { + Class[name] = this[name]; + } + } + + // copy new static props on class + inheritProps(klass, this, Class); + + // do namespace stuff + if ( fullName ) { + + var parts = fullName.split(/\./), + shortName = parts.pop(), + current = getObject(parts.join('.'), window, true), + namespace = current; + + //!steal-remove-start + if (!Class.nameOk ) { + //steal.dev.isHappyName(fullName) + } + if(current[shortName]){ + steal.dev.warn("class.js There's already something called "+fullName) + } + //!steal-remove-end + current[shortName] = Class; + } + + // set things that can't be overwritten + extend(Class, { + prototype: prototype, + /** + * @attribute namespace + * The namespaces object + * + * $.Class("MyOrg.MyClass",{},{}) + * MyOrg.MyClass.namespace //-> MyOrg + * + */ + namespace: namespace, + /** + * @attribute shortName + * The name of the class without its namespace, provided for introspection purposes. + * + * $.Class("MyOrg.MyClass",{},{}) + * MyOrg.MyClass.shortName //-> 'MyClass' + * MyOrg.MyClass.fullName //-> 'MyOrg.MyClass' + * + */ + shortName: shortName, + constructor: Class, + /** + * @attribute fullName + * The full name of the class, including namespace, provided for introspection purposes. + * + * $.Class("MyOrg.MyClass",{},{}) + * MyOrg.MyClass.shortName //-> 'MyClass' + * MyOrg.MyClass.fullName //-> 'MyOrg.MyClass' + * + */ + fullName: fullName + }); + + //make sure our prototype looks nice + Class[STR_PROTOTYPE].Class = Class[STR_PROTOTYPE].constructor = Class; + + + + // call the class setup + var args = Class.setup.apply(Class, concatArgs([_super_class],arguments)); + + // call the class init + if ( Class.init ) { + Class.init.apply(Class, args || concatArgs([_super_class],arguments)); + } + + /* @Prototype*/ + return Class; + /** + * @function setup + * 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 ( new Class( arguments ... )). + * + * $.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 )] + * } + * }) + * + *
        PRO TIP: + * Setup functions are used to normalize constructor arguments and provide a place for + * setup code that extending classes don't have to remember to call _super to + * run. + *
        + * + * 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 + * If an init 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: ( new Class( arguments ... )). + * + * $.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 + /** + * @attribute constructor + * + * A reference to the Class (or constructor function). This allows you to access + * a class's static properties from an instance. + * + * ### Quick Example + * + * // a class with a static property + * $.Class("MyClass", {staticProperty : true}, {}); + * + * // a new instance of myClass + * var mc1 = new MyClass(); + * + * // read the static property from the instance: + * mc1.constructor.staticProperty //-> true + * + * Getting static properties with the constructor property, like + * [jQuery.Class.static.fullName fullName], is very common. + * + */ + } + + }) + + + + + + clss.callback = clss[STR_PROTOTYPE].callback = clss[STR_PROTOTYPE]. + /** + * @function proxy + * Returns a method that sets 'this' to the current instance. This does the same thing as + * and is described better in [jQuery.Class.static.proxy]. + * The only difference is this proxy works + * on a instance instead of a class. + * @param {String|Array} fname If a string, it represents the function to be called. + * If it is an array, it will call each function in order and pass the return value of the prior function to the + * next function. + * @return {Function} the callback function + */ + proxy = clss.proxy; + + })(); diff --git a/class/class_test.js b/class/class_test.js index 61caa64d..ec95179c 100644 --- a/class/class_test.js +++ b/class/class_test.js @@ -14,7 +14,7 @@ test("Creating", function(){ }, { init: function() { - this.constructor.count++; + this.Class.count++; this.eyes = false; } } @@ -139,12 +139,12 @@ test("callback", function(){ } }) - var cb = Car.proxy('show'); + var cb = Car.callback('show'); curVal = 1; cb(1) curVal = 2; - var cb2 = Car.proxy('show',2) + var cb2 = Car.callback('show',2) cb2(); }); @@ -159,7 +159,7 @@ test("callback error", 1,function(){ } }) try{ - Car.proxy('huh'); + Car.callback('huh'); ok(false, "I should have errored") }catch(e){ ok(true, "Error was thrown") diff --git a/controller/controller.js b/controller/controller.js index 7dc47f21..f3bd8476 100644 --- a/controller/controller.js +++ b/controller/controller.js @@ -1,10 +1,1089 @@ -steal('jquery/class','can/control/plugin',function( $ ) { - can.Control.prototype.bind = can.Control.prototype.on; - can.Control.prototype.find = function(selector) { - return this.element.find(selector); - } - - $.Controller = can.Control; - $.fn.controller = $.fn.control; - $.fn.controllers = $.fn.controllers; +steal('jquery/class', 'jquery/lang/string', 'jquery/event/destroyed', function( $ ) { + // ------- HELPER FUNCTIONS ------ + + // Binds an element, returns a function that unbinds + var bind = function( el, ev, callback ) { + 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); + } + }; + } + 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() { + binder.unbind(ev, wrappedCallback || callback); + el = ev = callback = wrappedCallback = null; + }; + }, + makeArray = $.makeArray, + isArray = $.isArray, + isFunction = $.isFunction, + extend = $.extend, + Str = $.String, + each = $.each, + + STR_PROTOTYPE = 'prototype', + STR_CONSTRUCTOR = 'constructor', + slice = Array[STR_PROTOTYPE].slice, + + // Binds an element, returns a function that unbinds + delegate = function( el, selector, ev, callback ) { + var binder = el.delegate && el.undelegate ? el : $(isFunction(el) ? [el] : el) + binder.delegate(selector, ev, callback); + return function() { + binder.undelegate(selector, ev, callback); + binder = el = ev = callback = selector = null; + }; + }, + + // calls bind or unbind depending if there is a selector + binder = function( el, ev, callback, selector ) { + return selector ? delegate(el, selector, ev, callback) : bind(el, ev, callback); + }, + + // moves 'this' to the first argument, wraps it with jQuery if it's an element + shifter = function shifter(context, name) { + var method = typeof name == "string" ? context[name] : name; + return function() { + context.called = name; + return method.apply(context, [this.nodeName ? $(this) : this].concat( slice.call(arguments, 0) ) ); + }; + }, + // matches dots + dotsReg = /\./g, + // matches controller + controllersReg = /_?controllers?/ig, + //used to remove the controller from the name + underscoreAndRemoveController = function( className ) { + return Str.underscore(className.replace("jQuery.", "").replace(dotsReg, '_').replace(controllersReg, "")); + }, + // checks if it looks like an action + actionMatcher = /[^\w]/, + // handles parameterized action names + parameterReplacer = /\{([^\}]+)\}/g, + breaker = /^(?:(.*?)\s)?([\w\.\:>]+)$/, + basicProcessor, + data = function(el, data){ + return $.data(el, "controllers", data) + }; + /** + * @class jQuery.Controller + * @parent jquerymx + * @plugin jquery/controller + * @download http://jmvcsite.heroku.com/pluginify?plugins[]=jquery/controller/controller.js + * @test jquery/controller/qunit.html + * @inherits jQuery.Class + * @description jQuery widget factory. + * + * jQuery.Controller helps create organized, memory-leak free, rapidly performing + * jQuery widgets. Its extreme flexibility allows it to serve as both + * a traditional View and a traditional Controller. + * + * This means it is used to + * create things like tabs, grids, and contextmenus as well as + * organizing them into higher-order business rules. + * + * Controllers make your code deterministic, reusable, organized and can tear themselves + * down auto-magically. Read about [http://jupiterjs.com/news/writing-the-perfect-jquery-plugin + * the theory behind controller] and + * a [http://jupiterjs.com/news/organize-jquery-widgets-with-jquery-controller walkthrough of its features] + * on Jupiter's blog. [mvc.controller Get Started with jQueryMX] also has a great walkthrough. + * + * Controller inherits from [jQuery.Class $.Class] and makes heavy use of + * [http://api.jquery.com/delegate/ event delegation]. Make sure + * you understand these concepts before using it. + * + * ## Basic Example + * + * Instead of + * + * + * $(function(){ + * $('#tabs').click(someCallbackFunction1) + * $('#tabs .tab').click(someCallbackFunction2) + * $('#tabs .delete click').click(someCallbackFunction3) + * }); + * + * do this + * + * $.Controller('Tabs',{ + * click: function() {...}, + * '.tab click' : function() {...}, + * '.delete click' : function() {...} + * }) + * $('#tabs').tabs(); + * + * + * ## Tabs Example + * + * @demo jquery/controller/controller.html + * + * ## Using Controller + * + * Controller helps you build and organize jQuery plugins. It can be used + * to build simple widgets, like a slider, or organize multiple + * widgets into something greater. + * + * To understand how to use Controller, you need to understand + * the typical lifecycle of a jQuery widget and how that maps to + * controller's functionality: + * + * ### A controller class is created. + * + * $.Controller("MyWidget", + * { + * defaults : { + * message : "Remove Me" + * } + * }, + * { + * init : function(rawEl, rawOptions){ + * this.element.append( + * "
        "+this.options.message+"
        " + * ); + * }, + * "div click" : function(div, ev){ + * div.remove(); + * } + * }) + * + * This creates a $.fn.my_widget jQuery helper function + * that can be used to create a new controller instance on an element. Find + * more information [jquery.controller.plugin here] about the plugin gets created + * and the rules around its name. + * + * ### An instance of controller is created on an element + * + * $('.thing').my_widget(options) // calls new MyWidget(el, options) + * + * This calls new MyWidget(el, options) on + * each '.thing' element. + * + * When a new [jQuery.Class Class] instance is created, it calls the class's + * prototype setup and init methods. Controller's [jQuery.Controller.prototype.setup setup] + * method: + * + * - Sets [jQuery.Controller.prototype.element this.element] and adds the controller's name to element's className. + * - Merges passed in options with defaults object and sets it as [jQuery.Controller.prototype.options this.options] + * - Saves a reference to the controller in $.data. + * - [jquery.controller.listening Binds all event handler methods]. + * + * + * ### The controller responds to events + * + * Typically, Controller event handlers are automatically bound. However, there are + * multiple ways to [jquery.controller.listening listen to events] with a controller. + * + * Once an event does happen, the callback function is always called with 'this' + * referencing the controller instance. This makes it easy to use helper functions and + * save state on the controller. + * + * + * ### The widget is destroyed + * + * If the element is removed from the page, the + * controller's [jQuery.Controller.prototype.destroy] method is called. + * This is a great place to put any additional teardown functionality. + * + * You can also teardown a controller programatically like: + * + * $('.thing').my_widget('destroy'); + * + * ## Todos Example + * + * Lets look at a very basic example - + * a list of todos and a button you want to click to create a new todo. + * Your HTML might look like: + * + * @codestart html + * <div id='todos'> + * <ol> + * <li class="todo">Laundry</li> + * <li class="todo">Dishes</li> + * <li class="todo">Walk Dog</li> + * </ol> + * <a class="create">Create</a> + * </div> + * @codeend + * + * To add a mousover effect and create todos, your controller might look like: + * + * $.Controller('Todos',{ + * ".todo mouseover" : function( el, ev ) { + * el.css("backgroundColor","red") + * }, + * ".todo mouseout" : function( el, ev ) { + * el.css("backgroundColor","") + * }, + * ".create click" : function() { + * this.find("ol").append("
      1. New Todo
      2. "); + * } + * }) + * + * Now that you've created the controller class, you've must attach the event handlers on the '#todos' div by + * creating [jQuery.Controller.prototype.setup|a new controller instance]. There are 2 ways of doing this. + * + * @codestart + * //1. Create a new controller directly: + * new Todos($('#todos')); + * //2. Use jQuery function + * $('#todos').todos(); + * @codeend + * + * ## Controller Initialization + * + * It can be extremely useful to add an init method with + * setup functionality for your widget. + * + * In the following example, I create a controller that when created, will put a message as the content of the element: + * + * $.Controller("SpecialController", + * { + * init: function( el, message ) { + * this.element.html(message) + * } + * }) + * $(".special").special("Hello World") + * + * ## Removing Controllers + * + * Controller removal is built into jQuery. So to remove a controller, you just have to remove its element: + * + * @codestart + * $(".special_controller").remove() + * $("#containsControllers").html("") + * @codeend + * + * It's important to note that if you use raw DOM methods (innerHTML, removeChild), the controllers won't be destroyed. + * + * If you just want to remove controller functionality, call destroy on the controller instance: + * + * @codestart + * $(".special_controller").controller().destroy() + * @codeend + * + * ## Accessing Controllers + * + * Often you need to get a reference to a controller, there are a few ways of doing that. For the + * following example, we assume there are 2 elements with className="special". + * + * @codestart + * //creates 2 foo controllers + * $(".special").foo() + * + * //creates 2 bar controllers + * $(".special").bar() + * + * //gets all controllers on all elements: + * $(".special").controllers() //-> [foo, bar, foo, bar] + * + * //gets only foo controllers + * $(".special").controllers(FooController) //-> [foo, foo] + * + * //gets all bar controllers + * $(".special").controllers(BarController) //-> [bar, bar] + * + * //gets first controller + * $(".special").controller() //-> foo + * + * //gets foo controller via data + * $(".special").data("controllers")["FooController"] //-> foo + * @codeend + * + * ## Calling methods on Controllers + * + * Once you have a reference to an element, you can call methods on it. However, Controller has + * a few shortcuts: + * + * @codestart + * //creates foo controller + * $(".special").foo({name: "value"}) + * + * //calls FooController.prototype.update + * $(".special").foo({name: "value2"}) + * + * //calls FooController.prototype.bar + * $(".special").foo("bar","something I want to pass") + * @codeend + * + * These methods let you call one controller from another controller. + * + */ + $.Class("jQuery.Controller", + /** + * @Static + */ + { + /** + * Does 2 things: + * + * - Creates a jQuery helper for this controller. + * - Calculates and caches which functions listen for events. + * + * ### jQuery Helper Naming Examples + * + * + * "TaskController" -> $().task_controller() + * "Controllers.Task" -> $().controllers_task() + * + */ + setup: function() { + // Allow contollers to inherit "defaults" from superclasses as it done in $.Class + this._super.apply(this, arguments); + + // if you didn't provide a name, or are controller, don't do anything + if (!this.shortName || this.fullName == "jQuery.Controller" ) { + return; + } + // cache the underscored names + this._fullName = underscoreAndRemoveController(this.fullName); + this._shortName = underscoreAndRemoveController(this.shortName); + + var controller = this, + /** + * @attribute pluginName + * Setting the pluginName property allows you + * to change the jQuery plugin helper name from its + * default value. + * + * $.Controller("Mxui.Layout.Fill",{ + * pluginName: "fillWith" + * },{}); + * + * $("#foo").fillWith(); + */ + pluginname = this.pluginName || this._fullName, + funcName, forLint; + + // create jQuery plugin + if (!$.fn[pluginname] ) { + $.fn[pluginname] = function( options ) { + + var args = makeArray(arguments), + //if the arg is a method on this controller + isMethod = typeof options == "string" && isFunction(controller[STR_PROTOTYPE][options]), + meth = args[0]; + return this.each(function() { + //check if created + var controllers = data(this), + //plugin is actually the controller instance + plugin = controllers && controllers[pluginname]; + + if ( plugin ) { + if ( isMethod ) { + // call a method on the controller with the remaining args + plugin[meth].apply(plugin, args.slice(1)); + } else { + // call the plugin's update method + plugin.update.apply(plugin, args); + } + + } else { + //create a new controller instance + controller.newInstance.apply(controller, [this].concat(args)); + } + }); + }; + } + + // make sure listensTo is an array + //!steal-remove-start + if (!isArray(this.listensTo) ) { + throw "listensTo is not an array in " + this.fullName; + } + //!steal-remove-end + // calculate and cache actions + this.actions = {}; + + for ( funcName in this[STR_PROTOTYPE] ) { + if (funcName == 'constructor' || !isFunction(this[STR_PROTOTYPE][funcName]) ) { + continue; + } + if ( this._isAction(funcName) ) { + this.actions[funcName] = this._action(funcName); + } + } + }, + hookup: function( el ) { + return new this(el); + }, + + /** + * @hide + * @param {String} methodName a prototype function + * @return {Boolean} truthy if an action or not + */ + _isAction: function( methodName ) { + if ( actionMatcher.test(methodName) ) { + return true; + } else { + return $.inArray(methodName, this.listensTo) > -1 || $.event.special[methodName] || processors[methodName]; + } + + }, + /** + * @hide + * This takes a method name and the options passed to a controller + * and tries to return the data necessary to pass to a processor + * (something that binds things). + * + * For performance reasons, this called twice. First, it is called when + * the Controller class is created. If the methodName is templated + * like : "{window} foo", it returns null. If it is not templated + * it returns event binding data. + * + * The resulting data is added to this.actions. + * + * When a controller instance is created, _action is called again, but only + * on templated actions. + * + * @param {Object} methodName the method that will be bound + * @param {Object} [options] first param merged with class default options + * @return {Object} null or the processor and pre-split parts. + * The processor is what does the binding/subscribing. + */ + _action: function( methodName, options ) { + // reset the test index + parameterReplacer.lastIndex = 0; + + //if we don't have options (a controller instance), we'll run this later + if (!options && parameterReplacer.test(methodName) ) { + return null; + } + // If we have options, run sub to replace templates "{}" with a value from the options + // or the window + var convertedName = options ? Str.sub(methodName, [options, window]) : methodName, + + // If a "{}" resolves to an object, convertedName will be an array + arr = isArray(convertedName), + + // get the parts of the function = [convertedName, delegatePart, eventPart] + parts = (arr ? convertedName[1] : convertedName).match(breaker), + event = parts[2], + processor = processors[event] || basicProcessor; + return { + processor: processor, + parts: parts, + delegate : arr ? convertedName[0] : undefined + }; + }, + /** + * @attribute processors + * An object of {eventName : function} pairs that Controller uses to hook up events + * auto-magically. A processor function looks like: + * + * jQuery.Controller.processors. + * myprocessor = function( el, event, selector, cb, controller ) { + * //el - the controller's element + * //event - the event (myprocessor) + * //selector - the left of the selector + * //cb - the function to call + * //controller - the binding controller + * }; + * + * This would bind anything like: "foo~3242 myprocessor". + * + * 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 + * + * Listen to events on the document or window + * with templated event handlers: + * + * + * $.Controller('Sized',{ + * "{window} resize" : function(){ + * this.element.width(this.element.parent().width() / 2); + * } + * }); + * + * $('.foo').sized(); + */ + processors: {}, + /** + * @attribute listensTo + * An array 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: [], + /** + * @attribute defaults + * A object of name-value pairs that act as default values for a controller's + * [jQuery.Controller.prototype.options options]. + * + * $.Controller("Message", + * { + * defaults : { + * message : "Hello World" + * } + * },{ + * init : function(){ + * this.element.text(this.options.message); + * } + * }) + * + * $("#el1").message(); //writes "Hello World" + * $("#el12").message({message: "hi"}); //writes hi + * + * In [jQuery.Controller.prototype.setup setup] the options passed to the controller + * are merged with defaults. This is not a deep merge. + */ + defaults: {} + }, + /** + * @Prototype + */ + { + /** + * Setup is where most of controller's magic happens. It does the following: + * + * ### 1. Sets this.element + * + * The first parameter passed to new Controller(el, options) is expected to be + * an element. This gets converted to a jQuery wrapped element and set as + * [jQuery.Controller.prototype.element this.element]. + * + * ### 2. Adds the controller's name to the element's className. + * + * Controller adds it's plugin name to the element's className for easier + * debugging. For example, if your Controller is named "Foo.Bar", it adds + * "foo_bar" to the className. + * + * ### 3. Saves the controller in $.data + * + * A reference to the controller instance is saved in $.data. You can find + * instances of "Foo.Bar" like: + * + * $("#el").data("controllers")['foo_bar']. + * + * ### Binds event handlers + * + * Setup does the event binding described in [jquery.controller.listening Listening To Events]. + * + * @param {HTMLElement} element the element this instance operates on. + * @param {Object} [options] option values for the controller. These get added to + * this.options and merged with [jQuery.Controller.static.defaults defaults]. + * @return {Array} return an array if you wan to change what init is called with. By + * default it is called with the element and options passed to the controller. + */ + setup: function( element, options ) { + var funcName, ready, cls = this[STR_CONSTRUCTOR]; + + //want the raw element here + element = (typeof element == 'string' ? $(element) : + (element.jquery ? element : [element]) )[0]; + + //set element and className on element + var pluginname = cls.pluginName || cls._fullName; + + //set element and className on element + this.element = $(element).addClass(pluginname); + + //set in data + (data(element) || data(element, {}))[pluginname] = this; + + + /** + * @attribute options + * + * Options are used to configure an controller. They are + * the 2nd argument + * passed to a controller (or the first argument passed to the + * [jquery.controller.plugin controller's jQuery plugin]). + * + * For example: + * + * $.Controller('Hello') + * + * var h1 = new Hello($('#content1'), {message: 'World'} ); + * equal( h1.options.message , "World" ) + * + * var h2 = $('#content2').hello({message: 'There'}) + * .controller(); + * equal( h2.options.message , "There" ) + * + * Options are merged with [jQuery.Controller.static.defaults defaults] in + * [jQuery.Controller.prototype.setup setup]. + * + * For example: + * + * $.Controller("Tabs", + * { + * defaults : { + * activeClass: "ui-active-state" + * } + * }, + * { + * init : function(){ + * this.element.addClass(this.options.activeClass); + * } + * }) + * + * $("#tabs1").tabs() // adds 'ui-active-state' + * $("#tabs2").tabs({activeClass : 'active'}) // adds 'active' + * + * Options are typically updated by calling + * [jQuery.Controller.prototype.update update]; + * + */ + this.options = extend( extend(true, {}, cls.defaults), options); + + + + /** + * @attribute called + * String name of current function being called on controller instance. This is + * used for picking the right view in render. + * @hide + */ + this.called = "init"; + + // bind all event handlers + this.bind(); + + /** + * @attribute element + * The controller instance's delegated element. This + * is set by [jQuery.Controller.prototype.setup setup]. It + * is a jQuery wrapped element. + * + * For example, if I add MyWidget to a '#myelement' element like: + * + * $.Controller("MyWidget",{ + * init : function(){ + * this.element.css("color","red") + * } + * }) + * + * $("#myelement").my_widget() + * + * MyWidget will turn #myelement's font color red. + * + * ## Using a different element. + * + * Sometimes, you want a different element to be this.element. A + * very common example is making progressively enhanced form widgets. + * + * To change this.element, overwrite Controller's setup method like: + * + * $.Controller("Combobox",{ + * setup : function(el, options){ + * this.oldElement = $(el); + * var newEl = $('
        '); + * this.oldElement.wrap(newEl); + * this._super(newEl, options); + * }, + * init : function(){ + * this.element //-> the div + * }, + * ".option click" : function(){ + * // event handler bound on the div + * }, + * destroy : function(){ + * var div = this.element; //save reference + * this._super(); + * div.replaceWith(this.oldElement); + * } + * } + */ + return [this.element, this.options].concat(makeArray(arguments).slice(2)); + /** + * @function init + * + * Implement this. + */ + }, + /** + * Bind attaches event handlers that will be + * removed when the controller is removed. + * + * This used to be a good way to listen to events outside the controller's + * [jQuery.Controller.prototype.element element]. However, + * using templated event listeners is now the prefered way of doing this. + * + * ### Example: + * + * init: function() { + * // calls somethingClicked(el,ev) + * this.bind('click','somethingClicked') + * + * // calls function when the window is clicked + * this.bind(window, 'click', function(ev){ + * //do something + * }) + * }, + * somethingClicked: function( el, ev ) { + * + * } + * + * @param {HTMLElement|jQuery.fn|Object} [el=this.element] + * The element to be bound. If an eventName is provided, + * the controller's element is used instead. + * + * @param {String} eventName The event to listen for. + * @param {Function|String} func A callback function or the String name of a controller function. If a controller + * function name is given, the controller function is called back with the bound element and event as the first + * and second parameter. Otherwise the function is called back like a normal bind. + * @return {Integer} The id of the binding in this._bindings + */ + bind: function( el, eventName, func ) { + if( el === undefined ) { + //adds bindings + this._bindings = []; + //go through the cached list of actions and use the processor to bind + + var cls = this[STR_CONSTRUCTOR], + bindings = this._bindings, + actions = cls.actions, + element = this.element; + + for ( funcName in actions ) { + if ( actions.hasOwnProperty(funcName) ) { + ready = actions[funcName] || cls._action(funcName, this.options); + bindings.push( + ready.processor(ready.delegate || element, + ready.parts[2], + ready.parts[1], + funcName, + this)); + } + } + + + //setup to be destroyed ... don't bind b/c we don't want to remove it + var destroyCB = shifter(this,"destroy"); + element.bind("destroyed", destroyCB); + bindings.push(function( el ) { + $(el).unbind("destroyed", destroyCB); + }); + return bindings.length; + } + if ( typeof el == 'string' ) { + func = eventName; + eventName = el; + el = this.element; + } + return this._binder(el, eventName, func); + }, + _binder: function( el, eventName, func, selector ) { + if ( typeof func == 'string' ) { + func = shifter(this,func); + } + this._bindings.push(binder(el, eventName, func, selector)); + return this._bindings.length; + }, + _unbind : function(){ + var el = this.element[0]; + each(this._bindings, function( key, value ) { + value(el); + }); + //adds bindings + this._bindings = []; + }, + /** + * Delegate will delegate on an elememt and will be undelegated when the controller is removed. + * This is a good way to delegate on elements not in a controller's element.
        + *

        Example:

        + * @codestart + * // calls function when the any 'a.foo' is clicked. + * this.delegate(document.documentElement,'a.foo', 'click', function(ev){ + * //do something + * }) + * @codeend + * @param {HTMLElement|jQuery.fn} [element=this.element] the element to delegate from + * @param {String} selector the css selector + * @param {String} eventName the event to bind to + * @param {Function|String} func A callback function or the String name of a controller function. If a controller + * function name is given, the controller function is called back with the bound element and event as the first + * and second parameter. Otherwise the function is called back like a normal bind. + * @return {Integer} The id of the binding in this._bindings + */ + delegate: function( element, selector, eventName, func ) { + if ( typeof element == 'string' ) { + func = eventName; + eventName = selector; + selector = element; + element = this.element; + } + return this._binder(element, eventName, func, selector); + }, + /** + * Update extends [jQuery.Controller.prototype.options this.options] + * with the `options` argument and rebinds all events. It basically + * re-configures the controller. + * + * For example, the following controller wraps a recipe form. When the form + * is submitted, it creates the recipe on the server. When the recipe + * is `created`, it resets the form with a new instance. + * + * $.Controller('Creator',{ + * "{recipe} created" : function(){ + * this.update({recipe : new Recipe()}); + * this.element[0].reset(); + * this.find("[type=submit]").val("Create Recipe") + * }, + * "submit" : function(el, ev){ + * ev.preventDefault(); + * var recipe = this.options.recipe; + * recipe.attrs( this.element.formParams() ); + * this.find("[type=submit]").val("Saving...") + * recipe.save(); + * } + * }); + * $('#createRecipes').creator({recipe : new Recipe()}) + * + * + * @demo jquery/controller/demo-update.html + * + * Update is called if a controller's [jquery.controller.plugin jQuery helper] is + * called on an element that already has a controller instance + * of the same type. + * + * For example, a widget that listens for model updates + * and updates it's html would look like. + * + * $.Controller('Updater',{ + * // when the controller is created, update the html + * init : function(){ + * this.updateView(); + * }, + * + * // update the html with a template + * updateView : function(){ + * this.element.html( "content.ejs", + * this.options.model ); + * }, + * + * // if the model is updated + * "{model} updated" : function(){ + * this.updateView(); + * }, + * update : function(options){ + * // make sure you call super + * this._super(options); + * + * this.updateView(); + * } + * }) + * + * // create the controller + * // this calls init + * $('#item').updater({model: recipe1}); + * + * // later, update that model + * // this calls "{model} updated" + * recipe1.update({name: "something new"}); + * + * // later, update the controller with a new recipe + * // this calls update + * $('#item').updater({model: recipe2}); + * + * // later, update the new model + * // this calls "{model} updated" + * recipe2.update({name: "something newer"}); + * + * _NOTE:_ If you overwrite `update`, you probably need to call + * this._super. + * + * ### Example + * + * $.Controller("Thing",{ + * init: function( el, options ) { + * alert( 'init:'+this.options.prop ) + * }, + * update: function( options ) { + * this._super(options); + * alert('update:'+this.options.prop) + * } + * }); + * $('#myel').thing({prop : 'val1'}); // alerts init:val1 + * $('#myel').thing({prop : 'val2'}); // alerts update:val2 + * + * @param {Object} options A list of options to merge with + * [jQuery.Controller.prototype.options this.options]. Often, this method + * is called by the [jquery.controller.plugin jQuery helper function]. + */ + update: function( options ) { + extend(this.options, options); + this._unbind(); + this.bind(); + }, + /** + * Destroy unbinds and undelegates all event handlers on this controller, + * and prevents memory leaks. This is called automatically + * if the element is removed. You can overwrite it to add your own + * teardown functionality: + * + * $.Controller("ChangeText",{ + * init : function(){ + * this.oldText = this.element.text(); + * this.element.text("Changed!!!") + * }, + * destroy : function(){ + * this.element.text(this.oldText); + * this._super(); //Always call this! + * }) + * + * Make sure you always call _super when overwriting + * controller's destroy event. The base destroy functionality unbinds + * all event handlers the controller has created. + * + * You could call destroy manually on an element with ChangeText + * added like: + * + * $("#changed").change_text("destroy"); + * + */ + destroy: function() { + if ( this._destroyed ) { + throw this[STR_CONSTRUCTOR].shortName + " controller already deleted"; + } + var self = this, + fname = this[STR_CONSTRUCTOR].pluginName || this[STR_CONSTRUCTOR]._fullName, + controllers; + + // mark as destroyed + this._destroyed = true; + + // remove the className + this.element.removeClass(fname); + + // unbind bindings + this._unbind(); + // clean up + delete this._actions; + + delete this.element.data("controllers")[fname]; + + $(this).triggerHandler("destroyed"); //in case we want to know if the controller is removed + + this.element = null; + }, + /** + * Queries from the controller's element. + * @codestart + * ".destroy_all click" : function() { + * this.find(".todos").remove(); + * } + * @codeend + * @param {String} selector selection string + * @return {jQuery.fn} returns the matched elements + */ + find: function( selector ) { + return this.element.find(selector); + }, + //tells callback to set called on this. I hate this. + _set_called: true + }); + + var processors = $.Controller.processors, + + //------------- PROCESSSORS ----------------------------- + //processors do the binding. They return a function that + //unbinds when called. + //the basic processor that binds events + basicProcessor = function( el, event, selector, methodName, controller ) { + return binder(el, event, shifter(controller, methodName), selector); + }; + + + + + //set common events to be processed as a basicProcessor + 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; + }); + /** + * @add jQuery.fn + */ + + //used to determine if a controller instance is one of controllers + //controllers can be strings or classes + var i, isAControllerOf = function( instance, controllers ) { + for ( i = 0; i < controllers.length; i++ ) { + if ( typeof controllers[i] == 'string' ? instance[STR_CONSTRUCTOR]._shortName == controllers[i] : instance instanceof controllers[i] ) { + return true; + } + } + return false; + }; + $.fn.extend({ + /** + * @function controllers + * Gets all controllers in the jQuery element. + * @return {Array} an array of controller instances. + */ + controllers: function() { + var controllerNames = makeArray(arguments), + instances = [], + controllers, c, cname; + //check if arguments + this.each(function() { + + controllers = $.data(this, "controllers"); + for ( cname in controllers ) { + if ( controllers.hasOwnProperty(cname) ) { + c = controllers[cname]; + if (!controllerNames.length || isAControllerOf(c, controllerNames) ) { + instances.push(c); + } + } + } + }); + return instances; + }, + /** + * @function controller + * Gets a controller in the jQuery element. With no arguments, returns the first one found. + * @param {Object} controller (optional) if exists, the first controller instance with this class type will be returned. + * @return {jQuery.Controller} the first controller. + */ + controller: function( controller ) { + return this.controllers.apply(this, arguments)[0]; + } + }); + + }); diff --git a/controller/controller_test.js b/controller/controller_test.js index d2322b85..d94799e9 100644 --- a/controller/controller_test.js +++ b/controller/controller_test.js @@ -31,7 +31,7 @@ test("subscribe testing works", function(){ OpenAjax.hub.publish("a.b",{}) equals(subscribes,1, "can subscribe") var controllerInstance = ta.controller('my_test') - ok( controllerInstance.constructor == MyTest, "can get controller" ) + ok( controllerInstance.Class == MyTest, "can get controller" ) controllerInstance.destroy() equals(destroys,1, "destroy called once") @@ -117,7 +117,7 @@ test("delegate", function(){ }) var els = $("").appendTo($("#qunit-test-area")) var c = els.delegate_test(); - c.controller().on(els.find("span"), "a", "click", function(){ + c.controller().delegate(els.find("span"), "a", "click", function(){ called = true; }) els.find("a").trigger('click') diff --git a/controller/route/route.js b/controller/route/route.js index cbf9bb92..bc0f1c67 100644 --- a/controller/route/route.js +++ b/controller/route/route.js @@ -1 +1,31 @@ -steal('jquery/controller', 'can/control/route') +steal('jquery/dom/route','jquery/controller', function(){ + /** + * + * ":type route" // + * + * @param {Object} el + * @param {Object} event + * @param {Object} selector + * @param {Object} cb + */ + jQuery.Controller.processors.route = function(el, event, selector, funcName, controller){ + $.route(selector||"") + var batchNum; + var check = function(ev, attr, how){ + if($.route.attr('route') === (selector||"") && + (ev.batchNum === undefined || ev.batchNum !== batchNum ) ){ + + batchNum = ev.batchNum; + + var d = $.route.attrs(); + delete d.route; + + controller[funcName](d) + } + } + $.route.bind('change',check); + return function(){ + $.route.unbind('change',check) + } + } +}) diff --git a/controller/view/view.js b/controller/view/view.js index c7743076..c248fa5c 100644 --- a/controller/view/view.js +++ b/controller/view/view.js @@ -1 +1,121 @@ -steal('jquery/controller', 'can/control/view'); \ No newline at end of file +steal('jquery/controller', 'jquery/view').then(function( $ ) { + var URI = steal.URI || steal.File; + + jQuery.Controller.getFolder = function() { + return jQuery.String.underscore(this.fullName.replace(/\./g, "/")).replace("/Controllers", ""); + }; + + jQuery.Controller._calculatePosition = function( Class, view, action_name ) { + + var classParts = Class.fullName.split('.'), + classPartsWithoutPrefix = classParts.slice(0); + classPartsWithoutPrefix.splice(0, 2); // Remove prefix (usually 2 elements) + + var classPartsWithoutPrefixSlashes = classPartsWithoutPrefix.join('/'), + hasControllers = (classParts.length > 2) && classParts[1] == 'Controllers', + path = hasControllers? jQuery.String.underscore(classParts[0]): jQuery.String.underscore(classParts.join("/")), + controller_name = jQuery.String.underscore(classPartsWithoutPrefix.join('/')).toLowerCase(), + suffix = (typeof view == "string" && /\.[\w\d]+$/.test(view)) ? "" : jQuery.View.ext; + + //calculate view + if ( typeof view == "string" ) { + if ( view.substr(0, 2) == "//" ) { //leave where it is + } else { + view = "//" + URI(path).join( 'views/' + (view.indexOf('/') !== -1 ? view : (hasControllers ? controller_name + '/' : "") + view)) + suffix; + } + } else if (!view ) { + view = "//" + URI(path).join('views/' + (hasControllers ? controller_name + '/' : "") + action_name.replace(/\.|#/g, '').replace(/ /g, '_'))+ suffix; + } + return view; + }; + var calculateHelpers = function( myhelpers ) { + var helpers = {}; + if ( myhelpers ) { + if ( jQuery.isArray(myhelpers) ) { + for ( var h = 0; h < myhelpers.length; h++ ) { + jQuery.extend(helpers, myhelpers[h]); + } + } + else { + jQuery.extend(helpers, myhelpers); + } + } else { + if ( this._default_helpers ) { + helpers = this._default_helpers; + } + //load from name + var current = window; + var parts = this.constructor.fullName.split(/\./); + for ( var i = 0; i < parts.length; i++ ) { + if(current){ + if ( typeof current.Helpers == 'object' ) { + jQuery.extend(helpers, current.Helpers); + } + current = current[parts[i]]; + } + } + if (current && typeof current.Helpers == 'object' ) { + jQuery.extend(helpers, current.Helpers); + } + this._default_helpers = helpers; + } + return helpers; + }; + + /** + * @add jQuery.Controller.prototype + */ + + jQuery.Controller.prototype. + /** + * @tag view + * Renders a View template with the controller instance. If the first argument + * is not supplied, + * it looks for a view in /views/controller_name/action_name.ejs. + * If data is not provided, it uses the controller instance as data. + * @codestart + * TasksController = $.Controller.extend('TasksController',{ + * click: function( el ) { + * // renders with views/tasks/click.ejs + * el.html( this.view() ) + * // renders with views/tasks/under.ejs + * el.after( this.view("under", [1,2]) ); + * // renders with views/tasks/under.micro + * el.after( this.view("under.micro", [1,2]) ); + * // renders with views/shared/top.ejs + * el.before( this.view("shared/top", {phrase: "hi"}) ); + * } + * }) + * @codeend + * @plugin jquery/controller/view + * @return {String} the rendered result of the view. + * @param {String} [view] The view you are going to render. If a view isn't explicity given + * this function will try to guess at the correct view as show in the example code above. + * @param {Object} [data] data to be provided to the view. If not present, the controller instance + * is used. + * @param {Object} [myhelpers] an object of helpers that will be available in the view. If not present + * this controller class's "Helpers" property will be used. + * + */ + view = function( view, data, myhelpers ) { + //shift args if no view is provided + if ( typeof view != "string" && !myhelpers ) { + myhelpers = data; + data = view; + view = null; + } + //guess from controller name + view = jQuery.Controller._calculatePosition(this.Class, view, this.called); + + //calculate data + data = data || this; + + //calculate helpers + var helpers = calculateHelpers.call(this, myhelpers); + + + return jQuery.View(view, data, helpers); //what about controllers in other folders? + }; + + +}); \ No newline at end of file diff --git a/dom/fixture/fixture.html b/dom/fixture/fixture.html new file mode 100644 index 00000000..d59b1f30 --- /dev/null +++ b/dom/fixture/fixture.html @@ -0,0 +1,144 @@ + + + + Fixture Demo + + + +
        +
        +
        + + + + \ No newline at end of file diff --git a/dom/fixture/fixture.js b/dom/fixture/fixture.js index 93c82a75..d2355033 100644 --- a/dom/fixture/fixture.js +++ b/dom/fixture/fixture.js @@ -1,3 +1,903 @@ -steal('can/util/fixture',function($){ - $.fixture = can.fixture; -}) +steal('jquery/dom', + 'jquery/lang/object', + 'jquery/lang/string',function( $ ) { + + //used to check urls + + + + // 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 + var data = overwrite(settings); + + // if we don't have a fixture, do nothing + if(!settings.fixture){ + if(window.location.protocol === "file:"){ + steal.dev.log("ajax request to " + settings.url+", no fixture found"); + } + 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.mapJoin(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.type+" "+ 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"); + + if(data){ + $.extend(originalOptions.data, data) + } + // add to settings data from 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 = [], + // returns the index of an overwrite function + find = function(settings, exact){ + for(var i =0; i < overwrites.length; i++){ + if($fixture._similar(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; + return $fixture._getData(overwrites[index].url, settings.url) + } + + }, + /** + * 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 && typeof settings.data === "number") { + id = settings.data; + } + + /* + Check for id in params(if query string) + If this is just a string representation of an id, parse + if(id === undefined && typeof settings.data === "string") { + id = settings.data; + } + //*/ + + 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) + } + + return id; + }; + + /** + * @function jQuery.fixture + * @plugin jquery/dom/fixture + * @download http://jmvcsite.heroku.com/pluginify?plugins[]=jquery/dom/fixture/fixture.js + * @test jquery/dom/fixture/qunit.html + * @parent dom + * + * $.fixture intercepts a AJAX request and simulates + * the response with a file or function. They are a great technique + * when you want to develop JavaScript + * independently of the backend. + * + * ## Types of Fixtures + * + * There are two common ways of using fixtures. The first is to + * map Ajax requests to another file. The following + * intercepts requests to /tasks.json and directs them + * to fixtures/tasks.json: + * + * $.fixture("/tasks.json","fixtures/tasks.json"); + * + * The other common option is to generate the Ajax response with + * a function. The following intercepts updating tasks at + * /tasks/ID.json and responds with updated data: + * + * $.fixture("PUT /tasks/{id}.json", function(original, settings, headers){ + * return { updatedAt : new Date().getTime() } + * }) + * + * We categorize fixtures into the following types: + * + * - __Static__ - the response is in a file. + * - __Dynamic__ - the response is generated by a function. + * + * There are different ways to lookup static and dynamic fixtures. + * + * ## 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"); + * + * $.fixture("tasks/1", "//fixtures/task1.json"); + * + * ## Dynamic Fixtures + * + * Dynamic Fixtures are functions that get the details of + * the Ajax request and return the result of the mocked service + * request from your server. + * + * For example, the following returns a successful response + * with JSON data from the server: + * + * $.fixture("/foobar.json", function(orig, settings, headers){ + * return [200, "success", {json: {foo: "bar" } }, {} ] + * }) + * + * The fixture function has the following signature: + * + * function( originalOptions, options, headers ) { + * return [ status, statusText, responses, responseHeaders ] + * } + * + * where the fixture function is called with: + * + * - originalOptions - are the options provided to the ajax method, unmodified, + * and thus, without defaults from ajaxSettings + * - options - are the request options + * - headers - a map of key/value request headers + * + * and the fixture function returns an array as arguments for ajaxTransport's completeCallback with: + * + * - status - is the HTTP status code of the response. + * - statusText - the status text of the response + * - responses - a map of dataType/value that contains the responses for each data format supported + * - headers - response headers + * + * However, $.fixture handles the + * common case where you want a successful response with JSON data. The + * previous can be written like: + * + * $.fixture("/foobar.json", function(orig, settings, headers){ + * return {foo: "bar" }; + * }) + * + * If you want to return an array of data, wrap your array in another array: + * + * $.fixture("/tasks.json", function(orig, settings, headers){ + * return [ [ "first","second","third"] ]; + * }) + * + * $.fixture works closesly with jQuery's + * ajaxTransport system. Understanding it is the key to creating advanced + * fixtures. + * + * ### Templated Urls + * + * Often, you want a dynamic fixture to handle urls + * for multiple resources (for example a REST url scheme). $.fixture's + * templated urls allow you to match urls with a wildcard. + * + * The following example simulates services that get and update 100 todos. + * + * // create todos + * var todos = {}; + * for(var i = 0; i < 100; i++) { + * todos[i] = { + * id: i, + * name: "Todo "+i + * } + * } + * $.fixture("GET /todos/{id}", function(orig){ + * // return the JSON data + * // notice that id is pulled from the url and added to data + * return todos[orig.data.id] + * }) + * $.fixture("PUT /todos/{id}", function(orig){ + * // update the todo's data + * $.extend( todos[orig.data.id], orig.data ); + * + * // return data + * return {}; + * }) + * + * Notice that data found in templated urls (ex: {id}) is added to the original + * data object. + * + * ## Simulating Errors + * + * The following simulates an unauthorized request + * to /foo. + * + * $.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'}" + * } + * }) + * + * ## Turning off Fixtures + * + * You can remove a fixture by passing null for the fixture option: + * + * // add a fixture + * $.fixture("GET todos.json","//fixtures/todos.json"); + * + * // remove the fixture + * $.fixture("GET todos.json", null) + * + * You can also set [jQuery.fixture.on $.fixture.on] to false: + * + * $.fixture.on = false; + * + * ## Make + * + * [jQuery.fixture.make $.fixture.make] makes a CRUD service layer that handles sorting, grouping, + * filtering and more. + * + * ## Testing Performance + * + * Dynamic fixtures are awesome for performance testing. Want to see what + * 10000 files does to your app's performance? Make a fixture that returns 10000 items. + * + * What to see what the app feels like when a request takes 5 seconds to return? Set + * [jQuery.fixture.delay] to 5000. + * + * @demo jquery/dom/fixture/fixture.html + * + * @param {Object|String} settings Configures the AJAX requests the fixture should + * intercept. If an __object__ is passed, the object's properties and values + * are matched against the settings passed to $.ajax. + * + * If a __string__ is passed, it can be used to match the url and type. Urls + * can be templated, using {NAME} as wildcards. + * + * @param {Function|String} fixture The response to use for the AJAX + * request. If a __string__ url is passed, the ajax request is redirected + * to the url. If a __function__ is provided, it looks like: + * + * fixture( originalSettings, settings, headers ) + * + * where: + * + * - originalSettings - the orignal settings passed to $.ajax + * - settings - the settings after all filters have run + * - headers - request headers + * + * If __null__ is passed, and there is a fixture at settings, that fixture will be removed, + * allowing the AJAX request to behave normally. + */ + var $fixture = $.fixture = function( settings , fixture ){ + // if we provide a fixture ... + if(fixture !== undefined){ + if(typeof settings == 'string'){ + // handle url strings + var matches = settings.match(/(GET|POST|PUT|DELETE) (.+)/i); + if(!matches){ + settings = { + url : settings + }; + } else { + settings = { + url : matches[2], + type: matches[1] + }; + } + + } + + //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) + } + }; + var replacer = $.String._regs.replacer; + + $.extend($.fixture, { + // given ajax settings, find an overwrite + _similar : function(settings, overwrite, exact){ + if(exact){ + return $.Object.same(settings , overwrite, {fixture : null}) + } else { + return $.Object.subset(settings, overwrite, $.fixture._compare) + } + }, + _compare : { + url : function(a, b){ + return !! $fixture._getData(b, a) + }, + fixture : null, + type : "i" + }, + // gets data from a url like "/todo/{id}" given "todo/5" + _getData : function(fixtureUrl, url){ + var order = [], + fixtureUrlAdjusted = fixtureUrl.replace('.', '\\.').replace('?', '\\?'), + res = new RegExp(fixtureUrlAdjusted.replace(replacer, function(whole, part){ + order.push(part) + return "([^\/]+)" + })+"$").exec(url), + data = {}; + + if(!res){ + return null; + } + res.shift(); + $.each(order, function(i, name){ + data[name] = res.shift() + }) + return data; + }, + /** + * @hide + * Provides a rest update fixture function + */ + "-restUpdate": function( settings ) { + return [200,"succes",{ + id: getId(settings) + },{ + location: settings.url+"/"+getId(settings) + }]; + }, + + /** + * @hide + * Provides a rest destroy fixture function + */ + "-restDestroy": function( settings, cbType ) { + return {}; + }, + + /** + * @hide + * Provides a rest create fixture function + */ + "-restCreate": function( settings, cbType, nul, id ) { + var id = id || parseInt(Math.random() * 100000, 10); + return [200,"succes",{ + id: id + },{ + location: settings.url+"/"+id + }]; + }, + + /** + * @function jQuery.fixture.make + * @parent jQuery.fixture + * Used to make fixtures for findAll / findOne style requests. + * + * //makes a nested list of messages + * $.fixture.make(["messages","message"],1000, function(i, messages){ + * return { + * subject: "This is message "+i, + * body: "Here is some text for this message", + * date: Math.floor( new Date().getTime() ), + * parentId : i < 100 ? null : Math.floor(Math.random()*i) + * } + * }) + * //uses the message fixture to return messages limited by offset, limit, order, etc. + * $.ajax({ + * url: "messages", + * data:{ + * offset: 100, + * limit: 50, + * order: ["date ASC"], + * parentId: 5}, + * }, + * fixture: "-messages", + * success: function( messages ) { ... } + * }); + * + * @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. 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: + * + * + * function(item, settings){ + * if(settings.data.searchText){ + * var regex = new RegExp("^"+settings.data.searchText) + * return regex.test(item.name); + * } + * } + * + */ + make: function( types, count, make, filter ) { + if(typeof types === "string"){ + types = [types+"s",types ] + } + // make all items + 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); + + if (!item.id ) { + item.id = i; + } + items.push(item); + } + //set plural fixture for findAll + $.fixture["-" + types[0]] = function( settings ) { + //copy array of items + var retArr = items.slice(0); + settings.data = settings.data || {}; + //sort using order + //order looks like ["age ASC","gender DESC"] + $.each((settings.data.order || []).slice(0).reverse(), function( i, name ) { + var split = name.split(" "); + retArr = retArr.sort(function( a, b ) { + if ( split[1].toUpperCase() !== "ASC" ) { + if( a[split[0]] < b[split[0]] ) { + return 1; + } else if(a[split[0]] == b[split[0]]){ + return 0 + } else { + return -1; + } + } + else { + if( a[split[0]] < b[split[0]] ) { + return -1; + } else if(a[split[0]] == b[split[0]]){ + return 0 + } else { + return 1; + } + } + }); + }); + + //group is just like a sort + $.each((settings.data.group || []).slice(0).reverse(), function( i, name ) { + var split = name.split(" "); + retArr = retArr.sort(function( a, b ) { + return a[split[0]] > b[split[0]]; + }); + }); + + + var offset = parseInt(settings.data.offset, 10) || 0, + limit = parseInt(settings.data.limit, 10) || (items.length - offset), + i = 0; + + //filter results if someone added an attr like parentId + for ( var param in settings.data ) { + i=0; + if ( settings.data[param] !== undefined && // 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); + } else { + i++; + } + } + } + } + + + 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 [{ + "count": retArr.length, + "limit": settings.data.limit, + "offset": settings.data.offset, + "data": retArr.slice(offset, offset + limit) + }]; + }; + // findOne + $.fixture["-" + types[1]] = function( settings ) { + var item = findOne(getId(settings)); + return item ? [item] : []; + }; + // 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, undefined, item.id ); + }; + + + return { + getId: getId, + findOne : findOne, + find : function(settings){ + return findOne( getId(settings) ); + } + } + }, + /** + * @function jQuery.fixture.rand + * @parent jQuery.fixture + * + * Creates random integers or random arrays of + * other arrays. + * + * ## Examples + * + * var rand = $.fixture.rand; + * + * // get a random integer between 0 and 10 (inclusive) + * rand(11); + * + * // get a random number between -5 and 5 (inclusive) + * rand(-5, 6); + * + * // pick a random item from an array + * rand(["j","m","v","c"],1)[0] + * + * // pick a random number of items from an array + * rand(["j","m","v","c"]) + * + * // pick 2 items from an array + * rand(["j","m","v","c"],2) + * + * // pick between 2 and 3 items at random + * rand(["j","m","v","c"],2,3) + * + * + * @param {Array|Number} arr An array of items to select from. + * If a number is provided, a random number is returned. + * If min and max are not provided, a random number of items are selected + * from this array. + * @param {Number} [min] If only min is provided, min items + * are selected. + * @param {Number} [max] If min and max are provided, a random number of + * items between min and max (inclusive) is selected. + */ + rand : function(arr, min, max){ + if(typeof arr == 'number'){ + if(typeof min == 'number'){ + return arr+ Math.floor(Math.random() * (min - arr) ); + } else { + return Math.floor(Math.random() * arr); + } + + } + var rand = arguments.callee; + // get a random set + if(min === undefined){ + return rand(arr, rand(arr.length+1)) + } + // get a random selection of arr + var res = []; + arr = arr.slice(0); + // set max + if(!max){ + max = min; + } + //random max + max = min + Math.round( rand(max - min) ) + for(var i=0; i < max; i++){ + res.push(arr.splice( rand(arr.length), 1 )[0]) + } + return res; + }, + /** + * @hide + * Use $.fixture.xhr to create an object that looks like an xhr object. + * + * ## Example + * + * The following example shows how the -restCreate fixture uses xhr to return + * a simulated xhr object: + * @codestart + * "-restCreate" : function( settings, cbType ) { + * switch(cbType){ + * case "success": + * return [ + * {id: parseInt(Math.random()*1000)}, + * "success", + * $.fixture.xhr()]; + * case "complete": + * return [ + * $.fixture.xhr({ + * getResponseHeader: function() { + * return settings.url+"/"+parseInt(Math.random()*1000); + * } + * }), + * "success"]; + * } + * } + * @codeend + * @param {Object} [xhr] properties that you want to overwrite + * @return {Object} an object that looks like a successful XHR object. + */ + xhr: function( xhr ) { + return $.extend({}, { + abort: $.noop, + getAllResponseHeaders: function() { + return ""; + }, + getResponseHeader: function() { + return ""; + }, + open: $.noop, + overrideMimeType: $.noop, + readyState: 4, + responseText: "", + responseXML: null, + send: $.noop, + setRequestHeader: $.noop, + 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 $.fixture.delay + * @parent $.fixture + * Sets the delay in milliseconds between an ajax request is made and + * the success and complete handlers are called. This only sets + * functional fixtures. By default, the delay is 200ms. + * @codestart + * steal('jquery/dom/fixtures').then(function(){ + * $.fixture.delay = 1000; + * }) + * @codeend + */ + $.fixture.delay = 200; + + $.fixture["-handleFunction"] = function( settings ) { + if ( typeof settings.fixture === "string" && $.fixture[settings.fixture] ) { + settings.fixture = $.fixture[settings.fixture]; + } + if ( typeof settings.fixture == "function" ) { + setTimeout(function() { + if ( settings.success ) { + settings.success.apply(null, settings.fixture(settings, "success")); + } + if ( settings.complete ) { + settings.complete.apply(null, settings.fixture(settings, "complete")); + } + }, $.fixture.delay); + return true; + } + return false; + }; + + + + /** + * @page jquery.fixture.0organizing Organizing Fixtures + * @parent jQuery.fixture + * + * The __best__ way of organizing fixtures is to have a 'fixtures.js' file that steals + * jquery/dom/fixture and defines all your fixtures. For example, + * if you have a 'todo' application, you might + * have todo/fixtures/fixtures.js 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 todo/fixtures/fixtures.js in the + * app file (todo/todo.js) like: + * + * + * steal({path: '//todo/fixtures/fixtures.js',ignore: true}); + * + * //start of your app's steals + * steal( ... ) + * + * 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) + * } + * + */ + //Expose this for fixture debugging + $.fixture.overwrites = overwrites; +}); diff --git a/dom/fixture/fixture_test.js b/dom/fixture/fixture_test.js new file mode 100644 index 00000000..7559d1ba --- /dev/null +++ b/dom/fixture/fixture_test.js @@ -0,0 +1,332 @@ + +steal("jquery/dom/fixture", "jquery/model",'funcunit/qunit',function(){ + +module("jquery/dom/fixture"); + + +test("static fixtures", function(){ + stop(); + + $.fixture("GET something", "//jquery/dom/fixture/fixtures/test.json"); + $.fixture("POST something", "//jquery/dom/fixture/fixtures/test.json"); + + + $.get("something",function(data){ + equals(data.sweet,"ness","$.get works"); + + $.post("something",function(data){ + equals(data.sweet,"ness","$.post works"); + + + start(); + },'json'); + + },'json'); +}) + +test("dynamic fixtures",function(){ + stop(); + $.fixture.delay = 10; + $.fixture("something", function(){ + return [{sweet: "ness"}] + }) + + $.get("something",function(data){ + equals(data.sweet,"ness","$.get works"); + start(); + + },'json'); +}); + +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(); + } + }) +}) + +test("rand", function(){ + var rand = $.fixture.rand; + var num = rand(5); + equals(typeof num, "number"); + ok(num >= 0 && num < 5, "gets a number" ); + + stop(); + var zero, three, between, next = function(){ + start() + } + // make sure rand can be everything we need + setTimeout(function(){ + var res = rand([1,2,3]); + if(res.length == 0 ){ + zero = true; + } else if(res.length == 3){ + three = true; + } else { + between = true; + } + if(zero && three && between){ + ok(true, "got zero, three, between") + next(); + } else { + setTimeout(arguments.callee, 10) + } + }, 10) + +}); + + +test("_getData", function(){ + var data = $.fixture._getData("/thingers/{id}", "/thingers/5"); + equals(data.id, 5, "gets data"); + var data = $.fixture._getData("/thingers/5?hi.there", "/thingers/5?hi.there"); + deepEqual(data, {}, "gets data"); +}) + +test("_getData with double character value", function(){ + var data = $.fixture._getData("/days/{id}/time_slots.json", "/days/17/time_slots.json"); + equals(data.id, 17, "gets data"); +}); + +test("_compare", function(){ + var same = $.Object.same( + {url : "/thingers/5"}, + {url : "/thingers/{id}"}, $.fixture._compare) + + ok(same, "they are similar"); + + same = $.Object.same( + {url : "/thingers/5"}, + {url : "/thingers"}, $.fixture._compare); + + ok(!same, "they are not the same"); +}) + +test("_similar", function(){ + + var same = $.fixture._similar( + {url : "/thingers/5"}, + {url : "/thingers/{id}"}); + + ok(same, "similar"); + + same = $.fixture._similar( + {url : "/thingers/5", type: "get"}, + {url : "/thingers/{id}"}); + + ok(same, "similar with extra pops on settings"); + + var exact = $.fixture._similar( + {url : "/thingers/5", type: "get"}, + {url : "/thingers/{id}"}, true); + + ok(!exact, "not exact" ) + + var exact = $.fixture._similar( + {url : "/thingers/5"}, + {url : "/thingers/5"}, true); + + ok(exact, "exact" ) +}) + +test("fixture function gets id", function(){ + $.fixture("/thingers/{id}", function(settings){ + return { + id: settings.data.id, + name: "justin" + } + }) + stop(); + $.get("/thingers/5", {}, function(data){ + start(); + ok(data.id) + },'json') +}); + +test("replacing and removing a fixture", function(){ + var url = steal.root.join("jquery/dom/fixture/fixtures/remove.json")+'' + $.fixture("GET "+url, function(){ + return {weird: "ness!"} + }) + stop(); + $.get(url,{}, function(json){ + equals(json.weird,"ness!","fixture set right") + + $.fixture("GET "+url, function(){ + return {weird: "ness?"} + }) + + $.get(url,{}, function(json){ + equals(json.weird,"ness?","fixture set right"); + + $.fixture("GET "+url, null ) + + $.get(url,{}, function(json){ + equals(json.weird,"ness","fixture set right"); + + start(); + },'json'); + + + },'json') + + + + },'json') +}); + +return; // future fixture stuff + +// returning undefined means you want to control timing? +$.fixture('GET /foo', function(orig, settings, headers, cb){ + setTimeout(function(){ + cb(200, "success",{json : "{}"},{}) + },1000); +}) + +// fixture that hooks into model / vice versa? + +// fixture that creates a nice store + +var store = $.fixture.store(1000, function(){ + +}) + +store.find() + +// make cloud + +var clouds = $.fixture.store(1, function(){ + return { + name: "ESCCloud", + DN : "ESCCloud-ESCCloud", + type : "ESCCloud" + } +}); + +var computeCluster = $.fixture.store(5, function(i){ + return { + name : "", + parentDN : clouds.find()[0].DN, + type: "ComputeCluster", + DN : "ComputeCluster-ComputeCluster"+i + } +}); + +$.fixture("GET /computeclusters", function(){ + return [] +}); + +// hacking models? + + + + + +}); diff --git a/dom/fixture/fixtures/foo.json b/dom/fixture/fixtures/foo.json new file mode 100644 index 00000000..9fbc82d0 --- /dev/null +++ b/dom/fixture/fixtures/foo.json @@ -0,0 +1 @@ +{"a" : "b"} diff --git a/dom/fixture/fixtures/foobar.json b/dom/fixture/fixtures/foobar.json new file mode 100644 index 00000000..a50afea1 --- /dev/null +++ b/dom/fixture/fixtures/foobar.json @@ -0,0 +1,3 @@ +{ + "sweet" :"ner" +} diff --git a/dom/fixture/fixtures/messages.html b/dom/fixture/fixtures/messages.html new file mode 100644 index 00000000..19cf1492 --- /dev/null +++ b/dom/fixture/fixtures/messages.html @@ -0,0 +1,31 @@ + +

        Create a Message

        +

        Create a message, it will show up in "Get Messages".

        +
        + + + + + + + + + + + + + + + + +
        From:
        Subject:
        Body:
        +
        +

        Get Messages

        +

        Enter a limit and offset to get a range of messages. +

        +
        + Offset + Limit + +
        +
        diff --git a/dom/fixture/fixtures/remove.json b/dom/fixture/fixtures/remove.json new file mode 100644 index 00000000..1e152b58 --- /dev/null +++ b/dom/fixture/fixtures/remove.json @@ -0,0 +1,3 @@ +{ + "weird" : "ness" +} diff --git a/dom/fixture/fixtures/test.json b/dom/fixture/fixtures/test.json new file mode 100644 index 00000000..6be2ce4f --- /dev/null +++ b/dom/fixture/fixtures/test.json @@ -0,0 +1,3 @@ +{ + "sweet" :"ness" +} diff --git a/dom/fixture/qunit.html b/dom/fixture/qunit.html new file mode 100644 index 00000000..5207a04c --- /dev/null +++ b/dom/fixture/qunit.html @@ -0,0 +1,22 @@ + + + Fixtures Test Suite + + + + + + +

        Fixtures Test Suite

        +

        +
        +

        +
        +
          +
          + + \ No newline at end of file diff --git a/dom/route/route.js b/dom/route/route.js index 0b5bdc76..4159adec 100644 --- a/dom/route/route.js +++ b/dom/route/route.js @@ -1,4 +1,471 @@ -steal('can/route', 'jquery/event/hashchange', +steal('jquery/lang/observe', 'jquery/event/hashchange', 'jquery/lang/string/deparam', function( $ ) { - $.route=can.route + + // Helper methods used for matching routes. + var + // RegEx used to match route variables of the type ':name'. + // Any word character or a period is matched. + matcher = /\:([\w\.]+)/g, + // Regular expression for identifying &key=value lists. + paramsMatcher = /^(?:&[^=]+=[^&]*)+/, + // Converts a JS Object into a list of parameters that can be + // inserted into an html element tag. + makeProps = function( props ) { + var html = [], + name, val; + each(props, function(name, val){ + if ( name === 'className' ) { + name = 'class' + } + val && html.push(escapeHTML(name), "=\"", escapeHTML(val), "\" "); + }) + return html.join("") + }, + // Escapes ' and " for safe insertion into html tag parameters. + escapeHTML = function( content ) { + return content.replace(/"/g, '"').replace(/'/g, "'"); + }, + // Checks if a route matches the data provided. If any route variable + // is not present in the data the route does not match. If all route + // variables are present in the data the number of matches is returned + // to allow discerning between general and more specific routes. + matchesData = function(route, data) { + var count = 0; + for ( var i = 0; i < route.names.length; i++ ) { + if (!data.hasOwnProperty(route.names[i]) ) { + return -1; + } + count++; + } + return count; + }, + // + onready = true, + location = window.location, + encode = encodeURIComponent, + decode = decodeURIComponent, + each = $.each, + extend = $.extend; + + /** + * @class jQuery.route + * @inherits jQuery.Observe + * @plugin jquery/dom/route + * @parent dom + * @tag 3.2 + * + * jQuery.route helps manage browser history (and + * client state) by + * synchronizing the window.location.hash with + * an [jQuery.Observe]. + * + * ## Background Information + * + * To support the browser's back button and bookmarking + * in an Ajax application, most applications use + * the window.location.hash. By + * changing the hash (via a link or JavaScript), + * one is able to add to the browser's history + * without changing the page. The [jQuery.event.special.hashchange event] allows + * you to listen to when the hash is changed. + * + * Combined, this provides the basics needed to + * create history enabled Ajax websites. However, + * jQuery.Route addresses several other needs such as: + * + * - Pretty Routes + * - Keeping routes independent of application code + * - Listening to specific parts of the history changing + * - Setup / Teardown of widgets. + * + * ## How it works + * + * $.route is a [jQuery.Observe $.Observe] that represents the + * window.location.hash as an + * object. For example, if the hash looks like: + * + * #!type=videos&id=5 + * + * the data in $.route would look like: + * + * { type: 'videos', id: 5 } + * + * + * $.route keeps the state of the hash in-sync with the data in + * $.route. + * + * ## $.Observe + * + * $.route is a [jQuery.Observe $.Observe]. Understanding + * $.Observe is essential for using $.route correctly. + * + * You can + * listen to changes in an Observe with bind and + * delegate and change $.route's properties with + * attr and attrs. + * + * ### Listening to changes in an Observable + * + * Listen to changes in history + * by [jQuery.Observe.prototype.bind bind]ing to + * changes in $.route like: + * + * $.route.bind('change', function(ev, attr, how, newVal, oldVal) { + * + * }) + * + * - attr - the name of the changed attribute + * - how - the type of Observe change event (add, set or remove) + * - newVal/oldVal - the new and old values of the attribute + * + * You can also listen to specific changes + * with [jQuery.Observe.prototype.delegate delegate]: + * + * $.route.delegate('id','change', function(){ ... }) + * + * Observe lets you listen to the following events: + * + * - change - any change to the object + * - add - a property is added + * - set - a property value is added or changed + * - remove - a property is removed + * + * Listening for add is useful for widget setup + * behavior, remove is useful for teardown. + * + * ### Updating an observable + * + * Create changes in the route data like: + * + * $.route.attr('type','images'); + * + * Or change multiple properties at once with + * [jQuery.Observe.prototype.attrs attrs]: + * + * $.route.attr({type: 'pages', id: 5}, true) + * + * When you make changes to $.route, they will automatically + * change the hash. + * + * ## Creating a Route + * + * Use $.route(url, defaults) to create a + * route. A route is a mapping from a url to + * an object (that is the $.route's state). + * + * If no routes are added, or no route is matched, + * $.route's data is updated with the [jQuery.String.deparam deparamed] + * hash. + * + * location.hash = "#!type=videos"; + * // $.route -> {type : "videos"} + * + * Once routes are added and the hash changes, + * $.route looks for matching routes and uses them + * to update $.route's data. + * + * $.route( "content/:type" ); + * location.hash = "#!content/images"; + * // $.route -> {type : "images"} + * + * Default values can also be added: + * + * $.route("content/:type",{type: "videos" }); + * location.hash = "#!content/" + * // $.route -> {type : "videos"} + * + * ## Delay setting $.route + * + * By default, $.route sets its initial data + * on document ready. Sometimes, you want to wait to set + * this data. To wait, call: + * + * $.route.ready(false); + * + * and when ready, call: + * + * $.route.ready(true); + * + * ## Changing the route. + * + * Typically, you never want to set location.hash + * directly. Instead, you can change properties on $.route + * like: + * + * $.route.attr('type', 'videos') + * + * This will automatically look up the appropriate + * route and update the hash. + * + * Often, you want to create links. $.route provides + * the [jQuery.route.link] and [jQuery.route.url] helpers to make this + * easy: + * + * $.route.link("Videos", {type: 'videos'}) + * + * @param {String} url the fragment identifier to match. + * @param {Object} [defaults] an object of default values + * @return {jQuery.route} + */ + $.route = function( url, defaults ) { + // Extract the variable names and replace with regEx that will match an atual URL with values. + var names = [], + test = url.replace(matcher, function( whole, name ) { + names.push(name) + // TODO: I think this should have a + + return "([^\\/\\&]*)" // The '\\' is for string-escaping giving single '\' for regEx escaping + }); + + // Add route in a form that can be easily figured out + $.route.routes[url] = { + // A regular expression that will match the route when variable values + // are present; i.e. for :page/:type the regEx is /([\w\.]*)/([\w\.]*)/ which + // will match for any value of :page and :type (word chars or period). + test: new RegExp("^" + test+"($|&)"), + // The original URL, same as the index for this entry in routes. + route: url, + // An array of all the variable names in this route + names: names, + // Default values provided for the variables. + defaults: defaults || {}, + // The number of parts in the URL separated by '/'. + length: url.split('/').length + } + return $.route; + }; + + extend($.route, { + /** + * Parameterizes the raw JS object representation provided in data. + * If a route matching the provided data is found that URL is built + * from the data. Any remaining data is added at the end of the + * URL as & separated key/value parameters. + * + * @param {Object} data + * @return {String} The route URL and & separated parameters. + */ + param: function( data ) { + // Check if the provided data keys match the names in any routes; + // get the one with the most matches. + var route, + // need it to be at least 1 match + matches = 0, + matchCount, + routeName = data.route; + + delete data.route; + // if we have a route name in our $.route data, use it + if(routeName && (route = $.route.routes[routeName])){ + + } else { + // otherwise find route + each($.route.routes, function(name, temp){ + matchCount = matchesData(temp, data); + if ( matchCount > matches ) { + route = temp; + matches = matchCount + } + }); + } + // if this is match + + if ( route ) { + var cpy = extend({}, data), + // Create the url by replacing the var names with the provided data. + // If the default value is found an empty string is inserted. + res = route.route.replace(matcher, function( whole, name ) { + delete cpy[name]; + return data[name] === route.defaults[name] ? "" : encode( data[name] ); + }), + after; + // remove matching default values + each(route.defaults, function(name,val){ + if(cpy[name] === val) { + delete cpy[name] + } + }) + + // The remaining elements of data are added as + // $amp; separated parameters to the url. + after = $.param(cpy); + return res + (after ? "&" + after : "") + } + // If no route was found there is no hash URL, only paramters. + return $.isEmptyObject(data) ? "" : "&" + $.param(data); + }, + /** + * Populate the JS data object from a given URL. + * + * @param {Object} url + */ + deparam: function( url ) { + // See if the url matches any routes by testing it against the route.test regEx. + // By comparing the URL length the most specialized route that matches is used. + var route = { + length: -1 + }; + each($.route.routes, function(name, temp){ + if ( temp.test.test(url) && temp.length > route.length ) { + route = temp; + } + }); + // If a route was matched + if ( route.length > -1 ) { + var // Since RegEx backreferences are used in route.test (round brackets) + // the parts will contain the full matched string and each variable (backreferenced) value. + parts = url.match(route.test), + // start will contain the full matched string; parts contain the variable values. + start = parts.shift(), + // The remainder will be the &key=value list at the end of the URL. + remainder = url.substr(start.length - (parts[parts.length-1] === "&" ? 1 : 0) ), + // If there is a remainder and it contains a &key=value list deparam it. + obj = (remainder && paramsMatcher.test(remainder)) ? $.String.deparam( remainder.slice(1) ) : {}; + + // Add the default values for this route + obj = extend(true, {}, route.defaults, obj); + // Overwrite each of the default values in obj with those in parts if that part is not empty. + each(parts,function(i, part){ + if ( part && part !== '&') { + obj[route.names[i]] = decode( part ); + } + }); + obj.route = route.route; + return obj; + } + // If no route was matched it is parsed as a &key=value list. + if ( url.charAt(0) !== '&' ) { + url = '&' + url; + } + return paramsMatcher.test(url) ? $.String.deparam( url.slice(1) ) : {}; + }, + /** + * @hide + * A $.Observe that represents the state of the history. + */ + data: new $.Observe({}), + /** + * @attribute + * @type Object + * @hide + * + * A list of routes recognized by the router indixed by the url used to add it. + * Each route is an object with these members: + * + * - test - A regular expression that will match the route when variable values + * are present; i.e. for :page/:type the regEx is /([\w\.]*)/([\w\.]*)/ which + * will match for any value of :page and :type (word chars or period). + * + * - route - The original URL, same as the index for this entry in routes. + * + * - names - An array of all the variable names in this route + * + * - defaults - Default values provided for the variables or an empty object. + * + * - length - The number of parts in the URL separated by '/'. + */ + routes: {}, + /** + * Indicates that all routes have been added and sets $.route.data + * based upon the routes and the current hash. + * + * By default, ready is fired on jQuery's ready event. Sometimes + * you might want it to happen sooner or earlier. To do this call + * + * $.route.ready(false); //prevents firing by the ready event + * $.route.ready(true); // fire the first route change + * + * @param {Boolean} [start] + * @return $.route + */ + ready: function(val) { + if( val === false ) { + onready = false; + } + if( val === true || onready === true ) { + setState(); + } + return $.route; + }, + /** + * Returns a url from the options + * @param {Object} options + * @param {Boolean} merge true if the options should be merged with the current options + * @return {String} + */ + url: function( options, merge ) { + if (merge) { + return "#!" + $.route.param(extend({}, curParams, options)) + } else { + return "#!" + $.route.param(options) + } + }, + /** + * Returns a link + * @param {Object} name The text of the link. + * @param {Object} options The route options (variables) + * @param {Object} props Properties of the <a> other than href. + * @param {Boolean} merge true if the options should be merged with the current options + */ + link: function( name, options, props, merge ) { + return "" + name + ""; + }, + /** + * Returns true if the options represent the current page. + * @param {Object} options + * @return {Boolean} + */ + current: function( options ) { + return location.hash == "#!" + $.route.param(options) + } + }); + // onready + $(function() { + $.route.ready(); + }); + + // The functions in the following list applied to $.route (e.g. $.route.attr('...')) will + // instead act on the $.route.data Observe. + each(['bind','unbind','delegate','undelegate','attr','attrs','serialize','removeAttr'], function(i, name){ + $.route[name] = function(){ + return $.route.data[name].apply($.route.data, arguments) + } + }) + + var // A throttled function called multiple times will only fire once the + // timer runs down. Each call resets the timer. + throttle = function( func ) { + var timer; + return function() { + var args = arguments, + self = this; + clearTimeout(timer); + timer = setTimeout(function(){ + func.apply(self, args) + }, 1); + } + }, + // Intermediate storage for $.route.data. + curParams, + // Deparameterizes the portion of the hash of interest and assign the + // values to the $.route.data removing existing values no longer in the hash. + setState = function() { + var hash = location.hash.substr(1, 1) === '!' ? + location.hash.slice(2) : + location.hash.slice(1); // everything after #! + curParams = $.route.deparam( hash ); + $.route.attrs(curParams, true); + }; + + // If the hash changes, update the $.route.data + $(window).bind('hashchange', setState); + + // If the $.route.data changes, update the hash. + // Using .serialize() retrieves the raw data contained in the observable. + // This function is throttled so it only updates once even if multiple values changed. + $.route.bind("change", throttle(function() { + location.hash = "#!" + $.route.param($.route.serialize()) + })); }) \ No newline at end of file diff --git a/dom/route/route_test.js b/dom/route/route_test.js index 26bbfbe4..f487da94 100644 --- a/dom/route/route_test.js +++ b/dom/route/route_test.js @@ -241,15 +241,15 @@ test("linkTo", function(){ $.route.routes = {}; $.route(":foo"); var res = $.route.link("Hello",{foo: "bar", baz: 'foo'}); - equal( res, 'Hello'); + equal( res, 'Hello'); }) test("param with route defined", function(){ $.route.routes = {}; $.route("holler") $.route("foo"); - var data = {foo: "abc",route: "foo"} - var res = $.route.param(data); + + var res = $.route.param({foo: "abc",route: "foo"}); equal(res, "foo&foo=abc") }) diff --git a/jquery.js b/jquery.js index 4abd6777..74ce4119 100644 --- a/jquery.js +++ b/jquery.js @@ -1 +1,9266 @@ -steal('can/util/jquery/jquery.1.7.1.js') +/*! + * jQuery JavaScript Library v1.7.1 + * http://jquery.com/ + * + * 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 2011, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * + * Date: Mon Nov 21 21:11:03 2011 -0500 + */ +(function( window, undefined ) { + +// Use the correct document accordingly with window argument (sandbox) +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, rootjQuery ); + }, + + // 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 + // Prioritize #id over to avoid XSS via location.hash (#9521) + quickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/, + + // 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>)?$/, + + // 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.]+))?/, + + // Matches dashed string for camelizing + rdashAlpha = /-([a-z]|[0-9])/ig, + rmsPrefix = /^-ms-/, + + // Used by jQuery.camelCase as callback to replace() + fcamelCase = function( all, letter ) { + return ( letter + "" ).toUpperCase(); + }, + + // Keep a UserAgent string for use with jQuery.browser + userAgent = navigator.userAgent, + + // For matching the engine and version of the browser + browserMatch, + + // The deferred used 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 = { + constructor: jQuery, + init: function( selector, context, rootjQuery ) { + 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 = selector; + this.length = 1; + return this; + } + + // Handle HTML strings + if ( typeof selector === "string" ) { + // Are we dealing with HTML string or an ID? + 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 + // 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 ? jQuery.clone(ret.fragment) : 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: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return ( context || rootjQuery ).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( 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.7.1", + + // 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[ 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 = this.constructor(); + + 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(); + + // Add the callback + readyList.add( fn ); + + return this; + }, + + eq: function( i ) { + i = +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 || this.constructor(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 ) { + if ( window.$ === jQuery ) { + window.$ = _$; + } + + 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, + + // Hold (or release) the ready event + holdReady: function( hold ) { + if ( hold ) { + jQuery.readyWait++; + } else { + jQuery.ready( true ); + } + }, + + // 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 ); + } + + // 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 + readyList.fireWith( document, [ jQuery ] ); + + // Trigger any bound ready events + if ( jQuery.fn.trigger ) { + jQuery( document ).trigger( "ready" ).off( "ready" ); + } + } + }, + + bindReady: function() { + if ( readyList ) { + return; + } + + readyList = jQuery.Callbacks( "once memory" ); + + // 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; + }, + + isNumeric: function( obj ) { + return !isNaN( parseFloat(obj) ) && isFinite( 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; + } + + try { + // Not own constructor property must be Object + if ( obj.constructor && + !hasOwn.call(obj, "constructor") && + !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) { + return false; + } + } catch ( e ) { + // IE8,9 Will throw exceptions on certain host objects #9897 + 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 new Error( 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 ); + + // 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, "")) ) { + + return ( new Function( "return " + data ) )(); + + } + jQuery.error( "Invalid JSON: " + data ); + }, + + // Cross-browser xml parsing + parseXML: function( data ) { + var xml, tmp; + try { + 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 ); + } + } catch( e ) { + xml = undefined; + } + if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) { + jQuery.error( "Invalid XML: " + data ); + } + return xml; + }, + + noop: function() {}, + + // 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 ); + } + }, + + // Convert dashed to camelCase; used by the css and data modules + // Microsoft forgot to hump their vendor prefix (#9572) + camelCase: function( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); + }, + + 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 ( ; i < length; ) { + if ( callback.call( object[ i ], i, object[ i++ ] ) === false ) { + break; + } + } + } + } + + 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' + // 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, i ) { + var len; + + if ( array ) { + if ( indexOf ) { + return indexOf.call( array, elem, i ); + } + + len = array.length; + i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0; + + for ( ; i < len; i++ ) { + // Skip accessing in sparse arrays + if ( i in array && 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 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 + 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; + } + } + } + + // Flatten any nested arrays + return ret.concat.apply( [], ret ); + }, + + // A global GUID counter for objects + guid: 1, + + // 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; + } + + // 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; + } + + // Simulated bind + var args = slice.call( arguments, 2 ), + proxy = function() { + 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 + proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++; + + return proxy; + }, + + // Mutifunctional method to get and set values to a collection + // The value/s can optionally be executed if it's 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" }; + }, + + 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: {} +}); + +// 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; +} + +// IE doesn't match non-breaking spaces with \s +if ( rnotwhite.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(); +} + +return jQuery; + +})(); + + +// String to Object flags format cache +var flagsCache = {}; + +// Convert String-formatted flags into Object-formatted ones and store in cache +function createFlags( flags ) { + var object = flagsCache[ flags ] = {}, + i, length; + flags = flags.split( /\s+/ ); + for ( i = 0, length = flags.length; i < length; i++ ) { + object[ flags[i] ] = true; + } + return object; +} + +/* + * Create a callback list using the following parameters: + * + * flags: an optional list of space-separated flags that will change how + * the callback list behaves + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible flags: + * + * once: will ensure the callback list can only be fired once (like a Deferred) + * + * memory: will keep track of previous values and will call any callback added + * after the list has been fired right away with the latest "memorized" + * values (like a Deferred) + * + * unique: will ensure a callback can only be added once (no duplicate in the list) + * + * stopOnFalse: interrupt callings when a callback returns false + * + */ +jQuery.Callbacks = function( flags ) { + + // Convert flags from String-formatted to Object-formatted + // (we check in cache first) + flags = flags ? ( flagsCache[ flags ] || createFlags( flags ) ) : {}; + + var // Actual callback list + list = [], + // Stack of fire calls for repeatable lists + stack = [], + // Last fire value (for non-forgettable lists) + memory, + // Flag to know if list is currently firing + firing, + // First callback to fire (used internally by add and fireWith) + firingStart, + // End of the loop when firing + firingLength, + // Index of currently firing callback (modified by remove if needed) + firingIndex, + // Add one or several callbacks to the list + add = function( args ) { + var i, + length, + elem, + type, + actual; + for ( i = 0, length = args.length; i < length; i++ ) { + elem = args[ i ]; + type = jQuery.type( elem ); + if ( type === "array" ) { + // Inspect recursively + add( elem ); + } else if ( type === "function" ) { + // Add if not in unique mode and callback is not in + if ( !flags.unique || !self.has( elem ) ) { + list.push( elem ); + } + } + } + }, + // Fire callbacks + fire = function( context, args ) { + args = args || []; + memory = !flags.memory || [ context, args ]; + firing = true; + firingIndex = firingStart || 0; + firingStart = 0; + firingLength = list.length; + for ( ; list && firingIndex < firingLength; firingIndex++ ) { + if ( list[ firingIndex ].apply( context, args ) === false && flags.stopOnFalse ) { + memory = true; // Mark as halted + break; + } + } + firing = false; + if ( list ) { + if ( !flags.once ) { + if ( stack && stack.length ) { + memory = stack.shift(); + self.fireWith( memory[ 0 ], memory[ 1 ] ); + } + } else if ( memory === true ) { + self.disable(); + } else { + list = []; + } + } + }, + // Actual Callbacks object + self = { + // Add a callback or a collection of callbacks to the list + add: function() { + if ( list ) { + var length = list.length; + add( arguments ); + // Do we need to add the callbacks to the + // current firing batch? + if ( firing ) { + firingLength = list.length; + // With memory, if we're not firing then + // we should call right away, unless previous + // firing was halted (stopOnFalse) + } else if ( memory && memory !== true ) { + firingStart = length; + fire( memory[ 0 ], memory[ 1 ] ); + } + } + return this; + }, + // Remove a callback from the list + remove: function() { + if ( list ) { + var args = arguments, + argIndex = 0, + argLength = args.length; + for ( ; argIndex < argLength ; argIndex++ ) { + for ( var i = 0; i < list.length; i++ ) { + if ( args[ argIndex ] === list[ i ] ) { + // Handle firingIndex and firingLength + if ( firing ) { + if ( i <= firingLength ) { + firingLength--; + if ( i <= firingIndex ) { + firingIndex--; + } + } + } + // Remove the element + list.splice( i--, 1 ); + // If we have some unicity property then + // we only need to do this once + if ( flags.unique ) { + break; + } + } + } + } + } + return this; + }, + // Control if a given callback is in the list + has: function( fn ) { + if ( list ) { + var i = 0, + length = list.length; + for ( ; i < length; i++ ) { + if ( fn === list[ i ] ) { + return true; + } + } + } + return false; + }, + // Remove all callbacks from the list + empty: function() { + list = []; + return this; + }, + // Have the list do nothing anymore + disable: function() { + list = stack = memory = undefined; + return this; + }, + // Is it disabled? + disabled: function() { + return !list; + }, + // Lock the list in its current state + lock: function() { + stack = undefined; + if ( !memory || memory === true ) { + self.disable(); + } + return this; + }, + // Is it locked? + locked: function() { + return !stack; + }, + // Call all callbacks with the given context and arguments + fireWith: function( context, args ) { + if ( stack ) { + if ( firing ) { + if ( !flags.once ) { + stack.push( [ context, args ] ); + } + } else if ( !( flags.once && memory ) ) { + fire( context, args ); + } + } + return this; + }, + // Call all the callbacks with the given arguments + fire: function() { + self.fireWith( this, arguments ); + return this; + }, + // To know if the callbacks have already been called at least once + fired: function() { + return !!memory; + } + }; + + return self; +}; + + + + +var // Static reference to slice + sliceDeferred = [].slice; + +jQuery.extend({ + + Deferred: function( func ) { + var doneList = jQuery.Callbacks( "once memory" ), + failList = jQuery.Callbacks( "once memory" ), + progressList = jQuery.Callbacks( "memory" ), + state = "pending", + lists = { + resolve: doneList, + reject: failList, + notify: progressList + }, + promise = { + done: doneList.add, + fail: failList.add, + progress: progressList.add, + + state: function() { + return state; + }, + + // Deprecated + isResolved: doneList.fired, + isRejected: failList.fired, + + then: function( doneCallbacks, failCallbacks, progressCallbacks ) { + deferred.done( doneCallbacks ).fail( failCallbacks ).progress( progressCallbacks ); + return this; + }, + always: function() { + deferred.done.apply( deferred, arguments ).fail.apply( deferred, arguments ); + return this; + }, + pipe: function( fnDone, fnFail, fnProgress ) { + return jQuery.Deferred(function( newDefer ) { + jQuery.each( { + done: [ fnDone, "resolve" ], + fail: [ fnFail, "reject" ], + progress: [ fnProgress, "notify" ] + }, 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, newDefer.notify ); + } else { + newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ 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 ) { + obj = promise; + } else { + for ( var key in promise ) { + obj[ key ] = promise[ key ]; + } + } + return obj; + } + }, + deferred = promise.promise({}), + key; + + for ( key in lists ) { + deferred[ key ] = lists[ key ].fire; + deferred[ key + "With" ] = lists[ key ].fireWith; + } + + // Handle state + deferred.done( function() { + state = "resolved"; + }, failList.disable, progressList.lock ).fail( function() { + state = "rejected"; + }, doneList.disable, progressList.lock ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + + // All done! + return deferred; + }, + + // Deferred helper + when: function( firstParam ) { + var args = sliceDeferred.call( arguments, 0 ), + i = 0, + length = args.length, + pValues = new Array( length ), + count = length, + pCount = length, + deferred = length <= 1 && firstParam && jQuery.isFunction( firstParam.promise ) ? + firstParam : + jQuery.Deferred(), + promise = deferred.promise(); + function resolveFunc( i ) { + return function( value ) { + args[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value; + if ( !( --count ) ) { + deferred.resolveWith( deferred, args ); + } + }; + } + function progressFunc( i ) { + return function( value ) { + pValues[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value; + deferred.notifyWith( promise, pValues ); + }; + } + if ( length > 1 ) { + for ( ; i < length; i++ ) { + if ( args[ i ] && args[ i ].promise && jQuery.isFunction( args[ i ].promise ) ) { + args[ i ].promise().then( resolveFunc(i), deferred.reject, progressFunc(i) ); + } else { + --count; + } + } + if ( !count ) { + deferred.resolveWith( deferred, args ); + } + } else if ( deferred !== firstParam ) { + deferred.resolveWith( deferred, length ? [ firstParam ] : [] ); + } + return promise; + } +}); + + + + +jQuery.support = (function() { + + var support, + all, + a, + select, + opt, + input, + marginDiv, + fragment, + tds, + events, + eventName, + i, + isSupported, + div = document.createElement( "div" ), + documentElement = document.documentElement; + + // Preliminary tests + div.setAttribute("className", "t"); + div.innerHTML = "
          a"; + + all = div.getElementsByTagName( "*" ); + a = div.getElementsByTagName( "a" )[ 0 ]; + + // Can't get basic test support + if ( !all || !all.length || !a ) { + return {}; + } + + // 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 ), + + // 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 instead) + style: /top/.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: ( 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", + + // Tests for enctype support on a form(#6743) + enctype: !!document.createElement("form").enctype, + + // Makes sure cloning an html5 element does not cause problems + // Where outerHTML is undefined, this still works + html5Clone: document.createElement("nav").cloneNode( true ).outerHTML !== "<:nav>", + + // Will be defined later + submitBubbles: true, + changeBubbles: true, + focusinBubbles: false, + deleteExpando: true, + noCloneEvent: true, + inlineBlockNeedsLayout: false, + shrinkWrapBlocks: false, + 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 disabled) + select.disabled = true; + support.optDisabled = !opt.disabled; + + // Test to see if it's possible to delete an expando from an element + // Fails in Internet Explorer + try { + delete div.test; + } catch( e ) { + support.deleteExpando = false; + } + + if ( !div.addEventListener && div.attachEvent && div.fireEvent ) { + div.attachEvent( "onclick", function() { + // Cloning a node shouldn't copy over any + // bound event handlers (IE does this) + support.noCloneEvent = false; + }); + div.cloneNode( true ).fireEvent( "onclick" ); + } + + // Check if a radio maintains its value + // after being appended to the DOM + input = document.createElement("input"); + input.value = "t"; + input.setAttribute("type", "radio"); + support.radioValue = input.value === "t"; + + input.setAttribute("checked", "checked"); + div.appendChild( input ); + fragment = document.createDocumentFragment(); + fragment.appendChild( div.lastChild ); + + // WebKit doesn't clone checked state correctly in fragments + support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Check if a disconnected checkbox will retain its checked + // value of true after appended to the DOM (IE6/7) + support.appendChecked = input.checked; + + fragment.removeChild( input ); + fragment.appendChild( div ); + + 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 ( window.getComputedStyle ) { + marginDiv = document.createElement( "div" ); + marginDiv.style.width = "0"; + marginDiv.style.marginRight = "0"; + div.style.width = "2px"; + div.appendChild( marginDiv ); + support.reliableMarginRight = + ( parseInt( ( window.getComputedStyle( marginDiv, null ) || { marginRight: 0 } ).marginRight, 10 ) || 0 ) === 0; + } + + // Technique from Juriy Zaytsev + // http://perfectionkills.com/detecting-event-support-without-browser-sniffing/ + // 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; + } + } + + fragment.removeChild( div ); + + // Null elements to avoid leaks in IE + fragment = select = opt = marginDiv = div = input = null; + + // Run tests that need a body at doc ready + jQuery(function() { + var container, outer, inner, table, td, offsetSupport, + conMarginTop, ptlm, vb, style, html, + body = document.getElementsByTagName("body")[0]; + + if ( !body ) { + // Return for frameset docs that don't have a body + return; + } + + conMarginTop = 1; + ptlm = "position:absolute;top:0;left:0;width:1px;height:1px;margin:0;"; + vb = "visibility:hidden;border:0;"; + style = "style='" + ptlm + "border:5px solid #000;padding:0;'"; + html = "
          " + + "" + + "
          "; + + container = document.createElement("div"); + container.style.cssText = vb + "width:0;height:0;position:static;top:0;margin-top:" + conMarginTop + "px"; + body.insertBefore( container, body.firstChild ); + + // Construct the test element + div = document.createElement("div"); + container.appendChild( div ); + + // 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) + div.innerHTML = "
          t
          "; + tds = div.getElementsByTagName( "td" ); + 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 ); + + // Figure out if the W3C box model works as expected + div.innerHTML = ""; + div.style.width = div.style.paddingLeft = "1px"; + jQuery.boxModel = support.boxModel = div.offsetWidth === 2; + + if ( typeof div.style.zoom !== "undefined" ) { + // 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 = "
          "; + support.shrinkWrapBlocks = ( div.offsetWidth !== 2 ); + } + + div.style.cssText = ptlm + vb; + div.innerHTML = html; + + outer = div.firstChild; + inner = outer.firstChild; + td = outer.nextSibling.firstChild.firstChild; + + offsetSupport = { + doesNotAddBorder: ( inner.offsetTop !== 5 ), + doesAddBorderForTableAndCells: ( td.offsetTop === 5 ) + }; + + inner.style.position = "fixed"; + inner.style.top = "20px"; + + // safari subtracts parent border width here which is 5px + offsetSupport.fixedPosition = ( inner.offsetTop === 20 || inner.offsetTop === 15 ); + inner.style.position = inner.style.top = ""; + + outer.style.overflow = "hidden"; + outer.style.position = "relative"; + + offsetSupport.subtractsBorderForOverflowNotVisible = ( inner.offsetTop === -5 ); + offsetSupport.doesNotIncludeMarginInBodyOffset = ( body.offsetTop !== conMarginTop ); + + body.removeChild( container ); + div = container = null; + + jQuery.extend( support, offsetSupport ); + }); + + return support; +})(); + + + + +var rbrace = /^(?:\{.*\}|\[.*\])$/, + rmultiDash = /([A-Z])/g; + +jQuery.extend({ + cache: {}, + + // Please use with caution + uuid: 0, + + // 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. + noData: { + "embed": true, + // Ban all objects except for Flash (which handle expandos) + "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000", + "applet": true + }, + + 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; + } + + var privateCache, thisCache, ret, + internalKey = jQuery.expando, + getByName = typeof name === "string", + + // 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, + + // 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, + + // 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[ internalKey ] : elem[ internalKey ] && internalKey, + isEvents = name === "events"; + + // 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 || !cache[id] || (!isEvents && !pvt && !cache[id].data)) && getByName && data === undefined ) { + return; + } + + if ( !id ) { + // Only DOM nodes need a new unique ID for each element since their data + // ends up in the global cache + if ( isNode ) { + elem[ internalKey ] = id = ++jQuery.uuid; + } else { + id = internalKey; + } + } + + if ( !cache[ id ] ) { + cache[ id ] = {}; + + // 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 ] = jQuery.extend( cache[ id ], name ); + } else { + cache[ id ].data = jQuery.extend( cache[ id ].data, name ); + } + } + + privateCache = thisCache = cache[ id ]; + + // jQuery data() is stored in a separate object inside the object's internal data + // cache in order to avoid key collisions between internal data and user-defined + // data. + if ( !pvt ) { + if ( !thisCache.data ) { + thisCache.data = {}; + } + + thisCache = thisCache.data; + } + + if ( data !== undefined ) { + thisCache[ jQuery.camelCase( name ) ] = data; + } + + // Users should not attempt to inspect the internal events object using jQuery.data, + // it is undocumented and subject to change. But does anyone listen? No. + if ( isEvents && !thisCache[ name ] ) { + return privateCache.events; + } + + // Check for both converted-to-camel and non-converted data property names + // If a data property was specified + if ( getByName ) { + + // First Try to find as-is property data + ret = thisCache[ name ]; + + // Test for null|undefined property data + if ( ret == null ) { + + // Try to find the camelCased property + ret = thisCache[ jQuery.camelCase( name ) ]; + } + } else { + ret = thisCache; + } + + return ret; + }, + + removeData: function( elem, name, pvt /* Internal Use Only */ ) { + if ( !jQuery.acceptData( elem ) ) { + return; + } + + var thisCache, i, l, + + // Reference to internal data cache key + internalKey = jQuery.expando, + + isNode = elem.nodeType, + + // See jQuery.data for more information + cache = isNode ? jQuery.cache : elem, + + // See jQuery.data for more information + id = isNode ? elem[ internalKey ] : internalKey; + + // If there is already no cache entry for this object, there is no + // purpose in continuing + if ( !cache[ id ] ) { + return; + } + + if ( name ) { + + thisCache = pvt ? cache[ id ] : cache[ id ].data; + + if ( thisCache ) { + + // Support array or space separated string names for data keys + if ( !jQuery.isArray( name ) ) { + + // try the string as a key before any manipulation + if ( name in thisCache ) { + name = [ name ]; + } else { + + // split the camel cased version by spaces unless a key with the spaces exists + name = jQuery.camelCase( name ); + if ( name in thisCache ) { + name = [ name ]; + } else { + name = name.split( " " ); + } + } + } + + for ( i = 0, l = name.length; i < l; i++ ) { + delete thisCache[ name[i] ]; + } + + // If there is no data left in the cache, we want to continue + // and let the cache object itself get destroyed + if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) { + return; + } + } + } + + // See jQuery.data for more information + if ( !pvt ) { + delete cache[ id ].data; + + // Don't destroy the parent cache unless the internal data object + // had been the only thing left in it + if ( !isEmptyDataObject(cache[ id ]) ) { + return; + } + } + + // 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 + // Ensure that `cache` is not a window object #10080 + if ( jQuery.support.deleteExpando || !cache.setInterval ) { + delete cache[ id ]; + } else { + cache[ id ] = null; + } + + // We destroyed the cache and need to eliminate the expando on the node to avoid + // false lookups in the cache for entries that no longer exist + 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[ internalKey ]; + } else if ( elem.removeAttribute ) { + elem.removeAttribute( internalKey ); + } else { + elem[ internalKey ] = 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 ) { + 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 parts, attr, name, + data = null; + + if ( typeof key === "undefined" ) { + if ( this.length ) { + data = jQuery.data( this[0] ); + + if ( this[0].nodeType === 1 && !jQuery._data( this[0], "parsedAttrs" ) ) { + attr = this[0].attributes; + for ( var i = 0, l = attr.length; i < l; i++ ) { + name = attr[i].name; + + if ( name.indexOf( "data-" ) === 0 ) { + name = jQuery.camelCase( name.substring(5) ); + + dataAttr( this[0], name, data[ name ] ); + } + } + jQuery._data( this[0], "parsedAttrs", true ); + } + } + + return data; + + } else if ( typeof key === "object" ) { + return this.each(function() { + jQuery.data( this, key ); + }); + } + + 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 self = jQuery( this ), + args = [ parts[0], value ]; + + self.triggerHandler( "setData" + parts[1] + "!", args ); + jQuery.data( this, key, value ); + self.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 ) { + + var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase(); + + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = data === "true" ? true : + data === "false" ? false : + data === "null" ? null : + jQuery.isNumeric( 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; +} + +// checks a cache object for emptiness +function isEmptyDataObject( obj ) { + for ( var name in obj ) { + + // if the public data object is empty, the private is still empty + if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) { + continue; + } + if ( name !== "toJSON" ) { + return false; + } + } + + return true; +} + + + + +function handleQueueMarkDefer( elem, type, src ) { + var deferDataKey = type + "defer", + queueDataKey = type + "queue", + markDataKey = type + "mark", + defer = jQuery._data( elem, deferDataKey ); + if ( defer && + ( src === "queue" || !jQuery._data(elem, queueDataKey) ) && + ( src === "mark" || !jQuery._data(elem, markDataKey) ) ) { + // 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 ) && + !jQuery._data( elem, markDataKey ) ) { + jQuery.removeData( elem, deferDataKey, true ); + defer.fire(); + } + }, 0 ); + } +} + +jQuery.extend({ + + _mark: function( elem, type ) { + if ( elem ) { + type = ( type || "fx" ) + "mark"; + jQuery._data( elem, type, (jQuery._data( elem, type ) || 0) + 1 ); + } + }, + + _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 ) || 1) - 1 ); + if ( count ) { + jQuery._data( elem, key, count ); + } else { + jQuery.removeData( elem, key, true ); + handleQueueMarkDefer( elem, type, "mark" ); + } + } + }, + + queue: function( elem, type, data ) { + var q; + if ( elem ) { + type = ( type || "fx" ) + "queue"; + q = jQuery._data( elem, type ); + + // 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) ); + } else { + q.push( data ); + } + } + return q || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + fn = queue.shift(), + hooks = {}; + + // 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" ); + } + + jQuery._data( elem, type + ".run", hooks ); + fn.call( elem, function() { + jQuery.dequeue( elem, type ); + }, hooks ); + } + + if ( !queue.length ) { + jQuery.removeData( elem, type + "queue " + type + ".run", true ); + handleQueueMarkDefer( elem, type, "queue" ); + } + } +}); + +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() { + 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( next, hooks ) { + var timeout = setTimeout( next, time ); + hooks.stop = function() { + clearTimeout( timeout ); + }; + }); + }, + 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.Callbacks( "once memory" ), true ) )) { + count++; + tmp.add( resolve ); + } + } + resolve(); + return defer.promise(); + } +}); + + + + +var rclass = /[\n\t\r]/g, + rspace = /\s+/, + rreturn = /\r/g, + rtype = /^(?:button|input)$/i, + rfocusable = /^(?:button|input|object|select|textarea)$/i, + rclickable = /^a(?:rea)?$/i, + rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i, + getSetAttribute = jQuery.support.getSetAttribute, + nodeHook, boolHook, fixSpecified; + +jQuery.fn.extend({ + attr: function( name, value ) { + return jQuery.access( this, name, value, true, jQuery.attr ); + }, + + 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 ) { + var classNames, i, l, elem, + setClass, c, cl; + + if ( jQuery.isFunction( value ) ) { + return this.each(function( j ) { + jQuery( this ).addClass( value.call(this, j, this.className) ); + }); + } + + if ( value && typeof value === "string" ) { + classNames = value.split( rspace ); + + for ( i = 0, l = this.length; i < l; i++ ) { + elem = this[ i ]; + + if ( elem.nodeType === 1 ) { + if ( !elem.className && classNames.length === 1 ) { + elem.className = value; + + } else { + setClass = " " + elem.className + " "; + + for ( c = 0, cl = classNames.length; c < cl; c++ ) { + if ( !~setClass.indexOf( " " + classNames[ c ] + " " ) ) { + setClass += classNames[ c ] + " "; + } + } + elem.className = jQuery.trim( setClass ); + } + } + } + } + + return this; + }, + + removeClass: function( value ) { + var classNames, i, l, elem, className, c, cl; + + if ( jQuery.isFunction( value ) ) { + return this.each(function( j ) { + jQuery( this ).removeClass( value.call(this, j, this.className) ); + }); + } + + if ( (value && typeof value === "string") || value === undefined ) { + classNames = ( value || "" ).split( rspace ); + + for ( i = 0, l = this.length; i < l; i++ ) { + elem = this[ i ]; + + if ( elem.nodeType === 1 && elem.className ) { + if ( value ) { + className = (" " + elem.className + " ").replace( rclass, " " ); + for ( 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 ) { + jQuery( this ).toggleClass( value.call(this, i, this.className, 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 + " ", + i = 0, + l = this.length; + for ( ; i < l; i++ ) { + if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) { + return true; + } + } + + return false; + }, + + val: function( value ) { + var hooks, ret, isFunction, + elem = this[0]; + + if ( !arguments.length ) { + if ( elem ) { + hooks = jQuery.valHooks[ elem.nodeName.toLowerCase() ] || jQuery.valHooks[ elem.type ]; + + if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) { + return ret; + } + + ret = elem.value; + + return typeof ret === "string" ? + // handle most common string cases + ret.replace(rreturn, "") : + // handle cases where value is null/undef or number + ret == null ? "" : ret; + } + + return; + } + + isFunction = jQuery.isFunction( 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() ); + } else { + val = value; + } + + // 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 + ""; + }); + } + + 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, i, max, option, + 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 + i = one ? index : 0; + max = one ? index + 1 : options.length; + for ( ; i < max; i++ ) { + 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 ); + } + } + + // 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 ) { + elem.selectedIndex = -1; + } + return values; + } + } + }, + + attrFn: { + val: true, + css: true, + html: true, + text: true, + data: true, + width: true, + height: true, + offset: true + }, + + attr: function( elem, name, value, pass ) { + var ret, hooks, notxml, + nType = elem.nodeType; + + // don't get/set attributes on text, comment and attribute nodes + if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + if ( pass && name in jQuery.attrFn ) { + return jQuery( elem )[ name ]( value ); + } + + // Fallback to prop when attributes are not supported + if ( typeof elem.getAttribute === "undefined" ) { + return jQuery.prop( elem, name, value ); + } + + notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); + + // All attributes are lowercase + // Grab necessary hook if one is defined + if ( notxml ) { + name = name.toLowerCase(); + hooks = jQuery.attrHooks[ name ] || ( rboolean.test( name ) ? boolHook : nodeHook ); + } + + if ( value !== undefined ) { + + if ( value === null ) { + jQuery.removeAttr( elem, name ); + return; + + } else if ( hooks && "set" in hooks && notxml && (ret = hooks.set( elem, value, name )) !== undefined ) { + return ret; + + } else { + elem.setAttribute( name, "" + value ); + return value; + } + + } else if ( hooks && "get" in hooks && notxml && (ret = hooks.get( elem, name )) !== null ) { + return ret; + + } else { + + ret = elem.getAttribute( name ); + + // Non-existent attributes return null, we normalize to undefined + return ret === null ? + undefined : + ret; + } + }, + + removeAttr: function( elem, value ) { + var propName, attrNames, name, l, + i = 0; + + if ( value && elem.nodeType === 1 ) { + attrNames = value.toLowerCase().split( rspace ); + l = attrNames.length; + + for ( ; i < l; i++ ) { + name = attrNames[ i ]; + + if ( name ) { + propName = jQuery.propFix[ name ] || name; + + // See #9699 for explanation of this approach (setting first, then removal) + jQuery.attr( elem, name, "" ); + elem.removeAttribute( getSetAttribute ? name : propName ); + + // Set corresponding property to false for boolean attributes + if ( rboolean.test( name ) && propName 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; + } + } + }, + // Use the value property for back compat + // Use the nodeHook for button elements in IE6/7 (#1954) + value: { + get: function( elem, name ) { + if ( nodeHook && jQuery.nodeName( elem, "button" ) ) { + return nodeHook.get( elem, name ); + } + return name in elem ? + elem.value : + null; + }, + set: function( elem, value, name ) { + if ( nodeHook && jQuery.nodeName( elem, "button" ) ) { + return nodeHook.set( elem, value, name ); + } + // Does not return so that setAttribute is also used + elem.value = value; + } + } + }, + + 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 ret, hooks, notxml, + nType = elem.nodeType; + + // don't get/set properties on text, comment and attribute nodes + if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); + + if ( notxml ) { + // Fix name and attach hooks + name = 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 ); + } + + } else { + if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) { + return ret; + + } else { + return elem[ name ]; + } + } + }, + + propHooks: { + 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 ? + parseInt( attributeNode.value, 10 ) : + rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? + 0 : + undefined; + } + } + } +}); + +// Add the tabIndex propHook to attrHooks for back-compat (different case is intentional) +jQuery.attrHooks.tabindex = jQuery.propHooks.tabIndex; + +// Hook for boolean attributes +boolHook = { + get: function( elem, name ) { + // Align boolean attributes with corresponding properties + // Fall back to attribute presence where some booleans are not supported + var attrNode, + property = jQuery.prop( elem, name ); + return property === true || typeof property !== "boolean" && ( attrNode = elem.getAttributeNode(name) ) && attrNode.nodeValue !== false ? + 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 ] = true; + } + + elem.setAttribute( name, name.toLowerCase() ); + } + return name; + } +}; + +// IE6/7 do not support getting/setting some attributes with get/setAttribute +if ( !getSetAttribute ) { + + fixSpecified = { + name: true, + id: true + }; + + // Use this for any attribute in IE6/7 + // This fixes almost every IE6/7 issue + nodeHook = jQuery.valHooks.button = { + get: function( elem, name ) { + var ret; + ret = elem.getAttributeNode( name ); + return ret && ( fixSpecified[ name ] ? ret.nodeValue !== "" : ret.specified ) ? + ret.nodeValue : + undefined; + }, + set: function( elem, value, name ) { + // Set the existing or create a new attribute node + var ret = elem.getAttributeNode( name ); + if ( !ret ) { + ret = document.createAttribute( name ); + elem.setAttributeNode( ret ); + } + return ( ret.nodeValue = value + "" ); + } + }; + + // Apply the nodeHook to tabindex + jQuery.attrHooks.tabindex.set = nodeHook.set; + + // 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; + } + } + }); + }); + + // Set contenteditable to false on removals(#10429) + // Setting to empty string throws an error as an invalid value + jQuery.attrHooks.contenteditable = { + get: nodeHook.get, + set: function( elem, value, name ) { + if ( value === "" ) { + value = "false"; + } + nodeHook.set( elem, value, name ); + } + }; +} + + +// 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; + } + } + return null; + } + }); +} + +// IE6/7 call enctype encoding +if ( !jQuery.support.enctype ) { + jQuery.propFix.enctype = "encoding"; +} + +// 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 rformElems = /^(?:textarea|input|select)$/i, + rtypenamespace = /^([^\.]*)?(?:\.(.+))?$/, + rhoverHack = /\bhover(\.\S+)?\b/, + rkeyEvent = /^key/, + rmouseEvent = /^(?:mouse|contextmenu)|click/, + rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + rquickIs = /^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/, + quickParse = function( selector ) { + var quick = rquickIs.exec( selector ); + if ( quick ) { + // 0 1 2 3 + // [ _, tag, id, class ] + quick[1] = ( quick[1] || "" ).toLowerCase(); + quick[3] = quick[3] && new RegExp( "(?:^|\\s)" + quick[3] + "(?:\\s|$)" ); + } + return quick; + }, + quickIs = function( elem, m ) { + var attrs = elem.attributes || {}; + return ( + (!m[1] || elem.nodeName.toLowerCase() === m[1]) && + (!m[2] || (attrs.id || {}).value === m[2]) && + (!m[3] || m[3].test( (attrs[ "class" ] || {}).value )) + ); + }, + hoverHack = function( events ) { + return jQuery.event.special.hover ? events : events.replace( rhoverHack, "mouseenter$1 mouseleave$1" ); + }; + +/* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ +jQuery.event = { + + add: function( elem, types, handler, data, selector ) { + + var elemData, eventHandle, events, + t, tns, type, namespaces, handleObj, + handleObjIn, quick, handlers, special; + + // Don't attach events to noData or text/comment nodes (allow plain objects tho) + if ( elem.nodeType === 3 || elem.nodeType === 8 || !types || !handler || !(elemData = jQuery._data( elem )) ) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + events = elemData.events; + if ( !events ) { + elemData.events = events = {}; + } + eventHandle = elemData.handle; + if ( !eventHandle ) { + 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.dispatch.apply( eventHandle.elem, arguments ) : + undefined; + }; + // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events + eventHandle.elem = elem; + } + + // Handle multiple events separated by a space + // jQuery(...).bind("mouseover mouseout", fn); + types = jQuery.trim( hoverHack(types) ).split( " " ); + for ( t = 0; t < types.length; t++ ) { + + tns = rtypenamespace.exec( types[t] ) || []; + type = tns[1]; + namespaces = ( tns[2] || "" ).split( "." ).sort(); + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[ type ] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = ( selector ? special.delegateType : special.bindType ) || type; + + // Update special based on newly reset type + special = jQuery.event.special[ type ] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend({ + type: type, + origType: tns[1], + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + quick: quickParse( selector ), + namespace: namespaces.join(".") + }, handleObjIn ); + + // Init the event handler queue if we're the first + handlers = events[ type ]; + if ( !handlers ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // 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 to the element's handler list, delegates in front + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } + + // Keep track of which events have ever been used, for event optimization + 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, selector, mappedTypes ) { + + var elemData = jQuery.hasData( elem ) && jQuery._data( elem ), + t, tns, type, origType, namespaces, origCount, + j, events, special, handle, eventType, handleObj; + + if ( !elemData || !(events = elemData.events) ) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = jQuery.trim( hoverHack( types || "" ) ).split(" "); + for ( t = 0; t < types.length; t++ ) { + tns = rtypenamespace.exec( types[t] ) || []; + type = origType = tns[1]; + namespaces = tns[2]; + + // Unbind all events (on this namespace, if provided) for the element + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector? special.delegateType : special.bindType ) || type; + eventType = events[ type ] || []; + origCount = eventType.length; + namespaces = namespaces ? new RegExp("(^|\\.)" + namespaces.split(".").sort().join("\\.(?:.*\\.)?") + "(\\.|$)") : null; + + // Remove matching events + for ( j = 0; j < eventType.length; j++ ) { + handleObj = eventType[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !namespaces || namespaces.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) { + eventType.splice( j--, 1 ); + + if ( handleObj.selector ) { + eventType.delegateCount--; + } + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if ( eventType.length === 0 && origCount !== eventType.length ) { + if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) { + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + // Remove the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + handle = elemData.handle; + if ( handle ) { + handle.elem = null; + } + + // removeData also checks for emptiness and clears the expando if empty + // so use it instead of delete + jQuery.removeData( elem, [ "events", "handle" ], 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 + }, + + trigger: function( event, data, elem, onlyHandlers ) { + // Don't do events on text and comment nodes + if ( elem && (elem.nodeType === 3 || elem.nodeType === 8) ) { + return; + } + + // Event object or event type + var type = event.type || event, + namespaces = [], + cache, exclusive, i, cur, old, ontype, special, handle, eventPath, bubbleType; + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; + } + + 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(); + } + + 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; + } + + // 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.type = type; + event.isTrigger = true; + event.exclusive = exclusive; + event.namespace = namespaces.join( "." ); + event.namespace_re = event.namespace? new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.)?") + "(\\.|$)") : null; + ontype = type.indexOf( ":" ) < 0 ? "on" + type : ""; + + // Handle a global trigger + if ( !elem ) { + + // TODO: Stop taunting the data cache; remove global events and always attach to document + cache = jQuery.cache; + for ( i in cache ) { + if ( cache[ i ].events && cache[ i ].events[ type ] ) { + jQuery.event.trigger( event, data, cache[ i ].handle.elem, true ); + } + } + return; + } + + // Clean up the event in case it is being reused + event.result = undefined; + if ( !event.target ) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data != null ? jQuery.makeArray( data ) : []; + data.unshift( event ); + + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (#9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + eventPath = [[ elem, special.bindType || type ]]; + if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + cur = rfocusMorph.test( bubbleType + type ) ? elem : elem.parentNode; + old = null; + for ( ; cur; cur = cur.parentNode ) { + eventPath.push([ cur, bubbleType ]); + old = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( old && old === elem.ownerDocument ) { + eventPath.push([ old.defaultView || old.parentWindow || window, bubbleType ]); + } + } + + // Fire handlers on the event path + for ( i = 0; i < eventPath.length && !event.isPropagationStopped(); i++ ) { + + cur = eventPath[i][0]; + event.type = eventPath[i][1]; + + handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + // Note that this is a bare JS function and not a jQuery handler + handle = ontype && cur[ ontype ]; + if ( handle && jQuery.acceptData( cur ) && handle.apply( cur, data ) === false ) { + event.preventDefault(); + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if ( !onlyHandlers && !event.isDefaultPrevented() ) { + + if ( (!special._default || special._default.apply( elem.ownerDocument, data ) === 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. + // Don't do default actions on window, that's where global variables be (#6170) + // IE<9 dies on focus/blur to hidden element (#1486) + if ( ontype && elem[ type ] && ((type !== "focus" && type !== "blur") || event.target.offsetWidth !== 0) && !jQuery.isWindow( elem ) ) { + + // Don't re-trigger an onFOO event when we call its FOO() method + old = elem[ ontype ]; + + if ( old ) { + elem[ ontype ] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + elem[ type ](); + jQuery.event.triggered = undefined; + + if ( old ) { + elem[ ontype ] = old; + } + } + } + } + + return event.result; + }, + + dispatch: function( event ) { + + // Make a writable jQuery.Event from the native event object + event = jQuery.event.fix( event || window.event ); + + var handlers = ( (jQuery._data( this, "events" ) || {} )[ event.type ] || []), + delegateCount = handlers.delegateCount, + args = [].slice.call( arguments, 0 ), + run_all = !event.exclusive && !event.namespace, + handlerQueue = [], + i, j, cur, jqcur, ret, selMatch, matched, matches, handleObj, sel, related; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[0] = event; + event.delegateTarget = this; + + // Determine handlers that should run if there are delegated events + // Avoid disabled elements in IE (#6911) and non-left-click bubbling in Firefox (#3861) + if ( delegateCount && !event.target.disabled && !(event.button && event.type === "click") ) { + + // Pregenerate a single jQuery object for reuse with .is() + jqcur = jQuery(this); + jqcur.context = this.ownerDocument || this; + + for ( cur = event.target; cur != this; cur = cur.parentNode || this ) { + selMatch = {}; + matches = []; + jqcur[0] = cur; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + sel = handleObj.selector; + + if ( selMatch[ sel ] === undefined ) { + selMatch[ sel ] = ( + handleObj.quick ? quickIs( cur, handleObj.quick ) : jqcur.is( sel ) + ); + } + if ( selMatch[ sel ] ) { + matches.push( handleObj ); + } + } + if ( matches.length ) { + handlerQueue.push({ elem: cur, matches: matches }); + } + } + } + + // Add the remaining (directly-bound) handlers + if ( handlers.length > delegateCount ) { + handlerQueue.push({ elem: this, matches: handlers.slice( delegateCount ) }); + } + + // Run delegates first; they may want to stop propagation beneath us + for ( i = 0; i < handlerQueue.length && !event.isPropagationStopped(); i++ ) { + matched = handlerQueue[ i ]; + event.currentTarget = matched.elem; + + for ( j = 0; j < matched.matches.length && !event.isImmediatePropagationStopped(); j++ ) { + handleObj = matched.matches[ j ]; + + // Triggered event must either 1) be non-exclusive and have no namespace, or + // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace). + if ( run_all || (!event.namespace && !handleObj.namespace) || event.namespace_re && event.namespace_re.test( handleObj.namespace ) ) { + + event.data = handleObj.data; + event.handleObj = handleObj; + + ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler ) + .apply( matched.elem, args ); + + if ( ret !== undefined ) { + event.result = ret; + if ( ret === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + return event.result; + }, + + // Includes some event props shared by KeyEvent and MouseEvent + // *** attrChange attrName relatedNode srcElement are not normalized, non-W3C, deprecated, will be removed in 1.8 *** + props: "attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "), + + fixHooks: {}, + + keyHooks: { + props: "char charCode key keyCode".split(" "), + filter: function( event, original ) { + + // Add which for key events + if ( event.which == null ) { + event.which = original.charCode != null ? original.charCode : original.keyCode; + } + + return event; + } + }, + + mouseHooks: { + props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "), + filter: function( event, original ) { + var eventDoc, doc, body, + button = original.button, + fromElement = original.fromElement; + + // Calculate pageX/Y if missing and clientX/Y available + if ( event.pageX == null && original.clientX != null ) { + eventDoc = event.target.ownerDocument || document; + doc = eventDoc.documentElement; + body = eventDoc.body; + + event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 ); + event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 ); + } + + // Add relatedTarget, if necessary + if ( !event.relatedTarget && fromElement ) { + event.relatedTarget = fromElement === event.target ? original.toElement : fromElement; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + // Note: button is not normalized, so don't use it + if ( !event.which && button !== undefined ) { + event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) ); + } + + return event; + } + }, + + fix: function( event ) { + if ( event[ jQuery.expando ] ) { + return event; + } + + // Create a writable copy of the event object and normalize some properties + var i, prop, + originalEvent = event, + fixHook = jQuery.event.fixHooks[ event.type ] || {}, + copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props; + + event = jQuery.Event( originalEvent ); + + for ( i = copy.length; i; ) { + prop = copy[ --i ]; + event[ prop ] = originalEvent[ prop ]; + } + + // Fix target property, if necessary (#1925, IE 6/7/8 & Safari2) + if ( !event.target ) { + event.target = originalEvent.srcElement || document; + } + + // Target should not be a text node (#504, Safari) + if ( event.target.nodeType === 3 ) { + event.target = event.target.parentNode; + } + + // For mouse/key events; add metaKey if it's not there (#3368, IE6/7/8) + if ( event.metaKey === undefined ) { + event.metaKey = event.ctrlKey; + } + + return fixHook.filter? fixHook.filter( event, originalEvent ) : event; + }, + + special: { + ready: { + // Make sure the ready event is setup + setup: jQuery.bindReady + }, + + load: { + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + + focus: { + delegateType: "focusin" + }, + blur: { + delegateType: "focusout" + }, + + 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; + } + } + } + }, + + simulate: function( type, elem, event, bubble ) { + // 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. + var e = jQuery.extend( + new jQuery.Event(), + event, + { type: type, + isSimulated: true, + originalEvent: {} + } + ); + if ( bubble ) { + jQuery.event.trigger( e, null, elem ); + } else { + jQuery.event.dispatch.call( elem, e ); + } + if ( e.isDefaultPrevented() ) { + event.preventDefault(); + } + } +}; + +// Some plugins are using, but it's undocumented/deprecated and will be removed. +// The 1.7 special event interface should provide all the hooks needed now. +jQuery.event.handle = jQuery.event.dispatch; + +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, props ) { + // Allow instantiation without the 'new' keyword + if ( !(this instanceof jQuery.Event) ) { + 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 ); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.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 +}; + +// Create mouseenter/leave events using mouseover/out and event-time checks +jQuery.each({ + mouseenter: "mouseover", + mouseleave: "mouseout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var target = this, + related = event.relatedTarget, + handleObj = event.handleObj, + selector = handleObj.selector, + ret; + + // For mousenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || (related !== target && !jQuery.contains( target, related )) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; +}); + +// IE submit delegation +if ( !jQuery.support.submitBubbles ) { + + jQuery.event.special.submit = { + setup: function() { + // Only need this for delegated form submit events + if ( jQuery.nodeName( this, "form" ) ) { + return false; + } + + // Lazy-add a submit handler when a descendant form may potentially be submitted + jQuery.event.add( this, "click._submit keypress._submit", function( e ) { + // Node name check avoids a VML-related crash in IE (#9807) + var elem = e.target, + form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined; + if ( form && !form._submit_attached ) { + jQuery.event.add( form, "submit._submit", function( event ) { + // If form was submitted by the user, bubble the event up the tree + if ( this.parentNode && !event.isTrigger ) { + jQuery.event.simulate( "submit", this.parentNode, event, true ); + } + }); + form._submit_attached = true; + } + }); + // return undefined since we don't need an event listener + }, + + teardown: function() { + // Only need this for delegated form submit events + if ( jQuery.nodeName( this, "form" ) ) { + return false; + } + + // Remove delegated handlers; cleanData eventually reaps submit handlers attached above + jQuery.event.remove( this, "._submit" ); + } + }; +} + +// IE change delegation and checkbox/radio fix +if ( !jQuery.support.changeBubbles ) { + + jQuery.event.special.change = { + + setup: function() { + + if ( rformElems.test( this.nodeName ) ) { + // IE doesn't fire change on a check/radio until blur; trigger it on click + // after a propertychange. Eat the blur-change in special.change.handle. + // This still fires onchange a second time for check/radio after blur. + if ( this.type === "checkbox" || this.type === "radio" ) { + jQuery.event.add( this, "propertychange._change", function( event ) { + if ( event.originalEvent.propertyName === "checked" ) { + this._just_changed = true; + } + }); + jQuery.event.add( this, "click._change", function( event ) { + if ( this._just_changed && !event.isTrigger ) { + this._just_changed = false; + jQuery.event.simulate( "change", this, event, true ); + } + }); + } + return false; + } + // Delegated event; lazy-add a change handler on descendant inputs + jQuery.event.add( this, "beforeactivate._change", function( e ) { + var elem = e.target; + + if ( rformElems.test( elem.nodeName ) && !elem._change_attached ) { + jQuery.event.add( elem, "change._change", function( event ) { + if ( this.parentNode && !event.isSimulated && !event.isTrigger ) { + jQuery.event.simulate( "change", this.parentNode, event, true ); + } + }); + elem._change_attached = true; + } + }); + }, + + handle: function( event ) { + var elem = event.target; + + // Swallow native change events from checkbox/radio, we already triggered them above + if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) { + return event.handleObj.handler.apply( this, arguments ); + } + }, + + teardown: function() { + jQuery.event.remove( this, "._change" ); + + return rformElems.test( this.nodeName ); + } + }; +} + +// Create "bubbling" focus and blur events +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, + handler = function( event ) { + jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true ); + }; + + jQuery.event.special[ fix ] = { + setup: function() { + if ( attaches++ === 0 ) { + document.addEventListener( orig, handler, true ); + } + }, + teardown: function() { + if ( --attaches === 0 ) { + document.removeEventListener( orig, handler, true ); + } + } + }; + }); +} + +jQuery.fn.extend({ + + on: function( types, selector, data, fn, /*INTERNAL*/ one ) { + var origFn, type; + + // Types can be a map of types/handlers + if ( typeof types === "object" ) { + // ( types-Object, selector, data ) + if ( typeof selector !== "string" ) { + // ( types-Object, data ) + data = selector; + selector = undefined; + } + for ( type in types ) { + this.on( type, selector, data, types[ type ], one ); + } + return this; + } + + if ( data == null && fn == null ) { + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return this; + } + + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + // Can use an empty set, since event contains the info + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return this.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + }); + }, + one: function( types, selector, data, fn ) { + return this.on.call( this, types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + if ( types && types.preventDefault && types.handleObj ) { + // ( event ) dispatched jQuery.Event + var handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace? handleObj.type + "." + handleObj.namespace : handleObj.type, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + // ( types-object [, selector] ) + for ( var type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each(function() { + jQuery.event.remove( this, types, fn, selector ); + }); + }, + + bind: function( types, data, fn ) { + return this.on( types, null, data, fn ); + }, + unbind: function( types, fn ) { + return this.off( types, null, fn ); + }, + + live: function( types, data, fn ) { + jQuery( this.context ).on( types, this.selector, data, fn ); + return this; + }, + die: function( types, fn ) { + jQuery( this.context ).off( types, this.selector || "**", fn ); + return this; + }, + + delegate: function( selector, types, data, fn ) { + return this.on( types, selector, data, fn ); + }, + undelegate: function( selector, types, fn ) { + // ( namespace ) or ( selector, types [, fn] ) + return arguments.length == 1? this.off( selector, "**" ) : this.off( types, selector, fn ); + }, + + trigger: function( type, data ) { + return this.each(function() { + jQuery.event.trigger( type, data, this ); + }); + }, + triggerHandler: function( type, data ) { + if ( this[0] ) { + return jQuery.event.trigger( type, data, this[0], true ); + } + }, + + toggle: function( fn ) { + // Save reference to arguments for access in closure + var args = arguments, + 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 ) { + args[ i++ ].guid = guid; + } + + return this.click( toggler ); + }, + + hover: function( fnOver, fnOut ) { + return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); + } +}); + +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 contextmenu").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.on( name, null, data, fn ) : + this.trigger( name ); + }; + + if ( jQuery.attrFn ) { + jQuery.attrFn[ name ] = true; + } + + if ( rkeyEvent.test( name ) ) { + jQuery.event.fixHooks[ name ] = jQuery.event.keyHooks; + } + + if ( rmouseEvent.test( name ) ) { + jQuery.event.fixHooks[ name ] = jQuery.event.mouseHooks; + } +}); + + + +/*! + * Sizzle CSS Selector Engine + * Copyright 2011, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * More information: http://sizzlejs.com/ + */ +(function(){ + +var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, + expando = "sizcache" + (Math.random() + '').replace('.', ''), + done = 0, + toString = Object.prototype.toString, + hasDuplicate = false, + baseHasDuplicate = true, + rBackslash = /\\/g, + rReturn = /\r\n/g, + rNonWord = /\W/; + +// 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, seed ); + + } 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, seed ); + } + } + + } 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, i, len, match, type, left; + + if ( !expr ) { + return []; + } + + for ( i = 0, len = Expr.order.length; i < len; i++ ) { + type = Expr.order[i]; + + if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { + left = match[1]; + match.splice( 1, 1 ); + + if ( left.substr( left.length - 1 ) !== "\\" ) { + match[1] = (match[1] || "").replace( rBackslash, "" ); + set = Expr.find[ type ]( match, context, isXML ); + + if ( set != null ) { + expr = expr.replace( Expr.match[ type ], "" ); + break; + } + } + } + } + + if ( !set ) { + set = typeof context.getElementsByTagName !== "undefined" ? + context.getElementsByTagName( "*" ) : + []; + } + + return { set: set, expr: expr }; +}; + +Sizzle.filter = function( expr, set, inplace, not ) { + var match, anyFound, + type, found, item, filter, left, + i, pass, + old = expr, + result = [], + curLoop = set, + isXMLFilter = set && set[0] && Sizzle.isXML( set[0] ); + + while ( expr && set.length ) { + for ( type in Expr.filter ) { + if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) { + 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 ( i = 0; (item = curLoop[i]) != null; i++ ) { + if ( item ) { + found = filter( item, match, i, curLoop ); + 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 new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +/** + * Utility function for retreiving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ +var getText = Sizzle.getText = function( elem ) { + var i, node, + nodeType = elem.nodeType, + ret = ""; + + if ( nodeType ) { + if ( nodeType === 1 || nodeType === 9 ) { + // Use textContent || innerText for elements + if ( typeof elem.textContent === 'string' ) { + return elem.textContent; + } else if ( typeof elem.innerText === 'string' ) { + // Replace IE's carriage returns + return elem.innerText.replace( rReturn, '' ); + } else { + // Traverse it's children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + } else { + + // If no nodeType, this is expected to be an array + for ( i = 0; (node = elem[i]); i++ ) { + // Do not traverse comment nodes + if ( node.nodeType !== 8 ) { + ret += getText( node ); + } + } + } + return ret; +}; + +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|(#?(?:[\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( elem ) { + return elem.getAttribute( "href" ); + }, + type: function( elem ) { + return elem.getAttribute( "type" ); + } + }, + + relative: { + "+": function(checkSet, part){ + var isPartStr = typeof part === "string", + isTag = isPartStr && !rNonWord.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 && !rNonWord.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" && !rNonWord.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" && !rNonWord.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 ) { + if ( typeof context.getElementsByTagName !== "undefined" ) { + return context.getElementsByTagName( match[1] ); + } + } + }, + preFilter: { + CLASS: function( match, curLoop, inplace, result, not, isXML ) { + match = " " + match[1].replace( rBackslash, "" ) + " "; + + 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\r]/g, " ").indexOf(match) >= 0) ) { + if ( !inplace ) { + result.push( elem ); + } + + } else if ( inplace ) { + curLoop[i] = false; + } + } + } + + return false; + }, + + ID: function( match ) { + return match[1].replace( rBackslash, "" ); + }, + + TAG: function( match, curLoop ) { + 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( + 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; + } + else if ( match[2] ) { + Sizzle.error( match[0] ); + } + + // TODO: Move to normal caching system + match[0] = done++; + + return match; + }, + + ATTR: function( match, curLoop, inplace, result, not, isXML ) { + 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] + " "; + } + + 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 + if ( elem.parentNode ) { + 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 ) { + 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 elem.nodeName.toLowerCase() === "input" && "radio" === elem.type; + }, + + checkbox: function( elem ) { + return elem.nodeName.toLowerCase() === "input" && "checkbox" === elem.type; + }, + + file: function( elem ) { + return elem.nodeName.toLowerCase() === "input" && "file" === elem.type; + }, + + password: function( elem ) { + return elem.nodeName.toLowerCase() === "input" && "password" === elem.type; + }, + + submit: function( elem ) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && "submit" === elem.type; + }, + + image: function( elem ) { + return elem.nodeName.toLowerCase() === "input" && "image" === elem.type; + }, + + reset: function( elem ) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && "reset" === elem.type; + }, + + button: function( elem ) { + 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: { + 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 || 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( name ); + } + }, + + CHILD: function( elem, match ) { + var first, last, + doneName, parent, cache, + count, diff, + 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": + first = match[2]; + last = match[3]; + + if ( first === 1 && last === 0 ) { + return true; + } + + doneName = match[0]; + parent = elem.parentNode; + + if ( parent && (parent[ expando ] !== doneName || !elem.nodeIndex) ) { + count = 0; + + for ( node = parent.firstChild; node; node = node.nextSibling ) { + if ( node.nodeType === 1 ) { + node.nodeIndex = ++count; + } + } + + parent[ expando ] = doneName; + } + + 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 && 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 = Sizzle.attr ? + Sizzle.attr( elem, name ) : + 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 && Sizzle.attr ? + result != null : + 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 ) { + // 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 = [], + aup = a.parentNode, + bup = b.parentNode, + cur = aup; + + // If the nodes are siblings (or identical) we can do a quick check + 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; + }; +} + +// 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 = ""; + + // 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 = ""; + + 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 = "

          "; + + // 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 && !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) {} + + // 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 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 { + if ( !relativeHierarchySelector || hasParent ) { + return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra ); + } + + } catch(pseudoError) { + } finally { + if ( !old ) { + oldContext.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; + + 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; + } + + 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 ) ) { + 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) {} + } + + return Sizzle(expr, null, null, [node]).length > 0; + }; + } +})(); + +(function(){ + var div = document.createElement("div"); + + div.innerHTML = "
          "; + + // 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[ expando ] === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 && !isXML ){ + elem[ expando ] = 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[ expando ] === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 ) { + if ( !isXML ) { + elem[ expando ] = 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, seed ) { + 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, seed ); + } + + return Sizzle.filter( later, tmpSet ); +}; + +// EXPOSE +// Override sizzle attribute retrieval +Sizzle.attr = jQuery.attr; +Sizzle.selectors.attrMap = {}; +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, + // 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, n, r; + + 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 ( n = length; n < ret.length; n++ ) { + for ( 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 && ( + typeof selector === "string" ? + // If this is a positional selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + POS.test( selector ) ? + jQuery( selector, this.context ).index( this[0] ) >= 0 : + jQuery.filter( selector, this ).length > 0 : + this.filter( selector ).length > 0 ); + }, + + closest: function( selectors, context ) { + var ret = [], i, l, cur = this[0]; + + // Array (deprecated as of jQuery 1.7) + if ( jQuery.isArray( selectors ) ) { + var level = 1; + + while ( cur && cur.ownerDocument && cur !== context ) { + for ( i = 0; i < selectors.length; i++ ) { + + if ( jQuery( cur ).is( selectors[ i ] ) ) { + ret.push({ selector: selectors[ i ], elem: cur, level: level }); + } + } + + cur = cur.parentNode; + level++; + } + + return ret; + } + + // 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]; + + 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 || cur.nodeType === 11 ) { + 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 ) { + + // No argument, return index in parent + if ( !elem ) { + return ( this[0] && this[0].parentNode ) ? this.prevAll().length : -1; + } + + // index in selector + if ( typeof elem === "string" ) { + return jQuery.inArray( this[0], jQuery( elem ) ); + } + + // 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 ) : + jQuery.makeArray( selector && selector.nodeType ? [ selector ] : 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 && !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(",") ); + }; +}); + +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 ) { + + // 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 ); + 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; + }); +} + + + + +function createSafeFragment( document ) { + var list = nodeNames.split( "|" ), + safeFrag = document.createDocumentFragment(); + + if ( safeFrag.createElement ) { + while ( list.length ) { + safeFrag.createElement( + list.pop() + ); + } + } + return safeFrag; +} + +var nodeNames = "abbr|article|aside|audio|canvas|datalist|details|figcaption|figure|footer|" + + "header|hgroup|mark|meter|nav|output|progress|section|summary|time|video", + rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g, + rleadingWhitespace = /^\s+/, + rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig, + rtagName = /<([\w:]+)/, + rtbody = /<(?:" + nodeNames + ")", "i"), + // checked="checked" or checked + rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i, + rscriptType = /\/(java|ecma)script/i, + rcleanScript = /^\s*", "" ], + legend: [ 1, "
          ", "
          " ], + thead: [ 1, "", "
          " ], + tr: [ 2, "", "
          " ], + td: [ 3, "", "
          " ], + col: [ 2, "", "
          " ], + area: [ 1, "", "" ], + _default: [ 0, "", "" ] + }, + safeFragment = createSafeFragment( document ); + +wrapMap.optgroup = wrapMap.option; +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +// IE can't serialize and + + + + +

          tie Test Suite

          +

          +
          +

          +
          +
            +
            + + \ No newline at end of file diff --git a/tie/tie.html b/tie/tie.html new file mode 100644 index 00000000..16d9be57 --- /dev/null +++ b/tie/tie.html @@ -0,0 +1,114 @@ + + + + tie + + + + +
            +
            + + + + + + + + + + + \ No newline at end of file diff --git a/tie/tie.js b/tie/tie.js new file mode 100644 index 00000000..1a36416c --- /dev/null +++ b/tie/tie.js @@ -0,0 +1,97 @@ +steal('jquery/controller').then(function($){ + +/** + * @class jQuery.Tie + * @core + * + * The $.fn.tie plugin binds form elements and controllers with + * models and vice versa. The result is that a change in + * a model will automatically update the form element or controller + * AND a change event on the element will update the model. + * + * + * + * + * + */ +$.Controller("jQuery.Tie",{ + setup : function(el){ + this._super(el,{}) + return $.makeArray(arguments); + }, + init : function(el, inst, attr, type){ + // if there's a controller + if(!type){ + //find the first one that implements val + var controllers = this.element.data("controllers") || {}; + for(var name in controllers){ + var controller = controllers[name]; + if(typeof controller.val == 'function'){ + type = name; + break; + } + } + } + + this.type = type; + this.attr = attr; + this.inst = inst; + this.bind(inst, attr, "attrChanged"); + + //destroy this controller if the model instance is destroyed + this.bind(inst, "destroyed", "modelDestroyed"); + + var value = inst.attr(attr); + //set the value + this.lastValue = value; + if(type){ + + //destroy this controller if the controller is destroyed + this.bind(this.element.data("controllers")[type],"destroyed","destroy"); + this.element[type]("val",value); + + }else{ + this.element.val(value) + } + }, + attrChanged : function(inst, ev, val){ + if (val !== this.lastValue) { + this.setVal(val); + this.lastValue = val; + } + }, + modelDestroyed : function(){ + this.destroy() + }, + setVal : function(val){ + if (this.type) { + this.element[this.type]("val", val) + } + else { + this.element.val(val) + } + }, + change : function(el, ev, val){ + if(!this.type && val === undefined){ + val = this.element.val(); + } + + this.inst.attr(this.attr, val, null, this.proxy('setBack')) + + }, + setBack : function(){ + this.setVal(this.lastValue); + }, + destroy : function(){ + this.inst = null; + 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(); + } + + } +}); + + +}); \ No newline at end of file diff --git a/tie/tie_test.js b/tie/tie_test.js new file mode 100644 index 00000000..a4892441 --- /dev/null +++ b/tie/tie_test.js @@ -0,0 +1,125 @@ +steal + .then("funcunit/qunit", "jquery/tie",'jquery/model') + .then(function(){ + + + module("jquery/tie",{ + setup : function(){ + $.Model("Person",{ + setAge : function(age, success, error){ + age = +(age); + if(isNaN(age) || !isFinite(age) || age < 1 || age > 10){ + error() + }else{ + return age; + } + } + }); + } + }); + + test("sets age on tie", function(){ + + var person1 = new Person({age: 5}); + var inp = $("").appendTo( $("#qunit-test-area") ); + + inp.tie(person1, 'age'); + + equals(inp.val(), "5", "sets age"); + + var person2 = new Person(); + var inp2 = $("").appendTo( $("#qunit-test-area") ); + inp2.tie(person2, 'age'); + equals(inp2.val(), "", "nothing set"); + + person2.attr("age",6); + + equals(inp2.val(), "6", "nothing set"); + + + }); + + test("removing the controller, removes the tie ", 3, function(){ + var person1 = new Person({age: 5}); + var inp = $("
            ").appendTo( $("#qunit-test-area") ); + + $.Controller("Foo",{ + val : function(value){ + equals(value, 5, "Foo got the value correct") + } + }); + + inp.foo().tie(person1,"age"); + var foo = inp.controller('foo'), + tie = inp.controller('tie'); + inp.foo("destroy"); + + person1.attr("age",7) + ok(foo._destroyed, "Foo is destroyed"); + ok(tie._destroyed, "Tie is destroyed") + }) + + test("destroying the person, removes the tie", function(){ + var person1 = new Person({age: 5}); + var inp = $("
            ").appendTo( $("#qunit-test-area") ); + + $.Controller("Foo",{ + val : function(value){ + equals(value, 5, "Foo got the value correct") + } + }); + + inp.foo().tie(person1,"age"); + var foo = inp.controller('foo'), + tie = inp.controller('tie'); + + person1.destroyed(); + + person1.attr("age",7) + ok(!foo._destroyed, "Foo is not destroyed"); + ok(tie._destroyed, "Tie is destroyed") + }) + + test("removing html element removes the tie", function() { + var person1 = new Person({age: 5}); + var inp = $("
            ").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(){ + var person1 = new Person({age: 5}); + var inp = $("
            ").appendTo( $("#qunit-test-area") ); + + inp.tie(person1,"age"); + + inp.trigger("change",7); + equals(7, person1.attr('age'), "persons age set on change event"); + }); + + test("input error recovery", function(){ + var person1 = new Person({age: 5}); + var inp = $("").appendTo( $("#qunit-test-area") ); + + inp.tie(person1, 'age'); + + inp.val(100).trigger('change'); + + equals(inp.val(), "5", "input value stays the same"); + equals(person1.attr('age'), "5", "persons age stays the same"); + }) + + }); \ No newline at end of file diff --git a/view/ejs/ejs.js b/view/ejs/ejs.js index d87fae52..5805f71d 100644 --- a/view/ejs/ejs.js +++ b/view/ejs/ejs.js @@ -1,4 +1,665 @@ /*jslint evil: true */ -steal('jquery/view', 'can/view/ejs', function($){ - $.EJS = can.EJS; +steal('jquery/view', 'jquery/lang/string/rsplit').then(function( $ ) { + + // HELPER METHODS ============== + var myEval = function( script ) { + eval(script); + }, + // removes the last character from a string + // this is no longer needed + // chop = function( string ) { + // return string.substr(0, string.length - 1); + //}, + rSplit = $.String.rsplit, + extend = $.extend, + isArray = $.isArray, + // regular expressions for caching + returnReg = /\r\n/g, + retReg = /\r/g, + newReg = /\n/g, + nReg = /\n/, + slashReg = /\\/g, + quoteReg = /"/g, + singleQuoteReg = /'/g, + tabReg = /\t/g, + leftBracket = /\{/g, + rightBracket = /\}/g, + quickFunc = /\s*\(([\$\w]+)\)\s*->([^\n]*)/, + // escapes characters starting with \ + clean = function( content ) { + return content.replace(slashReg, '\\\\').replace(newReg, '\\n').replace(quoteReg, '\\"').replace(tabReg, '\\t'); + }, + // escapes html + // - from prototype http://www.prototypejs.org/ + escapeHTML = function( content ) { + return content.replace(/&/g, '&').replace(//g, '>').replace(quoteReg, '"').replace(singleQuoteReg, "'"); + }, + $View = $.View, + bracketNum = function(content){ + var lefts = content.match(leftBracket), + rights = content.match(rightBracket); + + return (lefts ? lefts.length : 0) - + (rights ? rights.length : 0); + }, + /** + * @class jQuery.EJS + * + * @plugin jquery/view/ejs + * @parent jQuery.View + * @download http://jmvcsite.heroku.com/pluginify?plugins[]=jquery/view/ejs/ejs.js + * @test jquery/view/ejs/qunit.html + * + * + * Ejs provides ERB + * style client side templates. Use them with controllers to easily build html and inject + * it into the DOM. + * + * ### Example + * + * The following generates a list of tasks: + * + * @codestart html + * <ul> + * <% for(var i = 0; i < tasks.length; i++){ %> + * <li class="task <%= tasks[i].identity %>"><%= tasks[i].name %></li> + * <% } %> + * </ul> + * @codeend + * + * For the following examples, we assume this view is in 'views\tasks\list.ejs'. + * + * + * ## 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 %> - Runs JS Code. + * For example: + * + * <% alert('hello world') %> + * + * - <%= CODE %> - Runs JS Code and writes the _escaped_ result into the result of the template. + * For example: + * + *

            <%= 'hello world' %>

            + * + * - <%== CODE %> - Runs JS Code and writes the _unescaped_ result into the result of the template. + * For example: + * + *

            <%== 'hello world' %>

            + * + * - <%%= CODE %> - Writes <%= CODE %> to the result of the template. This is very useful for generators. + * + * <%%= 'hello world' %> + * + * - <%# 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. + * View makes this easy. You just have to return the Contoller class you want to be hooked up. + * + * @codestart + * <ul <%= Mxui.Tabs%>>...<ul> + * @codeend + * + * You can even hook up multiple controllers: + * + * @codestart + * <ul <%= [Mxui.Tabs, Mxui.Filler]%>>...<ul> + * @codeend + * + * To hook up a controller with options or any other jQuery plugin use the + * [jQuery.EJS.Helpers.prototype.plugin | plugin view helper]: + * + * @codestart + * <ul <%= plugin('mxui_tabs', { option: 'value' }) %>>...<ul> + * @codeend + * + * Don't add a semicolon when using view helpers. + * + * + *

            View Helpers

            + * View Helpers return html code. View by default only comes with + * [jQuery.EJS.Helpers.prototype.view view] and [jQuery.EJS.Helpers.prototype.text text]. + * You can include more with the view/helpers plugin. But, you can easily make your own! + * Learn how in the [jQuery.EJS.Helpers Helpers] page. + * + * @constructor Creates a new view + * @param {Object} options A hash with the following options + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
            OptionDefaultDescription
            text uses the provided text as the template. Example:
            new View({text: '<%=user%>'}) + *
            type'<'type of magic tags. Options are '<' or '[' + *
            namethe element ID or url an optional name that is used for caching. + *
            + */ + EJS = function( options ) { + // If called without new, return a function that + // renders with data and helpers like + // EJS({text: '<%= message %>'})({message: 'foo'}); + // this is useful for steal's build system + if ( this.constructor != EJS ) { + var ejs = new EJS(options); + return function( data, helpers ) { + return ejs.render(data, helpers); + }; + } + // if we get a function directly, it probably is coming from + // a steal-packaged view + if ( typeof options == "function" ) { + this.template = { + fn: options + }; + return; + } + //set options on self + extend(this, EJS.options, options); + this.template = compile(this.text, this.type, this.name); + }; + // add EJS to jQuery if it exists + window.jQuery && (jQuery.EJS = EJS); + /** + * @Prototype + */ + EJS.prototype. + /** + * Renders an object with view helpers attached to the view. + * + * new EJS({text: "<%= message %>"}).render({ + * message: "foo" + * },{helper: function(){ ... }}) + * + * @param {Object} object data to be rendered + * @param {Object} [extraHelpers] an object with view helpers + * @return {String} returns the result of the string + */ + render = function( object, extraHelpers ) { + object = object || {}; + this._extra_helpers = extraHelpers; + var v = new EJS.Helpers(object, extraHelpers || {}); + return this.template.fn.call(object, object, v); + }; + /** + * @Static + */ + + extend(EJS, { + /** + * Used to convert what's in <%= %> magic tags to a string + * to be inserted in the rendered output. + * + * Typically, it's a string, and the string is just inserted. However, + * if it's a function or an object with a hookup method, it can potentially be + * be ran on the element after it's inserted into the page. + * + * This is a very nice way of adding functionality through the view. + * Usually this is done with [jQuery.EJS.Helpers.prototype.plugin] + * but the following fades in the div element after it has been inserted: + * + * @codestart + * <%= function(el){$(el).fadeIn()} %> + * @codeend + * + * @param {String|Object|Function} input the value in between the + * write magic tags: <%= %> + * @return {String} returns the content to be added to the rendered + * output. The content is different depending on the type: + * + * * string - the original string + * * null or undefined - the empty string "" + * * an object with a hookup method - the attribute "data-view-id='XX'", where XX is a hookup number for jQuery.View + * * a function - the attribute "data-view-id='XX'", where XX is a hookup number for jQuery.View + * * an array - the attribute "data-view-id='XX'", where XX is a hookup number for jQuery.View + */ + text: function( input ) { + // if it's a string, return + if ( typeof input == 'string' ) { + return input; + } + // if has no value + if ( input === null || input === undefined ) { + return ''; + } + + // if it's an object, and it has a hookup method + var hook = (input.hookup && + // make a function call the hookup method + + function( el, id ) { + input.hookup.call(input, el, id); + }) || + // or if it's a function, just use the input + (typeof input == 'function' && input) || + // of it its an array, make a function that calls hookup or the function + // on each item in the array + (isArray(input) && + function( el, id ) { + for ( var i = 0; i < input.length; i++ ) { + input[i].hookup ? input[i].hookup(el, id) : input[i](el, id); + } + }); + // finally, if there is a funciton to hookup on some dom + // pass it to hookup to get the data-view-id back + if ( hook ) { + return "data-view-id='" + $View.hookup(hook) + "'"; + } + // finally, if all else false, toString it + return input.toString ? input.toString() : ""; + }, + /** + * Escapes the text provided as html if it's a string. + * Otherwise, the value is passed to EJS.text(text). + * + * @param {String|Object|Array|Function} text to escape. Otherwise, + * the result of [jQuery.EJS.text] is returned. + * @return {String} the escaped text or likely a $.View data-view-id attribute. + */ + clean: function( text ) { + //return sanatized text + if ( typeof text == 'string' ) { + return escapeHTML(text); + } else if ( typeof text == 'number' ) { + return text; + } else { + return EJS.text(text); + } + }, + /** + * @attribute options + * Sets default options for all views. + * + * $.EJS.options.type = '[' + * + * Only one option is currently supported: type. + * + * Type is the left hand magic tag. + */ + options: { + type: '<', + ext: '.ejs' + } + }); + // ========= SCANNING CODE ========= + // Given a scanner, and source content, calls block with each token + // scanner - an object of magicTagName : values + // source - the source you want to scan + // block - function(token, scanner), called with each token + var scan = function( scanner, source, block ) { + // split on /\n/ to have new lines on their own line. + var source_split = rSplit(source, nReg), + 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); + } + } + }, + // creates a 'scanner' object. This creates + // values for the left and right magic tags + // it's splitter property is a regexp that splits content + // by all tags + makeScanner = function( left, right ) { + var scanner = {}; + extend(scanner, { + left: left + '%', + right: '%' + right, + dLeft: left + '%%', + dRight: '%%' + right, + eeLeft: left + '%==', + eLeft: left + '%=', + cmnt: left + '%#', + scan: scan, + lines: 0 + }); + scanner.splitter = new RegExp("(" + [scanner.dLeft, scanner.dRight, scanner.eeLeft, scanner.eLeft, scanner.cmnt, scanner.left, scanner.right + '\n', scanner.right, '\n'].join(")|("). + replace(/\[/g, "\\[").replace(/\]/g, "\\]") + ")"); + return scanner; + }, + // compiles a template where + // source - template text + // left - the left magic tag + // name - the name of the template (for debugging) + // returns an object like: {out : "", fn : function(){ ... }} where + // out - the converted JS source of the view + // fn - a function made from the JS source + compile = function( source, left, name ) { + // make everything only use \n + source = source.replace(returnReg, "\n").replace(retReg, "\n"); + // if no left is given, assume < + left = left || '<'; + + // put and insert cmds are used for adding content to the template + // currently they are identical, I am not sure why + var put_cmd = "___v1ew.push(", + insert_cmd = put_cmd, + // the text that starts the view code (or block function) + startTxt = 'var ___v1ew = [];', + // the text that ends the view code (or block function) + finishTxt = "return ___v1ew.join('')", + // initialize a buffer + buff = new EJS.Buffer([startTxt], []), + // content is used as the current 'processing' string + // this is the content between magic tags + content = '', + // adds something to be inserted into the view template + // this comes out looking like __v1ew.push("CONENT") + put = function( content ) { + buff.push(put_cmd, '"', clean(content), '");'); + }, + // the starting magic tag + startTag = null, + // cleans the running content + empty = function() { + content = '' + }, + // what comes after clean or text + doubleParen = "));", + // a stack used to keep track of how we should end a bracket } + // once we have a <%= %> with a leftBracket + // we store how the file should end here (either '))' or ';' ) + endStack =[]; + + // start going token to token + scan(makeScanner(left, left === '[' ? ']' : '>'), source || "", function( token, scanner ) { + // if we don't have a start pair + var bn; + if ( startTag === null ) { + switch ( token ) { + case '\n': + content = content + "\n"; + put(content); + buff.cr(); + empty(); + break; + // set start tag, add previous content (if there is some) + // clean content + case scanner.left: + case scanner.eLeft: + case scanner.eeLeft: + case scanner.cmnt: + // a new line, just add whatever content w/i a clean + // reset everything + startTag = token; + if ( content.length > 0 ) { + put(content); + } + empty(); + break; + + case scanner.dLeft: + // replace <%% with <% + content += scanner.left; + break; + default: + content += token; + break; + } + } + else { + //we have a start tag + switch ( token ) { + case scanner.right: + // %> + switch ( startTag ) { + case scanner.left: + // <% + + // get the number of { minus } + bn = bracketNum(content); + // how are we ending this statement + var last = + // if the stack has value and we are ending a block + endStack.length && bn == -1 ? + // use the last item in the block stack + endStack.pop() : + // or use the default ending + ";"; + + // if we are ending a returning block + // add the finish text which returns the result of the + // block + if(last === doubleParen) { + buff.push(finishTxt) + } + // add the remaining content + buff.push(content, last); + + // if we have a block, start counting + if(bn === 1 ){ + endStack.push(";") + } + break; + case scanner.eLeft: + // <%= clean content + bn = bracketNum(content); + if( bn ) { + endStack.push(doubleParen) + } + if(quickFunc.test(content)){ + var parts = content.match(quickFunc) + content = "function(__){var "+parts[1]+"=$(__);"+parts[2]+"}" + } + buff.push(insert_cmd, "jQuery.EJS.clean(", content,bn ? startTxt : doubleParen); + break; + case scanner.eeLeft: + // <%== content + + // get the number of { minus } + bn = bracketNum(content); + // if we have more {, it means there is a block + if( bn ){ + // when we return to the same # of { vs } end wiht a doubleParen + endStack.push(doubleParen) + } + + buff.push(insert_cmd, "jQuery.EJS.text(", content, + // if we have a block + bn ? + // start w/ startTxt "var _v1ew = [])" + startTxt : + // if not, add doubleParent to close push and text + doubleParen + ); + break; + } + startTag = null; + empty(); + break; + case scanner.dRight: + content += scanner.right; + break; + default: + content += token; + break; + } + } + }) + 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 + " "+finishTxt+"}}}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.fn = (function(_CONTEXT,_VIEW){' + out.out + '});\r\n//@ sourceURL=' + name + ".js"); + + return out; + }; + + + // A Buffer used to add content to. + // This is useful for performance and simplifying the + // code above. + // We also can use this so we know line numbers when there + // is an error. + // pre_cmd - code that sets up the buffer + // post - code that finalizes the buffer + EJS.Buffer = function( pre_cmd, post ) { + // the current line we are on + this.line = []; + // the combined content added to this buffer + this.script = []; + // content at the end of the buffer + this.post = post; + // add the pre commands to the first line + this.push.apply(this, pre_cmd); + }; + EJS.Buffer.prototype = { + // add content to this line + // need to maintain your own semi-colons (for performance) + push: function() { + this.line.push.apply(this.line, arguments); + }, + // starts a new line + cr: function() { + this.script.push(this.line.join(''), "\n"); + this.line = []; + }, + //returns the script too + close: function() { + // if we have ending line content, add it to the script + if ( this.line.length > 0 ) { + this.script.push(this.line.join('')); + this.line = []; + } + // if we have ending content, add it + this.post.length && this.push.apply(this, this.post); + // always end in a ; + this.script.push(";"); + return this.script.join(""); + } + + }; + + /** + * @class jQuery.EJS.Helpers + * @parent jQuery.EJS + * By adding functions to jQuery.EJS.Helpers.prototype, those functions will be available in the + * views. + * + * The following helper converts a given string to upper case: + * + * $.EJS.Helpers.prototype.toUpper = function(params) + * { + * return params.toUpperCase(); + * } + * + * Use it like this in any EJS template: + * + * <%= toUpper('javascriptmvc') %> + * + * To access the current DOM element return a function that takes the element as a parameter: + * + * $.EJS.Helpers.prototype.upperHtml = function(params) + * { + * return function(el) { + * $(el).html(params.toUpperCase()); + * } + * } + * + * In your EJS view you can then call the helper on an element tag: + * + *
            <%= upperHtml('javascriptmvc') %>>
            + * + * + * @constructor Creates a view helper. This function + * is called internally. You should never call it. + * @param {Object} data The data passed to the + * view. Helpers have access to it through this._data + */ + EJS.Helpers = function( data, extras ) { + this._data = data; + this._extras = extras; + extend(this, extras); + }; + /** + * @prototype + */ + EJS.Helpers.prototype = { + /** + * Hooks up a jQuery plugin on. + * @param {String} name the plugin name + */ + plugin: function( name ) { + var args = $.makeArray(arguments), + widget = args.shift(); + return function( el ) { + var jq = $(el); + jq[widget].apply(jq, args); + }; + }, + /** + * Renders a partial view. This is deprecated in favor of $.View(). + */ + view: function( url, data, helpers ) { + helpers = helpers || this._extras; + data = data || this._data; + return $View(url, data, helpers); //new EJS(options).render(data, helpers); + } + }; + + // options for steal's build + $View.register({ + suffix: "ejs", + //returns a function that renders the view + script: function( id, src ) { + return "jQuery.EJS(function(_CONTEXT,_VIEW) { " + new EJS({ + text: src, + name: id + }).template.out + " })"; + }, + renderer: function( id, text ) { + return EJS({ + text: text, + name: id + }); + } + }); }); \ No newline at end of file diff --git a/view/ejs/ejs_test.js b/view/ejs/ejs_test.js index 0b7c98ca..195625d1 100644 --- a/view/ejs/ejs_test.js +++ b/view/ejs/ejs_test.js @@ -11,12 +11,12 @@ module("jquery/view/ejs, rendering",{ } } - this.squareBrackets = "
              <% this.animals.each(function(animal){%>" + - "
            • <%= animal %>
            • " + - "<%});%>
            " - this.squareBracketsNoThis = "
              <% animals.each(function(animal){ %>" + - "
            • <%= animal %>
            • " + - "<%});%>
            " + this.squareBrackets = "
              [% this.animals.each(function(animal){%]" + + "
            • [%= animal %]
            • " + + "[%});%]
            " + this.squareBracketsNoThis = "
              [% animals.each(function(animal){%]" + + "
            • [%= animal %]
            • " + + "[%});%]
            " this.angleBracketsNoThis = "
              <% animals.each(function(animal){%>" + "
            • <%= animal %>
            • " + "<%});%>
            "; @@ -24,11 +24,11 @@ module("jquery/view/ejs, rendering",{ } }) test("render with left bracket", function(){ - var compiled = new $.EJS({text: this.squareBrackets}).render({animals: this.animals}) + var compiled = new $.EJS({text: this.squareBrackets, type: '['}).render({animals: this.animals}) equals(compiled, "
            • sloth
            • bear
            • monkey
            ", "renders with bracket") }) test("render with with", function(){ - var compiled = new $.EJS({text: this.squareBracketsNoThis}).render({animals: this.animals}) ; + var compiled = new $.EJS({text: this.squareBracketsNoThis, type: '['}).render({animals: this.animals}) ; equals(compiled, "
            • sloth
            • bear
            • monkey
            ", "renders bracket with no this") }) test("default carrot", function(){ @@ -74,7 +74,17 @@ test("escapedContent", function(){ equals(div.find('label').html(), "&" ); }) - +test("unescapedContent", function(){ + var text = "<%== tags %>
            <%= tags %>
            "; + var compiled = new $.EJS({text: text}).render({tags: "foobar", + quotes : "I use 'quote' fingers "a lot""}) ; + + var div = $('
            ').html(compiled) + equals(div.find('span').text(), "foobar" ); + equals(div.find('div').text().toLowerCase(), "foobar" ); + equals(div.find('span').html().toLowerCase(), "foobar" ); + equals(div.find('input').val(), "I use 'quote' fingers \"a lot\"" ); +}); test("returning blocks", function(){ var somethingHelper = function(cb){ @@ -84,8 +94,8 @@ test("returning blocks", function(){ var res = $.View("//jquery/view/ejs/test_template.ejs",{something: somethingHelper, items: ['a','b']}); // make sure expected values are in res - //ok(/\s4\s/.test(res), "first block called" ); - //equals(res.match(/ItemsLength4/g).length, 4, "innerBlock and each") + ok(/\s4\s/.test(res), "first block called" ); + equals(res.match(/ItemsLength4/g).length, 4, "innerBlock and each") }); test("easy hookup", function(){ diff --git a/view/test/qunit/qunit.js b/view/test/qunit/qunit.js index 84f2493f..a0d926a1 100644 --- a/view/test/qunit/qunit.js +++ b/view/test/qunit/qunit.js @@ -1,8 +1,8 @@ //we probably have to have this only describing where the tests are -steal("jquery/view", - "can/view/micro", - "can/view/ejs", - "can/view/jaml", - "can/view/tmpl") //load your app +steal("jquery/view","jquery/view/micro","jquery/view/ejs/ejs_test.js","jquery/view/jaml","jquery/view/tmpl") //load your app .then('funcunit/qunit') //load qunit - .then("./view_test.js"); \ No newline at end of file + .then("./view_test.js","jquery/view/tmpl/tmpl_test.js") + +if(steal.isRhino){ + steal('funcunit/qunit/rhino') +} \ No newline at end of file diff --git a/view/test/qunit/view_test.js b/view/test/qunit/view_test.js index 92de92f3..ef917d84 100644 --- a/view/test/qunit/view_test.js +++ b/view/test/qunit/view_test.js @@ -1,7 +1,19 @@ 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("

            hi

            "), 0, "renders stuff!") + }); + + equals(++order,2, "called synchronously"); +}) test("multiple template types work", function(){ @@ -15,8 +27,17 @@ test("multiple template types work", function(){ ok( /helloworld\s*/.test( $("#qunit-test-area").text()), this+": hello world present for ") }) }) - - +test("plugin in ejs", function(){ + $("#qunit-test-area").html(""); + $("#qunit-test-area").html("//jquery/view/test/qunit/plugin.ejs",{}) + ok(/something/.test( $("#something").text()),"something has something"); + $("#qunit-test-area").html(""); +}) +test("nested plugins", function(){ + $("#qunit-test-area").html(""); + $("#qunit-test-area").html("//jquery/view/test/qunit/nested_plugin.ejs",{}) + ok(/something/.test( $("#something").text()),"something has something"); +}) test("async templates, and caching work", function(){ $("#qunit-test-area").html(""); @@ -81,7 +102,7 @@ test("object of deferreds", function(){ foo : foo.promise(), bar : bar }).then(function(result){ - ok(result, "FOO and BAR"); + equals(result, "FOO and BAR"); start(); }); setTimeout(function(){ @@ -95,7 +116,7 @@ test("deferred", function(){ var foo = $.Deferred(); stop(); $.View("//jquery/view/test/qunit/deferred.ejs",foo).then(function(result){ - ok(result, "FOO"); + equals(result, "FOO"); start(); }); setTimeout(function(){ diff --git a/view/view.js b/view/view.js index e748ab4b..069e8899 100644 --- a/view/view.js +++ b/view/view.js @@ -1,3 +1,880 @@ -steal("can/view/modifiers",function(){ - $.View = can.view; -}); +steal("jquery").then(function( $ ) { + + // a path like string into something that's ok for an element ID + var toId = function( src ) { + return src.replace(/^\/\//, "").replace(/[\/\.]/g, "_"); + }, + makeArray = $.makeArray, + // used for hookup ids + id = 1; + // this might be useful for testing if html + // htmlTest = /^[\s\n\r\xA0]*<(.|[\r\n])*>[\s\n\r\xA0]*$/ + /** + * @class jQuery.View + * @parent jquerymx + * @plugin jquery/view + * @test jquery/view/qunit.html + * @download dist/jquery.view.js + * + * @description A JavaScript template framework. + * + * View provides a uniform interface for using templates with + * jQuery. When template engines [jQuery.View.register register] + * themselves, you are able to: + * + * - 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.replaceWith replaceWith], [jQuery.fn.text text]. + * - Template loading from html elements and external files. + * - Synchronous and asynchronous template loading. + * - [view.deferreds Deferred Rendering]. + * - Template caching. + * - Bundling of processed templates in production builds. + * - Hookup jquery plugins directly in the template. + * + * The [mvc.view Get Started with jQueryMX] has a good walkthrough of $.View. + * + * ## Use + * + * + * 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: + * + * $("#foo").html('mytemplate.ejs',{message: 'hello world'}) + * + * This code: + * + * - Loads the template a 'mytemplate.ejs'. It might look like: + *
            <h2><%= message %></h2>
            + * + * - Renders it with {message: 'hello world'}, resulting in: + *
            <div id='foo'>"<h2>hello world</h2></div>
            + * + * - Inserts the result into the foo element. Foo might look like: + *
            <div id='foo'><h2>hello world</h2></div>
            + * + * ## jQuery Modifiers + * + * You can use a template with the following jQuery modifiers: + * + * + * + * + * + * + * + * + * + *
            [jQuery.fn.after after] $('#bar').after('temp.jaml',{});
            [jQuery.fn.append append] $('#bar').append('temp.jaml',{});
            [jQuery.fn.before before] $('#bar').before('temp.jaml',{});
            [jQuery.fn.html html] $('#bar').html('temp.jaml',{});
            [jQuery.fn.prepend prepend] $('#bar').prepend('temp.jaml',{});
            [jQuery.fn.replaceWith replaceWith] $('#bar').replaceWith('temp.jaml',{});
            [jQuery.fn.text text] $('#bar').text('temp.jaml',{});
            + * + * You always have to pass a string and an object (or function) for the jQuery modifier + * to user a template. + * + * ## Template Locations + * + * View can load from script tags or from files. + * + * ## From Script Tags + * + * To load from a script tag, create a script tag with your template and an id like: + * + *
            <script type='text/ejs' id='recipes'>
            +	 * <% for(var i=0; i < recipes.length; i++){ %>
            +	 *   <li><%=recipes[i].name %></li>
            +	 * <%} %>
            +	 * </script>
            + * + * Render with this template like: + * + * @codestart + * $("#foo").html('recipes',recipeData) + * @codeend + * + * Notice we passed the id of the element we want to render. + * + * ## From File + * + * You can pass the path of a template file location like: + * + * $("#foo").html('templates/recipes.ejs',recipeData) + * + * However, you typically want to make the template work from whatever page they + * are called from. To do this, use // to look up templates from JMVC root: + * + * $("#foo").html('//app/views/recipes.ejs',recipeData) + * + * Finally, the [jQuery.Controller.prototype.view controller/view] plugin can make looking + * up a thread (and adding helpers) even easier: + * + * $("#foo").html( this.view('recipes', recipeData) ) + * + * ## Packaging Templates + * + * If you're making heavy use of templates, you want to organize + * them in files so they can be reused between pages and applications. + * + * 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. + * + * Fortunately, [steal.static.views steal.views] can build templates + * into your production files. You just have to point to the view file like: + * + * steal.views('path/to/the/view.ejs'); + * + * ## Asynchronous + * + * By default, retrieving requests is done synchronously. This is + * fine because StealJS packages view templates with your JS download. + * + * 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: + * + * $("#foo").html('recipes',recipeData, function(result){ + * this.fadeIn() + * }); + * + * 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 + * template without inserting it, you can do this with $.View: + * + * var out = $.View('path/to/template.jaml',{}); + * + * ## Preloading Templates + * + * You can preload templates asynchronously like: + * + * $.get('path/to/template.jaml',{},function(){},'view'); + * + * ## Supported Template Engines + * + * JavaScriptMVC comes with the following template languages: + * + * - EmbeddedJS + *
            <h2><%= message %></h2>
            + * + * - JAML + *
            h2(data.message);
            + * + * - Micro + *
            <h2>{%= message %}</h2>
            + * + * - jQuery.Tmpl + *
            <h2>${message}</h2>
            + + * + * The popular Mustache + * template engine is supported in a 2nd party plugin. + * + * ## Using other Template Engines + * + * It's easy to integrate your favorite template into $.View and Steal. Read + * how in [jQuery.View.register]. + * + * @constructor + * + * Looks up a template, processes it, caches it, then renders the template + * with data and optional helpers. + * + * With [stealjs StealJS], views are typically bundled in the production build. + * This makes it ok to use views synchronously like: + * + * @codestart + * $.View("//myplugin/views/init.ejs",{message: "Hello World"}) + * @codeend + * + * If you aren't using StealJS, it's best to use views asynchronously like: + * + * @codestart + * $.View("//myplugin/views/init.ejs", + * {message: "Hello World"}, function(result){ + * // do something with result + * }) + * @codeend + * + * @param {String} view The url or id of an element to use as the template's source. + * @param {Object} data The data to be passed to the view. + * @param {Object} [helpers] Optional helper functions the view might use. Not all + * templates support helpers. + * @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 or if deferreds + * are passed, a deferred that will resolve to + * the rendered result of the view. + */ + var $view = $.View = function( view, data, helpers, callback ) { + // if helpers is a function, it is actually a callback + if ( typeof helpers === 'function' ) { + callback = helpers; + helpers = undefined; + } + + // 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 ) { + // get all the resolved deferreds + var objs = makeArray(arguments), + // renderer is last [0] is the data + renderer = objs.pop()[0], + // the result of the template rendering with data + result; + + // make data look like the resolved deferreds + if ( isDeferred(data) ) { + data = usefulPart(resolved); + } + else { + // go through each prop in data again, + // replace the defferreds with what they resolved to + for ( var prop in data ) { + if ( isDeferred(data[prop]) ) { + data[prop] = usefulPart(objs.shift()); + } + } + } + // get the rendered result + result = renderer(data, helpers); + + //resolve with the rendered view + deferred.resolve(result); + // if there's a callback, call it back with the result + callback && callback(result); + }); + // return the deferred .... + return deferred.promise(); + } + else { + // no deferreds, render this bad boy + var response, + // if there's a callback function + async = typeof callback === "function", + // get the 'view' type + deferred = get(view, async); + + // if we are async, + if ( async ) { + // return the deferred + response = deferred; + // and callback callback with the rendered result + deferred.done(function( renderer ) { + callback(renderer(data, helpers)) + }) + } else { + // otherwise, the deferred is complete, so + // set response to the result of the rendering + deferred.done(function( renderer ) { + response = renderer(data, helpers); + }); + } + + return response; + } + }, + // makes sure there's a template, if not, has steal provide a warning + 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; + } + }, + // returns a 'view' renderer deferred + // url - the url to the view template + // async - if the ajax request should be synchronous + get = function( url, async ) { + return $.ajax({ + url: url, + dataType: "view", + async: async + }); + }, + // returns true if something looks like a deferred + 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 resolve to array (with success and such) + // returns the useful, content part + usefulPart = function( resolved ) { + return $.isArray(resolved) && resolved.length === 3 && resolved[1] === 'success' ? resolved[0] : resolved + }; + + + + // you can request a view renderer (a function you pass data to and get html) + // Creates a 'view' transport. These resolve to a 'view' renderer + // a 'view' renderer takes data and returns a string result. + // For example: + // + // $.ajax({dataType : 'view', src: 'foo.ejs'}).then(function(renderer){ + // renderer({message: 'hello world'}) + // }) + $.ajaxTransport("view", function( options, orig ) { + // the url (or possibly id) of the view content + var url = orig.url, + // check if a suffix exists (ex: "foo.ejs") + suffix = url.match(/\.[\w\d]+$/), + type, + // if we are reading a script element for the content of the template + // el will be set to that script element + el, + // a unique identifier for the view (used for caching) + // this is typically derived from the element id or + // the url for the template + id, + // the AJAX request used to retrieve the template content + jqXHR, + // used to generate the response + response = function( text ) { + // get the renderer function + var func = type.renderer(id, text); + // cache if if we are caching + if ( $view.cache ) { + $view.cached[id] = func; + } + // return the objects for the response's dataTypes + // (in this case view) + return { + view: func + }; + }; + + // if we have an inline template, derive the suffix from the 'text/???' part + // this only supports '' tags + if ( el = document.getElementById(url) ) { + suffix = "."+el.type.match(/\/(x\-)?(.+)/)[2]; + } + + // 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 + // you should only be using // if you are using steal + if ( url.match(/^\/\//) ) { + var sub = url.substr(2); + url = typeof steal === "undefined" ? + url = "/" + sub : + steal.root.mapJoin(sub) +''; + } + + //set the template engine type + type = $view.types[suffix]; + + // return the ajax transport contract: http://api.jquery.com/extending-ajax/ + return { + send: function( headers, callback ) { + // if it is cached, + if ( $view.cached[id] ) { + // return the catched renderer + return callback(200, "success", { + view: $view.cached[id] + }); + + // otherwise if we are getting this from a script elment + } else if ( el ) { + // resolve immediately with the element's innerHTML + callback(200, "success", response(el.innerHTML)); + } else { + // make an ajax request for text + jqXHR = $.ajax({ + async: orig.async, + url: url, + dataType: "text", + error: function() { + checkText("", url); + callback(404); + }, + success: function( text ) { + // make sure we got some text back + checkText(text, url); + // cache and send back text + callback(200, "success", response(text)) + } + }); + } + }, + abort: function() { + jqXHR && jqXHR.abort(); + } + } + }) + $.extend($view, { + /** + * @attribute hookups + * @hide + * A list of pending 'hookups' + */ + hookups: {}, + /** + * @function hookup + * 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 = "
            " + * $('.foo').html(html); + * + * + * @param {Function} cb a callback function to be called with the element + * @param {Number} the hookup number + */ + hookup: function( cb ) { + var myid = ++id; + $view.hookups[myid] = cb; + return myid; + }, + /** + * @attribute cached + * @hide + * Cached are put in this object + */ + cached: {}, + /** + * @attribute cache + * Should the views be cached or reloaded from the server. Defaults to true. + */ + cache: true, + /** + * @function register + * Registers a template engine to be used with + * view helpers and compression. + * + * ## Example + * + * @codestart + * $.View.register({ + * suffix : "tmpl", + * plugin : "jquery/view/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); }"; + * } + * }) + * @codeend + * Here's what each property does: + * + * * plugin - the location of the plugin + * * suffix - files that use this suffix will be processed by this template engine + * * renderer - returns a function that will render the template provided by text + * * script - returns a string form of the processed template function. + * + * @param {Object} info a object of method and properties + * + * that enable template integration: + *
              + *
            • plugin - the location of the plugin. EX: 'jquery/view/ejs'
            • + *
            • suffix - the view extension. EX: 'ejs'
            • + *
            • script(id, src) - a function that returns a string that when evaluated returns a function that can be + * used as the render (i.e. have func.call(data, data, helpers) called on it).
            • + *
            • renderer(id, text) - a function that takes the id of the template and the text of the template and + * returns a render function.
            • + *
            + */ + register: function( info ) { + this.types["." + info.suffix] = info; + + if ( window.steal ) { + steal.type(info.suffix + " view js", function( options, success, error ) { + var type = $view.types["." + options.type], + id = toId(options.rootSrc+''); + + options.text = type.script(id, options.text) + success(); + }) + } + }, + types: {}, + /** + * @attribute ext + * The default suffix to use if none is provided in the view's url. + * This is set to .ejs by default. + */ + ext: ".ejs", + /** + * Returns the text that + * @hide + * @param {Object} type + * @param {Object} id + * @param {Object} src + */ + registerScript: function( type, id, src ) { + return "$.View.preload('" + id + "'," + $view.types["." + type].script(id, src) + ");"; + }, + /** + * @hide + * Called by a production script to pre-load a renderer function + * into the view cache. + * @param {String} id + * @param {Function} renderer + */ + preload: function( id, renderer ) { + $view.cached[id] = function( data, helpers ) { + return renderer.call(data, data, helpers); + }; + } + + }); + if ( window.steal ) { + steal.type("view js", function( options, success, error ) { + var type = $view.types["." + options.type], + id = toId(options.rootSrc+''); + + options.text = "steal('" + (type.plugin || "jquery/view/" + options.type) + "').then(function($){" + "$.View.preload('" + id + "'," + options.text + ");\n})"; + success(); + }) + } + + //---- ADD jQUERY HELPERS ----- + //converts jquery functions to use views + var convert, modify, isTemplate, isHTML, isDOM, getCallback, hookupView, funcs, + // text and val cannot produce an element, so don't run hookups on them + noHookup = {'val':true,'text':true}; + + convert = function( func_name ) { + // save the old jQuery helper + var old = $.fn[func_name]; + + // replace it wiht our new helper + $.fn[func_name] = function() { + + var args = makeArray(arguments), + callbackNum, + callback, + self = this, + result; + + // if the first arg is a deferred + // wait until it finishes, and call + // modify with the result + if ( isDeferred(args[0]) ) { + args[0].done(function( res ) { + modify.call(self, [res], old); + }) + return this; + } + //check if a template + else if ( isTemplate(args) ) { + + // if we should operate async + if ((callbackNum = getCallback(args))) { + callback = args[callbackNum]; + args[callbackNum] = function( result ) { + modify.call(self, [result], old); + callback.call(self, result); + }; + $view.apply($view, args); + return this; + } + // call view with args (there might be deferreds) + result = $view.apply($view, args); + + // if we got a string back + if (!isDeferred(result) ) { + // we are going to call the old method with that string + args = [result]; + } else { + // if there is a deferred, wait until it is done before calling modify + result.done(function( res ) { + modify.call(self, [res], old); + }) + return this; + } + } + return noHookup[func_name] ? old.apply(this,args) : + modify.call(this, args, old); + }; + }; + + // modifies the content of the element + // but also will run any hookup + modify = function( args, old ) { + var res, stub, hooks; + + //check if there are new hookups + for ( var hasHookups in $view.hookups ) { + break; + } + + //if there are hookups, get jQuery object + if ( hasHookups && args[0] && isHTML(args[0]) ) { + hooks = $view.hookups; + $view.hookups = {}; + args[0] = $(args[0]); + } + res = old.apply(this, args); + + //now hookup the hookups + if ( hooks + /* && args.length*/ + ) { + hookupView(args[0], hooks); + } + return res; + }; + + // returns true or false if the args indicate a template is being used + // $('#foo').html('/path/to/template.ejs',{data}) + // in general, we want to make sure the first arg is a string + // and the second arg is data + isTemplate = function( args ) { + // save the second arg type + var secArgType = typeof args[1]; + + // the first arg is a string + return typeof args[0] == "string" && + // the second arg is an object or function + (secArgType == 'object' || secArgType == 'function') && + // but it is not a dom element + !isDOM(args[1]); + }; + // returns true if the arg is a jQuery object or HTMLElement + isDOM = function(arg){ + return arg.nodeType || arg.jquery + }; + // returns whether the argument is some sort of HTML data + isHTML = function( arg ) { + if ( isDOM(arg) ) { + // if jQuery object or DOM node we're good + return true; + } else if ( typeof arg === "string" ) { + // if string, do a quick sanity check that we're HTML + arg = $.trim(arg); + return arg.substr(0, 1) === "<" && arg.substr(arg.length - 1, 1) === ">" && arg.length >= 3; + } else { + // don't know what you are + return false; + } + }; + + //returns the callback arg number if there is one (for async view use) + getCallback = function( args ) { + return typeof args[3] === 'function' ? 3 : typeof args[2] === 'function' && 2; + }; + + hookupView = function( els, hooks ) { + //remove all hookups + var hookupEls, len, i = 0, + id, func; + els = els.filter(function() { + return this.nodeType != 3; //filter out text nodes + }) + hookupEls = els.add("[data-view-id]", els); + len = hookupEls.length; + for (; i < len; i++ ) { + if ( hookupEls[i].getAttribute && (id = hookupEls[i].getAttribute('data-view-id')) && (func = hooks[id]) ) { + func(hookupEls[i], id); + delete hooks[id]; + hookupEls[i].removeAttribute('data-view-id'); + } + } + //copy remaining hooks back + $.extend($view.hookups, hooks); + }; + + /** + * @add jQuery.fn + * @parent jQuery.View + * Called on a jQuery collection that was rendered with $.View with pending hookups. $.View can render a + * template with hookups, but not actually perform the hookup, because it returns a string without actual DOM + * elements to hook up to. So hookup performs the hookup and clears the pending hookups, preventing errors in + * future templates. + * + * @codestart + * $($.View('//views/recipes.ejs',recipeData)).hookup() + * @codeend + */ + $.fn.hookup = function() { + var hooks = $view.hookups; + $view.hookups = {}; + hookupView(this, hooks); + return this; + }; + + /** + * @add jQuery.fn + */ + $.each([ + /** + * @function prepend + * @parent jQuery.View + * + * Extending the original [http://api.jquery.com/prepend/ jQuery().prepend()] + * to render [jQuery.View] templates inserted at the beginning of each element in the set of matched elements. + * + * $('#test').prepend('path/to/template.ejs', { name : 'javascriptmvc' }); + * + * @param {String|Object|Function} content A template filename or the id of a view script tag + * or a DOM element, array of elements, HTML string, or jQuery object. + * @param {Object} [data] The data to render the view with. + * If rendering a view template this parameter always has to be present + * (use the empty object initializer {} for no data). + */ + "prepend", + /** + * @function append + * @parent jQuery.View + * + * Extending the original [http://api.jquery.com/append/ jQuery().append()] + * to render [jQuery.View] templates inserted at the end of each element in the set of matched elements. + * + * $('#test').append('path/to/template.ejs', { name : 'javascriptmvc' }); + * + * @param {String|Object|Function} content A template filename or the id of a view script tag + * or a DOM element, array of elements, HTML string, or jQuery object. + * @param {Object} [data] The data to render the view with. + * If rendering a view template this parameter always has to be present + * (use the empty object initializer {} for no data). + */ + "append", + /** + * @function after + * @parent jQuery.View + * + * Extending the original [http://api.jquery.com/after/ jQuery().after()] + * to render [jQuery.View] templates inserted after each element in the set of matched elements. + * + * $('#test').after('path/to/template.ejs', { name : 'javascriptmvc' }); + * + * @param {String|Object|Function} content A template filename or the id of a view script tag + * or a DOM element, array of elements, HTML string, or jQuery object. + * @param {Object} [data] The data to render the view with. + * If rendering a view template this parameter always has to be present + * (use the empty object initializer {} for no data). + */ + "after", + /** + * @function before + * @parent jQuery.View + * + * Extending the original [http://api.jquery.com/before/ jQuery().before()] + * to render [jQuery.View] templates inserted before each element in the set of matched elements. + * + * $('#test').before('path/to/template.ejs', { name : 'javascriptmvc' }); + * + * @param {String|Object|Function} content A template filename or the id of a view script tag + * or a DOM element, array of elements, HTML string, or jQuery object. + * @param {Object} [data] The data to render the view with. + * If rendering a view template this parameter always has to be present + * (use the empty object initializer {} for no data). + */ + "before", + /** + * @function text + * @parent jQuery.View + * + * Extending the original [http://api.jquery.com/text/ jQuery().text()] + * to render [jQuery.View] templates as the content of each matched element. + * Unlike [jQuery.fn.html] jQuery.fn.text also works with XML, escaping the provided + * string as necessary. + * + * $('#test').text('path/to/template.ejs', { name : 'javascriptmvc' }); + * + * @param {String|Object|Function} content A template filename or the id of a view script tag + * or a DOM element, array of elements, HTML string, or jQuery object. + * @param {Object} [data] The data to render the view with. + * If rendering a view template this parameter always has to be present + * (use the empty object initializer {} for no data). + */ + "text", + /** + * @function html + * @parent jQuery.View + * + * Extending the original [http://api.jquery.com/html/ jQuery().html()] + * to render [jQuery.View] templates as the content of each matched element. + * + * $('#test').html('path/to/template.ejs', { name : 'javascriptmvc' }); + * + * @param {String|Object|Function} content A template filename or the id of a view script tag + * or a DOM element, array of elements, HTML string, or jQuery object. + * @param {Object} [data] The data to render the view with. + * If rendering a view template this parameter always has to be present + * (use the empty object initializer {} for no data). + */ + "html", + /** + * @function replaceWith + * @parent jQuery.View + * + * Extending the original [http://api.jquery.com/replaceWith/ jQuery().replaceWith()] + * to render [jQuery.View] templates replacing each element in the set of matched elements. + * + * $('#test').replaceWith('path/to/template.ejs', { name : 'javascriptmvc' }); + * + * @param {String|Object|Function} content A template filename or the id of a view script tag + * or a DOM element, array of elements, HTML string, or jQuery object. + * @param {Object} [data] The data to render the view with. + * If rendering a view template this parameter always has to be present + * (use the empty object initializer {} for no data). + */ + "replaceWith", "val"],function(i, func){ + convert(func); + }); + + //go through helper funcs and convert + + +}); \ No newline at end of file From fd3167c78423d5afa9c7a8feeefb1e7cf3b71487 Mon Sep 17 00:00:00 2001 From: David Luecke Date: Mon, 30 Apr 2012 14:23:44 -0600 Subject: [PATCH 100/103] Fixing test to work on every day of the month --- model/test/qunit/model_test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/model/test/qunit/model_test.js b/model/test/qunit/model_test.js index a9077d93..0719d4e3 100644 --- a/model/test/qunit/model_test.js +++ b/model/test/qunit/model_test.js @@ -422,7 +422,8 @@ test("converters and serializes", function(){ } },{}); var d = new Date(); - d.setMonth(1) + d.setDate(1); + d.setMonth(1); var task1=new Task1({ createdAt: d, name:"Task1" From d756b44b9d00dffc70692fca0779c519c0104281 Mon Sep 17 00:00:00 2001 From: Sebastian Schleicher Date: Wed, 26 Sep 2012 13:40:32 +0200 Subject: [PATCH 101/103] fixed exception if steal is not used --- dom/fixture/fixture.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/dom/fixture/fixture.js b/dom/fixture/fixture.js index d2355033..672b5a7b 100644 --- a/dom/fixture/fixture.js +++ b/dom/fixture/fixture.js @@ -35,7 +35,10 @@ steal('jquery/dom', var url = settings.fixture; if (/^\/\//.test(url) ) { - url = steal.root.mapJoin(settings.fixture.substr(2))+''; + var sub = settings.fixture.substr(2) + ''; + url = typeof steal === "undefined" ? + url = "/" + sub : + steal.root.mapJoin(sub) +''; } //!steal-remove-start steal.dev.log("looking for fixture in " + url); From 03897328ddc9bd46bdc2cd3848f67a406a82454e Mon Sep 17 00:00:00 2001 From: Bradley Momberger/Karen Morand Date: Thu, 27 Sep 2012 15:37:40 -0400 Subject: [PATCH 102/103] Fixed tap so that quick horizontal/vertical swipes did not cause tap events --- event/tap/tap.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/event/tap/tap.js b/event/tap/tap.js index 91228123..356cfd30 100644 --- a/event/tap/tap.js +++ b/event/tap/tap.js @@ -32,7 +32,7 @@ $.event.setupHelper( ["tap"], touchStartEvent, function(ev){ function upHandler(event){ stop = data(event); - if ((Math.abs( start.coords[0] - stop.coords[0] ) < 10) || + if ((Math.abs( start.coords[0] - stop.coords[0] ) < 10) && ( Math.abs( start.coords[1] - stop.coords[1] ) < 10) ){ $.each($.event.find(delegate, ["tap"], selector), function(){ this.call(entered, ev, {start : start, end: stop}) From 473930190a7bc1a6a208e6063d0c045c55386bf6 Mon Sep 17 00:00:00 2001 From: David Luecke Date: Wed, 10 Oct 2012 13:38:44 -0600 Subject: [PATCH 103/103] Fixing @ sourceUrl for IE --- view/ejs/ejs.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/view/ejs/ejs.js b/view/ejs/ejs.js index 5805f71d..e5132159 100644 --- a/view/ejs/ejs.js +++ b/view/ejs/ejs.js @@ -528,7 +528,7 @@ steal('jquery/view', 'jquery/lang/string/rsplit').then(function( $ ) { out: 'try { with(_VIEW) { with (_CONTEXT) {' + template + " "+finishTxt+"}}}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.fn = (function(_CONTEXT,_VIEW){' + out.out + '});\r\n//@ sourceURL=' + name + ".js"); + myEval.call(out, 'this.fn = (function(_CONTEXT,_VIEW){' + out.out + '});\r\n//@ sourceURL="' + name + '.js"'); return out; };