From b6dc2dec79bd30269f367eb2cd476276f76d6f3b Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Sat, 21 Nov 2009 03:13:58 +0000 Subject: [PATCH] ENHANCEMENT Added initial implementation of jquery-changetracker as a replacement for the existing ChangeTracker behaviour/prototype javascript class git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/cms/trunk@92681 467b73ca-7a2a-4603-9d3b-597d59a354a9 --- javascript/jquery-changetracker/README.md | 17 + .../lib/jquery.changetracker.js | 103 + .../spec/.tmp_spec.html.49538~ | 31 + .../spec/.tmp_spec.html.8678~ | 34 + .../spec/.tmp_spec.html.970~ | 32 + .../spec/.tmp_spec_selectors.html.13329~ | 31 + .../spec/.tmp_speed.html.70283~ | 105 + .../spec/spec.changetracker.basics.js | 128 + .../jquery-changetracker/spec/spec.core.js | 8 + .../jquery-changetracker/spec/spec.dom.html | 24 + .../jquery-changetracker/spec/spec.rhino.js | 9 + .../vendor/TrivialReporter.js | 61 + .../jquery-changetracker/vendor/jasmine.css | 24 + .../jquery-changetracker/vendor/jasmine.js | 900 ++++ .../vendor/jquery-1.3.2.js | 4376 +++++++++++++++++ 15 files changed, 5883 insertions(+) create mode 100644 javascript/jquery-changetracker/README.md create mode 100644 javascript/jquery-changetracker/lib/jquery.changetracker.js create mode 100644 javascript/jquery-changetracker/spec/.tmp_spec.html.49538~ create mode 100644 javascript/jquery-changetracker/spec/.tmp_spec.html.8678~ create mode 100644 javascript/jquery-changetracker/spec/.tmp_spec.html.970~ create mode 100644 javascript/jquery-changetracker/spec/.tmp_spec_selectors.html.13329~ create mode 100644 javascript/jquery-changetracker/spec/.tmp_speed.html.70283~ create mode 100644 javascript/jquery-changetracker/spec/spec.changetracker.basics.js create mode 100644 javascript/jquery-changetracker/spec/spec.core.js create mode 100644 javascript/jquery-changetracker/spec/spec.dom.html create mode 100644 javascript/jquery-changetracker/spec/spec.rhino.js create mode 100644 javascript/jquery-changetracker/vendor/TrivialReporter.js create mode 100644 javascript/jquery-changetracker/vendor/jasmine.css create mode 100755 javascript/jquery-changetracker/vendor/jasmine.js create mode 100644 javascript/jquery-changetracker/vendor/jquery-1.3.2.js diff --git a/javascript/jquery-changetracker/README.md b/javascript/jquery-changetracker/README.md new file mode 100644 index 00000000..81b85b85 --- /dev/null +++ b/javascript/jquery-changetracker/README.md @@ -0,0 +1,17 @@ +# jquery.changetracker - Change tracking for forms # + +## Setup ## + + jQuery(').changetracker(); + +## Usage ## + +Finding out if the form has changed: + jQuery(').is('.changed'); + +## Options ## + +* fieldSelector: jQuery selector string for tracked fields + (Default: ':input:not(:submit),:select:not(:submit)') +* ignoreFieldSelector: jQuery selector string for specifically excluded fields +* changedCssClass: CSS class attribute which is appended to all changed fields and the form itself \ No newline at end of file diff --git a/javascript/jquery-changetracker/lib/jquery.changetracker.js b/javascript/jquery-changetracker/lib/jquery.changetracker.js new file mode 100644 index 00000000..136fe344 --- /dev/null +++ b/javascript/jquery-changetracker/lib/jquery.changetracker.js @@ -0,0 +1,103 @@ +(function($){ + // Copyright (c) 2009, SilverStripe Ltd. + // All rights reserved. + // + // Redistribution and use in source and binary forms, with or without + // modification, are permitted provided that the following conditions are met: + // * Redistributions of source code must retain the above copyright + // notice, this list of conditions and the following disclaimer. + // * Redistributions in binary form must reproduce the above copyright + // notice, this list of conditions and the following disclaimer in the + // documentation and/or other materials provided with the distribution. + // * Neither the name of the nor the + // names of its contributors may be used to endorse or promote products + // derived from this software without specific prior written permission. + // + // THIS SOFTWARE IS PROVIDED BY SilverStripe Ltd. ''AS IS'' AND ANY + // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + // DISCLAIMED. IN NO EVENT SHALL SilverStripe Ltd. BE LIABLE FOR ANY + // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + /** + * @class Tracks onchange events on all form fields. + * + * @todo Implement form reset handling + * + * @name jQuery.changetracker + * @author Ingo Schommer, SilverStripe Ltd. + * @license BSD License + */ + $.fn.changetracker = function(_options) { + if (this.length > 1){ + this.each(function() { $(this).changetracker(_options); }); + return this; + } + + this.defaults = { + fieldSelector: ':input:not(:submit),:select:not(:submit)', + ignoreFieldSelector: "", + changedCssClass: 'changed' + }; + + var options = $.extend({}, this.defaults, _options); + + this.initialize = function() { + var self = this; + + // optional metadata plugin support + if ($.meta) options = $.extend({}, options, this.data()); + + // setup original values + this.getFields().each(function() { + $(this).data('changetracker.origVal', $(this).val()); + }) + .bind('change', function(e) { + var $field = $(e.target); + var origVal = $field.data('changetracker.origVal'); + if(origVal === null || $field.val() != origVal) { + $field.addClass(options.changedCssClass); + self.addClass(options.changedCssClass); + } + }); + }; + + /** + * Reset change state of all form fields and the form itself. + */ + this.reset = function() { + console.debug('reset called'); + var self = this; + + this.getFields().each(function() { + self.resetField(this); + }); + + this.removeClass(options.changedCssClass); + }; + + /** + * Reset the change single form field. + * Does not reset to the original value. + * + * @param DOMElement field + */ + this.resetField = function(field) { + return $(field).data('changetracker', null); + }; + + /** + * @return jQuery Collection of fields + */ + this.getFields = function() { + return this.find(options.fieldSelector).not(options.ignoreFieldSelector); + }; + + return this.initialize(); + }; +}(jQuery)); \ No newline at end of file diff --git a/javascript/jquery-changetracker/spec/.tmp_spec.html.49538~ b/javascript/jquery-changetracker/spec/.tmp_spec.html.49538~ new file mode 100644 index 00000000..3b96db71 --- /dev/null +++ b/javascript/jquery-changetracker/spec/.tmp_spec.html.49538~ @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + +

JSpec

