From 692233ef766ed54e321e2b99fcd8d0b345e1762b Mon Sep 17 00:00:00 2001 From: David Regla Date: Sun, 18 Dec 2011 14:38:38 -0600 Subject: [PATCH 01/26] 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 8a7ffcb6354d8230de9a3e38aaf3462499766406 Mon Sep 17 00:00:00 2001 From: David Regla Date: Mon, 30 Jan 2012 15:28:16 -0600 Subject: [PATCH 02/26] merge --- dom/route/route.js | 471 --------------------------------------------- 1 file changed, 471 deletions(-) delete mode 100644 dom/route/route.js diff --git a/dom/route/route.js b/dom/route/route.js deleted file mode 100644 index 4159adec..00000000 --- a/dom/route/route.js +++ /dev/null @@ -1,471 +0,0 @@ -steal('jquery/lang/observe', 'jquery/event/hashchange', 'jquery/lang/string/deparam', -function( $ ) { - - // 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 From df1bcabbdfc80f5caa571a058e5e0a05b74c734e Mon Sep 17 00:00:00 2001 From: David Regla Date: Mon, 30 Jan 2012 15:35:19 -0600 Subject: [PATCH 03/26] merge --- dom/route/route.js | 471 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 471 insertions(+) create mode 100644 dom/route/route.js diff --git a/dom/route/route.js b/dom/route/route.js new file mode 100644 index 00000000..4159adec --- /dev/null +++ b/dom/route/route.js @@ -0,0 +1,471 @@ +steal('jquery/lang/observe', 'jquery/event/hashchange', 'jquery/lang/string/deparam', +function( $ ) { + + // 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 From 8e3987e0fe7941d530c83a207f7c22e1ac475083 Mon Sep 17 00:00:00 2001 From: Frederick Polgardy Date: Wed, 8 Feb 2012 08:10:57 -0600 Subject: [PATCH 04/26] Fixed $.Class function to return class definition (from @fmarsoni) --- class/class.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/class/class.js b/class/class.js index 18854c74..8df6f95d 100644 --- a/class/class.js +++ b/class/class.js @@ -349,7 +349,7 @@ steal("jquery","jquery/lang/string",function( $ ) { clss = $.Class = function() { if (arguments.length) { - clss.extend.apply(clss, arguments); + return clss.extend.apply(clss, arguments); } }; From 5b30e30f6a47a7f6e219b5afc3bf4d2b3f6f83dc Mon Sep 17 00:00:00 2001 From: TQ White II/qbook Date: Wed, 15 Feb 2012 10:33:48 -0600 Subject: [PATCH 05/26] form_params() was incorrectly returning scalar form values as arrays, now it only returns array for checkbox --- dom/form_params/form_params.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dom/form_params/form_params.js b/dom/form_params/form_params.js index 69f56ac2..bbf78325 100644 --- a/dom/form_params/form_params.js +++ b/dom/form_params/form_params.js @@ -145,7 +145,7 @@ steal("jquery/dom").then(function( $ ) { //now we are on the last part, set the value if (current[lastPart]) { - if (!$.isArray(current[lastPart]) ) { + if (!$.isArray(current[lastPart] && type === "checkbox") ) { current[lastPart] = current[lastPart] === undefined ? [] : [current[lastPart]]; } if ( write ) { From 766abeb9f60abef2b9b09c5e740b3d84b06250b4 Mon Sep 17 00:00:00 2001 From: Ralph Holzmann Date: Wed, 15 Feb 2012 13:19:00 -0600 Subject: [PATCH 06/26] Fixed bug where last checkbox of checkboxes with multiple names would return a scalar instead of an array. Tests attached, 100% passing. --- dom/form_params/form_params.js | 137 +++++++++++++++------------- dom/form_params/form_params_test.js | 7 +- dom/form_params/test/basics.micro | 10 ++ 3 files changed, 92 insertions(+), 62 deletions(-) diff --git a/dom/form_params/form_params.js b/dom/form_params/form_params.js index bbf78325..7e7a66cd 100644 --- a/dom/form_params/form_params.js +++ b/dom/form_params/form_params.js @@ -2,22 +2,58 @@ * @add jQuery.fn */ steal("jquery/dom").then(function( $ ) { - var radioCheck = /radio|checkbox/i, - keyBreaker = /[^\[\]]+/g, - numberMatcher = /^[\-+]?[0-9]*\.?[0-9]+([eE][\-+]?[0-9]+)?$/; + var keyBreaker = /[^\[\]]+/g, + convertValue = function( value ) { + if ( $.isNumeric( value )) { + return parseFloat( value ); + } else if ( value === 'true') { + return true; + } else if ( value === 'false' ) { + return false; + } else if ( value === '' ) { + return undefined; + } + return value; + }, + nestData = function( elem, type, data, parts, value, seen ) { + var name = parts.shift(); + + if ( parts.length ) { + if ( ! data[ name ] ) { + data[ name ] = {}; + } + // Recursive call + nestData( elem, type, data[ name ], parts, value, seen ); + } else { + + // Handle same name case, as well as "last checkbox checked" + // case + if ( name in seen && type != "radio" && ! $.isArray( data[ name ] )) { + if ( name in data ) { + data[ name ] = [ data[name] ]; + } else { + data[ name ] = []; + } + } else { + seen[ name ] = true; + } - var isNumber = function( value ) { - if ( typeof value == 'number' ) { - return true; - } + // Finally, assign data + if ( ( type == "radio" || type == "checkbox" ) && ! elem.is(":checked") ) { + return + } - if ( typeof value != 'string' ) { - return false; - } + if ( ! data[ name ] ) { + data[ name ] = value; + } else { + data[ name ].push( value ); + } + - return value.match(numberMatcher); - }; + } + }; + $.fn.extend({ /** * @parent dom @@ -53,7 +89,9 @@ steal("jquery/dom").then(function( $ ) { * to the result. Defaults to false. * @return {Object} An object of name-value pairs. */ - formParams: function( params, convert ) { + formParams: function( params ) { + + var convert; // Quick way to determine if something is a boolean if ( !! params === params ) { @@ -63,10 +101,9 @@ steal("jquery/dom").then(function( $ ) { 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); + } else if ( this.is("form") ) { + return this.getParams( convert ); } - return jQuery("input[name], textarea[name], select[name]", this[0]).getParams(convert); }, setParams: function( params ) { @@ -100,63 +137,41 @@ steal("jquery/dom").then(function( $ ) { }, getParams: function( convert ) { var data = {}, + // This is used to keep track of the checkbox names that we've + // already seen, so we know that we should return an array if + // we see it multiple times. Fixes last checkbox checked bug. + seen = {}, current; - convert = convert === undefined ? false : convert; - this.each(function() { - var el = this, - type = el.type && el.type.toLowerCase(); - //if we are submit, ignore - if ((type == 'submit') || !el.name ) { + this.find("[name]").each(function() { + var $this = $(this), + type = $this.attr("type"), + name = $this.attr("name"), + value = $this.val(), + parts; + + // Don't accumulate submit buttons and nameless elements + if ( type == "submit" || ! name ) { return; } - var key = el.name, - value = $.data(el, "value") || $.fn.val.call([el]), - isRadioCheck = radioCheck.test(el.type), - parts = key.match(keyBreaker), - write = !isRadioCheck || !! el.checked, - //make an array of values - lastPart; - - if ( convert ) { - if ( isNumber(value) ) { - value = parseFloat(value); - } else if ( value === 'true') { - value = true; - } else if ( value === 'false' ) { - value = false; - } - if(value === '') { - value = undefined; - } + // Figure out name parts + parts = name.match( keyBreaker ); + if ( ! parts.length ) { + parts = [name]; } - // go through and create nested objects - current = data; - for ( var i = 0; i < parts.length - 1; i++ ) { - if (!current[parts[i]] ) { - current[parts[i]] = {}; - } - current = current[parts[i]]; + // Convert the value + if ( convert ) { + value = convertValue( value ); } - lastPart = parts[parts.length - 1]; - - //now we are on the last part, set the value - if (current[lastPart]) { - if (!$.isArray(current[lastPart] && type === "checkbox") ) { - current[lastPart] = current[lastPart] === undefined ? [] : [current[lastPart]]; - } - if ( write ) { - current[lastPart].push(value); - } - } else if ( write || !current[lastPart] ) { - current[lastPart] = write ? value : undefined; - } + // Assign data recursively + nestData( $this, type, data, parts, value, seen ); }); + return data; } }); diff --git a/dom/form_params/form_params_test.js b/dom/form_params/form_params_test.js index 93d50fd1..ff158c11 100644 --- a/dom/form_params/form_params_test.js +++ b/dom/form_params/form_params_test.js @@ -10,6 +10,7 @@ module("jquery/dom/form_params") test("with a form", function(){ $("#qunit-test-area").html("//jquery/dom/form_params/test/basics.micro",{}) + var formParams = $("#qunit-test-area form").formParams() ; ok(formParams.params.one === "1","one is right"); @@ -17,9 +18,13 @@ test("with a form", function(){ ok(formParams.params.three === "3","three is right"); same(formParams.params.four,["4","1"],"four is right"); same(formParams.params.five,["2","3"],"five is right"); - equal(typeof formParams.id , 'string', "Id value is empty"); + equal( typeof formParams.singleRadio, "string", "Type of single named radio is string" ); + equal( formParams.singleRadio, "2", "Value of single named radio is right" ); + + ok( $.isArray(formParams.lastOneChecked), "Type of checkbox with last option checked is array" ); + equal( formParams.lastOneChecked, "4", "Value of checkbox with the last option checked is 4" ); }); diff --git a/dom/form_params/test/basics.micro b/dom/form_params/test/basics.micro index dfc247ff..14e76957 100644 --- a/dom/form_params/test/basics.micro +++ b/dom/form_params/test/basics.micro @@ -11,6 +11,10 @@ + + + + + + + + + + From 228c7ab7207334818e0fb811d5bea11befbf5da0 Mon Sep 17 00:00:00 2001 From: Austin McDaniel Date: Wed, 15 Feb 2012 16:21:47 -0600 Subject: [PATCH 07/26] Cleanup for Model.Store Code cleanup and added comments to 'jquery/model/store', Enhanced 'jquery/model/store' to listen for 'add' event, Added 'removeSet' method to 'jquery/model/store', Added event triggers for 'findAll' and 'findOne' for 'jquery/model/store', Optimized 'for' itterators in 'jquery/model/store', Added 'remove' method and moved old remove method to 'destroyed' to match rest of names --- model/store/store.js | 425 +++++++++++++++++++++++++++++-------------- 1 file changed, 289 insertions(+), 136 deletions(-) diff --git a/model/store/store.js b/model/store/store.js index cfc4f078..ab73370a 100644 --- a/model/store/store.js +++ b/model/store/store.js @@ -1,30 +1,74 @@ -steal('jquery/model/list','jquery/lang/object',function($){ +steal('jquery/model/list','jquery/lang/object', function($){ -var same = $.Object.same; +var same = $.Object.same, + trigger = function(obj, event, args){ + $.event.trigger(event, args, obj, true) + }, + $method = function( name ) { + return function( eventType, handler ) { + return $.fn[name].apply($([this]), arguments); + } + }, + bind = $method('bind'), + unbind = $method('unbind'); $.Class('jQuery.Model.Store', { + id: "id", + bind: bind, + unbind: unbind, + compare : {}, + init : function(){ if(this.fullName === 'jQuery.Model.Store'){ return; } - /** - * which sets are represented in this store ... - */ + this.sets = []; this.data = {}; - // listen on create and add ... listen on destroy and remove - this.namespace.bind('destroyed', this.callback('remove')) - this.namespace.bind('updated', this.callback('updated')) + // listen on create and add ... listen on destroy and remove + this.namespace.bind('destroyed', this.proxy('destroyed')); + this.namespace.bind('updated', this.proxy('updated')); + this.namespace.bind("created", this.proxy('created')); }, - updated : function(ev, item){ + + /** + * Internal compare method. + * + * @param {Object} prop + * @param {Object} itemData + * @param {Object} paramData + */ + _compare : function(prop, itemData, paramData){ + return same(itemData, paramData, this.compare[prop]); + }, + + /** + * Creates an item in the sets. Triggered from a model + * event indicating an item was created. + * + * @param {Object} event + * @param {Object} item + */ + created: function(ev,item){ + this.add([item]); + }, + + /** + * Updates an item in the sets. Triggered from a model + * event indicating an item was updated. + * + * @param {Object} event + * @param {Object} item + */ + updated: function(ev, item){ // go through lists and remove this guy if he is in the list and should not be ... var sets = this.sets.slice(0), report = ["Store - updating "]; - for(var i=0; i < sets.length; i++){ + for(var i =0, length = this.sets.length; i < length; i++){ var set = sets[i], inSet = this.filter(item, set.params) !== false, inList = set.list.get(item)[0]; @@ -37,52 +81,73 @@ $.Class('jQuery.Model.Store', set.list.remove(item.id) } } - /*if(report.length > 1) { - console.log.apply(console, report); - } else { - console.log("Store - Updated, but no changes") - }*/ }, - // this is mostly unnecessary - remove : function(ev,id){ + + /** + * Destroy triggered by model event. + * Calls remove function to remove item from lists. + * + * @param {Object} event + * @param {Object} id + */ + destroyed : function(ev,id){ + this.remove(id); + }, + + /** + * @function remove + * + * Removes an item from the sets. + * + * @param {Object} id + */ + remove:function(id){ var idProp = this.id; + if(id[idProp] !== undefined){ id = id[idProp]; } + var item = this.data[id]; + if(!item){ return; } - // need to unbind? Of course lists should cause this to happen + delete this.data[id]; - // go through sets ... + }, + + /** + * @function removeSet + * + * Removes a set given a parms object and + * removes each one of the items from the data. + * + * @param {Object} params + */ + removeSet: function(params){ + var matchIdx; - /*var sets = this.sets.slice(0), - report = ["Store - removing from "]; - for(var i=0; i < sets.length; i++){ - var set = sets[i], - removed; - - if(set.list){ - removed = set.list.remove(item) - } - - if(removed.length) { - report.push(set.params, "; "); + $.each(this.sets, this.proxy(function(i,set){ + if($.Object.same(params, set.params, this.compare)){ + set.list.each(this.proxy(function(i,item){ + delete this.data[item[this.id]]; + })); + matchIdx = i; + return false; } - } - if(report.length > 1) { - console.log.apply(console, report); - } else { - console.log("Store - Items to remove, but no matches") - }*/ + })); + + matchIdx != undefined && this.sets.splice(matchIdx, 1); }, - id: "id", + /** - * Adds items ... this essentially creates / updates them ... - * or looks - * @param {Array} items - * @param {Object} [params] will only add to matching sets + * @function add + * + * Adds items to the set(s) given the matching params. + * + * @param {Object} items + * @param {Object} params */ add : function(items, params){ // need to check the filter rules, if we can even add this ... @@ -93,71 +158,80 @@ $.Class('jQuery.Model.Store', idProp = this.id, id, added = []; + for(; i< len; i++){ - item = items[i] - id = item[idProp] + item = items[i]; + id = item[idProp]; + if( this.data[id] ){ + // if there is something there ... take care of it .. this.update(this.data[id], item); + + // if the item already exists from say a 'findOne' call + // the item will already exist in 'data' but not the 'list' + added.push(item) } else { - added.push(this.data[id] = this.create(item)) - } - + added.push(this.data[id] = item); + } } + // go through sets and add to them ... // slice so that if in callback, the number of sets increases, you are ok var sets = this.sets.slice(0), report = ["Store - adding "]; - for(var i=0; i < sets.length; i++){ + + for(var i=0, iLength = sets.length; i < iLength; i++){ var set = sets[i], itemsForSet = []; - for(var j =0; j< added.length; j++){ + for(var j =0, jLength = added.length; j< jLength; j++){ item = added[j] if( this.filter(item, set.params) !== false) { itemsForSet.push(item) } } + if(itemsForSet.length) { report.push(itemsForSet.length,"to", set.params, "; "); set.list.push(itemsForSet); } } - - /*if(report.length > 1) { - console.log.apply(console, report); - } else { - console.log("Store - Got new items, but no matches") - }*/ - - // check if item would be added to set - - // make sure item isn't already in set? }, + /** - * updates the properties of currentItem + * @function update + * + * Updates the properties of currentItem + * + * @param {Object} currentItem + * @param {Object} newItem */ update : function(currentItem, newItem){ currentItem.attrs(newItem.serialize()); }, + /** + * @function sort + * + * Returns if a set contains the parameters. * - * @param {Object} newItem - */ - create : function(newItem){ - return newItem; - }, + * @param {Object} params + **/ has : function(params){ // check if it has an evil param ... - return $.Object.subsets(params, this.sets).length }, + /** + * @function filter + * * Called with the item and the current params. * Should return __false__ if the item should be filtered out of the result. * * By default this goes through each param in params and see if it matches the * same property in item (if item has the property defined). + * * @param {Object} item * @param {Object} params */ @@ -170,20 +244,24 @@ $.Class('jQuery.Model.Store', // in fixtures we ignore null, I don't want to now if ( paramValue !== undefined && item[param] !== undefined - && !this._compare(param, item[param] ,paramValue) ) { + && !this._compare(param, item[param], paramValue) ) { return false; } } }, - compare : {}, - _compare : function(prop, itemData, paramData){ - return same(itemData, paramData, this.compare[prop]) //this.compare[prop] ? this.compare[prop](itemData, paramData) : itemData == paramData; - }, + /** - * Sorts the object in place - * - * By default uses an order property in the param + * @function sort + * + * Sorts the object in place. By default uses an order + * property in the param of the class. + * + * @codestart + * var models = $.Model.Store.sort(myModelListInstance); + * @codeend + * * @param {Object} items + * @param {Object} params */ sort : function(items, params){ $.each((params.order || []).slice(0).reverse(), function( i, name ) { @@ -211,41 +289,136 @@ $.Class('jQuery.Model.Store', }); return items }, + + /** + * @function pagination + * + * Paginates the item in place. By default uses an order + * property in the param of the class. + * + * @codestart + * var models = $.Model.Store.pagination(myModelListInstance); + * @codeend + * + * @param {Object} items + * @param {Object} params + */ pagination : function(items, params){ var offset = parseInt(params.offset, 10) || 0, limit = parseInt(params.limit, 10) || (items.length - offset); return items.slice(offset, offset + limit); }, + + /** + * @function get + * + * Retrieves an item(s) given an id or array of ids from the global data + * set of the model store. If the item is not returned yet, it will return + * the deffered. + * + * @codestart + * var model = $.Model.Store.get(222); + * @codeend + * + * @codestart + * var models = $.Model.Store.get([222, 223, 224]); + * @codeend + * + * @param {Object} id int or array of ints + */ get : function(id){ - return this.data[id]; + if($.isArray(id)) { + var returnArr = []; + + $.each(id, this.proxy(function(i,d){ + var m = this.data[d]; + m && returnArr.push(m); + })); + + return returnArr; + } else { + return this.data[id]; + } }, + + /** + * @function findOne + * + * FindOne attempts to retrieve an individual model + * from the sets of currently fetched data. If the model + * was not previously fetched, it will then execute a request on the + * static 'findOne' method of the model. It returns + * the deffered object. + * + * @codestart + * $.Model.Store.findOne(222).done(success); + * @codeend + * + * + * You can listen for 'findOne' to be triggered by + * binding to the 'findOne' event on the class. + * + * @codestart + * $.Model.Store.bind('findOne', function(id){ ... }); + * @codeend + * + * + * @param {Object} id of item + * @param {Function} success handler + * @param {Function} error handler + **/ findOne : function(id, success, error){ - //console.log("findOne ", id) - if(this.data[id]){ - // check if it is a deferred or not - if(this.data[id].isRejected){ - return this.data[id] + var data = this.data[id], + def; + + if(data){ + if(data.isRejected){ + return data; } else { - var def = $.Deferred() - def.resolve(this.data[id]) + def = $.Deferred(); + def.resolve(data); } } else { - var def = this.namespace.findOne({id: id}), - self = this; - def.done(function(item){ - self[id] = item; - }) + this.data[id] = def = this.namespace.findOne({ id: id }); + + def.done(this.proxy(function(item){ + this.data[id] = item; + })); } - def.done(success) + + def.done(success); + trigger(this, 'findOne', id); + return def; }, + /** - * Returns a list that interacts with the store + * @function findAll + * + * FindAll attempts to retrieve a list of model(s) + * from the sets of currently fetched data. If the model(s) + * were not previously fetched, it will then execute a request on the + * static 'findAll' method of the model. It returns + * the deffered object. + * + * @codestart + * $.Model.Store.findAll({ parentId: 2222 }).done(success); + * @codeend + * + * + * You can listen for 'findAll' to be triggered by + * binding to the 'findAll' event on the class. + * + * @codestart + * $.Model.Store.bind('findAll', function(params){ ... }); + * @codeend + * + * * @param {Object} params - * @param {Boolean} register registers this list as owning some content, but does not - * actually do the request ... - */ + * @param {Boolean} register registers this list as owning some content, but does not + * @param {Boolean} ready + **/ findAll : function(params, register, ready){ // find the first set that is the same // or is a subset with a def @@ -262,40 +435,34 @@ $.Class('jQuery.Model.Store', } ready = ready || function(){}; - for(var i =0; i < this.sets.length; i++){ + for(var i =0, length = this.sets.length; i < length; i++){ var set = this.sets[i]; - if( $.Object.subset(params, set.params, this.compare) ){ + if( $.Object.subset(params, set.params, this.compare) ){ parentLoadedSet = set; - //console.log($.Object.same( set.params, params), set.params, params ); - if( $.Object.same( set.params, params, this.compare) ){ - + + if( $.Object.same(set.params, params, this.compare) ){ // what if it's not loaded if(!set.def){ - //console.log("Store - a listening list, but not loaded", params, ready); var def = this.namespace.findAll(params); set.def = def; def.done(function(items){ - //console.log("adding items from findALL", params, items.length) list = items; self.add(items, params) - cb();; + cb && cb(); }) } else { - //console.log("Store - already loaded exact match",params, ready); list = set.list; if(set.def.isResolved()){ setTimeout(cb, 1); } else { set.def.done(cb); } - //ready && ready(set.list); } return set.list; } } } - // create a list, a set and add the set to our list of sets list = new this.namespace.List(); @@ -306,7 +473,6 @@ $.Class('jQuery.Model.Store', this.sets.push(sameSet); - // we have loaded or are loading what we need if( parentLoadedSet ) { // find the first set with a deferred @@ -316,58 +482,44 @@ $.Class('jQuery.Model.Store', } else if( parentLoadedSet.def.isResolved() ){ // add right away - //console.log("Store - already loaded parent set",params); var items = self.findAllCached(params); - //list.reset(); list.push(items); setTimeout(cb, 1);; } else { // this will be filled when add is called ... parentLoadedSet.def.done(function(){ - //console.log("Store - already loading parent set, waiting for it to return",params, ready); var items = self.findAllCached(params); - //list.reset(); list.push(items); - cb(); + cb && cb(); }) } } else { - if( register ) { - // do nothing ... - - - } else { + if( !register ) { // we need to load it - //console.log("Store - loading data for the first time", params, ready); - var def = this.namespace.findAll(params); - sameSet.def = def; + var def = sameSet.def = this.namespace.findAll(params); def.done(function(items){ self.add(items, params); - cb();//ready && ready(sameSet.list); - }) - + cb && cb(); + }); } - } + trigger(this, 'findAll', params); - - // wait until the items are loaded, do the reset and pushing ... - - // check later if no one is listening ... - setTimeout(function(){ - //console.log('unbinding ...?') - /*if(!list.bound){ - this.sets = $.grep(this.sets, function(set){ set !== sameSet}); - // we need to remove these items too ... (unless we are a superset) - }*/ - },10); return list; - }, + + /** + * @function findAllCached + * + * FindAll attempts to retrieve a list of model(s) + * only from the cache. + * + * @param {Object} params + **/ findAllCached : function(params){ // remove anything not filtering .... // - sorting, grouping, limit, and offset @@ -375,6 +527,7 @@ $.Class('jQuery.Model.Store', var list = [], data = this.data, item; + for(var id in data){ item = data[id]; if( this.filter(item, params) !== false) { @@ -384,10 +537,10 @@ $.Class('jQuery.Model.Store', // do sorting / grouping list = this.pagination(this.sort(list, params), params); + // take limit and offset ... return list; } -},{}); - +},{ }); -}); +}); \ No newline at end of file From 8fa6e3c62d134ef1080885d781d980dddd56bcba Mon Sep 17 00:00:00 2001 From: Justin Meyer Date: Wed, 22 Feb 2012 09:58:24 -0600 Subject: [PATCH 08/26] fixes problem with delegate being removed within another delegate --- lang/observe/delegate/delegate.js | 10 ++++----- lang/observe/delegate/delegate_test.js | 29 ++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/lang/observe/delegate/delegate.js b/lang/observe/delegate/delegate.js index 583802b1..385ac26e 100644 --- a/lang/observe/delegate/delegate.js +++ b/lang/observe/delegate/delegate.js @@ -51,26 +51,23 @@ steal('jquery/lang/observe',function(){ delegate = function(event, prop, how, newVal, oldVal){ // pre-split properties to save some regexp time var props = prop.split("."), - delegates = $.data(this,"_observe_delegates") || [], + delegates = ($.data(this,"_observe_delegates") || []).slice(0), delegate, - len = delegates.length, attr, matchedAttr, hasMatch, valuesEqual; event.attr = prop; event.lastAttr = props[props.length -1 ]; - // for each delegate - for(var i =0; i < len; i++){ + for(var i =0; delegate = delegates[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){ + if((event.batchNum && delegate.batchNum === event.batchNum) || delegate.undelegated ){ continue; } @@ -294,6 +291,7 @@ steal('jquery/lang/observe',function(){ delegateOb = delegates[i]; if( delegateOb.callback === cb || (!cb && delegateOb.selector === selector) ){ + delegateOb.undelegated = true; delegates.splice(i,1) } else { i++; diff --git a/lang/observe/delegate/delegate_test.js b/lang/observe/delegate/delegate_test.js index ec61a9c4..c938e285 100644 --- a/lang/observe/delegate/delegate_test.js +++ b/lang/observe/delegate/delegate_test.js @@ -221,4 +221,33 @@ test("compound sets", function(){ equals(count, 3, "setting person does not fire anything"); }) +test("undelegate within event loop",1, function(){ + + var state = new $.Observe({ + type : "person", + id: "5" + }); + var f1 = function(){ + state.undelegate("type","add",f2); + }, + f2 = function(){ + ok(false,"I am removed, how am I called") + }, + f3 = function(){ + state.undelegate("type","add",f1); + }, + f4 = function(){ + ok(true,"f4 called") + }; + state.delegate("type", "set", f1); + state.delegate("type","set",f2); + state.delegate("type","set",f3); + state.delegate("type","set",f4); + state.attr("type","other"); + +}) + + + + }); \ No newline at end of file From 1b85f8a9cb45bba6e121c04688baf70e9d5e103b Mon Sep 17 00:00:00 2001 From: Justin Meyer Date: Fri, 24 Feb 2012 02:19:42 -0600 Subject: [PATCH 09/26] fixing demos --- event/drop/drop.html | 54 ++++++++++++++++++++++-------------------- event/hover/hover.html | 8 ++++--- 2 files changed, 33 insertions(+), 29 deletions(-) diff --git a/event/drop/drop.html b/event/drop/drop.html index 5d4af6c6..439f11f1 100644 --- a/event/drop/drop.html +++ b/event/drop/drop.html @@ -41,33 +41,35 @@

Dropmove/Dropon

Drop Count 0 + \ 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 10/26] 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("
  • New Todo
  • "); - * } - * }) - * - * 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 16/26] 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 17/26] 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 18/26] 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 19/26] 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 20/26] 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 21/26] 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 22/26] 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 23/26] 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 24/26] 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 25/26] 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 26/26] 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; };