From 4e02781692e4bc58dc2d8585db1570e7cae748d1 Mon Sep 17 00:00:00 2001 From: "olau@iola.dk" Date: Thu, 17 Mar 2011 14:20:06 +0000 Subject: [PATCH] Refactor replot behaviour so Flot tries to reuse the existing canvas, adding shutdown() methods to the plot (based on patch by Ryley Breiddal, issue 269). This prevents a memory leak in Chrome and hopefully makes replotting faster for those who are using $.plot instead of .setData()/.draw(). Also update jQuery to 1.5.1 to prevent IE leaks fixed some time ago in jQuery. git-svn-id: https://flot.googlecode.com/svn/trunk@317 1e0a6537-2640-0410-bfb7-f154510ff394 --- API.txt | 28 +- NEWS.txt | 10 +- PLUGINS.txt | 68 +- jquery.flot.crosshair.js | 54 +- jquery.flot.js | 136 +- jquery.flot.navigate.js | 124 +- jquery.flot.selection.js | 32 +- jquery.js | 10514 +++++++++++++++++++++++++------------ 8 files changed, 7542 insertions(+), 3424 deletions(-) diff --git a/API.txt b/API.txt index 20c968a..8a8dbc2 100644 --- a/API.txt +++ b/API.txt @@ -901,7 +901,19 @@ can call: o = pointOffset({ x: xpos, y: ypos, xaxis: 2, yaxis: 3 }) // o.left and o.top now contains the offset within the div - + + - resize() + + Tells Flot to resize the drawing canvas to the size of the + placeholder. You need to run setupGrid() and draw() afterwards as + canvas resizing is a destructive operation. This is used + internally by the resize plugin. + + - shutdown() + + Cleans up any event handlers Flot has currently registered. This + is used internally. + There are also some members that let you peek inside the internal workings of Flot which is useful in some cases. Note that if you change @@ -998,6 +1010,8 @@ Here's an overview of the phases Flot goes through: 7. Responding to events, if any + 8. Shutdown: this mostly happens in case a plot is overwritten + Each hook is simply a function which is put in the appropriate array. You can add them through the "hooks" option, and they are also available after the plot is constructed as the "hooks" attribute on the returned @@ -1146,6 +1160,16 @@ hooks in the plugins bundled with Flot. crosshair plugin for an example. + - shutdown [phase 8] + + function (plot, eventHolder) + + Run when plot.shutdown() is called, which usually only happens in + case a plot is overwritten by a new plot. If you're writing a + plugin that adds extra DOM elements or event handlers, you should + add a callback to clean up after you. Take a look at the section in + PLUGINS.txt for more info. + Plugins ------- @@ -1163,7 +1187,7 @@ Here's a brief explanation of how the plugin plumbings work: Each plugin registers itself in the global array $.plot.plugins. When you make a new plot object with $.plot, Flot goes through this array calling the "init" function of each plugin and merging default options -from the plugin's "option" attribute. The init function gets a +from the "option" attribute of the plugin. The init function gets a reference to the plot object created and uses this to register hooks and add new public methods if needed. diff --git a/NEWS.txt b/NEWS.txt index d63fb1f..b616a76 100644 --- a/NEWS.txt +++ b/NEWS.txt @@ -94,8 +94,14 @@ Changes: - The version comment is now included in the minified jquery.flot.min.js. - New options.grid.minBorderMargin for adjusting the minimum margin provided around the border (based on patch by corani, issue 188). - -- New hooks: drawSeries +- Refactor replot behaviour so Flot tries to reuse the existing + canvas, adding shutdown() methods to the plot (based on patch by + Ryley Breiddal, issue 269). This prevents a memory leak in Chrome + and hopefully makes replotting faster for those who are using $.plot + instead of .setData()/.draw(). Also update jQuery to 1.5.1 to + prevent IE leaks fixed in jQuery. + +- New hooks: drawSeries, shutdown Bug fixes: diff --git a/PLUGINS.txt b/PLUGINS.txt index 00bf2e5..af3d90b 100644 --- a/PLUGINS.txt +++ b/PLUGINS.txt @@ -1,20 +1,22 @@ Writing plugins --------------- -To make a new plugin, create an init function and a set of options (if -needed), stuff it into an object and put it in the $.plot.plugins -array. For example: +All you need to do to make a new plugin is creating an init function +and a set of options (if needed), stuffing it into an object and +putting it in the $.plot.plugins array. For example: - function myCoolPluginInit(plot) { plot.coolstring = "Hello!" }; - var myCoolOptions = { coolstuff: { show: true } } - $.plot.plugins.push({ init: myCoolPluginInit, options: myCoolOptions }); + function myCoolPluginInit(plot) { + plot.coolstring = "Hello!"; + }; - // now when $.plot is called, the returned object will have the + $.plot.plugins.push({ init: myCoolPluginInit, options: { ... } }); + + // if $.plot is called, it will return a plot object with the // attribute "coolstring" Now, given that the plugin might run in many different places, it's -a good idea to avoid leaking names. We can avoid this by wrapping the -above lines in an anonymous function which we call immediately, like +a good idea to avoid leaking names. The usual trick here is wrap the +above lines in an anonymous function which is called immediately, like this: (function () { inner code ... })(). To make it even more robust in case $ is not bound to jQuery but some other Javascript library, we can write it as @@ -24,6 +26,13 @@ can write it as // ... })(jQuery); +There's a complete example below, but you should also check out the +plugins bundled with Flot. + + +Complete example +---------------- + Here is a simple debug plugin which alerts each of the series in the plot. It has a single option that control whether it is enabled and how much info to output: @@ -75,16 +84,41 @@ This simple plugin illustrates a couple of points: - Variables in the init function can be used to store plot-specific state between the hooks. +The two last points are important because there may be multiple plots +on the same page, and you'd want to make sure they are not mixed up. + + +Shutting down a plugin +---------------------- + +Each plot object has a shutdown hook which is run when plot.shutdown() +is called. This usually mostly happens in case another plot is made on +top of an existing one. + +The purpose of the hook is to give you a chance to unbind any event +handlers you've registered and remove any extra DOM things you've +inserted. + +The problem with event handlers is that you can have registered a +handler which is run in some point in the future, e.g. with +setTimeout(). Meanwhile, the plot may have been shutdown and removed, +but because your event handler is still referencing it, it can't be +garbage collected yet, and worse, if your handler eventually runs, it +may overwrite stuff on a completely different plot. + -Options guidelines -================== +Some hints on the options +------------------------- Plugins should always support appropriate options to enable/disable them because the plugin user may have several plots on the same page -where only one should use the plugin. +where only one should use the plugin. In most cases it's probably a +good idea if the plugin is turned off rather than on per default, just +like most of the powerful features in Flot. -If the plugin needs series-specific options, you can put them in -"series" in the options object, e.g. +If the plugin needs options that are specific to each series, like the +points or lines options in core Flot, you can put them in "series" in +the options object, e.g. var options = { series: { @@ -95,10 +129,8 @@ If the plugin needs series-specific options, you can put them in } } -Then they will be copied by Flot into each series, providing the -defaults in case the plugin user doesn't specify any. Again, in most -cases it's probably a good idea if the plugin is turned off rather -than on per default, just like most of the powerful features in Flot. +Then they will be copied by Flot into each series, providing default +values in case none are specified. Think hard and long about naming the options. These names are going to be public API, and code is going to depend on them if the plugin is diff --git a/jquery.flot.crosshair.js b/jquery.flot.crosshair.js index 30b1ec9..1d433f0 100644 --- a/jquery.flot.crosshair.js +++ b/jquery.flot.crosshair.js @@ -90,34 +90,37 @@ The plugin also adds four public methods: crosshair.locked = false; } - plot.hooks.bindEvents.push(function (plot, eventHolder) { - if (!plot.getOptions().crosshair.mode) + function onMouseOut(e) { + if (crosshair.locked) return; - eventHolder.mouseout(function () { - if (crosshair.locked) - return; + if (crosshair.x != -1) { + crosshair.x = -1; + plot.triggerRedrawOverlay(); + } + } - if (crosshair.x != -1) { - crosshair.x = -1; - plot.triggerRedrawOverlay(); - } - }); - - eventHolder.mousemove(function (e) { - if (crosshair.locked) - return; + function onMouseMove(e) { + if (crosshair.locked) + return; - if (plot.getSelection && plot.getSelection()) { - crosshair.x = -1; // hide the crosshair while selecting - return; - } + if (plot.getSelection && plot.getSelection()) { + crosshair.x = -1; // hide the crosshair while selecting + return; + } - var offset = plot.offset(); - crosshair.x = Math.max(0, Math.min(e.pageX - offset.left, plot.width())); - crosshair.y = Math.max(0, Math.min(e.pageY - offset.top, plot.height())); - plot.triggerRedrawOverlay(); - }); + var offset = plot.offset(); + crosshair.x = Math.max(0, Math.min(e.pageX - offset.left, plot.width())); + crosshair.y = Math.max(0, Math.min(e.pageY - offset.top, plot.height())); + plot.triggerRedrawOverlay(); + } + + plot.hooks.bindEvents.push(function (plot, eventHolder) { + if (!plot.getOptions().crosshair.mode) + return; + + eventHolder.mouseout(onMouseOut); + eventHolder.mousemove(onMouseMove); }); plot.hooks.drawOverlay.push(function (plot, ctx) { @@ -148,6 +151,11 @@ The plugin also adds four public methods: } ctx.restore(); }); + + plot.hooks.shutdown.push(function (plot, eventHolder) { + eventHolder.unbind("mouseout", onMouseOut); + eventHolder.unbind("mousemove", onMouseMove); + }); } $.plot.plugins.push({ diff --git a/jquery.flot.js b/jquery.flot.js index 5c2a690..33d3651 100644 --- a/jquery.flot.js +++ b/jquery.flot.js @@ -148,7 +148,8 @@ drawSeries: [], draw: [], bindEvents: [], - drawOverlay: [] + drawOverlay: [], + shutdown: [] }, plot = this; @@ -190,6 +191,12 @@ top: parseInt(yaxes[axisNumber(point, "y") - 1].p2c(+point.y) + plotOffset.top) }; }; + plot.shutdown = shutdown; + plot.resize = function () { + getCanvasDimensions(); + resizeCanvas(canvas); + resizeCanvas(overlay); + }; // public attributes plot.hooks = hooks; @@ -197,7 +204,7 @@ // initialize initPlugins(plot); parseOptions(options_); - constructCanvas(); + setupCanvases(); setData(data_); setupGrid(); draw(); @@ -673,56 +680,110 @@ }); } - function constructCanvas() { - canvasWidth = placeholder.width(); - canvasHeight = placeholder.height(); + function makeCanvas(skipPositioning, cls) { + var c = document.createElement('canvas'); + c.className = cls; + c.width = canvasWidth; + c.height = canvasHeight; + + if (!skipPositioning) + $(c).css({ position: 'absolute', left: 0, top: 0 }); + + $(c).appendTo(placeholder); + + if (!c.getContext) // excanvas hack + c = window.G_vmlCanvasManager.initElement(c); - // excanvas hack, if there are any canvases here, whack - // the state on them manually - if (window.G_vmlCanvasManager) - placeholder.find("canvas").each(function () { - this.context_ = null; - }); - - placeholder.html(""); // clear placeholder + // used for resetting in case we get replotted + c.getContext("2d").save(); - if (placeholder.css("position") == 'static') - placeholder.css("position", "relative"); // for positioning labels and overlay - placeholder.css({ padding: 0 }); + return c; + } + function getCanvasDimensions() { + canvasWidth = placeholder.width(); + canvasHeight = placeholder.height(); + if (canvasWidth <= 0 || canvasHeight <= 0) throw "Invalid dimensions for plot, width = " + canvasWidth + ", height = " + canvasHeight; + } - function makeCanvas(skipPositioning) { - var c = document.createElement('canvas'); + function resizeCanvas(c) { + // resizing should reset the state (excanvas seems to be + // buggy though) + if (c.width != canvasWidth) c.width = canvasWidth; + + if (c.height != canvasHeight) c.height = canvasHeight; + + // so try to get back to the initial state (even if it's + // gone now, this should be safe according to the spec) + var cctx = c.getContext("2d"); + cctx.restore(); + + // and save again + cctx.save(); + } + + function setupCanvases() { + var reused, + existingCanvas = placeholder.children("canvas.base"), + existingOverlay = placeholder.children("canvas.overlay"); + + if (existingCanvas.length == 0 || existingOverlay == 0) { + // init everything - if (!skipPositioning) - $(c).css({ position: 'absolute', left: 0, top: 0 }); + placeholder.html(""); // make sure placeholder is clear + + placeholder.css({ padding: 0 }); // padding messes up the positioning - $(c).appendTo(placeholder); + if (placeholder.css("position") == 'static') + placeholder.css("position", "relative"); // for positioning labels and overlay + + getCanvasDimensions(); - if (!c.getContext) // excanvas hack - c = window.G_vmlCanvasManager.initElement(c); + canvas = makeCanvas(true, "base"); + overlay = makeCanvas(false, "overlay"); // overlay canvas for interactive features - return c; + reused = false; + } + else { + // reuse existing elements + + canvas = existingCanvas.get(0); + overlay = existingOverlay.get(0); + + reused = true; } - - // the canvas - canvas = makeCanvas(true); - ctx = canvas.getContext("2d"); - // overlay canvas for interactive features - overlay = makeCanvas(); + ctx = canvas.getContext("2d"); octx = overlay.getContext("2d"); - } - function bindEvents() { // we include the canvas in the event holder too, because IE 7 // sometimes has trouble with the stacking order eventHolder = $([overlay, canvas]); + if (reused) { + // run shutdown in the old plot object + placeholder.data("plot").shutdown(); + + // reset reused canvases + plot.resize(); + + // make sure overlay pixels are cleared (canvas is cleared when we redraw) + octx.clearRect(0, 0, canvasWidth, canvasHeight); + + // then whack any remaining obvious garbage left + eventHolder.unbind(); + placeholder.children().not([canvas, overlay]).remove(); + } + + // save in case we get replotted + placeholder.data("plot", plot); + } + + function bindEvents() { // bind events if (options.grid.hoverable) { eventHolder.mousemove(onMouseMove); @@ -735,6 +796,17 @@ executeHooks(hooks.bindEvents, [eventHolder]); } + function shutdown() { + if (redrawTimeout) + clearTimeout(redrawTimeout); + + eventHolder.unbind("mousemove", onMouseMove); + eventHolder.unbind("mouseleave", onMouseLeave); + eventHolder.unbind("click", onClick); + + executeHooks(hooks.shutdown, [eventHolder]); + } + function setTransformationHelpers(axis) { // set helper functions on the axis, assumes plot area // has been computed already diff --git a/jquery.flot.navigate.js b/jquery.flot.navigate.js index fcb34e5..f2b9760 100644 --- a/jquery.flot.navigate.js +++ b/jquery.flot.navigate.js @@ -126,63 +126,72 @@ Licensed under the MIT License ~ http://threedubmedia.googlecode.com/files/MIT-L }; function init(plot) { + function onZoomClick(e, zoomOut) { + var c = plot.offset(); + c.left = e.pageX - c.left; + c.top = e.pageY - c.top; + if (zoomOut) + plot.zoomOut({ center: c }); + else + plot.zoom({ center: c }); + } + + function onMouseWheel(e, delta) { + onZoomClick(e, delta < 0); + return false; + } + + var prevCursor = 'default', prevPageX = 0, prevPageY = 0, + panTimeout = null; + + function onDragStart(e) { + if (e.which != 1) // only accept left-click + return false; + var c = plot.getPlaceholder().css('cursor'); + if (c) + prevCursor = c; + plot.getPlaceholder().css('cursor', plot.getOptions().pan.cursor); + prevPageX = e.pageX; + prevPageY = e.pageY; + } + + function onDrag(e) { + var frameRate = plot.getOptions().pan.frameRate; + if (panTimeout || !frameRate) + return; + + panTimeout = setTimeout(function () { + plot.pan({ left: prevPageX - e.pageX, + top: prevPageY - e.pageY }); + prevPageX = e.pageX; + prevPageY = e.pageY; + + panTimeout = null; + }, 1 / frameRate * 1000); + } + + function onDragEnd(e) { + if (panTimeout) { + clearTimeout(panTimeout); + panTimeout = null; + } + + plot.getPlaceholder().css('cursor', prevCursor); + plot.pan({ left: prevPageX - e.pageX, + top: prevPageY - e.pageY }); + } + function bindEvents(plot, eventHolder) { var o = plot.getOptions(); if (o.zoom.interactive) { - function clickHandler(e, zoomOut) { - var c = plot.offset(); - c.left = e.pageX - c.left; - c.top = e.pageY - c.top; - if (zoomOut) - plot.zoomOut({ center: c }); - else - plot.zoom({ center: c }); - } - - eventHolder[o.zoom.trigger](clickHandler); - - eventHolder.mousewheel(function (e, delta) { - clickHandler(e, delta < 0); - return false; - }); + eventHolder[o.zoom.trigger](onZoomClick); + eventHolder.mousewheel(onMouseWheel); } + if (o.pan.interactive) { - var prevCursor = 'default', pageX = 0, pageY = 0, - panTimeout = null; - - eventHolder.bind("dragstart", { distance: 10 }, function (e) { - if (e.which != 1) // only accept left-click - return false; - var c = eventHolder.css('cursor'); - if (c) - prevCursor = c; - eventHolder.css('cursor', o.pan.cursor); - pageX = e.pageX; - pageY = e.pageY; - }); - eventHolder.bind("drag", function (e) { - if (panTimeout || !o.pan.frameRate) - return; - - panTimeout = setTimeout(function () { - plot.pan({ left: pageX - e.pageX, - top: pageY - e.pageY }); - pageX = e.pageX; - pageY = e.pageY; - - panTimeout = null; - }, 1/o.pan.frameRate * 1000); - }); - eventHolder.bind("dragend", function (e) { - if (panTimeout) { - clearTimeout(panTimeout); - panTimeout = null; - } - - eventHolder.css('cursor', prevCursor); - plot.pan({ left: pageX - e.pageX, - top: pageY - e.pageY }); - }); + eventHolder.bind("dragstart", { distance: 10 }, onDragStart); + eventHolder.bind("drag", onDrag); + eventHolder.bind("dragend", onDragEnd); } } @@ -303,8 +312,19 @@ Licensed under the MIT License ~ http://threedubmedia.googlecode.com/files/MIT-L if (!args.preventEvent) plot.getPlaceholder().trigger("plotpan", [ plot ]); } + + function shutdown(plot, eventHolder) { + eventHolder.unbind(plot.getOptions().zoom.trigger, onZoomClick); + eventHolder.unbind("mousewheel", onMouseWheel); + eventHolder.unbind("dragstart", onDragStart); + eventHolder.unbind("drag", onDrag); + eventHolder.unbind("dragend", onDragEnd); + if (panTimeout) + clearTimeout(panTimeout); + } plot.hooks.bindEvents.push(bindEvents); + plot.hooks.shutdown.push(shutdown); } $.plot.plugins.push({ diff --git a/jquery.flot.selection.js b/jquery.flot.selection.js index 8bfbbab..7f7b326 100644 --- a/jquery.flot.selection.js +++ b/jquery.flot.selection.js @@ -81,11 +81,13 @@ The plugin allso adds the following methods to the plot object: // make this plugin much slimmer. var savedhandlers = {}; + var mouseUpHandler = null; + function onMouseMove(e) { if (selection.active) { - plot.getPlaceholder().trigger("plotselecting", [ getSelection() ]); - updateSelection(e); + + plot.getPlaceholder().trigger("plotselecting", [ getSelection() ]); } } @@ -109,18 +111,24 @@ The plugin allso adds the following methods to the plot object: setSelectionPos(selection.first, e); selection.active = true; + + // this is a bit silly, but we have to use a closure to be + // able to whack the same handler again + mouseUpHandler = function (e) { onMouseUp(e); }; - $(document).one("mouseup", onMouseUp); + $(document).one("mouseup", mouseUpHandler); } function onMouseUp(e) { + mouseUpHandler = null; + // revert drag stuff for old-school browsers if (document.onselectstart !== undefined) document.onselectstart = savedhandlers.onselectstart; if (document.ondrag !== undefined) document.ondrag = savedhandlers.ondrag; - // no more draggy-dee-drag + // no more dragging selection.active = false; updateSelection(e); @@ -277,11 +285,10 @@ The plugin allso adds the following methods to the plot object: plot.hooks.bindEvents.push(function(plot, eventHolder) { var o = plot.getOptions(); - if (o.selection.mode != null) + if (o.selection.mode != null) { eventHolder.mousemove(onMouseMove); - - if (o.selection.mode != null) eventHolder.mousedown(onMouseDown); + } }); @@ -312,6 +319,15 @@ The plugin allso adds the following methods to the plot object: ctx.restore(); } }); + + plot.hooks.shutdown.push(function (plot, eventHolder) { + eventHolder.unbind("mousemove", onMouseMove); + eventHolder.unbind("mousedown", onMouseDown); + + if (mouseUpHandler) + $(document).unbind("mouseup", mouseUpHandler); + }); + } $.plot.plugins.push({ @@ -323,6 +339,6 @@ The plugin allso adds the following methods to the plot object: } }, name: 'selection', - version: '1.0' + version: '1.1' }); })(jQuery); diff --git a/jquery.js b/jquery.js index 9263574..78fcfa4 100644 --- a/jquery.js +++ b/jquery.js @@ -1,153 +1,261 @@ /*! - * jQuery JavaScript Library v1.3.2 + * jQuery JavaScript Library v1.5.1 * http://jquery.com/ * - * Copyright (c) 2009 John Resig - * Dual licensed under the MIT and GPL licenses. - * http://docs.jquery.com/License + * Copyright 2011, John Resig + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license * - * Date: 2009-02-19 17:34:21 -0500 (Thu, 19 Feb 2009) - * Revision: 6246 + * Includes Sizzle.js + * http://sizzlejs.com/ + * Copyright 2011, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * + * Date: Wed Feb 23 13:55:29 2011 -0500 */ -(function(){ +(function( window, undefined ) { + +// Use the correct document accordingly with window argument (sandbox) +var document = window.document; +var jQuery = (function() { + +// Define a local copy of jQuery +var jQuery = function( selector, context ) { + // The jQuery object is actually just the init constructor 'enhanced' + return new jQuery.fn.init( selector, context, rootjQuery ); + }, -var - // Will speed up references to window, and allows munging its name. - window = this, - // Will speed up references to undefined, and allows munging its name. - undefined, // Map over jQuery in case of overwrite _jQuery = window.jQuery, + // Map over the $ in case of overwrite _$ = window.$, - jQuery = window.jQuery = window.$ = function( selector, context ) { - // The jQuery object is actually just the init constructor 'enhanced' - return new jQuery.fn.init( selector, context ); - }, + // A central reference to the root jQuery(document) + rootjQuery, // A simple way to check for HTML strings or ID strings // (both of which we optimize for) - quickExpr = /^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/, - // Is it a simple selector - isSimple = /^.[^:#\[\.,]*$/; + 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+$/, + + // Check for digits + rdigit = /\d/, + + // Match a standalone tag + rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/, + + // JSON RegExp + rvalidchars = /^[\],:{}\s]*$/, + rvalidescape = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, + rvalidtokens = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, + rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g, + + // Useragent RegExp + rwebkit = /(webkit)[ \/]([\w.]+)/, + ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/, + rmsie = /(msie) ([\w.]+)/, + rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/, + + // Keep a UserAgent string for use with jQuery.browser + userAgent = navigator.userAgent, + + // For matching the engine and version of the browser + browserMatch, + + // Has the ready events already been bound? + readyBound = false, + + // The deferred used on DOM ready + readyList, + + // Promise methods + promiseMethods = "then done fail isResolved isRejected promise".split( " " ), + + // The ready event handler + DOMContentLoaded, + + // Save a reference to some core methods + toString = Object.prototype.toString, + hasOwn = Object.prototype.hasOwnProperty, + push = Array.prototype.push, + slice = Array.prototype.slice, + trim = String.prototype.trim, + indexOf = Array.prototype.indexOf, + + // [[Class]] -> type pairs + class2type = {}; jQuery.fn = jQuery.prototype = { - init: function( selector, context ) { - // Make sure that a selection was provided - selector = selector || document; + 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[0] = selector; + this.context = this[0] = selector; + this.length = 1; + return this; + } + + // The body element only exists once, optimize finding it + if ( selector === "body" && !context && document.body ) { + this.context = document; + this[0] = document.body; + this.selector = "body"; this.length = 1; - this.context = selector; return this; } + // Handle HTML strings if ( typeof selector === "string" ) { // Are we dealing with HTML string or an ID? - var match = quickExpr.exec( selector ); + 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] ) - selector = jQuery.clean( [ match[1] ], context ); + 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 { - var elem = document.getElementById( match[3] ); + } 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 ); + } - // Handle the case where IE and Opera return items - // by name instead of ID - if ( elem && elem.id != match[3] ) - return jQuery().find( selector ); + // Otherwise, we inject the element directly into the jQuery object + this.length = 1; + this[0] = elem; + } - // Otherwise, we inject the element directly into the jQuery object - var ret = jQuery( elem || [] ); - ret.context = document; - ret.selector = selector; - return ret; + this.context = document; + this.selector = selector; + return this; } - // HANDLE: $(expr, [context]) - // (which is just equivalent to: $(content).find(expr) - } else - return jQuery( context ).find( selector ); + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return (context || rootjQuery).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( context ).find( selector ); + } // HANDLE: $(function) // Shortcut for document ready - } else if ( jQuery.isFunction( selector ) ) - return jQuery( document ).ready( selector ); + } else if ( jQuery.isFunction( selector ) ) { + return rootjQuery.ready( selector ); + } - // Make sure that old selector state is passed along - if ( selector.selector && selector.context ) { + if (selector.selector !== undefined) { this.selector = selector.selector; this.context = selector.context; } - return this.setArray(jQuery.isArray( selector ) ? - selector : - jQuery.makeArray(selector)); + return jQuery.makeArray( selector, this ); }, // Start with an empty selector selector: "", // The current version of jQuery being used - jquery: "1.3.2", + jquery: "1.5.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 === undefined ? + return num == null ? // Return a 'clean' array - Array.prototype.slice.call( this ) : + this.toArray() : // Return just the object - this[ num ]; + ( num < 0 ? this[ this.length + num ] : this[ num ] ); }, // Take an array of elements and push it onto the stack // (returning the new matched element set) pushStack: function( elems, name, selector ) { // Build a new jQuery matched element set - var ret = jQuery( elems ); + 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" ) + if ( name === "find" ) { ret.selector = this.selector + (this.selector ? " " : "") + selector; - else if ( name ) + } else if ( name ) { ret.selector = this.selector + "." + name + "(" + selector + ")"; + } // Return the newly-formed element set return ret; }, - // Force the current matched set of elements to become - // the specified array of elements (destroying the stack in the process) - // You should use pushStack() in order to do this, but maintain the stack - setArray: function( elems ) { - // Resetting the length to 0, then using the native Array push - // is a super-fast way to populate an object with array-like properties - this.length = 0; - Array.prototype.push.apply( this, elems ); - - return this; - }, - // 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.) @@ -155,508 +263,338 @@ jQuery.fn = jQuery.prototype = { return jQuery.each( this, callback, args ); }, - // Determine the position of an element within - // the matched set of elements - index: function( elem ) { - // Locate the position of the desired element - return jQuery.inArray( - // If it receives a jQuery object, the first element is used - elem && elem.jquery ? elem[0] : elem - , this ); - }, - - attr: function( name, value, type ) { - var options = name; - - // Look for the case where we're accessing a style value - if ( typeof name === "string" ) - if ( value === undefined ) - return this[0] && jQuery[ type || "attr" ]( this[0], name ); - - else { - options = {}; - options[ name ] = value; - } - - // Check to see if we're setting style values - return this.each(function(i){ - // Set all the styles - for ( name in options ) - jQuery.attr( - type ? - this.style : - this, - name, jQuery.prop( this, options[ name ], type, i, name ) - ); - }); - }, - - css: function( key, value ) { - // ignore negative width and height values - if ( (key == 'width' || key == 'height') && parseFloat(value) < 0 ) - value = undefined; - return this.attr( key, value, "curCSS" ); - }, - - text: function( text ) { - if ( typeof text !== "object" && text != null ) - return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) ); - - var ret = ""; - - jQuery.each( text || this, function(){ - jQuery.each( this.childNodes, function(){ - if ( this.nodeType != 8 ) - ret += this.nodeType != 1 ? - this.nodeValue : - jQuery.fn.text( [ this ] ); - }); - }); - - return ret; - }, - - wrapAll: function( html ) { - if ( this[0] ) { - // The elements to wrap the target around - var wrap = jQuery( html, this[0].ownerDocument ).clone(); - - if ( this[0].parentNode ) - wrap.insertBefore( this[0] ); - - wrap.map(function(){ - var elem = this; - - while ( elem.firstChild ) - elem = elem.firstChild; + ready: function( fn ) { + // Attach the listeners + jQuery.bindReady(); - return elem; - }).append(this); - } + // Add the callback + readyList.done( fn ); return this; }, - wrapInner: function( html ) { - return this.each(function(){ - jQuery( this ).contents().wrapAll( html ); - }); - }, - - wrap: function( html ) { - return this.each(function(){ - jQuery( this ).wrapAll( html ); - }); + eq: function( i ) { + return i === -1 ? + this.slice( i ) : + this.slice( i, +i + 1 ); }, - append: function() { - return this.domManip(arguments, true, function(elem){ - if (this.nodeType == 1) - this.appendChild( elem ); - }); + first: function() { + return this.eq( 0 ); }, - prepend: function() { - return this.domManip(arguments, true, function(elem){ - if (this.nodeType == 1) - this.insertBefore( elem, this.firstChild ); - }); + last: function() { + return this.eq( -1 ); }, - before: function() { - return this.domManip(arguments, false, function(elem){ - this.parentNode.insertBefore( elem, this ); - }); + slice: function() { + return this.pushStack( slice.apply( this, arguments ), + "slice", slice.call(arguments).join(",") ); }, - after: function() { - return this.domManip(arguments, false, function(elem){ - this.parentNode.insertBefore( elem, this.nextSibling ); - }); + map: function( callback ) { + return this.pushStack( jQuery.map(this, function( elem, i ) { + return callback.call( elem, i, elem ); + })); }, end: function() { - return this.prevObject || jQuery( [] ); + return this.prevObject || this.constructor(null); }, // For internal use only. // Behaves like an Array's method, not like a jQuery method. - push: [].push, + push: push, sort: [].sort, - splice: [].splice, + splice: [].splice +}; - find: function( selector ) { - if ( this.length === 1 ) { - var ret = this.pushStack( [], "find", selector ); - ret.length = 0; - jQuery.find( selector, this[0], ret ); - return ret; - } else { - return this.pushStack( jQuery.unique(jQuery.map(this, function(elem){ - return jQuery.find( selector, elem ); - })), "find", selector ); - } - }, - - clone: function( events ) { - // Do the clone - var ret = this.map(function(){ - if ( !jQuery.support.noCloneEvent && !jQuery.isXMLDoc(this) ) { - // IE copies events bound via attachEvent when - // using cloneNode. Calling detachEvent on the - // clone will also remove the events from the orignal - // In order to get around this, we use innerHTML. - // Unfortunately, this means some modifications to - // attributes in IE that are actually only stored - // as properties will not be copied (such as the - // the name attribute on an input). - var html = this.outerHTML; - if ( !html ) { - var div = this.ownerDocument.createElement("div"); - div.appendChild( this.cloneNode(true) ); - html = div.innerHTML; - } - - return jQuery.clean([html.replace(/ jQuery\d+="(?:\d+|null)"/g, "").replace(/^\s*/, "")])[0]; - } else - return this.cloneNode(true); - }); +// Give the init function the jQuery prototype for later instantiation +jQuery.fn.init.prototype = jQuery.fn; - // Copy the events from the original to the clone - if ( events === true ) { - var orig = this.find("*").andSelf(), i = 0; +jQuery.extend = jQuery.fn.extend = function() { + var options, name, src, copy, copyIsArray, clone, + target = arguments[0] || {}, + i = 1, + length = arguments.length, + deep = false; - ret.find("*").andSelf().each(function(){ - if ( this.nodeName !== orig[i].nodeName ) - return; + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + target = arguments[1] || {}; + // skip the boolean and the target + i = 2; + } - var events = jQuery.data( orig[i], "events" ); + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !jQuery.isFunction(target) ) { + target = {}; + } - for ( var type in events ) { - for ( var handler in events[ type ] ) { - jQuery.event.add( this, type, events[ type ][ handler ], events[ type ][ handler ].data ); - } - } + // extend jQuery itself if only one argument is passed + if ( length === i ) { + target = this; + --i; + } - 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 ]; - // Return the cloned set - return ret; - }, + // Prevent never-ending loop + if ( target === copy ) { + continue; + } - filter: function( selector ) { - return this.pushStack( - jQuery.isFunction( selector ) && - jQuery.grep(this, function(elem, i){ - return selector.call( elem, i ); - }) || + // 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 : []; - jQuery.multiFilter( selector, jQuery.grep(this, function(elem){ - return elem.nodeType === 1; - }) ), "filter", selector ); - }, + } else { + clone = src && jQuery.isPlainObject(src) ? src : {}; + } - closest: function( selector ) { - var pos = jQuery.expr.match.POS.test( selector ) ? jQuery(selector) : null, - closer = 0; + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); - return this.map(function(){ - var cur = this; - while ( cur && cur.ownerDocument ) { - if ( pos ? pos.index(cur) > -1 : jQuery(cur).is(selector) ) { - jQuery.data(cur, "closest", closer); - return cur; + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; } - cur = cur.parentNode; - closer++; } - }); - }, + } + } - not: function( selector ) { - if ( typeof selector === "string" ) - // test special case where just one selector is passed in - if ( isSimple.test( selector ) ) - return this.pushStack( jQuery.multiFilter( selector, this, true ), "not", selector ); - else - selector = jQuery.multiFilter( selector, this ); - - var isArrayLike = selector.length && selector[selector.length - 1] !== undefined && !selector.nodeType; - return this.filter(function() { - return isArrayLike ? jQuery.inArray( this, selector ) < 0 : this != selector; - }); - }, + // Return the modified object + return target; +}; - add: function( selector ) { - return this.pushStack( jQuery.unique( jQuery.merge( - this.get(), - typeof selector === "string" ? - jQuery( selector ) : - jQuery.makeArray( selector ) - ))); - }, +jQuery.extend({ + noConflict: function( deep ) { + window.$ = _$; - is: function( selector ) { - return !!selector && jQuery.multiFilter( selector, this ).length > 0; - }, + if ( deep ) { + window.jQuery = _jQuery; + } - hasClass: function( selector ) { - return !!selector && this.is( "." + selector ); + return jQuery; }, - val: function( value ) { - if ( value === undefined ) { - var elem = this[0]; - - if ( elem ) { - if( jQuery.nodeName( elem, 'option' ) ) - return (elem.attributes.value || {}).specified ? elem.value : elem.text; - - // We need to handle select boxes special - if ( jQuery.nodeName( elem, "select" ) ) { - var index = elem.selectedIndex, - values = [], - options = elem.options, - one = elem.type == "select-one"; - - // Nothing was selected - if ( index < 0 ) - return null; + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, - // Loop through all the selected options - for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) { - var option = options[ i ]; + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, - if ( option.selected ) { - // Get the specifc value for the option - value = jQuery(option).val(); + // Handle when the DOM is ready + ready: function( wait ) { + // A third-party is pushing the ready event forwards + if ( wait === true ) { + jQuery.readyWait--; + } - // We don't need an array for one selects - if ( one ) - return value; + // Make sure that the DOM is not already loaded + if ( !jQuery.readyWait || (wait !== true && !jQuery.isReady) ) { + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if ( !document.body ) { + return setTimeout( jQuery.ready, 1 ); + } - // Multi-Selects return an array - values.push( value ); - } - } + // Remember that the DOM is ready + jQuery.isReady = true; - return values; - } + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } - // Everything else, we just grab the value - return (elem.value || "").replace(/\r/g, ""); + // If there are functions bound, to execute + readyList.resolveWith( document, [ jQuery ] ); + // Trigger any bound ready events + if ( jQuery.fn.trigger ) { + jQuery( document ).trigger( "ready" ).unbind( "ready" ); } + } + }, - return undefined; + bindReady: function() { + if ( readyBound ) { + return; } - if ( typeof value === "number" ) - value += ''; + readyBound = true; - return this.each(function(){ - if ( this.nodeType != 1 ) - return; + // 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 ); + } - if ( jQuery.isArray(value) && /radio|checkbox/.test( this.type ) ) - this.checked = (jQuery.inArray(this.value, value) >= 0 || - jQuery.inArray(this.name, value) >= 0); + // Mozilla, Opera and webkit nightlies currently support this event + if ( document.addEventListener ) { + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false ); - else if ( jQuery.nodeName( this, "select" ) ) { - var values = jQuery.makeArray(value); + // A fallback to window.onload, that will always work + window.addEventListener( "load", jQuery.ready, false ); - jQuery( "option", this ).each(function(){ - this.selected = (jQuery.inArray( this.value, values ) >= 0 || - jQuery.inArray( this.text, values ) >= 0); - }); + // 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); - if ( !values.length ) - this.selectedIndex = -1; + // A fallback to window.onload, that will always work + window.attachEvent( "onload", jQuery.ready ); - } else - this.value = value; - }); - }, + // If IE and not a frame + // continually check to see if the document is ready + var toplevel = false; - html: function( value ) { - return value === undefined ? - (this[0] ? - this[0].innerHTML.replace(/ jQuery\d+="(?:\d+|null)"/g, "") : - null) : - this.empty().append( value ); + try { + toplevel = window.frameElement == null; + } catch(e) {} + + if ( document.documentElement.doScroll && toplevel ) { + doScrollCheck(); + } + } }, - replaceWith: function( value ) { - return this.after( value ).remove(); + // 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"; }, - eq: function( i ) { - return this.slice( i, +i + 1 ); + isArray: Array.isArray || function( obj ) { + return jQuery.type(obj) === "array"; }, - slice: function() { - return this.pushStack( Array.prototype.slice.apply( this, arguments ), - "slice", Array.prototype.slice.call(arguments).join(",") ); + // A crude way of determining if an object is a window + isWindow: function( obj ) { + return obj && typeof obj === "object" && "setInterval" in obj; }, - map: function( callback ) { - return this.pushStack( jQuery.map(this, function(elem, i){ - return callback.call( elem, i, elem ); - })); + isNaN: function( obj ) { + return obj == null || !rdigit.test( obj ) || isNaN( obj ); }, - andSelf: function() { - return this.add( this.prevObject ); + type: function( obj ) { + return obj == null ? + String( obj ) : + class2type[ toString.call(obj) ] || "object"; }, - domManip: function( args, table, callback ) { - if ( this[0] ) { - var fragment = (this[0].ownerDocument || this[0]).createDocumentFragment(), - scripts = jQuery.clean( args, (this[0].ownerDocument || this[0]), fragment ), - first = fragment.firstChild; + 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; + } - if ( first ) - for ( var i = 0, l = this.length; i < l; i++ ) - callback.call( root(this[i], first), this.length > 1 || i > 0 ? - fragment.cloneNode(true) : fragment ); - - if ( scripts ) - jQuery.each( scripts, evalScript ); - } - - return this; - - function root( elem, cur ) { - return table && jQuery.nodeName(elem, "table") && jQuery.nodeName(cur, "tr") ? - (elem.getElementsByTagName("tbody")[0] || - elem.appendChild(elem.ownerDocument.createElement("tbody"))) : - elem; + // Not own constructor property must be Object + if ( obj.constructor && + !hasOwn.call(obj, "constructor") && + !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) { + return false; } - } -}; - -// Give the init function the jQuery prototype for later instantiation -jQuery.fn.init.prototype = jQuery.fn; - -function evalScript( i, elem ) { - if ( elem.src ) - jQuery.ajax({ - url: elem.src, - async: false, - dataType: "script" - }); - - else - jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" ); - - if ( elem.parentNode ) - elem.parentNode.removeChild( elem ); -} - -function now(){ - return +new Date; -} - -jQuery.extend = jQuery.fn.extend = function() { - // copy reference to target object - var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options; - - // 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 = {}; + // Own properties are enumerated firstly, so to speed up, + // if last one is own, then all properties are own. - // extend jQuery itself if only one argument is passed - if ( length == i ) { - target = this; - --i; - } + var key; + for ( key in obj ) {} - for ( ; i < length; i++ ) - // Only deal with non-null/undefined values - if ( (options = arguments[ i ]) != null ) - // Extend the base object - for ( var name in options ) { - var src = target[ name ], copy = options[ name ]; + return key === undefined || hasOwn.call( obj, key ); + }, - // Prevent never-ending loop - if ( target === copy ) - continue; + isEmptyObject: function( obj ) { + for ( var name in obj ) { + return false; + } + return true; + }, - // Recurse if we're merging object values - if ( deep && copy && typeof copy === "object" && !copy.nodeType ) - target[ name ] = jQuery.extend( deep, - // Never move original objects, clone them - src || ( copy.length != null ? [ ] : { } ) - , copy ); + error: function( msg ) { + throw msg; + }, - // Don't bring in undefined values - else if ( copy !== undefined ) - target[ name ] = copy; + 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 ); - // Return the modified object - return target; -}; + // 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, "")) ) { -// exclude the following css properties to add px -var exclude = /z-?index|font-?weight|opacity|zoom|line-?height/i, - // cache defaultView - defaultView = document.defaultView || {}, - toString = Object.prototype.toString; + // Try to use the native JSON parser first + return window.JSON && window.JSON.parse ? + window.JSON.parse( data ) : + (new Function("return " + data))(); -jQuery.extend({ - noConflict: function( deep ) { - window.$ = _$; + } else { + jQuery.error( "Invalid JSON: " + data ); + } + }, - if ( deep ) - window.jQuery = _jQuery; + // Cross-browser xml parsing + // (xml & tmp used internally) + parseXML: function( data , xml , tmp ) { + + 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 ); + } - return jQuery; - }, + tmp = xml.documentElement; - // See test/unit/core.js for details concerning isFunction. - // Since version 1.3, DOM methods and functions like alert - // aren't supported. They return false on IE (#2968). - isFunction: function( obj ) { - return toString.call(obj) === "[object Function]"; - }, + if ( ! tmp || ! tmp.nodeName || tmp.nodeName === "parsererror" ) { + jQuery.error( "Invalid XML: " + data ); + } - isArray: function( obj ) { - return toString.call(obj) === "[object Array]"; + return xml; }, - // check if an element is in a (or is an) XML document - isXMLDoc: function( elem ) { - return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" || - !!elem.ownerDocument && jQuery.isXMLDoc( elem.ownerDocument ); - }, + noop: function() {}, // Evalulates a script in a global context globalEval: function( data ) { - if ( data && /\S/.test(data) ) { + if ( data && rnotwhite.test(data) ) { // Inspired by code by Andrea Giammarchi // http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html - var head = document.getElementsByTagName("head")[0] || document.documentElement, - script = document.createElement("script"); + var head = document.head || document.getElementsByTagName( "head" )[0] || document.documentElement, + script = document.createElement( "script" ); - script.type = "text/javascript"; - if ( jQuery.support.scriptEval ) + if ( jQuery.support.scriptEval() ) { script.appendChild( document.createTextNode( data ) ); - else + } else { script.text = data; + } - // Use insertBefore instead of appendChild to circumvent an IE6 bug. + // Use insertBefore instead of appendChild to circumvent an IE6 bug. // This arises when a base node is used (#2709). head.insertBefore( script, head.firstChild ); head.removeChild( script ); @@ -664,3105 +602,6821 @@ jQuery.extend({ }, nodeName: function( elem, name ) { - return elem.nodeName && elem.nodeName.toUpperCase() == name.toUpperCase(); + 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; + var name, i = 0, + length = object.length, + isObj = length === undefined || jQuery.isFunction(object); if ( args ) { - if ( length === undefined ) { - for ( name in object ) - if ( callback.apply( object[ name ], args ) === false ) + 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 ) + } + } + } 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 ( length === undefined ) { - for ( name in object ) - if ( callback.call( object[ name ], name, object[ name ] ) === false ) + if ( isObj ) { + for ( name in object ) { + if ( callback.call( object[ name ], name, object[ name ] ) === false ) { break; - } else + } + } + } else { for ( var value = object[0]; - i < length && callback.call( value, i, value ) !== false; value = object[++i] ){} + i < length && callback.call( value, i, value ) !== false; value = object[++i] ) {} + } } return object; }, - prop: function( elem, value, type, i, name ) { - // Handle executable functions - if ( jQuery.isFunction( value ) ) - value = value.call( elem, i ); - - // Handle passing in a number to a CSS property - return typeof value === "number" && type == "curCSS" && !exclude.test( name ) ? - value + "px" : - value; - }, - - className: { - // internal only, use addClass("class") - add: function( elem, classNames ) { - jQuery.each((classNames || "").split(/\s+/), function(i, className){ - if ( elem.nodeType == 1 && !jQuery.className.has( elem.className, className ) ) - elem.className += (elem.className ? " " : "") + className; - }); + // 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, "" ); }, - // internal only, use removeClass("class") - remove: function( elem, classNames ) { - if (elem.nodeType == 1) - elem.className = classNames !== undefined ? - jQuery.grep(elem.className.split(/\s+/), function(className){ - return !jQuery.className.has( classNames, className ); - }).join(" ") : - ""; - }, + // results is for internal usage only + makeArray: function( array, results ) { + var ret = results || []; + + if ( array != null ) { + // The window, strings (and functions) also have 'length' + // The extra typeof function check is to prevent crashes + // in Safari 2 (See: #3039) + // Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930 + var type = jQuery.type(array); - // internal only, use hasClass("class") - has: function( elem, className ) { - return elem && jQuery.inArray( className, (elem.className || elem).toString().split(/\s+/) ) > -1; + if ( array.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( array ) ) { + push.call( ret, array ); + } else { + jQuery.merge( ret, array ); + } } + + return ret; }, - // A method for quickly swapping in/out CSS properties to get correct calculations - swap: function( elem, options, callback ) { - var old = {}; - // Remember the old values, and insert the new ones - for ( var name in options ) { - old[ name ] = elem.style[ name ]; - elem.style[ name ] = options[ name ]; + inArray: function( elem, array ) { + if ( array.indexOf ) { + return array.indexOf( elem ); } - callback.call( elem ); + for ( var i = 0, length = array.length; i < length; i++ ) { + if ( array[ i ] === elem ) { + return i; + } + } - // Revert the old values - for ( var name in options ) - elem.style[ name ] = old[ name ]; + return -1; }, - css: function( elem, name, force, extra ) { - if ( name == "width" || name == "height" ) { - var val, props = { position: "absolute", visibility: "hidden", display:"block" }, which = name == "width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ]; - - function getWH() { - val = name == "width" ? elem.offsetWidth : elem.offsetHeight; - - if ( extra === "border" ) - return; + merge: function( first, second ) { + var i = first.length, + j = 0; - jQuery.each( which, function() { - if ( !extra ) - val -= parseFloat(jQuery.curCSS( elem, "padding" + this, true)) || 0; - if ( extra === "margin" ) - val += parseFloat(jQuery.curCSS( elem, "margin" + this, true)) || 0; - else - val -= parseFloat(jQuery.curCSS( elem, "border" + this + "Width", true)) || 0; - }); + if ( typeof second.length === "number" ) { + for ( var l = second.length; j < l; j++ ) { + first[ i++ ] = second[ j ]; } - if ( elem.offsetWidth !== 0 ) - getWH(); - else - jQuery.swap( elem, props, getWH ); - - return Math.max(0, Math.round(val)); + } else { + while ( second[j] !== undefined ) { + first[ i++ ] = second[ j++ ]; + } } - return jQuery.curCSS( elem, name, force ); - }, + first.length = i; - curCSS: function( elem, name, force ) { - var ret, style = elem.style; + return first; + }, - // We need to handle opacity special in IE - if ( name == "opacity" && !jQuery.support.opacity ) { - ret = jQuery.attr( style, "opacity" ); + grep: function( elems, callback, inv ) { + var ret = [], retVal; + inv = !!inv; - return ret == "" ? - "1" : - ret; + // 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 ] ); + } } - // Make sure we're using the right name for getting the float value - if ( name.match( /float/i ) ) - name = styleFloat; + return ret; + }, - if ( !force && style && style[ name ] ) - ret = style[ name ]; + // arg is for internal usage only + map: function( elems, callback, arg ) { + var ret = [], value; + + // Go through the array, translating each of the items to their + // new value (or values). + for ( var i = 0, length = elems.length; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); - else if ( defaultView.getComputedStyle ) { + if ( value != null ) { + ret[ ret.length ] = value; + } + } - // Only "float" is needed here - if ( name.match( /float/i ) ) - name = "float"; + // Flatten any nested arrays + return ret.concat.apply( [], ret ); + }, - name = name.replace( /([A-Z])/g, "-$1" ).toLowerCase(); + // A global GUID counter for objects + guid: 1, - var computedStyle = defaultView.getComputedStyle( elem, null ); + proxy: function( fn, proxy, thisObject ) { + if ( arguments.length === 2 ) { + if ( typeof proxy === "string" ) { + thisObject = fn; + fn = thisObject[ proxy ]; + proxy = undefined; - if ( computedStyle ) - ret = computedStyle.getPropertyValue( name ); + } else if ( proxy && !jQuery.isFunction( proxy ) ) { + thisObject = proxy; + proxy = undefined; + } + } - // We should always get a number back from opacity - if ( name == "opacity" && ret == "" ) - ret = "1"; + if ( !proxy && fn ) { + proxy = function() { + return fn.apply( thisObject || this, arguments ); + }; + } - } else if ( elem.currentStyle ) { - var camelCase = name.replace(/\-(\w)/g, function(all, letter){ - return letter.toUpperCase(); - }); + // Set the guid of unique handler to the same of original handler, so it can be removed + if ( fn ) { + proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++; + } - ret = elem.currentStyle[ name ] || elem.currentStyle[ camelCase ]; + // So proxy can be declared as an argument + return proxy; + }, - // From the awesome hack by Dean Edwards - // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 + // Mutifunctional method to get and set values to a collection + // The value/s can be optionally by executed if its a function + access: function( elems, key, value, exec, fn, pass ) { + var length = elems.length; - // If we're not dealing with a regular pixel number - // but a number that has a weird ending, we need to convert it to pixels - if ( !/^\d+(px)?$/i.test( ret ) && /^\d/.test( ret ) ) { - // Remember the original values - var left = style.left, rsLeft = elem.runtimeStyle.left; + // Setting many attributes + if ( typeof key === "object" ) { + for ( var k in key ) { + jQuery.access( elems, k, key[k], exec, fn, value ); + } + return elems; + } - // Put in the new values to get a computed value out - elem.runtimeStyle.left = elem.currentStyle.left; - style.left = ret || 0; - ret = style.pixelLeft + "px"; + // Setting one attribute + if ( value !== undefined ) { + // Optionally, function values get executed if exec is true + exec = !pass && exec && jQuery.isFunction(value); - // Revert the changed values - style.left = left; - elem.runtimeStyle.left = rsLeft; + 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; } - return ret; + // Getting an attribute + return length ? fn( elems[0], key ) : undefined; }, - clean: function( elems, context, fragment ) { - context = context || document; + now: function() { + return (new Date()).getTime(); + }, - // !context.createElement fails in IE with an error but returns typeof 'object' - if ( typeof context.createElement === "undefined" ) - context = context.ownerDocument || context[0] && context[0].ownerDocument || document; + // Create a simple deferred (one callbacks list) + _Deferred: function() { + var // callbacks list + callbacks = [], + // stored [ context , args ] + fired, + // to avoid firing when already doing so + firing, + // flag to know if the deferred has been cancelled + cancelled, + // the deferred itself + deferred = { + + // done( f1, f2, ...) + done: function() { + if ( !cancelled ) { + var args = arguments, + i, + length, + elem, + type, + _fired; + if ( fired ) { + _fired = fired; + fired = 0; + } + for ( i = 0, length = args.length; i < length; i++ ) { + elem = args[ i ]; + type = jQuery.type( elem ); + if ( type === "array" ) { + deferred.done.apply( deferred, elem ); + } else if ( type === "function" ) { + callbacks.push( elem ); + } + } + if ( _fired ) { + deferred.resolveWith( _fired[ 0 ], _fired[ 1 ] ); + } + } + return this; + }, + + // resolve with given context and args + resolveWith: function( context, args ) { + if ( !cancelled && !fired && !firing ) { + firing = 1; + try { + while( callbacks[ 0 ] ) { + callbacks.shift().apply( context, args ); + } + } + // We have to add a catch block for + // IE prior to 8 or else the finally + // block will never get executed + catch (e) { + throw e; + } + finally { + fired = [ context, args ]; + firing = 0; + } + } + return this; + }, + + // resolve with this as context and given arguments + resolve: function() { + deferred.resolveWith( jQuery.isFunction( this.promise ) ? this.promise() : this, arguments ); + return this; + }, + + // Has this deferred been resolved? + isResolved: function() { + return !!( firing || fired ); + }, + + // Cancel + cancel: function() { + cancelled = 1; + callbacks = []; + return this; + } + }; - // If a single string is passed in and it's a single tag - // just do a createElement and skip the rest - if ( !fragment && elems.length === 1 && typeof elems[0] === "string" ) { - var match = /^<(\w+)\s*\/?>$/.exec(elems[0]); - if ( match ) - return [ context.createElement( match[1] ) ]; + return deferred; + }, + + // Full fledged deferred (two callbacks list) + Deferred: function( func ) { + var deferred = jQuery._Deferred(), + failDeferred = jQuery._Deferred(), + promise; + // Add errorDeferred methods, then and promise + jQuery.extend( deferred, { + then: function( doneCallbacks, failCallbacks ) { + deferred.done( doneCallbacks ).fail( failCallbacks ); + return this; + }, + fail: failDeferred.done, + rejectWith: failDeferred.resolveWith, + reject: failDeferred.resolve, + isRejected: failDeferred.isResolved, + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + if ( obj == null ) { + if ( promise ) { + return promise; + } + promise = obj = {}; + } + var i = promiseMethods.length; + while( i-- ) { + obj[ promiseMethods[i] ] = deferred[ promiseMethods[i] ]; + } + return obj; + } + } ); + // Make sure only one callback list will be used + deferred.done( failDeferred.cancel ).fail( deferred.cancel ); + // Unexpose cancel + delete deferred.cancel; + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); } + return deferred; + }, - var ret = [], scripts = [], div = context.createElement("div"); + // Deferred helper + when: function( object ) { + var lastIndex = arguments.length, + deferred = lastIndex <= 1 && object && jQuery.isFunction( object.promise ) ? + object : + jQuery.Deferred(), + promise = deferred.promise(); + + if ( lastIndex > 1 ) { + var array = slice.call( arguments, 0 ), + count = lastIndex, + iCallback = function( index ) { + return function( value ) { + array[ index ] = arguments.length > 1 ? slice.call( arguments, 0 ) : value; + if ( !( --count ) ) { + deferred.resolveWith( promise, array ); + } + }; + }; + while( ( lastIndex-- ) ) { + object = array[ lastIndex ]; + if ( object && jQuery.isFunction( object.promise ) ) { + object.promise().then( iCallback(lastIndex), deferred.reject ); + } else { + --count; + } + } + if ( !count ) { + deferred.resolveWith( promise, array ); + } + } else if ( deferred !== object ) { + deferred.resolve( object ); + } + return promise; + }, - jQuery.each(elems, function(i, elem){ - if ( typeof elem === "number" ) - elem += ''; + // Use of jQuery.browser is frowned upon. + // More details: http://docs.jquery.com/Utilities/jQuery.browser + uaMatch: function( ua ) { + ua = ua.toLowerCase(); - if ( !elem ) - return; + var match = rwebkit.exec( ua ) || + ropera.exec( ua ) || + rmsie.exec( ua ) || + ua.indexOf("compatible") < 0 && rmozilla.exec( ua ) || + []; - // Convert html string into DOM nodes - if ( typeof elem === "string" ) { - // Fix "XHTML"-style tags in all browsers - elem = elem.replace(/(<(\w+)[^>]*?)\/>/g, function(all, front, tag){ - return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i) ? - all : - front + ">"; - }); + return { browser: match[1] || "", version: match[2] || "0" }; + }, - // Trim whitespace, otherwise indexOf won't work as expected - var tags = elem.replace(/^\s+/, "").substring(0, 10).toLowerCase(); + sub: function() { + function jQuerySubclass( selector, context ) { + return new jQuerySubclass.fn.init( selector, context ); + } + jQuery.extend( true, jQuerySubclass, this ); + jQuerySubclass.superclass = this; + jQuerySubclass.fn = jQuerySubclass.prototype = this(); + jQuerySubclass.fn.constructor = jQuerySubclass; + jQuerySubclass.subclass = this.subclass; + jQuerySubclass.fn.init = function init( selector, context ) { + if ( context && context instanceof jQuery && !(context instanceof jQuerySubclass) ) { + context = jQuerySubclass(context); + } - var wrap = - // option or optgroup - !tags.indexOf("", "" ] || + return jQuery.fn.init.call( this, selector, context, rootjQuerySubclass ); + }; + jQuerySubclass.fn.init.prototype = jQuerySubclass.fn; + var rootjQuerySubclass = jQuerySubclass(document); + return jQuerySubclass; + }, - !tags.indexOf("", "" ] || + browser: {} +}); - tags.match(/^<(thead|tbody|tfoot|colg|cap)/) && - [ 1, "", "
" ] || +// Create readyList deferred +readyList = jQuery._Deferred(); - !tags.indexOf("", "" ] || +// Populate the class2type map +jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +}); - // matched above - (!tags.indexOf("", "" ] || +browserMatch = jQuery.uaMatch( userAgent ); +if ( browserMatch.browser ) { + jQuery.browser[ browserMatch.browser ] = true; + jQuery.browser.version = browserMatch.version; +} - !tags.indexOf("", "" ] || +// Deprecated, use jQuery.browser.webkit instead +if ( jQuery.browser.webkit ) { + jQuery.browser.safari = true; +} - // IE can't serialize and