+
+
+ + \ No newline at end of file diff --git a/javascript/jquery-changetracker/spec/.tmp_spec.html.8678~ b/javascript/jquery-changetracker/spec/.tmp_spec.html.8678~ new file mode 100644 index 00000000..5dd2c7c0 --- /dev/null +++ b/javascript/jquery-changetracker/spec/.tmp_spec.html.8678~ @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + +

JSpec

+
+
+ + \ No newline at end of file diff --git a/javascript/jquery-changetracker/spec/.tmp_spec.html.970~ b/javascript/jquery-changetracker/spec/.tmp_spec.html.970~ new file mode 100644 index 00000000..f725363f --- /dev/null +++ b/javascript/jquery-changetracker/spec/.tmp_spec.html.970~ @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + +

JSpec

+
+
+ + \ No newline at end of file diff --git a/javascript/jquery-changetracker/spec/.tmp_spec_selectors.html.13329~ b/javascript/jquery-changetracker/spec/.tmp_spec_selectors.html.13329~ new file mode 100644 index 00000000..3b96db71 --- /dev/null +++ b/javascript/jquery-changetracker/spec/.tmp_spec_selectors.html.13329~ @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + +

JSpec

+
+
+ + \ No newline at end of file diff --git a/javascript/jquery-changetracker/spec/.tmp_speed.html.70283~ b/javascript/jquery-changetracker/spec/.tmp_speed.html.70283~ new file mode 100644 index 00000000..59ffd70c --- /dev/null +++ b/javascript/jquery-changetracker/spec/.tmp_speed.html.70283~ @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + +
Regular is
+
Fast is
+
    +
+
+
+
+
+
+
+
+
+
+ + \ No newline at end of file diff --git a/javascript/jquery-changetracker/spec/spec.changetracker.basics.js b/javascript/jquery-changetracker/spec/spec.changetracker.basics.js new file mode 100644 index 00000000..3e516e70 --- /dev/null +++ b/javascript/jquery-changetracker/spec/spec.changetracker.basics.js @@ -0,0 +1,128 @@ +describe 'ChangeTracker' + describe 'Basics' + before_each + $('body').append( + '
' + ); + end + after_each + $('#form_test').remove() + end + + it 'doesnt mark unaltered forms as changed' + $('#form_test').append( + '' + ); + + $('#form_test').changetracker(); + + $('#form_test').is('.changed').should.be_false + end + + it 'can track changes on input type=text fields with existing values' + $('#form_test').append( + '' + ); + + $('#form_test').changetracker(); + + $(':input[name=field_text]').val('newval').trigger('change'); + $('#form_test').is('.changed').should.be_true + $(':input[name=field_text]').is('.changed').should.be_true + end + + it 'can track changes on input type=radio fields with existing values' + $('#form_test').append( + '' + + '' + ); + + $('#form_test').changetracker(); + + $(':input[name=field_radio]').val(1).trigger('change'); + $('#form_test').is('.changed').should.be_true + $(':input[name=field_radio]').is('.changed').should.be_true + end + + it 'can track changes on select fields with existing values' + $('#form_test').append( + '' + ); + + $('#form_test').changetracker(); + + $(':input[name=field_select]').val(2).trigger('change'); + $('#form_test').is('.changed').should.be_true + $(':input[name=field_select]').is('.changed').should.be_true + end + + it 'can exclude certain fields via an optional selector' + $('#form_test').append( + '' + + '' + ); + + $('#form_test').changetracker({ + ignoreFieldSelector: ':input[name=field_text_ignored]' + }); + + $(':input[name=field_text_ignored]').val('newval').trigger('change'); + $('#form_test').is('.changed').should.be_false + $(':input[name=field_text_ignored]').is('.changed').should.be_false + + $(':input[name=field_text]').val('newval').trigger('change'); + $('#form_test').is('.changed').should.be_true + $(':input[name=field_text]').is('.changed').should.be_true + end + + it 'can attach custom CSS classes for tracking changed state' + $('#form_test').append( + '' + ); + + $('#form_test').changetracker({ + changedCssClass: 'customchanged' + }); + + $(':input[name=field_text]').val('newval').trigger('change'); + $('#form_test').hasClass('changed').should.be_false + $('#form_test').hasClass('customchanged').should.be_true + $(':input[name=field_text]').hasClass('changed').should.be_false + $(':input[name=field_text]').hasClass('customchanged').should.be_true + end + + it 'can reset changed state of individual fields' + $('#form_test').append( + '' + + '' + ); + $('#form_test').changetracker(); + + $(':input[name=field_text1]').val('newval').trigger('change'); + $(':input[name=field_text2]').val('newval').trigger('change'); + $('#form_test').changetracker('resetfield', $(':input[name=field_text1]')); + $(':input[name=field_text1]').is('.changed').should.be_false + $(':input[name=field_text2]').is('.changed').should.be_true + $('#form_test').is('.changed').should.be_true + end + + it 'can reset all fields in the form' + $('#form_test').append( + '' + + '' + ); + $('#form_test').changetracker(); + + $(':input[name=field_text1]').val('newval').trigger('change'); + $(':input[name=field_text2]').val('newval').trigger('change'); + $('#form_test').changetracker('reset'); + $(':input[name=field_text1]').is('.changed').should.be_false + $(':input[name=field_text2]').is('.changed').should.be_false + $('#form_test').is('.changed').should.be_false + end + + end +end diff --git a/javascript/jquery-changetracker/spec/spec.core.js b/javascript/jquery-changetracker/spec/spec.core.js new file mode 100644 index 00000000..953e6d61 --- /dev/null +++ b/javascript/jquery-changetracker/spec/spec.core.js @@ -0,0 +1,8 @@ + +describe 'YourLib' + describe '.someMethod()' + it 'should do something' + true.should.be true + end + end +end \ No newline at end of file diff --git a/javascript/jquery-changetracker/spec/spec.dom.html b/javascript/jquery-changetracker/spec/spec.dom.html new file mode 100644 index 00000000..1b0aab59 --- /dev/null +++ b/javascript/jquery-changetracker/spec/spec.dom.html @@ -0,0 +1,24 @@ + + + + + + + + + + + + +

JSpec

+
+
+ + \ No newline at end of file diff --git a/javascript/jquery-changetracker/spec/spec.rhino.js b/javascript/jquery-changetracker/spec/spec.rhino.js new file mode 100644 index 00000000..e36b9283 --- /dev/null +++ b/javascript/jquery-changetracker/spec/spec.rhino.js @@ -0,0 +1,9 @@ +load('vendor/jquery-1.3.2.js') +load('vendor/jspec/lib/jspec.js') +load('vendor/jspec/lib/jspec.jquery.js') +load('lib/jquery.changetracker.js') + +JSpec +.exec('spec/spec.changetracker.basics.js') +.run({ formatter : JSpec.formatters.Terminal }) +.report() \ No newline at end of file diff --git a/javascript/jquery-changetracker/vendor/TrivialReporter.js b/javascript/jquery-changetracker/vendor/TrivialReporter.js new file mode 100644 index 00000000..14c35ea8 --- /dev/null +++ b/javascript/jquery-changetracker/vendor/TrivialReporter.js @@ -0,0 +1,61 @@ +jasmine.TrivialReporter = function() { +}; + +jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) { + var el = document.createElement(type); + + for (var i = 2; i < arguments.length; i++) { + var child = arguments[i]; + + if (typeof child === 'string') { + el.appendChild(document.createTextNode(child)); + } else { + el.appendChild(child); + } + } + + for (var attr in attrs) { + if (attr == 'className') { + el.setAttribute('class', attrs[attr]); + } else { + if (attr.indexOf('x-') == 0) { + el.setAttribute(attr, attrs[attr]); + } else { + el[attr] = attrs[attr]; + } + } + } + + return el; +}; + +jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) { + console.log(runner); +}; + +jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) { + console.log(suite); +}; + +jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) { + var specDiv = this.createDom('div', { + className: spec.getResults().passed() ? 'spec passed' : 'spec failed' + }, spec.getFullName()); + + var resultItems = spec.getResults().getItems(); + for (var i = 0; i < resultItems.length; i++) { + var result = resultItems[i]; + if (!result.passed) { + var resultMessageDiv = this.createDom('div', {className: 'resultMessage'}); + resultMessageDiv.innerHTML = result.message; // todo: lame; mend + specDiv.appendChild(resultMessageDiv); + specDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack)); + } + } + + document.body.appendChild(specDiv); +}; + +jasmine.TrivialReporter.prototype.log = function() { + console.log.apply(console, arguments); +}; diff --git a/javascript/jquery-changetracker/vendor/jasmine.css b/javascript/jquery-changetracker/vendor/jasmine.css new file mode 100644 index 00000000..0c302a2c --- /dev/null +++ b/javascript/jquery-changetracker/vendor/jasmine.css @@ -0,0 +1,24 @@ +body { + font: 14px "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; + padding-left: 40px; +} + +h1 { + padding-top: 20px; + font-weight: bold; + font: 24px; /* "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; */ +} + +p { + margin-top: 5px; + padding-left: 20px; +} + +p.fail { + background: url( ../images/fail-16.png ) no-repeat; + color: red; +} + +p.fail_in_summary { + color: red; +} \ No newline at end of file diff --git a/javascript/jquery-changetracker/vendor/jasmine.js b/javascript/jquery-changetracker/vendor/jasmine.js new file mode 100755 index 00000000..00bb5e6d --- /dev/null +++ b/javascript/jquery-changetracker/vendor/jasmine.js @@ -0,0 +1,900 @@ +/* + * Jasmine internal classes & objects + */ + +var Jasmine = {}; + +Jasmine.util = {}; + +/** @deprecated Use Jasmine.util instead */ +Jasmine.Util = Jasmine.util; + +Jasmine.util.inherit = function(childClass, parentClass) { + var subclass = function() { }; + subclass.prototype = parentClass.prototype; + childClass.prototype = new subclass; +}; + +/* + * Holds results; allows for the results array to hold another Jasmine.NestedResults + */ +Jasmine.NestedResults = function() { + this.totalCount = 0; + this.passedCount = 0; + this.failedCount = 0; + this.results = []; +}; + +Jasmine.NestedResults.prototype.rollupCounts = function(result) { + this.totalCount += result.totalCount; + this.passedCount += result.passedCount; + this.failedCount += result.failedCount; +}; + +Jasmine.NestedResults.prototype.push = function(result) { + if (result.results) { + this.rollupCounts(result); + } else { + this.totalCount++; + if (result.passed) { + this.passedCount++; + } else { + this.failedCount++; + } + } + this.results.push(result); +}; + +Jasmine.NestedResults.prototype.passed = function() { + return this.passedCount === this.totalCount; +}; + + +/* + * base for Runner & Suite: allows for a queue of functions to get executed, allowing for + * any one action to complete, including asynchronous calls, before going to the next + * action. + * + **/ +Jasmine.ActionCollection = function() { + this.actions = []; + this.index = 0; + this.finished = false; + this.results = new Jasmine.NestedResults(); +}; + +Jasmine.ActionCollection.prototype.finish = function() { + if (this.finishCallback) { + this.finishCallback(); + } + this.finished = true; +}; + +Jasmine.ActionCollection.prototype.report = function(result) { + this.results.push(result); +}; + +Jasmine.ActionCollection.prototype.execute = function() { + if (this.actions.length > 0) { + this.next(); + } +}; + +Jasmine.ActionCollection.prototype.getCurrentAction = function() { + return this.actions[this.index]; +}; + +Jasmine.ActionCollection.prototype.next = function() { + if (this.index >= this.actions.length) { + this.finish(); + return; + } + + var currentAction = this.getCurrentAction(); + + currentAction.execute(this); + + if (currentAction.afterCallbacks) { + for (var i = 0; i < currentAction.afterCallbacks.length; i++) { + try { + currentAction.afterCallbacks[i](); + } catch (e) { + alert(e); + } + } + } + + this.waitForDone(currentAction); +}; + +Jasmine.ActionCollection.prototype.waitForDone = function(action) { + var self = this; + var afterExecute = function() { + self.report(action.results); + self.index++; + self.next(); + }; + + if (action.finished) { + afterExecute(); + return; + } + + var id = setInterval(function() { + if (action.finished) { + clearInterval(id); + afterExecute(); + } + }, 150); +}; + +Jasmine.safeExecuteBeforeOrAfter = function(spec, func) { + try { + func.apply(spec); + } catch (e) { + var fail = {passed: false, message: func.typeName + '() fail: ' + Jasmine.util.formatException(e)}; + spec.results.push(fail); + } +}; + +/* + * QueuedFunction is how ActionCollections' actions are implemented + */ +Jasmine.QueuedFunction = function(func, timeout, latchFunction, spec) { + this.func = func; + this.timeout = timeout; + this.latchFunction = latchFunction; + this.spec = spec; + + this.totalTimeSpentWaitingForLatch = 0; + this.latchTimeoutIncrement = 100; +}; + +Jasmine.QueuedFunction.prototype.next = function() { + this.spec.finish(); // default value is to be done after one function +}; + +Jasmine.QueuedFunction.prototype.safeExecute = function() { + if (console) { + console.log('>> Jasmine Running ' + this.spec.suite.description + ' ' + this.spec.description + '...'); + } + + try { + this.func.apply(this.spec); + } catch (e) { + this.fail(e); + } +}; + +Jasmine.QueuedFunction.prototype.execute = function() { + var self = this; + var executeNow = function() { + self.safeExecute(); + self.next(); + }; + + var executeLater = function() { + setTimeout(executeNow, self.timeout); + }; + + var executeNowOrLater = function() { + var latchFunctionResult; + + try { + latchFunctionResult = self.latchFunction.apply(self.spec); + } catch (e) { + self.fail(e); + self.next(); + return; + } + + if (latchFunctionResult) { + executeNow(); + } else if (self.totalTimeSpentWaitingForLatch >= self.timeout) { + var message = 'timed out after ' + self.timeout + ' msec waiting for ' + (self.latchFunction.description || 'something to happen'); + self.fail({ name: 'timeout', message: message }); + self.next(); + } else { + self.totalTimeSpentWaitingForLatch += self.latchTimeoutIncrement; + setTimeout(executeNowOrLater, self.latchTimeoutIncrement); + } + }; + + if (this.latchFunction !== undefined) { + executeNowOrLater(); + } else if (this.timeout > 0) { + executeLater(); + } else { + executeNow(); + } +}; + +Jasmine.QueuedFunction.prototype.fail = function(e) { + this.spec.results.push({passed:false, message: Jasmine.util.formatException(e)}); +}; + +/****************************************************************************** + * Jasmine + ******************************************************************************/ + +Jasmine.Env = function() { + this.currentSpec = null; + this.currentSuite = null; + this.currentRunner = null; +}; + +Jasmine.Env.prototype.execute = function() { + this.currentRunner.execute(); +}; + +Jasmine.currentEnv_ = new Jasmine.Env(); + +/** @deprecated use Jasmine.getEnv() instead */ +var jasmine = Jasmine.currentEnv_; + +Jasmine.getEnv = function() { + return Jasmine.currentEnv_; +}; + +Jasmine.isArray_ = function(value) { + return value && + typeof value === 'object' && + typeof value.length === 'number' && + typeof value.splice === 'function' && + !(value.propertyIsEnumerable('length')); +}; + +Jasmine.arrayToString_ = function(array) { + var formatted_value = ''; + for (var i = 0; i < array.length; i++) { + if (i > 0) { + formatted_value += ', '; + } + ; + formatted_value += Jasmine.pp(array[i]); + } + return '[ ' + formatted_value + ' ]'; +}; + +Jasmine.objectToString_ = function(obj) { + var formatted_value = ''; + var first = true; + for (var property in obj) { + if (property == '__Jasmine_pp_has_traversed__') continue; + + if (first) { + first = false; + } else { + formatted_value += ', '; + } + formatted_value += property; + formatted_value += ' : '; + formatted_value += Jasmine.pp(obj[property]); + } + + return '{ ' + formatted_value + ' }'; +}; + +Jasmine.ppNestLevel_ = 0; + +Jasmine.pp = function(value) { + if (Jasmine.ppNestLevel_ > 40) { +// return '(Jasmine.pp nested too deeply!)'; + throw new Error('Jasmine.pp nested too deeply!'); + } + + Jasmine.ppNestLevel_++; + try { + return Jasmine.pp_(value); + } finally { + Jasmine.ppNestLevel_--; + } +}; + +Jasmine.pp_ = function(value) { + if (value === undefined) { + return 'undefined'; + } + if (value === null) { + return 'null'; + } + + if (value.navigator && value.frames && value.setTimeout) { + return ''; + } + + if (value instanceof Jasmine.Any) return value.toString(); + + if (typeof value === 'string') { + return "'" + Jasmine.util.htmlEscape(value) + "'"; + } + if (typeof value === 'function') { + return 'Function'; + } + if (typeof value.nodeType === 'number') { + return 'HTMLNode'; + } + + if (value.__Jasmine_pp_has_traversed__) { + return ''; + } + + if (Jasmine.isArray_(value) || typeof value == 'object') { + value.__Jasmine_pp_has_traversed__ = true; + var stringified = Jasmine.isArray_(value) ? Jasmine.arrayToString_(value) : Jasmine.objectToString_(value); + delete value.__Jasmine_pp_has_traversed__; + return stringified; + } + + return Jasmine.util.htmlEscape(value.toString()); +}; + +/* + * Jasmine.Matchers methods; add your own by extending Jasmine.Matchers.prototype - don't forget to write a test + * + */ + +Jasmine.Matchers = function(actual, results) { + this.actual = actual; + this.passing_message = 'Passed.'; + this.results = results || new Jasmine.NestedResults(); +}; + +Jasmine.Matchers.prototype.report = function(result, failing_message) { + + this.results.push({ + passed: result, + message: result ? this.passing_message : failing_message + }); + + return result; +}; + +Jasmine.isDomNode = function(obj) { + return obj['nodeType'] > 0; +}; + +Jasmine.Any = function(expectedClass) { + this.expectedClass = expectedClass; +}; + +Jasmine.Any.prototype.matches = function(other) { + if (this.expectedClass == String) { + return typeof other == 'string' || other instanceof String; + } + + if (this.expectedClass == Number) { + return typeof other == 'number' || other instanceof Number; + } + + return other instanceof this.expectedClass; +}; + +Jasmine.Any.prototype.toString = function() { + return ''; +}; + +Jasmine.any = function(clazz) { + return new Jasmine.Any(clazz); +}; + +Jasmine.Matchers.prototype.toEqual = function(expected) { + var mismatchKeys = []; + var mismatchValues = []; + + var hasKey = function(obj, keyName) { + return obj!=null && obj[keyName] !== undefined; + }; + + var equal = function(a, b) { + if (a == undefined || a == null) { + return (a == undefined && b == undefined); + } + + if (Jasmine.isDomNode(a) && Jasmine.isDomNode(b)) { + return a === b; + } + + if (typeof a === "object" && typeof b === "object") { + for (var property in b) { + if (!hasKey(a, property) && hasKey(b, property)) { + mismatchKeys.push("expected has key '" + property + "', but missing from actual."); + } + } + for (property in a) { + if (!hasKey(b, property) && hasKey(a, property)) { + mismatchKeys.push("expected missing key '" + property + "', but present in actual."); + } + } + for (property in b) { + if (!equal(a[property], b[property])) { + mismatchValues.push("'" + property + "' was

'" + (b[property] ? Jasmine.util.htmlEscape(b[property].toString()) : b[property]) + "'

in expected, but was

'" + (a[property] ? Jasmine.util.htmlEscape(a[property].toString()) : a[property]) + "'

in actual.
"); + } + } + + + return (mismatchKeys.length == 0 && mismatchValues.length == 0); + } + + if (b instanceof Jasmine.Any) { + return b.matches(a); + } + + // functions are considered equivalent if their bodies are equal // todo: remove this + if (typeof a === "function" && typeof b === "function") { + return a.toString() == b.toString(); + } + + //Straight check + return (a === b); + }; + + var formatMismatches = function(name, array) { + if (array.length == 0) return ''; + var errorOutput = '

Different ' + name + ':
'; + for (var i = 0; i < array.length; i++) { + errorOutput += array[i] + '
'; + } + return errorOutput; + + }; + + return this.report(equal(this.actual, expected), + 'Expected

' + Jasmine.pp(expected) + + '

but got

' + Jasmine.pp(this.actual) + + '
' + + formatMismatches('Keys', mismatchKeys) + + formatMismatches('Values', mismatchValues)); +}; +/** @deprecated */ +Jasmine.Matchers.prototype.should_equal = Jasmine.Matchers.prototype.toEqual; + +Jasmine.Matchers.prototype.toNotEqual = function(expected) { + return this.report((this.actual !== expected), + 'Expected ' + Jasmine.pp(expected) + ' to not equal ' + Jasmine.pp(this.actual) + ', but it does.'); +}; +/** @deprecated */ +Jasmine.Matchers.prototype.should_not_equal = Jasmine.Matchers.prototype.toNotEqual; + +Jasmine.Matchers.prototype.toMatch = function(reg_exp) { + return this.report((new RegExp(reg_exp).test(this.actual)), + 'Expected ' + this.actual + ' to match ' + reg_exp + '.'); +}; +/** @deprecated */ +Jasmine.Matchers.prototype.should_match = Jasmine.Matchers.prototype.toMatch; + +Jasmine.Matchers.prototype.toNotMatch = function(reg_exp) { + return this.report((!new RegExp(reg_exp).test(this.actual)), + 'Expected ' + this.actual + ' to not match ' + reg_exp + '.'); +}; +/** @deprecated */ +Jasmine.Matchers.prototype.should_not_match = Jasmine.Matchers.prototype.toNotMatch; + +Jasmine.Matchers.prototype.toBeDefined = function() { + return this.report((this.actual !== undefined), + 'Expected a value to be defined but it was undefined.'); +}; +/** @deprecated */ +Jasmine.Matchers.prototype.should_be_defined = Jasmine.Matchers.prototype.toBeDefined; + +Jasmine.Matchers.prototype.toBeNull = function() { + return this.report((this.actual === null), + 'Expected a value to be null but it was not.'); +}; +/** @deprecated */ +Jasmine.Matchers.prototype.should_be_null = Jasmine.Matchers.prototype.toBeNull; + +Jasmine.Matchers.prototype.toBeTruthy = function() { + return this.report((this.actual), + 'Expected a value to be truthy but it was not.'); +}; +/** @deprecated */ +Jasmine.Matchers.prototype.should_be_truthy = Jasmine.Matchers.prototype.toBeTruthy; + +Jasmine.Matchers.prototype.toBeFalsy = function() { + return this.report((!this.actual), + 'Expected a value to be falsy but it was not.'); +}; +/** @deprecated */ +Jasmine.Matchers.prototype.should_be_falsy = Jasmine.Matchers.prototype.toBeFalsy; + +Jasmine.Matchers.prototype.wasCalled = function() { + if (!this.actual.isSpy) { + return this.report(false, 'Expected value to be a spy, but it was not.'); + } + return this.report((this.actual.wasCalled), + 'Expected spy to have been called, but it was not.'); +}; +/** @deprecated */ +Jasmine.Matchers.prototype.was_called = Jasmine.Matchers.prototype.wasCalled; + +Jasmine.Matchers.prototype.wasNotCalled = function() { + if (!this.actual.isSpy) { + return this.report(false, 'Expected value to be a spy, but it was not.'); + } + return this.report((!this.actual.wasCalled), + 'Expected spy to not have been called, but it was.'); +}; +/** @deprecated */ +Jasmine.Matchers.prototype.was_not_called = Jasmine.Matchers.prototype.wasNotCalled; + +Jasmine.Matchers.prototype.wasCalledWith = function() { + if (!this.wasCalled()) return false; + var argMatcher = new Jasmine.Matchers(this.actual.mostRecentCall.args, this.results); + return argMatcher.toEqual(Jasmine.util.argsToArray(arguments)); +}; +/** @deprecated */ +Jasmine.Matchers.prototype.was_called = Jasmine.Matchers.prototype.wasCalled; + +Jasmine.Matchers.prototype.toContain = function(item) { + return this.report((this.actual.indexOf(item) >= 0), + 'Expected ' + Jasmine.pp(this.actual) + ' to contain ' + Jasmine.pp(item) + ', but it does not.'); +}; + +Jasmine.Matchers.prototype.toNotContain = function(item) { + return this.report((this.actual.indexOf(item) < 0), + 'Expected ' + Jasmine.pp(this.actual) + ' not to contain ' + Jasmine.pp(item) + ', but it does.'); +}; + +Jasmine.createSpy = function() { + var spyObj = function() { + spyObj.wasCalled = true; + spyObj.callCount++; + var args = Jasmine.util.argsToArray(arguments); + spyObj.mostRecentCall = { + object: this, + args: args + }; + spyObj.argsForCall.push(args); + return spyObj.plan.apply(this, arguments); + }; + + spyObj.isSpy = true; + + spyObj.plan = function() { + }; + + spyObj.andCallThrough = function() { + spyObj.plan = spyObj.originalValue; + return spyObj; + }; + spyObj.andReturn = function(value) { + spyObj.plan = function() { + return value; + }; + return spyObj; + }; + spyObj.andThrow = function(exceptionMsg) { + spyObj.plan = function() { + throw exceptionMsg; + }; + return spyObj; + }; + spyObj.andCallFake = function(fakeFunc) { + spyObj.plan = fakeFunc; + return spyObj; + }; + spyObj.reset = function() { + spyObj.wasCalled = false; + spyObj.callCount = 0; + spyObj.argsForCall = []; + spyObj.mostRecentCall = {}; + }; + spyObj.reset(); + + return spyObj; +}; + +Jasmine.spyOn = function(obj, methodName) { + var spec = Jasmine.getEnv().currentSpec; + spec.after(function() { + spec.removeAllSpies(); + }); + + if (obj == undefined) { + throw "spyOn could not find an object to spy upon"; + } + + if (obj[methodName] === undefined) { + throw methodName + '() method does not exist'; + } + + if (obj[methodName].isSpy) { + throw new Error(methodName + ' has already been spied upon'); + } + + var spyObj = Jasmine.createSpy(); + + spec.spies_.push(spyObj); + spyObj.baseObj = obj; + spyObj.methodName = methodName; + spyObj.originalValue = obj[methodName]; + + obj[methodName] = spyObj; + + return spyObj; +}; + +var spyOn = Jasmine.spyOn; + +/* + * Jasmine spec constructor + */ + +Jasmine.Spec = function(description) { + this.suite = null; + this.description = description; + this.queue = []; + this.currentTimeout = 0; + this.currentLatchFunction = undefined; + this.finished = false; + this.afterCallbacks = []; + this.spies_ = []; + + this.results = new Jasmine.NestedResults(); +}; + +Jasmine.Spec.prototype.freezeSuite = function(suite) { + this.suite = suite; +}; + +/** @deprecated */ +Jasmine.Spec.prototype.expects_that = function(actual) { + return new Jasmine.Matchers(actual, this.results); +}; + +Jasmine.Spec.prototype.waits = function(timeout) { + this.currentTimeout = timeout; + this.currentLatchFunction = undefined; + return this; +}; + +Jasmine.Spec.prototype.waitsFor = function(timeout, latchFunction, message) { + this.currentTimeout = timeout; + this.currentLatchFunction = latchFunction; + this.currentLatchFunction.description = message; + return this; +}; + +Jasmine.Spec.prototype.resetTimeout = function() { + this.currentTimeout = 0; + this.currentLatchFunction = undefined; +}; + +Jasmine.Spec.prototype.finishCallback = function() { + if (Jasmine.getEnv().reporter) { + Jasmine.getEnv().reporter.reportSpecResults(this.results); + } +}; + +Jasmine.Spec.prototype.finish = function() { + if (this.suite.afterEach) { + Jasmine.safeExecuteBeforeOrAfter(this, this.suite.afterEach); + } + this.finishCallback(); + this.finished = true; +}; + +Jasmine.Spec.prototype.after = function(doAfter) { + this.afterCallbacks.push(doAfter); +}; + +Jasmine.Spec.prototype.execute = function() { + Jasmine.getEnv().currentSpec = this; + if (this.suite.beforeEach) { + Jasmine.safeExecuteBeforeOrAfter(this, this.suite.beforeEach); + } + if (this.queue[0]) { + this.queue[0].execute(); + } else { + this.finish(); + } +}; + +Jasmine.Spec.prototype.explodes = function() { + throw 'explodes function should not have been called'; +}; + +Jasmine.Spec.prototype.spyOn = Jasmine.spyOn; + +Jasmine.Spec.prototype.removeAllSpies = function() { + for (var i = 0; i < this.spies_.length; i++) { + var spy = this.spies_[i]; + spy.baseObj[spy.methodName] = spy.originalValue; + } + this.spies_ = []; +}; + +var it = function(description, func) { + var that = new Jasmine.Spec(description); + + var addToQueue = function(func) { + var currentFunction = new Jasmine.QueuedFunction(func, that.currentTimeout, that.currentLatchFunction, that); + that.queue.push(currentFunction); + + if (that.queue.length > 1) { + var previousFunction = that.queue[that.queue.length - 2]; + previousFunction.next = function() { + currentFunction.execute(); + }; + } + + that.resetTimeout(); + return that; + }; + + that.expectationResults = that.results.results; + that.runs = addToQueue; + that.freezeSuite(Jasmine.getEnv().currentSuite); + + Jasmine.getEnv().currentSuite.specs.push(that); + + Jasmine.getEnv().currentSpec = that; + + if (func) { + addToQueue(func); + } + + that.results.description = description; + return that; +}; + +//this mirrors the spec syntax so you can define a spec description that will not run. +var xit = function() { + return {runs: function() { + } }; +}; + +var expect = function() { + return Jasmine.getEnv().currentSpec.expects_that.apply(Jasmine.getEnv().currentSpec, arguments); +}; + +var runs = function(func) { + Jasmine.getEnv().currentSpec.runs(func); +}; + +var waits = function(timeout) { + Jasmine.getEnv().currentSpec.waits(timeout); +}; + +var waitsFor = function(timeout, latchFunction, message) { + Jasmine.getEnv().currentSpec.waitsFor(timeout, latchFunction, message); +}; + +var beforeEach = function(beforeEach) { + beforeEach.typeName = 'beforeEach'; + Jasmine.getEnv().currentSuite.beforeEach = beforeEach; +}; + +var afterEach = function(afterEach) { + afterEach.typeName = 'afterEach'; + Jasmine.getEnv().currentSuite.afterEach = afterEach; +}; + +Jasmine.Description = function(description, specDefinitions) { + Jasmine.ActionCollection.call(this); + + this.description = description; + this.specs = this.actions; +}; +Jasmine.util.inherit(Jasmine.Description, Jasmine.ActionCollection); + +var describe = function(description, spec_definitions) { + var that = new Jasmine.Description(description, spec_definitions); + + Jasmine.getEnv().currentSuite = that; + Jasmine.getEnv().currentRunner.suites.push(that); + + spec_definitions(); + + that.results.description = description; + that.specResults = that.results.results; + + that.finishCallback = function() { + if (Jasmine.getEnv().reporter) { + Jasmine.getEnv().reporter.reportSuiteResults(that.results); + } + }; + + return that; +}; + +var xdescribe = function() { + return {execute: function() { + }}; +}; + +Jasmine.Runner = function() { + Jasmine.ActionCollection.call(this); + + this.suites = this.actions; + this.results.description = 'All Jasmine Suites'; +}; +Jasmine.util.inherit(Jasmine.Runner, Jasmine.ActionCollection); + +var Runner = function() { + var that = new Jasmine.Runner(); + + that.finishCallback = function() { + if (Jasmine.getEnv().reporter) { + Jasmine.getEnv().reporter.reportRunnerResults(that.results); + } + }; + + that.suiteResults = that.results.results; + + Jasmine.getEnv().currentRunner = that; + return that; +}; + +Jasmine.getEnv().currentRunner = Runner(); + +/* JasmineReporters.reporter + * Base object that will get called whenever a Spec, Suite, or Runner is done. It is up to + * descendants of this object to do something with the results (see json_reporter.js) + */ +Jasmine.Reporters = {}; + +Jasmine.Reporters.reporter = function(callbacks) { + var that = { + callbacks: callbacks || {}, + + doCallback: function(callback, results) { + if (callback) { + callback(results); + } + }, + + reportRunnerResults: function(results) { + that.doCallback(that.callbacks.runnerCallback, results); + }, + reportSuiteResults: function(results) { + that.doCallback(that.callbacks.suiteCallback, results); + }, + reportSpecResults: function(results) { + that.doCallback(that.callbacks.specCallback, results); + } + }; + + return that; +}; + +Jasmine.util.formatException = function(e) { + var lineNumber; + if (e.line) { + lineNumber = e.line; + } + else if (e.lineNumber) { + lineNumber = e.lineNumber; + } + + var file; + + if (e.sourceURL) { + file = e.sourceURL; + } + else if (e.fileName) { + file = e.fileName; + } + + var message = (e.name && e.message) ? (e.name + ': ' + e.message) : e.toString(); + + if (file && lineNumber) { + message += ' in ' + file + ' (line ' + lineNumber + ')'; + } + + return message; +}; + +Jasmine.util.htmlEscape = function(str) { + if (!str) return str; + return str.replace(/&/g, '&') + .replace(//g, '>'); +}; + +Jasmine.util.argsToArray = function(args) { + var arrayOfArgs = []; + for (var i = 0; i < args.length; i++) arrayOfArgs.push(args[i]); + return arrayOfArgs; +}; diff --git a/javascript/jquery-changetracker/vendor/jquery-1.3.2.js b/javascript/jquery-changetracker/vendor/jquery-1.3.2.js new file mode 100644 index 00000000..92635743 --- /dev/null +++ b/javascript/jquery-changetracker/vendor/jquery-1.3.2.js @@ -0,0 +1,4376 @@ +/*! + * jQuery JavaScript Library v1.3.2 + * http://jquery.com/ + * + * Copyright (c) 2009 John Resig + * Dual licensed under the MIT and GPL licenses. + * http://docs.jquery.com/License + * + * Date: 2009-02-19 17:34:21 -0500 (Thu, 19 Feb 2009) + * Revision: 6246 + */ +(function(){ + +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 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 = /^.[^:#\[\.,]*$/; + +jQuery.fn = jQuery.prototype = { + init: function( selector, context ) { + // Make sure that a selection was provided + selector = selector || document; + + // Handle $(DOMElement) + if ( selector.nodeType ) { + this[0] = selector; + 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 ); + + // 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 ); + + // HANDLE: $("#id") + else { + var elem = document.getElementById( match[3] ); + + // 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 + var ret = jQuery( elem || [] ); + ret.context = document; + ret.selector = selector; + return ret; + } + + // HANDLE: $(expr, [context]) + // (which is just equivalent to: $(content).find(expr) + } else + return jQuery( context ).find( selector ); + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( jQuery.isFunction( selector ) ) + return jQuery( document ).ready( selector ); + + // Make sure that old selector state is passed along + if ( selector.selector && selector.context ) { + this.selector = selector.selector; + this.context = selector.context; + } + + return this.setArray(jQuery.isArray( selector ) ? + selector : + jQuery.makeArray(selector)); + }, + + // Start with an empty selector + selector: "", + + // The current version of jQuery being used + jquery: "1.3.2", + + // The number of elements contained in the matched element set + size: function() { + return this.length; + }, + + // 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 a 'clean' array + Array.prototype.slice.call( this ) : + + // Return just the object + 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 ); + + // 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; + }, + + // 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.) + each: function( callback, args ) { + 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; + + return elem; + }).append(this); + } + + 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 ); + }); + }, + + append: function() { + return this.domManip(arguments, true, function(elem){ + if (this.nodeType == 1) + this.appendChild( elem ); + }); + }, + + prepend: function() { + return this.domManip(arguments, true, function(elem){ + if (this.nodeType == 1) + this.insertBefore( elem, this.firstChild ); + }); + }, + + before: function() { + return this.domManip(arguments, false, function(elem){ + this.parentNode.insertBefore( elem, this ); + }); + }, + + after: function() { + return this.domManip(arguments, false, function(elem){ + this.parentNode.insertBefore( elem, this.nextSibling ); + }); + }, + + end: function() { + return this.prevObject || jQuery( [] ); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: [].push, + sort: [].sort, + 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); + }); + + // Copy the events from the original to the clone + if ( events === true ) { + var orig = this.find("*").andSelf(), i = 0; + + ret.find("*").andSelf().each(function(){ + if ( this.nodeName !== orig[i].nodeName ) + return; + + var events = jQuery.data( orig[i], "events" ); + + for ( var type in events ) { + for ( var handler in events[ type ] ) { + jQuery.event.add( this, type, events[ type ][ handler ], events[ type ][ handler ].data ); + } + } + + i++; + }); + } + + // Return the cloned set + return ret; + }, + + filter: function( selector ) { + return this.pushStack( + jQuery.isFunction( selector ) && + jQuery.grep(this, function(elem, i){ + return selector.call( elem, i ); + }) || + + jQuery.multiFilter( selector, jQuery.grep(this, function(elem){ + return elem.nodeType === 1; + }) ), "filter", selector ); + }, + + closest: function( selector ) { + var pos = jQuery.expr.match.POS.test( selector ) ? jQuery(selector) : null, + closer = 0; + + 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; + } + 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; + }); + }, + + add: function( selector ) { + return this.pushStack( jQuery.unique( jQuery.merge( + this.get(), + typeof selector === "string" ? + jQuery( selector ) : + jQuery.makeArray( selector ) + ))); + }, + + is: function( selector ) { + return !!selector && jQuery.multiFilter( selector, this ).length > 0; + }, + + hasClass: function( selector ) { + return !!selector && this.is( "." + selector ); + }, + + val: function( value ) { + if ( value === undefined ) { + var elem = this[0]; + + if ( elem ) { + if( jQuery.nodeName( elem, 'option' ) ) + return (elem.attributes.value || {}).specified ? elem.value : elem.text; + + // We need to handle select boxes special + if ( jQuery.nodeName( elem, "select" ) ) { + var index = elem.selectedIndex, + values = [], + options = elem.options, + one = elem.type == "select-one"; + + // Nothing was selected + if ( index < 0 ) + return null; + + // Loop through all the selected options + for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) { + var option = options[ i ]; + + if ( option.selected ) { + // Get the specifc value for the option + value = jQuery(option).val(); + + // We don't need an array for one selects + if ( one ) + return value; + + // Multi-Selects return an array + values.push( value ); + } + } + + return values; + } + + // Everything else, we just grab the value + return (elem.value || "").replace(/\r/g, ""); + + } + + return undefined; + } + + if ( typeof value === "number" ) + value += ''; + + return this.each(function(){ + if ( this.nodeType != 1 ) + return; + + if ( jQuery.isArray(value) && /radio|checkbox/.test( this.type ) ) + this.checked = (jQuery.inArray(this.value, value) >= 0 || + jQuery.inArray(this.name, value) >= 0); + + else if ( jQuery.nodeName( this, "select" ) ) { + var values = jQuery.makeArray(value); + + jQuery( "option", this ).each(function(){ + this.selected = (jQuery.inArray( this.value, values ) >= 0 || + jQuery.inArray( this.text, values ) >= 0); + }); + + if ( !values.length ) + this.selectedIndex = -1; + + } else + this.value = value; + }); + }, + + html: function( value ) { + return value === undefined ? + (this[0] ? + this[0].innerHTML.replace(/ jQuery\d+="(?:\d+|null)"/g, "") : + null) : + this.empty().append( value ); + }, + + replaceWith: function( value ) { + return this.after( value ).remove(); + }, + + eq: function( i ) { + return this.slice( i, +i + 1 ); + }, + + slice: function() { + return this.pushStack( Array.prototype.slice.apply( this, arguments ), + "slice", Array.prototype.slice.call(arguments).join(",") ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map(this, function(elem, i){ + return callback.call( elem, i, elem ); + })); + }, + + andSelf: function() { + return this.add( this.prevObject ); + }, + + 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; + + 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; + } + } +}; + +// 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 = {}; + + // 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 ( var name in options ) { + var src = target[ name ], copy = options[ name ]; + + // Prevent never-ending loop + if ( target === copy ) + continue; + + // 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 ); + + // Don't bring in undefined values + else if ( copy !== undefined ) + target[ name ] = copy; + + } + + // Return the modified object + return target; +}; + +// 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; + +jQuery.extend({ + noConflict: function( deep ) { + window.$ = _$; + + if ( deep ) + window.jQuery = _jQuery; + + return jQuery; + }, + + // See test/unit/core.js for details concerning isFunction. + // Since version 1.3, DOM methods and functions like alert + // aren't supported. They return false on IE (#2968). + isFunction: function( obj ) { + return toString.call(obj) === "[object Function]"; + }, + + isArray: function( obj ) { + return toString.call(obj) === "[object Array]"; + }, + + // 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 ); + }, + + // Evalulates a script in a global context + globalEval: function( data ) { + if ( data && /\S/.test(data) ) { + // Inspired by code by Andrea Giammarchi + // http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html + var head = document.getElementsByTagName("head")[0] || document.documentElement, + script = document.createElement("script"); + + script.type = "text/javascript"; + if ( jQuery.support.scriptEval ) + script.appendChild( document.createTextNode( data ) ); + else + script.text = data; + + // Use insertBefore instead of appendChild to circumvent an IE6 bug. + // This arises when a base node is used (#2709). + head.insertBefore( script, head.firstChild ); + head.removeChild( script ); + } + }, + + nodeName: function( elem, name ) { + return elem.nodeName && elem.nodeName.toUpperCase() == name.toUpperCase(); + }, + + // args is for internal usage only + each: function( object, callback, args ) { + var name, i = 0, length = object.length; + + if ( args ) { + if ( length === undefined ) { + 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 ( length === undefined ) { + for ( name in object ) + if ( callback.call( object[ name ], name, object[ name ] ) === false ) + break; + } else + for ( var value = object[0]; + i < length && callback.call( value, i, value ) !== false; value = object[++i] ){} + } + + return object; + }, + + 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; + }); + }, + + // 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(" ") : + ""; + }, + + // internal only, use hasClass("class") + has: function( elem, className ) { + return elem && jQuery.inArray( className, (elem.className || elem).toString().split(/\s+/) ) > -1; + } + }, + + // A method for quickly swapping in/out CSS properties to get correct calculations + swap: function( elem, options, callback ) { + var old = {}; + // Remember the old values, and insert the new ones + for ( var name in options ) { + old[ name ] = elem.style[ name ]; + elem.style[ name ] = options[ name ]; + } + + callback.call( elem ); + + // Revert the old values + for ( var name in options ) + elem.style[ name ] = old[ name ]; + }, + + 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; + + 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 ( elem.offsetWidth !== 0 ) + getWH(); + else + jQuery.swap( elem, props, getWH ); + + return Math.max(0, Math.round(val)); + } + + return jQuery.curCSS( elem, name, force ); + }, + + curCSS: function( elem, name, force ) { + var ret, style = elem.style; + + // We need to handle opacity special in IE + if ( name == "opacity" && !jQuery.support.opacity ) { + ret = jQuery.attr( style, "opacity" ); + + return ret == "" ? + "1" : + ret; + } + + // Make sure we're using the right name for getting the float value + if ( name.match( /float/i ) ) + name = styleFloat; + + if ( !force && style && style[ name ] ) + ret = style[ name ]; + + else if ( defaultView.getComputedStyle ) { + + // Only "float" is needed here + if ( name.match( /float/i ) ) + name = "float"; + + name = name.replace( /([A-Z])/g, "-$1" ).toLowerCase(); + + var computedStyle = defaultView.getComputedStyle( elem, null ); + + if ( computedStyle ) + ret = computedStyle.getPropertyValue( name ); + + // We should always get a number back from opacity + if ( name == "opacity" && ret == "" ) + ret = "1"; + + } else if ( elem.currentStyle ) { + var camelCase = name.replace(/\-(\w)/g, function(all, letter){ + return letter.toUpperCase(); + }); + + ret = elem.currentStyle[ name ] || elem.currentStyle[ camelCase ]; + + // From the awesome hack by Dean Edwards + // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 + + // If we're not dealing with a regular pixel number + // but a number that has a weird ending, we need to convert it to pixels + if ( !/^\d+(px)?$/i.test( ret ) && /^\d/.test( ret ) ) { + // Remember the original values + var left = style.left, rsLeft = elem.runtimeStyle.left; + + // Put in the new values to get a computed value out + elem.runtimeStyle.left = elem.currentStyle.left; + style.left = ret || 0; + ret = style.pixelLeft + "px"; + + // Revert the changed values + style.left = left; + elem.runtimeStyle.left = rsLeft; + } + } + + return ret; + }, + + clean: function( elems, context, fragment ) { + context = context || document; + + // !context.createElement fails in IE with an error but returns typeof 'object' + if ( typeof context.createElement === "undefined" ) + context = context.ownerDocument || context[0] && context[0].ownerDocument || document; + + // 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] ) ]; + } + + var ret = [], scripts = [], div = context.createElement("div"); + + jQuery.each(elems, function(i, elem){ + if ( typeof elem === "number" ) + elem += ''; + + if ( !elem ) + return; + + // 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 + ">"; + }); + + // Trim whitespace, otherwise indexOf won't work as expected + var tags = elem.replace(/^\s+/, "").substring(0, 10).toLowerCase(); + + var wrap = + // option or optgroup + !tags.indexOf("", "" ] || + + !tags.indexOf("", "" ] || + + tags.match(/^<(thead|tbody|tfoot|colg|cap)/) && + [ 1, "", "
" ] || + + !tags.indexOf("", "" ] || + + // matched above + (!tags.indexOf("", "" ] || + + !tags.indexOf("", "" ] || + + // IE can't serialize and