Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

1.0rc

  • Loading branch information...
commit 7bf5e270b3296e4c97dca1d6f1d6d4199515c749 0 parents
@tomasztunik authored
11 README.md
@@ -0,0 +1,11 @@
+----
+# Backbone.subviews
+
+A Backbone.js extension for managing more complex view hierarchies and helping avoiding creation of memory leaks when dealing with persistent or long living models and collections, jQuery plugins or detached views.
+
+-----
+**author**: Tomasz Tunik
+**version**: 1.0rc
+**compatibility**: Backbone 0.9+
+
+Backbone.subviews by extending the Backbone.View class makes it easier to manage more complex view hierarchies. Apart of storing view hierarchy references with views keeping references to all its subviews and to its parents Backbone.subviews also propagates the remove of a view down the view hierarchy tree and, if defined, calls the onRemove method on all the view's subviews making it easy to clean up all the references to jQuery plugins, persistent models and collections or make any extra teardown operations.
135 backbone.subviews.js
@@ -0,0 +1,135 @@
+// Backbone.subviews.js 1.0rc
+
+// (c) 2012 Tomasz Tunik,
+// Backbone.subviews may be freely distributed under the MIT license.
+// For all details and documentation:
+// http://provoke.it/.org
+
+(function(){
+
+ // extend the Backbone.View with new functionalities
+ _.extend(Backbone.View.prototype, {
+
+ // adds a subview to the hierarchy and returns it for further use
+ // if passed second optional name parameter adds a subview and stores
+ // a reference to that subview on an identity map;
+ // names are unique and adding a view with the same name destroys
+ // the old view automatically making it easy to replace views
+ // one can retrieve a named view using the `getSubview` method and passing view's name
+ addSubview: function(subview, name) {
+ // check if subviews array already exists if not create it
+ this._subviews || (this._subviews = []);
+ // add reference to superview to create vertical view hierarchy
+ subview.parent = this;
+ this._subviews.push(subview);
+ // if it is a named view add the view to the named subviews collection
+ if (name) {
+ subview._name = name;
+ this._namedSubviews || (this._namedSubviews = {});
+ this._namedSubviews[name] && this.removeSubview(name);
+ this._namedSubviews[name] = subview;
+ }
+ return subview;
+ },
+
+ // returns a subview with a given name
+ getSubview: function(name) {
+ return this._namedSubviews[name];
+ },
+
+ // Remove this view from the DOM. Note that the view isn't present in the
+ // DOM by default, so calling this method may be a no-op.
+ // remove shouldn't be altered - any changes user wants to make during the removal should
+ // be handled by overwriting the `onRemove` method
+ remove: function() {
+ this.$el.remove();
+ // clear refs from parent view
+ this.parent && this.parent._cleanRefs(this);
+ // trigger the cleanup
+ this._remove();
+ return this;
+ },
+
+ // removes references to and removes subview given by reference or name
+ removeSubview: function(nameOrView) {
+ var subview = nameOrView instanceof Backbone.View ? nameOrView : this._namedSubviews && this._namedSubviews[nameOrView];
+ subview && subview.remove();
+ return subview;
+ },
+
+ // removes and cleans up all the subviews in hierarchy without removing the view
+ removeSubviews: function() {
+ _.invoke(this._subviews, 'remove');
+ },
+
+ // onRemove will be automatically called on all the subviews, onRemove is called before
+ // element is removed from the view and after all the subviews were destroyed/cleaned up first
+ // it should be overwritten by user - can be used to clean up bindings to global objects,
+ // persistant collections and models, nullify references or trigger custom events etc.
+ onRemove: function() {},
+
+ // Detach maps to jQuery detach method and marks the view as detached
+ // it's very important when it comes to memory menagement and helping the garbage collector as
+ // all the jquery events, backbone event delegations and jquery data won't be cleaned up if the
+ // superview is removed while subview is detached from the DOM structure.
+ // Because of that we are marking the the view as detached so later on in the `_onDestroy` method we can manualy call
+ // the `cleanData` method to make sure we are not leaving any references to jquery events and data for elements that are
+ // never making it back to the DOM.
+ // Also sometimes we pre-create views on initialization or as content for some popoup/dialog jquery plugins -
+ // if we use subviews like that we should mark views as detached ourselves as if they aren't children to the DOM
+ // tree of the parent view they will never get cleaned up properly
+ // If you know superview is not going to be removed any time soon (or at all) or that the view have a small chance of
+ // being reattached it's better to play safe and use remove and just recreate the view when required
+ detach: function() {
+ this.detached = true;
+ this.$el.detach();
+ return this;
+ },
+
+ // maps to jquery cleanData to properly clean up detached view
+ cleanData: function() {
+ $.cleanData(this.el.getElementsByTagName("*"));
+ $.cleanData([this.el]);
+ },
+
+ // `_remove` is a private method and shouldn't be altered to make sure it is always working as intended,
+ // this method invokes the `_remove` method on all view's subviews and calls the onRemove to clean up any
+ // backbone references or trigger custom actions if user has overriden the `onRemove` method on given view and
+ // all the subviews in its view hierarchy
+ _remove: function() {
+ // we are calling only _remove on the subviews as we don't want to remove every single subview
+ // from the DOM tree one by one - we want to clean up all the refs and trigger the onRemove if
+ // any kind of custom cleaning up is required, all hierarchy references are cleaned up after
+ // _remove is invoked on all the subviews
+ _.invoke(this._subviews, '_remove');
+ // cleanup the view hierarchy references after the _remove on all the children was called
+ this.parent = null;
+ this._subviews = null;
+ this._namedSubviews = null;
+ // if element isn't attached to the view we need to manualy take care of cleaning up all the jQuery refs
+ // if view was detached, we check if it was reattached to the DOM if not we manualy clean up the jquery refs
+ (this.detached || this.options.detached) && this.cleanData();
+ // call the onRemove callback to do user defined cleanup
+ this.onRemove();
+ // unbind any events that could have been possibly bound to this view as handlers may keep the view in the memory
+ this.unbind();
+ // clear references to model and collection in case these were persistent, also clears the references to options
+ // as often we are passing references to persistent or external objects that might persist beyond the life of the view
+ // needs to be last in case onRemove call needed access to these properties.
+ this.model = null;
+ this.collection = null;
+ this.options = null;
+ this.el = null;
+ this.$el = null;
+ },
+
+ // cleans the references between parent view and subview
+ _cleanRefs: function(subview) {
+ // removes name references
+ subview._name && (delete this._namedSubviews[subview._name]);
+ // remove the subview from the subviews collection
+ this._subviews = _.without(this._subviews, subview);
+ }
+ });
+
+}).call(this);
141 test/leaks.js
@@ -0,0 +1,141 @@
+$(document).ready(function() {
+
+ module("leaks: jQuery cache related");
+
+ var onRemoveCallbacksCounter;
+ var fixture = $('#qunit-fixtures');
+
+ var reset = QUnit.reset;
+
+ QUnit.reset = function() {
+ onRemoveCallbacksCounter = 0;
+ reset();
+ }
+
+ var SubviewClass = Backbone.View.extend({
+ className : 'test-subview',
+ events: {
+ 'click': 'noop'
+ },
+ onRemove: function() {
+ onRemoveCallbacksCounter++;
+ },
+ noop: function() {}
+ });
+
+ // these tests will show leaks which can be caused by jquery cache not being
+ // cleared properly and thus forever keeping the views and all other objects
+ // they reference in the memory
+
+ // Here we create a subview in the initializer - it would have been inserted into the DOM tree
+ // as a result of an evnet, user action etc.
+ test("Leak: Subview created in initialize never attached to the DOM tree with delegated events will leak", function() {
+ var startCacheSize = _.size($.cache);
+ var view = new (Backbone.View.extend({
+ initialize: function() {
+ this.subview = new SubviewClass;
+ }
+ }));
+ fixture.html(view.render().el);
+ view.remove();
+ ok(startCacheSize < _.size($.cache), "$.cache size should increase and prove a leak");
+ });
+
+ // to fix we can use newly added "detached" flag which will mark a view as a suspect to being detached
+ // and then add the view using the new addSubview method
+ test("Leak fix: Subview created in initialize never attached to the DOM tree with delegated events using addSubview and detached flag should not leak", function() {
+ var startCacheSize = _.size($.cache);
+ var view = new (Backbone.View.extend({
+ initialize: function() {
+ this.subview = this.addSubview(new SubviewClass({ detached: true }));
+ }
+ }));
+ fixture.html(view.render().el);
+ view.remove();
+ equal(_.size($.cache), startCacheSize, "$.cache size should not increase");
+ });
+
+ test("Leak: Subview detached from the DOM tree with jquery detach method will leak", function() {
+ var startCacheSize = _.size($.cache);
+ var view = new (Backbone.View.extend({
+ initialize: function() {
+ this.subview = new SubviewClass;
+ },
+ render: function() {
+ this.$el.append(this.subview.render().el);
+ return this;
+ }
+ }));
+ fixture.html(view.render().el);
+ view.subview.$el.detach();
+ view.remove();
+ ok(startCacheSize < _.size($.cache), "$.cache size should increase and prove a leak");
+ });
+
+ // to fix we can use newly added detach method which will flag the view as detached
+ // and then add the view using the new addSubview method
+ test("Leak fix: Subview detached to the DOM tree using new detach method will not leak", function() {
+ var startCacheSize = _.size($.cache);
+ var view = new (Backbone.View.extend({
+ initialize: function() {
+ this.subview = this.addSubview(new SubviewClass);
+ },
+ render: function() {
+ this.$el.append(this.subview.render().el);
+ return this;
+ }
+ }));
+ fixture.html(view.render().el);
+ view.subview.detach();
+ view.remove();
+ equal(_.size($.cache), startCacheSize, "$.cache size should not increase");
+ });
+
+ module('leaks: References to persistent resources');
+
+ test("Leak: Subview with persistent collection or model without cleaning it's event bindings will leak", function() {
+ var persistentCollection = new Backbone.Collection;
+ var eventsBound = persistentCollection._callbacks && persistentCollection._callbacks['change'] || 0;
+ var viewClass = Backbone.View.extend({
+ collection: persistentCollection,
+ initialize: function() {
+ this.collection.on('change', this.noop);
+ },
+ noop: function() {}
+ });
+
+ equal(persistentCollection._callbacks, undefined, "initially collection's _callbacks object shoudl be undefined");
+
+ new viewClass().remove();
+ new viewClass().remove();
+ new viewClass().remove();
+ new viewClass().remove();
+
+ ok(persistentCollection._callbacks['change'].next, "callback's collection of change event should have the events still bound thus keeping views in memory and leaking");
+ });
+
+ test("Leak fix: Subview with persistent collection or model cleaned using added onRemove method will not leak", function() {
+ var persistentCollection = new Backbone.Collection;
+ var eventsBound = persistentCollection._callbacks && persistentCollection._callbacks['change'] || 0;
+ var viewClass = Backbone.View.extend({
+ collection: persistentCollection,
+ initialize: function() {
+ this.detached = true;
+ this.collection.on('change', this.noop, this);
+ },
+ noop: function() {},
+ onRemove: function() {
+ this.collection.off('change', this.noop, this);
+ }
+ });
+
+ equal(persistentCollection._callbacks, undefined, "initially collection's _callbacks object shoudl be undefined");
+
+ new viewClass().remove();
+ new viewClass().remove();
+ new viewClass().remove();
+ new viewClass().remove();
+
+ equal(persistentCollection._callbacks['change'], undefined, "callback's collection of change events should be cleaned and not leak");
+ });
+});
243 test/subviews.js
@@ -0,0 +1,243 @@
+$(document).ready(function() {
+
+ module("Backbone.subviews");
+
+ var onRemoveCallbacksCounter;
+ var fixture = $('#qunit-fixtures');
+
+ var reset = QUnit.reset;
+
+ QUnit.reset = function() {
+ onRemoveCallbacksCounter = 0;
+ reset();
+ }
+
+ var SubviewClass = Backbone.View.extend({
+ className : 'test-subview',
+ onRemove: function() {
+ onRemoveCallbacksCounter++;
+ }
+ });
+
+ test("Add one subview", function() {
+ var view = new (Backbone.View.extend({
+ className : 'test-view',
+ initialize: function() {
+ this.subview = this.addSubview(new SubviewClass);
+ },
+ render: function() {
+ this.$el.append(this.subview.render().el);
+ return this;
+ }
+ }));
+ fixture.html(view.render().el);
+ equal(view._subviews.length, 1, "View should have one subview");
+ equal(view._subviews[0].parent, view, "Subview should have view as a parent");
+ equal(_.size(view._namedSubviews), 0, "View named subviews map should be empty");
+ equal(fixture.find('.test-view').length, 1, "One View should be added to DOM");
+ equal(fixture.find('.test-subview').length, 1, "Subview should be added to DOM");
+ });
+
+ test("Add multiple views", function() {
+ var view = new (Backbone.View.extend({
+ className: 'test-view',
+ render: function() {
+ this.$el.append(this.addSubview(new SubviewClass).render().el);
+ this.$el.append(this.addSubview(new SubviewClass).render().el);
+ this.$el.append(this.addSubview(new SubviewClass).render().el);
+ this.$el.append(this.addSubview(new SubviewClass).render().el);
+ return this;
+ }
+ }));
+ fixture.html(view.render().el);
+ equal(view._subviews.length, 4, "View should have four subviews");
+ equal(view._subviews[0].parent, view, "Subview should have view as a parent");
+ equal(_.size(view._namedSubviews), 0, "View named subviews map should be empty");
+ equal(fixture.find('.test-view').length, 1, "One View should be added to DOM");
+ equal(view.$('.test-subview').length, 4, "Four Subviews should be present in the DOM as a child to the view");
+ });
+
+ test("Add one named subview", function() {
+ var view = new (Backbone.View.extend({
+ className : 'test-view',
+ initialize: function() {
+ this.subview = this.addSubview(new SubviewClass, 'test');
+ },
+ render: function() {
+ this.$el.html('view');
+ this.$el.append(this.subview.render().el);
+ return this;
+ }
+ }));
+ fixture.html(view.render().el);
+ equal(view._subviews.length, 1, "View should have one subview");
+ equal(view._subviews[0].parent, view, "Subview should have view as a parent");
+ equal(view._subviews[0]._name, 'test', "Subview should have 'test' as a _name");
+ equal(_.size(view._namedSubviews), 1, "View named subviews map should contain one subview");
+ equal(fixture.find('.test-view').length, 1, "One View should be added to DOM");
+ equal(view.$('.test-subview').length, 1, "One Subview should be added to DOM as a child to the view");
+ });
+
+ test("Add multiple named views using different names", function() {
+ var view = new (Backbone.View.extend({
+ className : 'test-view',
+ render: function() {
+ this.$el.html('view');
+ this.$el.append(this.addSubview(new SubviewClass, 'test1').render().el);
+ this.$el.append(this.addSubview(new SubviewClass, 'test2').render().el);
+ this.$el.append(this.addSubview(new SubviewClass, 'test3').render().el);
+ this.$el.append(this.addSubview(new SubviewClass, 'test4').render().el);
+ return this;
+ }
+ }));
+ fixture.html(view.render().el);
+ equal(view._subviews.length, 4, "View should have four subviews");
+ equal(_.size(view._namedSubviews), 4, "View named subviews map should contain four subviews");
+ equal(fixture.find('.test-view').length, 1, "One View should be added to DOM");
+ equal(view.$('.test-subview').length, 4, "Four Subviews should be present in the DOM as a child to the view");
+ });
+
+ test("Add named view four times using same name", function() {
+ var view = new (Backbone.View.extend({
+ className : 'test-view',
+ render: function() {
+ this.$el.html('view');
+ this.$el.append(this.addSubview(new SubviewClass, 'test').render().el);
+ this.$el.append(this.addSubview(new SubviewClass, 'test').render().el);
+ this.$el.append(this.addSubview(new SubviewClass, 'test').render().el);
+ this.$el.append(this.addSubview(new SubviewClass, 'test').render().el);
+ return this;
+ }
+ }));
+ fixture.html(view.render().el);
+ equal(view._subviews.length, 1, "View should have one subview");
+ equal(_.size(view._namedSubviews), 1, "View named subviews map should contain one subview");
+ equal(fixture.find('.test-view').length, 1, "One View should be added to DOM");
+ equal(onRemoveCallbacksCounter, 3, "onRemove callback should have been called three times");
+ equal(view.$('.test-subview').length, 1, "One Subview should be present in the DOM as a child to the view");
+ });
+
+ test("retrieving a named subview with getSubview", function() {
+ var view = new (Backbone.View.extend({
+ render: function() {
+ this.$el.append(this.addSubview(new SubviewClass, 'test').render().el);
+ return this;
+ }
+ }));
+ view.render();
+ equal(view.getSubview('test'), view._subviews[0],
+ "Subview retrieved with getSubviews for name 'test' should be equal to the only available subview");
+ equal(view.getSubview('test'), view._namedSubviews['test'],
+ "Subview retrieved with getSubviews for name 'test' should be equal to the subview retrieved from identity map for name 'test'");
+ });
+
+ test("Removing a view", function() {
+ var view = new (Backbone.View.extend({
+ onRemove: function() {
+ onRemoveCallbacksCounter++;
+ }
+ }));
+ fixture.html(view.render().el);
+ equal(fixture.find('*').length, 1, "Fixture should have one child element");
+ view.remove();
+ equal(fixture.find('*').length, 0, "Fixture should not have any child elements");
+ equal(onRemoveCallbacksCounter, 1, "onRemove callback should have been called once");
+ });
+
+ test("Removing a view with two levels deep view hierarchy", function() {
+ var SubviewWithSubviewClass = Backbone.View.extend({
+ render: function() {
+ this.$el.append(this.addSubview(new SubviewClass).render().el);
+ return this;
+ },
+ onRemove: function() {
+ onRemoveCallbacksCounter++;
+ }
+ });
+ var view = new (Backbone.View.extend({
+ className : 'test-view',
+ render: function() {
+ this.$el.append(this.addSubview(new SubviewWithSubviewClass).render().el);
+ return this;
+ },
+ onRemove: function() {
+ onRemoveCallbacksCounter++;
+ }
+ }));
+ fixture.html(view.render().el);
+ equal(fixture.find('*').length, 3, "Fixture should have three child elements");
+ equal(view._subviews.length, 1, "View should have one subview");
+ equal(view._subviews[0]._subviews.length, 1, "View's subview should have one subview");
+ equal(view._subviews[0]._subviews[0]._subviews, undefined, "Subview's subview should not have any subviews");
+ view.remove();
+ equal(fixture.find('*').length, 0, "Fixture should not have any child elements");
+ equal(view._subviews, null, "View subviews collection should be nullified");
+ equal(onRemoveCallbacksCounter, 3, "onRemove callback should have been called three times");
+ });
+
+ test("Removing all the view's subviews with removeSubviews", function() {
+ var view = new (Backbone.View.extend({
+ render: function() {
+ this.$el.append(this.addSubview(new SubviewClass).render().el);
+ this.$el.append(this.addSubview(new SubviewClass).render().el);
+ this.$el.append(this.addSubview(new SubviewClass).render().el);
+ this.$el.append(this.addSubview(new SubviewClass).render().el);
+ return this;
+ },
+ onRemove: function() {
+ onRemoveCallbacksCounter++;
+ }
+ }));
+ fixture.html(view.render().el);
+ equal(fixture.find('*').length, 5, "Fixture should have five child elements");
+ equal(view._subviews.length, 4, "View should have four subviews");
+ view.removeSubviews();
+ equal(fixture.find('*').length, 1, "Fixture should have one child element");
+ equal(view._subviews.length, 0, "View should not have any subviews");
+ equal(onRemoveCallbacksCounter, 4, "onRemove callback should have been called four times");
+ });
+
+ test("Detaching subview", function() {
+ var view = new (Backbone.View.extend({
+ render: function() {
+ this.$el.append(this.addSubview(new SubviewClass, 'test').render().el);
+ return this;
+ }
+ }));
+ fixture.html(view.render().el);
+ equal(fixture.find('*').length, 2, "Fixture should have two child elements");
+ equal(view._subviews.length, 1, "View should have one subview");
+ view.getSubview('test').detach();
+ equal(fixture.find('*').length, 1, "Fixture should have one child element");
+ equal(view._subviews.length, 1, "View should not have one subview");
+ equal(onRemoveCallbacksCounter, 0, "onRemove callback should not have been called");
+ });
+
+ test("Removing view with a detached subview", function() {
+ var view = new (Backbone.View.extend({
+ render: function() {
+ this.$el.append(this.addSubview(new SubviewClass, 'test').render().el);
+ return this;
+ },
+ onRemove: function() {
+ onRemoveCallbacksCounter++;
+ }
+ }));
+ fixture.html(view.render().el);
+ equal(fixture.find('*').length, 2, "Fixture should have two child elements");
+ equal(view._subviews.length, 1, "View should have one subview");
+ view.getSubview('test').detach();
+ equal(fixture.find('*').length, 1, "Fixture should have one child element");
+ equal(view._subviews.length, 1, "View should not have one subview");
+ view.remove();
+ equal(fixture.find('*').length, 0, "Fixture should not have any child elements");
+ equal(onRemoveCallbacksCounter, 2, "onRemove callback should have been called two times");
+ });
+
+ test("Removing a view with given name if subviews array wasn't initialized shouldn't throw an error", function() {
+
+ var view = new Backbone.View;
+ var subview = view.removeSubview('subview');
+ equal(subview, undefined);
+ });
+});
30 test/test.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Backbone Test Suite</title>
+ <link rel="stylesheet" href="vendor/qunit.css" type="text/css" media="screen" />
+ <script type="text/javascript" src="vendor/json2.js"></script>
+ <script type="text/javascript" src="vendor/jquery-1.7.1.js"></script>
+ <script type="text/javascript" src="vendor/qunit.js"></script>
+ <script type="text/javascript">
+ QUnit.config.reorder = false;
+ </script>
+ <script type="text/javascript" src="vendor/jslitmus.js"></script>
+ <script type="text/javascript" src="vendor/underscore-1.3.1.js"></script>
+ <script type="text/javascript" src="vendor/backbone-min.js"></script>
+ <script type="text/javascript" src="../backbone.subviews.js"></script>
+
+ <script type="text/javascript" src="subviews.js"></script>
+ <script type="text/javascript" src="leaks.js"></script>
+</head>
+<body>
+ <h1 id="qunit-header">Backbone Test Suite</h1>
+ <div id="qunit-testrunner-toolbar"></div>
+ <h2 id="qunit-banner"></h2>
+ <h2 id="qunit-userAgent"></h2>
+ <ol id="qunit-tests"></ol>
+ <br /><br />
+ <div id="jslitmus_container" style="margin: 20px 10px;"></div>
+ <div id="qunit-fixtures"></div>
+</body>
+</html>
37 test/vendor/backbone-min.js
@@ -0,0 +1,37 @@
+// Backbone.js 0.9.1
+
+// (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.
+// Backbone may be freely distributed under the MIT license.
+// For all details and documentation:
+// http://backbonejs.org
+(function(){var i=this,r=i.Backbone,s=Array.prototype.slice,t=Array.prototype.splice,g;g="undefined"!==typeof exports?exports:i.Backbone={};g.VERSION="0.9.1";var f=i._;!f&&"undefined"!==typeof require&&(f=require("underscore"));var h=i.jQuery||i.Zepto||i.ender;g.setDomLibrary=function(a){h=a};g.noConflict=function(){i.Backbone=r;return this};g.emulateHTTP=!1;g.emulateJSON=!1;g.Events={on:function(a,b,c){for(var d,a=a.split(/\s+/),e=this._callbacks||(this._callbacks={});d=a.shift();){d=e[d]||(e[d]=
+{});var f=d.tail||(d.tail=d.next={});f.callback=b;f.context=c;d.tail=f.next={}}return this},off:function(a,b,c){var d,e,f;if(a){if(e=this._callbacks)for(a=a.split(/\s+/);d=a.shift();)if(f=e[d],delete e[d],b&&f)for(;(f=f.next)&&f.next;)if(!(f.callback===b&&(!c||f.context===c)))this.on(d,f.callback,f.context)}else delete this._callbacks;return this},trigger:function(a){var b,c,d,e;if(!(d=this._callbacks))return this;e=d.all;for((a=a.split(/\s+/)).push(null);b=a.shift();)e&&a.push({next:e.next,tail:e.tail,
+event:b}),(c=d[b])&&a.push({next:c.next,tail:c.tail});for(e=s.call(arguments,1);c=a.pop();){b=c.tail;for(d=c.event?[c.event].concat(e):e;(c=c.next)!==b;)c.callback.apply(c.context||this,d)}return this}};g.Events.bind=g.Events.on;g.Events.unbind=g.Events.off;g.Model=function(a,b){var c;a||(a={});b&&b.parse&&(a=this.parse(a));if(c=j(this,"defaults"))a=f.extend({},c,a);b&&b.collection&&(this.collection=b.collection);this.attributes={};this._escapedAttributes={};this.cid=f.uniqueId("c");if(!this.set(a,
+{silent:!0}))throw Error("Can't create an invalid model");delete this._changed;this._previousAttributes=f.clone(this.attributes);this.initialize.apply(this,arguments)};f.extend(g.Model.prototype,g.Events,{idAttribute:"id",initialize:function(){},toJSON:function(){return f.clone(this.attributes)},get:function(a){return this.attributes[a]},escape:function(a){var b;if(b=this._escapedAttributes[a])return b;b=this.attributes[a];return this._escapedAttributes[a]=f.escape(null==b?"":""+b)},has:function(a){return null!=
+this.attributes[a]},set:function(a,b,c){var d,e;f.isObject(a)||null==a?(d=a,c=b):(d={},d[a]=b);c||(c={});if(!d)return this;d instanceof g.Model&&(d=d.attributes);if(c.unset)for(e in d)d[e]=void 0;if(!this._validate(d,c))return!1;this.idAttribute in d&&(this.id=d[this.idAttribute]);var b=this.attributes,k=this._escapedAttributes,n=this._previousAttributes||{},h=this._setting;this._changed||(this._changed={});this._setting=!0;for(e in d)if(a=d[e],f.isEqual(b[e],a)||delete k[e],c.unset?delete b[e]:b[e]=
+a,this._changing&&!f.isEqual(this._changed[e],a)&&(this.trigger("change:"+e,this,a,c),this._moreChanges=!0),delete this._changed[e],!f.isEqual(n[e],a)||f.has(b,e)!=f.has(n,e))this._changed[e]=a;h||(!c.silent&&this.hasChanged()&&this.change(c),this._setting=!1);return this},unset:function(a,b){(b||(b={})).unset=!0;return this.set(a,null,b)},clear:function(a){(a||(a={})).unset=!0;return this.set(f.clone(this.attributes),a)},fetch:function(a){var a=a?f.clone(a):{},b=this,c=a.success;a.success=function(d,
+e,f){if(!b.set(b.parse(d,f),a))return!1;c&&c(b,d)};a.error=g.wrapError(a.error,b,a);return(this.sync||g.sync).call(this,"read",this,a)},save:function(a,b,c){var d,e;f.isObject(a)||null==a?(d=a,c=b):(d={},d[a]=b);c=c?f.clone(c):{};c.wait&&(e=f.clone(this.attributes));a=f.extend({},c,{silent:!0});if(d&&!this.set(d,c.wait?a:c))return!1;var k=this,h=c.success;c.success=function(a,b,e){b=k.parse(a,e);c.wait&&(b=f.extend(d||{},b));if(!k.set(b,c))return!1;h?h(k,a):k.trigger("sync",k,a,c)};c.error=g.wrapError(c.error,
+k,c);b=this.isNew()?"create":"update";b=(this.sync||g.sync).call(this,b,this,c);c.wait&&this.set(e,a);return b},destroy:function(a){var a=a?f.clone(a):{},b=this,c=a.success,d=function(){b.trigger("destroy",b,b.collection,a)};if(this.isNew())return d();a.success=function(e){a.wait&&d();c?c(b,e):b.trigger("sync",b,e,a)};a.error=g.wrapError(a.error,b,a);var e=(this.sync||g.sync).call(this,"delete",this,a);a.wait||d();return e},url:function(){var a=j(this.collection,"url")||j(this,"urlRoot")||o();return this.isNew()?
+a:a+("/"==a.charAt(a.length-1)?"":"/")+encodeURIComponent(this.id)},parse:function(a){return a},clone:function(){return new this.constructor(this.attributes)},isNew:function(){return null==this.id},change:function(a){if(this._changing||!this.hasChanged())return this;this._moreChanges=this._changing=!0;for(var b in this._changed)this.trigger("change:"+b,this,this._changed[b],a);for(;this._moreChanges;)this._moreChanges=!1,this.trigger("change",this,a);this._previousAttributes=f.clone(this.attributes);
+delete this._changed;this._changing=!1;return this},hasChanged:function(a){return!arguments.length?!f.isEmpty(this._changed):this._changed&&f.has(this._changed,a)},changedAttributes:function(a){if(!a)return this.hasChanged()?f.clone(this._changed):!1;var b,c=!1,d=this._previousAttributes,e;for(e in a)if(!f.isEqual(d[e],b=a[e]))(c||(c={}))[e]=b;return c},previous:function(a){return!arguments.length||!this._previousAttributes?null:this._previousAttributes[a]},previousAttributes:function(){return f.clone(this._previousAttributes)},
+isValid:function(){return!this.validate(this.attributes)},_validate:function(a,b){if(b.silent||!this.validate)return!0;var a=f.extend({},this.attributes,a),c=this.validate(a,b);if(!c)return!0;b&&b.error?b.error(this,c,b):this.trigger("error",this,c,b);return!1}});g.Collection=function(a,b){b||(b={});b.comparator&&(this.comparator=b.comparator);this._reset();this.initialize.apply(this,arguments);a&&this.reset(a,{silent:!0,parse:b.parse})};f.extend(g.Collection.prototype,g.Events,{model:g.Model,initialize:function(){},
+toJSON:function(){return this.map(function(a){return a.toJSON()})},add:function(a,b){var c,d,e,g,h,i={},j={};b||(b={});a=f.isArray(a)?a.slice():[a];for(c=0,d=a.length;c<d;c++){if(!(e=a[c]=this._prepareModel(a[c],b)))throw Error("Can't add an invalid model to a collection");if(i[g=e.cid]||this._byCid[g]||null!=(h=e.id)&&(j[h]||this._byId[h]))throw Error("Can't add the same model to a collection twice");i[g]=j[h]=e}for(c=0;c<d;c++)(e=a[c]).on("all",this._onModelEvent,this),this._byCid[e.cid]=e,null!=
+e.id&&(this._byId[e.id]=e);this.length+=d;t.apply(this.models,[null!=b.at?b.at:this.models.length,0].concat(a));this.comparator&&this.sort({silent:!0});if(b.silent)return this;for(c=0,d=this.models.length;c<d;c++)if(i[(e=this.models[c]).cid])b.index=c,e.trigger("add",e,this,b);return this},remove:function(a,b){var c,d,e,g;b||(b={});a=f.isArray(a)?a.slice():[a];for(c=0,d=a.length;c<d;c++)if(g=this.getByCid(a[c])||this.get(a[c]))delete this._byId[g.id],delete this._byCid[g.cid],e=this.indexOf(g),this.models.splice(e,
+1),this.length--,b.silent||(b.index=e,g.trigger("remove",g,this,b)),this._removeReference(g);return this},get:function(a){return null==a?null:this._byId[null!=a.id?a.id:a]},getByCid:function(a){return a&&this._byCid[a.cid||a]},at:function(a){return this.models[a]},sort:function(a){a||(a={});if(!this.comparator)throw Error("Cannot sort a set without a comparator");var b=f.bind(this.comparator,this);1==this.comparator.length?this.models=this.sortBy(b):this.models.sort(b);a.silent||this.trigger("reset",
+this,a);return this},pluck:function(a){return f.map(this.models,function(b){return b.get(a)})},reset:function(a,b){a||(a=[]);b||(b={});for(var c=0,d=this.models.length;c<d;c++)this._removeReference(this.models[c]);this._reset();this.add(a,{silent:!0,parse:b.parse});b.silent||this.trigger("reset",this,b);return this},fetch:function(a){a=a?f.clone(a):{};void 0===a.parse&&(a.parse=!0);var b=this,c=a.success;a.success=function(d,e,f){b[a.add?"add":"reset"](b.parse(d,f),a);c&&c(b,d)};a.error=g.wrapError(a.error,
+b,a);return(this.sync||g.sync).call(this,"read",this,a)},create:function(a,b){var c=this,b=b?f.clone(b):{},a=this._prepareModel(a,b);if(!a)return!1;b.wait||c.add(a,b);var d=b.success;b.success=function(e,f){b.wait&&c.add(e,b);d?d(e,f):e.trigger("sync",a,f,b)};a.save(null,b);return a},parse:function(a){return a},chain:function(){return f(this.models).chain()},_reset:function(){this.length=0;this.models=[];this._byId={};this._byCid={}},_prepareModel:function(a,b){a instanceof g.Model?a.collection||
+(a.collection=this):(b.collection=this,a=new this.model(a,b),a._validate(a.attributes,b)||(a=!1));return a},_removeReference:function(a){this==a.collection&&delete a.collection;a.off("all",this._onModelEvent,this)},_onModelEvent:function(a,b,c,d){("add"==a||"remove"==a)&&c!=this||("destroy"==a&&this.remove(b,d),b&&a==="change:"+b.idAttribute&&(delete this._byId[b.previous(b.idAttribute)],this._byId[b.id]=b),this.trigger.apply(this,arguments))}});f.each("forEach,each,map,reduce,reduceRight,find,detect,filter,select,reject,every,all,some,any,include,contains,invoke,max,min,sortBy,sortedIndex,toArray,size,first,initial,rest,last,without,indexOf,shuffle,lastIndexOf,isEmpty,groupBy".split(","),
+function(a){g.Collection.prototype[a]=function(){return f[a].apply(f,[this.models].concat(f.toArray(arguments)))}});g.Router=function(a){a||(a={});a.routes&&(this.routes=a.routes);this._bindRoutes();this.initialize.apply(this,arguments)};var u=/:\w+/g,v=/\*\w+/g,w=/[-[\]{}()+?.,\\^$|#\s]/g;f.extend(g.Router.prototype,g.Events,{initialize:function(){},route:function(a,b,c){g.history||(g.history=new g.History);f.isRegExp(a)||(a=this._routeToRegExp(a));c||(c=this[b]);g.history.route(a,f.bind(function(d){d=
+this._extractParameters(a,d);c&&c.apply(this,d);this.trigger.apply(this,["route:"+b].concat(d));g.history.trigger("route",this,b,d)},this));return this},navigate:function(a,b){g.history.navigate(a,b)},_bindRoutes:function(){if(this.routes){var a=[],b;for(b in this.routes)a.unshift([b,this.routes[b]]);b=0;for(var c=a.length;b<c;b++)this.route(a[b][0],a[b][1],this[a[b][1]])}},_routeToRegExp:function(a){a=a.replace(w,"\\$&").replace(u,"([^/]+)").replace(v,"(.*?)");return RegExp("^"+a+"$")},_extractParameters:function(a,
+b){return a.exec(b).slice(1)}});g.History=function(){this.handlers=[];f.bindAll(this,"checkUrl")};var m=/^[#\/]/,x=/msie [\w.]+/,l=!1;f.extend(g.History.prototype,g.Events,{interval:50,getFragment:function(a,b){if(null==a)if(this._hasPushState||b){var a=window.location.pathname,c=window.location.search;c&&(a+=c)}else a=window.location.hash;a=decodeURIComponent(a);a.indexOf(this.options.root)||(a=a.substr(this.options.root.length));return a.replace(m,"")},start:function(a){if(l)throw Error("Backbone.history has already been started");
+this.options=f.extend({},{root:"/"},this.options,a);this._wantsHashChange=!1!==this.options.hashChange;this._wantsPushState=!!this.options.pushState;this._hasPushState=!(!this.options.pushState||!window.history||!window.history.pushState);var a=this.getFragment(),b=document.documentMode;if(b=x.exec(navigator.userAgent.toLowerCase())&&(!b||7>=b))this.iframe=h('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo("body")[0].contentWindow,this.navigate(a);this._hasPushState?h(window).bind("popstate",
+this.checkUrl):this._wantsHashChange&&"onhashchange"in window&&!b?h(window).bind("hashchange",this.checkUrl):this._wantsHashChange&&(this._checkUrlInterval=setInterval(this.checkUrl,this.interval));this.fragment=a;l=!0;a=window.location;b=a.pathname==this.options.root;if(this._wantsHashChange&&this._wantsPushState&&!this._hasPushState&&!b)return this.fragment=this.getFragment(null,!0),window.location.replace(this.options.root+"#"+this.fragment),!0;this._wantsPushState&&this._hasPushState&&b&&a.hash&&
+(this.fragment=a.hash.replace(m,""),window.history.replaceState({},document.title,a.protocol+"//"+a.host+this.options.root+this.fragment));if(!this.options.silent)return this.loadUrl()},stop:function(){h(window).unbind("popstate",this.checkUrl).unbind("hashchange",this.checkUrl);clearInterval(this._checkUrlInterval);l=!1},route:function(a,b){this.handlers.unshift({route:a,callback:b})},checkUrl:function(){var a=this.getFragment();a==this.fragment&&this.iframe&&(a=this.getFragment(this.iframe.location.hash));
+if(a==this.fragment||a==decodeURIComponent(this.fragment))return!1;this.iframe&&this.navigate(a);this.loadUrl()||this.loadUrl(window.location.hash)},loadUrl:function(a){var b=this.fragment=this.getFragment(a);return f.any(this.handlers,function(a){if(a.route.test(b))return a.callback(b),!0})},navigate:function(a,b){if(!l)return!1;if(!b||!0===b)b={trigger:b};var c=(a||"").replace(m,"");this.fragment==c||this.fragment==decodeURIComponent(c)||(this._hasPushState?(0!=c.indexOf(this.options.root)&&(c=
+this.options.root+c),this.fragment=c,window.history[b.replace?"replaceState":"pushState"]({},document.title,c)):this._wantsHashChange?(this.fragment=c,this._updateHash(window.location,c,b.replace),this.iframe&&c!=this.getFragment(this.iframe.location.hash)&&(b.replace||this.iframe.document.open().close(),this._updateHash(this.iframe.location,c,b.replace))):window.location.assign(this.options.root+a),b.trigger&&this.loadUrl(a))},_updateHash:function(a,b,c){c?a.replace(a.toString().replace(/(javascript:|#).*$/,
+"")+"#"+b):a.hash=b}});g.View=function(a){this.cid=f.uniqueId("view");this._configure(a||{});this._ensureElement();this.initialize.apply(this,arguments);this.delegateEvents()};var y=/^(\S+)\s*(.*)$/,p="model,collection,el,id,attributes,className,tagName".split(",");f.extend(g.View.prototype,g.Events,{tagName:"div",$:function(a){return this.$el.find(a)},initialize:function(){},render:function(){return this},remove:function(){this.$el.remove();return this},make:function(a,b,c){a=document.createElement(a);
+b&&h(a).attr(b);c&&h(a).html(c);return a},setElement:function(a,b){this.$el=h(a);this.el=this.$el[0];!1!==b&&this.delegateEvents();return this},delegateEvents:function(a){if(a||(a=j(this,"events"))){this.undelegateEvents();for(var b in a){var c=a[b];f.isFunction(c)||(c=this[a[b]]);if(!c)throw Error('Event "'+a[b]+'" does not exist');var d=b.match(y),e=d[1],d=d[2],c=f.bind(c,this),e=e+(".delegateEvents"+this.cid);""===d?this.$el.bind(e,c):this.$el.delegate(d,e,c)}}},undelegateEvents:function(){this.$el.unbind(".delegateEvents"+
+this.cid)},_configure:function(a){this.options&&(a=f.extend({},this.options,a));for(var b=0,c=p.length;b<c;b++){var d=p[b];a[d]&&(this[d]=a[d])}this.options=a},_ensureElement:function(){if(this.el)this.setElement(this.el,!1);else{var a=j(this,"attributes")||{};this.id&&(a.id=this.id);this.className&&(a["class"]=this.className);this.setElement(this.make(this.tagName,a),!1)}}});g.Model.extend=g.Collection.extend=g.Router.extend=g.View.extend=function(a,b){var c=z(this,a,b);c.extend=this.extend;return c};
+var A={create:"POST",update:"PUT","delete":"DELETE",read:"GET"};g.sync=function(a,b,c){var d=A[a],e={type:d,dataType:"json"};c.url||(e.url=j(b,"url")||o());if(!c.data&&b&&("create"==a||"update"==a))e.contentType="application/json",e.data=JSON.stringify(b.toJSON());g.emulateJSON&&(e.contentType="application/x-www-form-urlencoded",e.data=e.data?{model:e.data}:{});if(g.emulateHTTP&&("PUT"===d||"DELETE"===d))g.emulateJSON&&(e.data._method=d),e.type="POST",e.beforeSend=function(a){a.setRequestHeader("X-HTTP-Method-Override",
+d)};"GET"!==e.type&&!g.emulateJSON&&(e.processData=!1);return h.ajax(f.extend(e,c))};g.wrapError=function(a,b,c){return function(d,e){e=d===b?e:d;a?a(b,e,c):b.trigger("error",b,e,c)}};var q=function(){},z=function(a,b,c){var d;d=b&&b.hasOwnProperty("constructor")?b.constructor:function(){a.apply(this,arguments)};f.extend(d,a);q.prototype=a.prototype;d.prototype=new q;b&&f.extend(d.prototype,b);c&&f.extend(d,c);d.prototype.constructor=d;d.__super__=a.prototype;return d},j=function(a,b){return!a||!a[b]?
+null:f.isFunction(a[b])?a[b]():a[b]},o=function(){throw Error('A "url" property or function must be specified');}}).call(this);
1,678 test/vendor/ender-jeesh.js
@@ -0,0 +1,1678 @@
+/*!
+ * =============================================================
+ * Ender: open module JavaScript framework (https://ender.no.de)
+ * Build: ender build jeesh
+ * =============================================================
+ */
+
+/*!
+ * Ender-JS: open module JavaScript framework (client-lib)
+ * copyright Dustin Diaz & Jacob Thornton 2011 (@ded @fat)
+ * https://ender.no.de
+ * License MIT
+ */
+!function (context) {
+
+ // a global object for node.js module compatiblity
+ // ============================================
+
+ context['global'] = context;
+
+ // Implements simple module system
+ // losely based on CommonJS Modules spec v1.1.1
+ // ============================================
+
+ var modules = {};
+
+ function require (identifier) {
+ var module = modules[identifier] || window[identifier];
+ if (!module) throw new Error("Requested module '" + identifier + "' has not been defined.");
+ return module;
+ }
+
+ function provide (name, what) {
+ return modules[name] = what;
+ }
+
+ context['provide'] = provide;
+ context['require'] = require;
+
+ // Implements Ender's $ global access object
+ // =========================================
+
+ function aug(o, o2) {
+ for (var k in o2) {
+ k != 'noConflict' && k != '_VERSION' && (o[k] = o2[k]);
+ }
+ return o;
+ }
+
+ function boosh(s, r, els) {
+ // string || node || nodelist || window
+ if (ender._select && (typeof s == 'string' || s.nodeName || s.length && 'item' in s || s == window)) {
+ els = ender._select(s, r);
+ els.selector = s;
+ } else {
+ els = isFinite(s.length) ? s : [s];
+ }
+ return aug(els, boosh);
+ }
+
+ function ender(s, r) {
+ return boosh(s, r);
+ }
+
+ aug(ender, {
+ _VERSION: '0.2.5',
+ ender: function (o, chain) {
+ aug(chain ? boosh : ender, o);
+ },
+ fn: context.$ && context.$.fn || {} // for easy compat to jQuery plugins
+ });
+
+ aug(boosh, {
+ forEach: function (fn, scope, i) {
+ // opt out of native forEach so we can intentionally call our own scope
+ // defaulting to the current item and be able to return self
+ for (i = 0, l = this.length; i < l; ++i) {
+ i in this && fn.call(scope || this[i], this[i], i, this);
+ }
+ // return self for chaining
+ return this;
+ },
+ $: ender // handy reference to self
+ });
+
+ var old = context.$;
+ ender.noConflict = function () {
+ context.$ = old;
+ return this;
+ };
+
+ (typeof module !== 'undefined') && module.exports && (module.exports = ender);
+ // use subscript notation as extern for Closure compilation
+ context['ender'] = context['$'] = context['ender'] || ender;
+
+}(this);
+
+!function () {
+
+ var module = { exports: {} }, exports = module.exports;
+
+ /*!
+ * bean.js - copyright Jacob Thornton 2011
+ * https://github.com/fat/bean
+ * MIT License
+ * special thanks to:
+ * dean edwards: http://dean.edwards.name/
+ * dperini: https://github.com/dperini/nwevents
+ * the entire mootools team: github.com/mootools/mootools-core
+ */
+ !function (context) {
+ var __uid = 1, registry = {}, collected = {},
+ overOut = /over|out/,
+ namespace = /[^\.]*(?=\..*)\.|.*/,
+ stripName = /\..*/,
+ addEvent = 'addEventListener',
+ attachEvent = 'attachEvent',
+ removeEvent = 'removeEventListener',
+ detachEvent = 'detachEvent',
+ doc = context.document || {},
+ root = doc.documentElement || {},
+ W3C_MODEL = root[addEvent],
+ eventSupport = W3C_MODEL ? addEvent : attachEvent,
+
+ isDescendant = function (parent, child) {
+ var node = child.parentNode;
+ while (node !== null) {
+ if (node == parent) {
+ return true;
+ }
+ node = node.parentNode;
+ }
+ },
+
+ retrieveUid = function (obj, uid) {
+ return (obj.__uid = uid || obj.__uid || __uid++);
+ },
+
+ retrieveEvents = function (element) {
+ var uid = retrieveUid(element);
+ return (registry[uid] = registry[uid] || {});
+ },
+
+ listener = W3C_MODEL ? function (element, type, fn, add) {
+ element[add ? addEvent : removeEvent](type, fn, false);
+ } : function (element, type, fn, add, custom) {
+ custom && add && (element['_on' + custom] = element['_on' + custom] || 0);
+ element[add ? attachEvent : detachEvent]('on' + type, fn);
+ },
+
+ nativeHandler = function (element, fn, args) {
+ return function (event) {
+ event = fixEvent(event || ((this.ownerDocument || this.document || this).parentWindow || context).event);
+ return fn.apply(element, [event].concat(args));
+ };
+ },
+
+ customHandler = function (element, fn, type, condition, args) {
+ return function (e) {
+ if (condition ? condition.apply(this, arguments) : W3C_MODEL ? true : e && e.propertyName == '_on' + type || !e) {
+ fn.apply(element, Array.prototype.slice.call(arguments, e ? 0 : 1).concat(args));
+ }
+ };
+ },
+
+ addListener = function (element, orgType, fn, args) {
+ var type = orgType.replace(stripName, ''),
+ events = retrieveEvents(element),
+ handlers = events[type] || (events[type] = {}),
+ originalFn = fn,
+ uid = retrieveUid(fn, orgType.replace(namespace, ''));
+ if (handlers[uid]) {
+ return element;
+ }
+ var custom = customEvents[type];
+ if (custom) {
+ fn = custom.condition ? customHandler(element, fn, type, custom.condition) : fn;
+ type = custom.base || type;
+ }
+ var isNative = nativeEvents[type];
+ fn = isNative ? nativeHandler(element, fn, args) : customHandler(element, fn, type, false, args);
+ isNative = W3C_MODEL || isNative;
+ if (type == 'unload') {
+ var org = fn;
+ fn = function () {
+ removeListener(element, type, fn) && org();
+ };
+ }
+ element[eventSupport] && listener(element, isNative ? type : 'propertychange', fn, true, !isNative && type);
+ handlers[uid] = fn;
+ fn.__uid = uid;
+ fn.__originalFn = originalFn;
+ return type == 'unload' ? element : (collected[retrieveUid(element)] = element);
+ },
+
+ removeListener = function (element, orgType, handler) {
+ var uid, names, uids, i, events = retrieveEvents(element), type = orgType.replace(stripName, '');
+ if (!events || !events[type]) {
+ return element;
+ }
+ names = orgType.replace(namespace, '');
+ uids = names ? names.split('.') : [handler.__uid];
+
+ function destroyHandler(uid) {
+ handler = events[type][uid];
+ if (!handler) return;
+ delete events[type][uid];
+ if (element[eventSupport]) {
+ type = customEvents[type] ? customEvents[type].base : type;
+ var isNative = W3C_MODEL || nativeEvents[type];
+ listener(element, isNative ? type : 'propertychange', handler, false, !isNative && type);
+ }
+ }
+
+ destroyHandler(names) //get combos
+ for (i = uids.length; i--; destroyHandler(uids[i])); //get singles
+
+ return element;
+ },
+
+ del = function (selector, fn, $) {
+ return function (e) {
+ var array = typeof selector == 'string' ? $(selector, this) : selector;
+ for (var target = e.target; target && target != this; target = target.parentNode) {
+ for (var i = array.length; i--;) {
+ if (array[i] == target) {
+ return fn.apply(target, arguments);
+ }
+ }
+ }
+ };
+ },
+
+ add = function (element, events, fn, delfn, $) {
+ if (typeof events == 'object' && !fn) {
+ for (var type in events) {
+ events.hasOwnProperty(type) && add(element, type, events[type]);
+ }
+ } else {
+ var isDel = typeof fn == 'string', types = (isDel ? fn : events).split(' ');
+ fn = isDel ? del(events, delfn, $) : fn;
+ for (var i = types.length; i--;) {
+ addListener(element, types[i], fn, Array.prototype.slice.call(arguments, isDel ? 4 : 3));
+ }
+ }
+ return element;
+ },
+
+ remove = function (element, orgEvents, fn) {
+ var k, type, events, i,
+ isString = typeof(orgEvents) == 'string',
+ names = isString && orgEvents.replace(namespace, ''),
+ rm = removeListener,
+ attached = retrieveEvents(element);
+ if (isString && /\s/.test(orgEvents)) {
+ orgEvents = orgEvents.split(' ');
+ i = orgEvents.length - 1;
+ while (remove(element, orgEvents[i]) && i--) {}
+ return element;
+ }
+ events = isString ? orgEvents.replace(stripName, '') : orgEvents;
+ if (!attached || (isString && !attached[events])) {
+ if (attached && names) {
+ for (k in attached) {
+ if (attached.hasOwnProperty(k)) {
+ for (i in attached[k]) {
+ attached[k].hasOwnProperty(i) && new RegExp('^' + names + '(\\..*)?$').test(i) && rm(element, [k, i].join('.'));
+ }
+ }
+ }
+ }
+ return element;
+ }
+ if (typeof fn == 'function') {
+ rm(element, events, fn);
+ } else if (names) {
+ rm(element, orgEvents);
+ } else {
+ rm = events ? rm : remove;
+ type = isString && events;
+ events = events ? (fn || attached[events] || events) : attached;
+ for (k in events) {
+ if (events.hasOwnProperty(k)) {
+ rm(element, type || k, events[k]);
+ delete events[k]; // remove unused leaf keys
+ }
+ }
+ }
+ return element;
+ },
+
+ fire = function (element, type, args) {
+ var evt, k, i, types = type.split(' ');
+ for (i = types.length; i--;) {
+ type = types[i].replace(stripName, '');
+ var isNative = nativeEvents[type],
+ isNamespace = types[i].replace(namespace, ''),
+ handlers = retrieveEvents(element)[type];
+ if (isNamespace) {
+ isNamespace = isNamespace.split('.');
+ for (k = isNamespace.length; k--;) {
+ handlers && handlers[isNamespace[k]] && handlers[isNamespace[k]].apply(element, [false].concat(args));
+ }
+ } else if (!args && element[eventSupport]) {
+ fireListener(isNative, type, element);
+ } else {
+ for (k in handlers) {
+ handlers.hasOwnProperty(k) && handlers[k].apply(element, [false].concat(args));
+ }
+ }
+ }
+ return element;
+ },
+
+ fireListener = W3C_MODEL ? function (isNative, type, element) {
+ evt = document.createEvent(isNative ? "HTMLEvents" : "UIEvents");
+ evt[isNative ? 'initEvent' : 'initUIEvent'](type, true, true, context, 1);
+ element.dispatchEvent(evt);
+ } : function (isNative, type, element) {
+ isNative ? element.fireEvent('on' + type, document.createEventObject()) : element['_on' + type]++;
+ },
+
+ clone = function (element, from, type) {
+ var events = retrieveEvents(from), obj, k;
+ var uid = retrieveUid(element);
+ obj = type ? events[type] : events;
+ for (k in obj) {
+ obj.hasOwnProperty(k) && (type ? add : clone)(element, type || from, type ? obj[k].__originalFn : k);
+ }
+ return element;
+ },
+
+ fixEvent = function (e) {
+ var result = {};
+ if (!e) {
+ return result;
+ }
+ var type = e.type, target = e.target || e.srcElement;
+ result.preventDefault = fixEvent.preventDefault(e);
+ result.stopPropagation = fixEvent.stopPropagation(e);
+ result.target = target && target.nodeType == 3 ? target.parentNode : target;
+ if (~type.indexOf('key')) {
+ result.keyCode = e.which || e.keyCode;
+ } else if ((/click|mouse|menu/i).test(type)) {
+ result.rightClick = e.which == 3 || e.button == 2;
+ result.pos = { x: 0, y: 0 };
+ if (e.pageX || e.pageY) {
+ result.clientX = e.pageX;
+ result.clientY = e.pageY;
+ } else if (e.clientX || e.clientY) {
+ result.clientX = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
+ result.clientY = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
+ }
+ overOut.test(type) && (result.relatedTarget = e.relatedTarget || e[(type == 'mouseover' ? 'from' : 'to') + 'Element']);
+ }
+ for (var k in e) {
+ if (!(k in result)) {
+ result[k] = e[k];
+ }
+ }
+ return result;
+ };
+
+ fixEvent.preventDefault = function (e) {
+ return function () {
+ if (e.preventDefault) {
+ e.preventDefault();
+ }
+ else {
+ e.returnValue = false;
+ }
+ };
+ };
+
+ fixEvent.stopPropagation = function (e) {
+ return function () {
+ if (e.stopPropagation) {
+ e.stopPropagation();
+ } else {
+ e.cancelBubble = true;
+ }
+ };
+ };
+
+ var nativeEvents = { click: 1, dblclick: 1, mouseup: 1, mousedown: 1, contextmenu: 1, //mouse buttons
+ mousewheel: 1, DOMMouseScroll: 1, //mouse wheel
+ mouseover: 1, mouseout: 1, mousemove: 1, selectstart: 1, selectend: 1, //mouse movement
+ keydown: 1, keypress: 1, keyup: 1, //keyboard
+ orientationchange: 1, // mobile
+ touchstart: 1, touchmove: 1, touchend: 1, touchcancel: 1, // touch
+ gesturestart: 1, gesturechange: 1, gestureend: 1, // gesture
+ focus: 1, blur: 1, change: 1, reset: 1, select: 1, submit: 1, //form elements
+ load: 1, unload: 1, beforeunload: 1, resize: 1, move: 1, DOMContentLoaded: 1, readystatechange: 1, //window
+ error: 1, abort: 1, scroll: 1 }; //misc
+
+ function check(event) {
+ var related = event.relatedTarget;
+ if (!related) {
+ return related === null;
+ }
+ return (related != this && related.prefix != 'xul' && !/document/.test(this.toString()) && !isDescendant(this, related));
+ }
+
+ var customEvents = {
+ mouseenter: { base: 'mouseover', condition: check },
+ mouseleave: { base: 'mouseout', condition: check },
+ mousewheel: { base: /Firefox/.test(navigator.userAgent) ? 'DOMMouseScroll' : 'mousewheel' }
+ };
+
+ var bean = { add: add, remove: remove, clone: clone, fire: fire };
+
+ var clean = function (el) {
+ var uid = remove(el).__uid;
+ if (uid) {
+ delete collected[uid];
+ delete registry[uid];
+ }
+ };
+
+ if (context[attachEvent]) {
+ add(context, 'unload', function () {
+ for (var k in collected) {
+ collected.hasOwnProperty(k) && clean(collected[k]);
+ }
+ context.CollectGarbage && CollectGarbage();
+ });
+ }
+
+ var oldBean = context.bean;
+ bean.noConflict = function () {
+ context.bean = oldBean;
+ return this;
+ };
+
+ (typeof module !== 'undefined' && module.exports) ?
+ (module.exports = bean) :
+ (context['bean'] = bean);
+
+ }(this);
+
+ provide("bean", module.exports);
+
+ !function ($) {
+ var b = require('bean'),
+ integrate = function (method, type, method2) {
+ var _args = type ? [type] : [];
+ return function () {
+ for (var args, i = 0, l = this.length; i < l; i++) {
+ args = [this[i]].concat(_args, Array.prototype.slice.call(arguments, 0));
+ args.length == 4 && args.push($);
+ !arguments.length && method == 'add' && type && (method = 'fire');
+ b[method].apply(this, args);
+ }
+ return this;
+ };
+ };
+
+ var add = integrate('add'),
+ remove = integrate('remove'),
+ fire = integrate('fire');
+
+ var methods = {
+
+ on: add,
+ addListener: add,
+ bind: add,
+ listen: add,
+ delegate: add,
+
+ unbind: remove,
+ unlisten: remove,
+ removeListener: remove,
+ undelegate: remove,
+
+ emit: fire,
+ trigger: fire,
+
+ cloneEvents: integrate('clone'),
+
+ hover: function (enter, leave, i) { // i for internal
+ for (i = this.length; i--;) {
+ b.add.call(this, this[i], 'mouseenter', enter);
+ b.add.call(this, this[i], 'mouseleave', leave);
+ }
+ return this;
+ }
+ };
+
+ var i, shortcuts = [
+ 'blur', 'change', 'click', 'dblclick', 'error', 'focus', 'focusin',
+ 'focusout', 'keydown', 'keypress', 'keyup', 'load', 'mousedown',
+ 'mouseenter', 'mouseleave', 'mouseout', 'mouseover', 'mouseup', 'mousemove',
+ 'resize', 'scroll', 'select', 'submit', 'unload'
+ ];
+
+ for (i = shortcuts.length; i--;) {
+ methods[shortcuts[i]] = integrate('add', shortcuts[i]);
+ }
+
+ $.ender(methods, true);
+ }(ender);
+
+}();
+
+!function () {
+
+ var module = { exports: {} }, exports = module.exports;
+
+ /*!
+ * bonzo.js - copyright @dedfat 2011
+ * https://github.com/ded/bonzo
+ * Follow our software http://twitter.com/dedfat
+ * MIT License
+ */
+ !function (context, win) {
+
+ var doc = context.document,
+ html = doc.documentElement,
+ parentNode = 'parentNode',
+ query = null,
+ specialAttributes = /^checked|value|selected$/,
+ specialTags = /select|fieldset|table|tbody|tfoot|td|tr|colgroup/i,
+ table = 'table',
+ tagMap = { thead: table, tbody: table, tfoot: table, tr: 'tbody', th: 'tr', td: 'tr', fieldset: 'form', option: 'select' },
+ stateAttributes = /^checked|selected$/,
+ ie = /msie/i.test(navigator.userAgent),
+ uidList = [],
+ uuids = 0,
+ digit = /^-?[\d\.]+$/,
+ px = 'px',
+ // commonly used methods
+ setAttribute = 'setAttribute',
+ getAttribute = 'getAttribute',
+ trimReplace = /(^\s*|\s*$)/g,
+ unitless = { lineHeight: 1, zoom: 1, zIndex: 1, opacity: 1 };
+
+ function classReg(c) {
+ return new RegExp("(^|\\s+)" + c + "(\\s+|$)");
+ }
+
+ function each(ar, fn, scope) {
+ for (var i = 0, l = ar.length; i < l; i++) {
+ fn.call(scope || ar[i], ar[i], i, ar);
+ }
+ return ar;
+ }
+
+ var trim = String.prototype.trim ?
+ function (s) {
+ return s.trim();
+ } :
+ function (s) {
+ return s.replace(trimReplace, '');
+ };
+
+ function camelize(s) {
+ return s.replace(/-(.)/g, function (m, m1) {
+ return m1.toUpperCase();
+ });
+ }
+
+ function is(node) {
+ return node && node.nodeName && node.nodeType == 1;
+ }
+
+ function some(ar, fn, scope) {
+ for (var i = 0, j = ar.length; i < j; ++i) {
+ if (fn.call(scope, ar[i], i, ar)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ var getStyle = doc.defaultView && doc.defaultView.getComputedStyle ?
+ function (el, property) {
+ var value = null;
+ if (property == 'float') {
+ property = 'cssFloat';
+ }
+ var computed = doc.defaultView.getComputedStyle(el, '');
+ computed && (value = computed[camelize(property)]);
+ return el.style[property] || value;
+
+ } : (ie && html.currentStyle) ?
+
+ function (el, property) {
+ property = camelize(property);
+ property = property == 'float' ? 'styleFloat' : property;
+
+ if (property == 'opacity') {
+ var val = 100;
+ try {
+ val = el.filters['DXImageTransform.Microsoft.Alpha'].opacity;
+ } catch (e1) {
+ try {
+ val = el.filters('alpha').opacity;
+ } catch (e2) {}
+ }
+ return val / 100;
+ }
+ var value = el.currentStyle ? el.currentStyle[property] : null;
+ return el.style[property] || value;
+ } :
+
+ function (el, property) {
+ return el.style[camelize(property)];
+ };
+
+ function insert(target, host, fn) {
+ var i = 0, self = host || this, r = [],
+ nodes = query && typeof target == 'string' && target.charAt(0) != '<' ? function (n) {
+ return (n = query(target)) && (n.selected = 1) && n;
+ }() : target;
+ each(normalize(nodes), function (t) {
+ each(self, function (el) {
+ var n = !el[parentNode] || (el[parentNode] && !el[parentNode][parentNode]) ?
+ function () {
+ var c = el.cloneNode(true);
+ self.$ && self.cloneEvents && self.$(c).cloneEvents(el);
+ return c;
+ }() :
+ el;
+ fn(t, n);
+ r[i] = n;
+ i++;
+ });
+ }, this);
+ each(r, function (e, i) {
+ self[i] = e;
+ });
+ self.length = i;
+ return self;
+ }
+
+ function xy(el, x, y) {
+ var $el = bonzo(el),
+ style = $el.css('position'),
+ offset = $el.offset(),
+ rel = 'relative',
+ isRel = style == rel,
+ delta = [parseInt($el.css('left'), 10), parseInt($el.css('top'), 10)];
+
+ if (style == 'static') {
+ $el.css('position', rel);
+ style = rel;
+ }
+
+ isNaN(delta[0]) && (delta[0] = isRel ? 0 : el.offsetLeft);
+ isNaN(delta[1]) && (delta[1] = isRel ? 0 : el.offsetTop);
+
+ x !== null && (el.style.left = x - offset.left + delta[0] + px);
+ y !== null && (el.style.top = y - offset.top + delta[1] + px);
+
+ }
+
+ function Bonzo(elements) {
+ this.length = 0;
+ if (elements) {
+ elements = typeof elements !== 'string' &&
+ !elements.nodeType &&
+ typeof elements.length !== 'undefined' ?
+ elements :
+ [elements];
+ this.length = elements.length;
+ for (var i = 0; i < elements.length; i++) {
+ this[i] = elements[i];
+ }
+ }
+ }
+
+ Bonzo.prototype = {
+
+ get: function (index) {
+ return this[index];
+ },
+
+ each: function (fn, scope) {
+ return each(this, fn, scope);
+ },
+
+ map: function (fn, reject) {
+ var m = [], n, i;
+ for (i = 0; i < this.length; i++) {
+ n = fn.call(this, this[i], i);
+ reject ? (reject(n) && m.push(n)) : m.push(n);
+ }
+ return m;
+ },
+
+ first: function () {
+ return bonzo(this[0]);
+ },
+
+ last: function () {
+ return bonzo(this[this.length - 1]);
+ },
+
+ html: function (h, text) {
+ var method = text ?
+ html.textContent == null ?
+ 'innerText' :
+ 'textContent' :
+ 'innerHTML', m;
+ function append(el) {
+ while (el.firstChild) {
+ el.removeChild(el.firstChild);
+ }
+ each(normalize(h), function (node) {
+ el.appendChild(node);
+ });
+ }
+ return typeof h !== 'undefined' ?
+ this.each(function (el) {
+ (m = el.tagName.match(specialTags)) ?
+ append(el, m[0]) :
+ (el[method] = h);
+ }) :
+ this[0] ? this[0][method] : '';
+ },
+
+ text: function (text) {
+ return this.html(text, 1);
+ },
+
+ addClass: function (c) {
+ return this.each(function (el) {
+ this.hasClass(el, c) || (el.className = trim(el.className + ' ' + c));
+ }, this);
+ },
+
+ removeClass: function (c) {
+ return this.each(function (el) {
+ this.hasClass(el, c) && (el.className = trim(el.className.replace(classReg(c), ' ')));
+ }, this);
+ },
+
+ hasClass: function (el, c) {
+ return typeof c == 'undefined' ?
+ some(this, function (i) {
+ return classReg(el).test(i.className);
+ }) :
+ classReg(c).test(el.className);
+ },
+
+ toggleClass: function (c, condition) {
+ if (typeof condition !== 'undefined' && !condition) {
+ return this;
+ }
+ return this.each(function (el) {
+ this.hasClass(el, c) ?
+ (el.className = trim(el.className.replace(classReg(c), ' '))) :
+ (el.className = trim(el.className + ' ' + c));
+ }, this);
+ },
+
+ show: function (type) {
+ return this.each(function (el) {
+ el.style.display = type || '';
+ });
+ },
+
+ hide: function (elements) {
+ return this.each(function (el) {
+ el.style.display = 'none';
+ });
+ },
+
+ append: function (node) {
+ return this.each(function (el) {
+ each(normalize(node), function (i) {
+ el.appendChild(i);
+ });
+ });
+ },
+
+ prepend: function (node) {
+ return this.each(function (el) {
+ var first = el.firstChild;
+ each(normalize(node), function (i) {
+ el.insertBefore(i, first);
+ });
+ });
+ },
+
+ appendTo: function (target, host) {
+ return insert.call(this, target, host, function (t, el) {
+ t.appendChild(el);
+ });
+ },
+
+ prependTo: function (target, host) {
+ return insert.call(this, target, host, function (t, el) {
+ t.insertBefore(el, t.firstChild);
+ });
+ },
+
+ next: function () {
+ return this.related('nextSibling');
+ },
+
+ previous: function () {
+ return this.related('previousSibling');
+ },
+
+ related: function (method) {
+ return this.map(
+ function (el) {
+ el = el[method];
+ while (el && el.nodeType !== 1) {
+ el = el[method];
+ }
+ return el || 0;
+ },
+ function (el) {
+ return el;
+ }
+ );
+ },
+
+ before: function (node) {
+ return this.each(function (el) {
+ each(bonzo.create(node), function (i) {
+ el[parentNode].insertBefore(i, el);
+ });
+ });
+ },
+
+ after: function (node) {
+ return this.each(function (el) {
+ each(bonzo.create(node), function (i) {
+ el[parentNode].insertBefore(i, el.nextSibling);
+ });
+ });
+ },
+
+ insertBefore: function (target, host) {
+ return insert.call(this, target, host, function (t, el) {
+ t[parentNode].insertBefore(el, t);
+ });
+ },
+
+ insertAfter: function (target, host) {
+ return insert.call(this, target, host, function (t, el) {
+ var sibling = t.nextSibling;
+ if (sibling) {
+ t[parentNode].insertBefore(el, sibling);
+ }
+ else {
+ t[parentNode].appendChild(el);
+ }
+ });
+ },
+
+ css: function (o, v, p) {
+ // is this a request for just getting a style?
+ if (v === undefined && typeof o == 'string') {
+ // repurpose 'v'
+ v = this[0];
+ if (!v) {
+ return null;
+ }
+ if (v == doc || v == win) {
+ p = (v == doc) ? bonzo.doc() : bonzo.viewport();
+ return o == 'width' ? p.width :
+ o == 'height' ? p.height : '';
+ }
+ return getStyle(v, o);
+ }
+ var iter = o;
+ if (typeof o == 'string') {
+ iter = {};
+ iter[o] = v;
+ }
+
+ if (ie && iter.opacity) {
+ // oh this 'ol gamut
+ iter.filter = 'alpha(opacity=' + (iter.opacity * 100) + ')';
+ // give it layout
+ iter.zoom = o.zoom || 1;
+ delete iter.opacity;
+ }
+
+ if (v = iter['float']) {
+ // float is a reserved style word. w3 uses cssFloat, ie uses styleFloat
+ ie ? (iter.styleFloat = v) : (iter.cssFloat = v);
+ delete iter['float'];
+ }
+
+ var fn = function (el, p, v) {
+ for (var k in iter) {
+ if (iter.hasOwnProperty(k)) {
+ v = iter[k];
+ // change "5" to "5px" - unless you're line-height, which is allowed
+ (p = camelize(k)) && digit.test(v) && !(p in unitless) && (v += px);
+ el.style[p] = v;
+ }
+ }
+ };
+ return this.each(fn);
+ },
+
+ offset: function (x, y) {
+ if (typeof x == 'number' || typeof y == 'number') {
+ return this.each(function (el) {
+ xy(el, x, y);
+ });
+ }
+ var el = this[0],
+ width = el.offsetWidth,
+ height = el.offsetHeight,
+ top = el.offsetTop,
+ left = el.offsetLeft;
+ while (el = el.offsetParent) {
+ top = top + el.offsetTop;
+ left = left + el.offsetLeft;
+ }
+
+ return {
+ top: top,
+ left: left,
+ height: height,
+ width: width
+ };
+ },
+
+ attr: function (k, v) {
+ var el = this[0];
+ if (typeof k != 'string' && !(k instanceof String)) {
+ for (var n in k) {
+ k.hasOwnProperty(n) && this.attr(n, k[n]);
+ }
+ return this;
+ }
+ return typeof v == 'undefined' ?
+ specialAttributes.test(k) ?
+ stateAttributes.test(k) && typeof el[k] == 'string' ?
+ true : el[k] : el[getAttribute](k) :
+ this.each(function (el) {
+ k == 'value' ? (el.value = v) : el[setAttribute](k, v);
+ });
+ },
+
+ val: function (s) {
+ return (typeof s == 'string') ? this.attr('value', s) : this[0].value;
+ },
+
+ removeAttr: function (k) {
+ return this.each(function (el) {
+ el.removeAttribute(k);
+ });
+ },
+
+ data: function (k, v) {
+ var el = this[0];
+ if (typeof v === 'undefined') {
+ el[getAttribute]('data-node-uid') || el[setAttribute]('data-node-uid', ++uuids);
+ var uid = el[getAttribute]('data-node-uid');
+ uidList[uid] || (uidList[uid] = {});
+ return uidList[uid][k];
+ } else {
+ return this.each(function (el) {
+ el[getAttribute]('data-node-uid') || el[setAttribute]('data-node-uid', ++uuids);
+ var uid = el[getAttribute]('data-node-uid');
+ var o = {};
+ o[k] = v;
+ uidList[uid] = o;
+ });
+ }
+ },
+
+ remove: function () {
+ return this.each(function (el) {
+ el[parentNode] && el[parentNode].removeChild(el);
+ });
+ },
+
+ empty: function () {
+ return this.each(function (el) {
+ while (el.firstChild) {
+ el.removeChild(el.firstChild);
+ }
+ });
+ },
+
+ detach: function () {
+ return this.map(function (el) {
+ return el[parentNode].removeChild(el);
+ });
+ },
+
+ scrollTop: function (y) {
+ return scroll.call(this, null, y, 'y');
+ },
+
+ scrollLeft: function (x) {
+ return scroll.call(this, x, null, 'x');
+ }
+ };
+
+ function normalize(node) {
+ return typeof node == 'string' ? bonzo.create(node) : is(node) ? [node] : node; // assume [nodes]
+ }
+
+ function scroll(x, y, type) {
+ var el = this[0];
+ if (x == null && y == null) {
+ return (isBody(el) ? getWindowScroll() : { x: el.scrollLeft, y: el.scrollTop })[type];
+ }
+ if (isBody(el)) {
+ win.scrollTo(x, y);
+ } else {
+ x != null && (el.scrollLeft = x);
+ y != null && (el.scrollTop = y);
+ }
+ return this;
+ }
+
+ function isBody(element) {
+ return element === win || (/^(?:body|html)$/i).test(element.tagName);
+ }
+
+ function getWindowScroll() {
+ return { x: win.pageXOffset || html.scrollLeft, y: win.pageYOffset || html.scrollTop };
+ }
+
+ function bonzo(els, host) {
+ return new Bonzo(els, host);
+ }
+
+ bonzo.setQueryEngine = function (q) {
+ query = q;
+ delete bonzo.setQueryEngine;
+ };
+
+ bonzo.aug = function (o, target) {
+ for (var k in o) {
+ o.hasOwnProperty(k) && ((target || Bonzo.prototype)[k] = o[k]);
+ }
+ };
+
+ bonzo.create = function (node) {
+ return typeof node == 'string' ?
+ function () {
+ var tag = /^<([^\s>]+)/.exec(node);
+ var el = doc.createElement(tag && tagMap[tag[1].toLowerCase()] || 'div'), els = [];
+ el.innerHTML = node;
+ var nodes = el.childNodes;
+ el = el.firstChild;
+ els.push(el);
+ while (el = el.nextSibling) {
+ (el.nodeType == 1) && els.push(el);
+ }
+ return els;
+
+ }() : is(node) ? [node.cloneNode(true)] : [];
+ };
+
+ bonzo.doc = function () {
+ var vp = this.viewport();
+ return {
+ width: Math.max(doc.body.scrollWidth, html.scrollWidth, vp.width),
+ height: Math.max(doc.body.scrollHeight, html.scrollHeight, vp.height)
+ };
+ };
+
+ bonzo.firstChild = function (el) {
+ for (var c = el.childNodes, i = 0, j = (c && c.length) || 0, e; i < j; i++) {
+ if (c[i].nodeType === 1) {
+ e = c[j = i];
+ }
+ }
+ return e;
+ };
+
+ bonzo.viewport = function () {
+ return {
+ width: ie ? html.clientWidth : self.innerWidth,
+ height: ie ? html.clientHeight : self.innerHeight
+ };
+ };
+
+ bonzo.isAncestor = 'compareDocumentPosition' in html ?
+ function (container, element) {
+ return (container.compareDocumentPosition(element) & 16) == 16;
+ } : 'contains' in html ?
+ function (container, element) {
+ return container !== element && container.contains(element);
+ } :
+ function (container, element) {
+ while (element = element[parentNode]) {
+ if (element === container) {
+ return true;
+ }
+ }
+ return false;
+ };
+
+ var old = context.bonzo;
+ bonzo.noConflict = function () {
+ context.bonzo = old;
+ return this;
+ };
+ context['bonzo'] = bonzo;
+
+ }(this, window);
+
+
+ provide("bonzo", module.exports);
+
+ !function ($) {
+
+ var b = bonzo;
+ b.setQueryEngine($);
+ $.ender(b);
+ $.ender(b(), true);
+ $.ender({
+ create: function (node) {
+ return $(b.create(node));
+ }
+ });
+
+ $.id = function (id) {
+ return $([document.getElementById(id)]);
+ };
+
+ function indexOf(ar, val) {
+ for (var i = 0; i < ar.length; i++) {
+ if (ar[i] === val) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ function uniq(ar) {
+ var a = [], i, j;
+ label:
+ for (i = 0; i < ar.length; i++) {
+ for (j = 0; j < a.length; j++) {
+ if (a[j] == ar[i]) {
+ continue label;
+ }
+ }
+ a[a.length] = ar[i];
+ }
+ return a;
+ }
+
+ $.ender({
+ parents: function (selector, closest) {
+ var collection = $(selector), j, k, p, r = [];
+ for (j = 0, k = this.length; j < k; j++) {
+ p = this[j];
+ while (p = p.parentNode) {
+ if (indexOf(collection, p) !== -1) {
+ r.push(p);
+ if (closest) break;
+ }
+ }
+ }
+ return $(uniq(r));
+ },
+
+ closest: function (selector) {
+ return this.parents(selector, true);
+ },
+
+ first: function () {
+ return $(this[0]);
+ },
+
+ last: function () {
+ return $(this[this.length - 1]);
+ },
+
+ next: function () {
+ return $(b(this).next());
+ },
+
+ previous: function () {
+ return $(b(this).previous());
+ },
+
+ appendTo: function (t) {
+ return b(this.selector).appendTo(t, this);
+ },
+
+ prependTo: function (t) {
+ return b(this.selector).prependTo(t, this);
+ },
+
+ insertAfter: function (t) {
+ return b(this.selector).insertAfter(t, this);
+ },
+
+ insertBefore: function (t) {
+ return b(this.selector).insertBefore(t, this);
+ },
+
+ siblings: function () {
+ var i, l, p, r = [];
+ for (i = 0, l = this.length; i < l; i++) {
+ p = this[i];
+ while (p = p.previousSibling) {
+ p.nodeType == 1 && r.push(p);
+ }
+ p = this[i];
+ while (p = p.nextSibling) {
+ p.nodeType == 1 && r.push(p);
+ }
+ }
+ return $(r);
+ },
+
+ children: function () {
+ var i, el, r = [];
+ for (i = 0, l = this.length; i < l; i++) {
+ if (!(el = b.firstChild(this[i]))) {
+ continue;
+ }
+ r.push(el);
+ while (el = el.nextSibling) {
+ el.nodeType == 1 && r.push(el);
+ }
+ }
+ return $(uniq(r));
+ },
+
+ height: function (v) {
+ return dimension(v, this, 'height')
+ },
+
+ width: function (v) {
+ return dimension(v, this, 'width')
+ }
+ }, true);
+
+ function dimension(v, self, which) {
+ return v ?
+ self.css(which, v) :
+ function (r) {
+ r = parseInt(self.css(which), 10);
+ return isNaN(r) ? self[0]['offset' + which.replace(/^\w/, function (m) {return m.toUpperCase()})] : r
+ }()
+ }
+
+ }(ender || $);
+
+
+}();
+
+!function () {
+
+ var module = { exports: {} }, exports = module.exports;
+
+ !function (context, doc) {
+ var fns = [], ol, fn, f = false,
+ testEl = doc.documentElement,
+ hack = testEl.doScroll,
+ domContentLoaded = 'DOMContentLoaded',
+ addEventListener = 'addEventListener',
+ onreadystatechange = 'onreadystatechange',
+ loaded = /^loade|c/.test(doc.readyState);
+
+ function flush(i) {
+ loaded = 1;
+ while (i = fns.shift()) { i() }
+ }
+ doc[addEventListener] && doc[addEventListener](domContentLoaded, fn = function () {
+ doc.removeEventListener(domContentLoaded, fn, f);
+ flush();
+ }, f);
+
+
+ hack && doc.attachEvent(onreadystatechange, (ol = function () {
+ if (/^c/.test(doc.readyState)) {
+ doc.detachEvent(onreadystatechange, ol);
+ flush();
+ }
+ }));
+
+ context['domReady'] = hack ?
+ function (fn) {
+ self != top ?
+ loaded ? fn() : fns.push(fn) :
+ function () {
+ try {
+ testEl.doScroll('left');
+ } catch (e) {
+ return setTimeout(function() { context['domReady'](fn) }, 50);
+ }
+ fn();
+ }()
+ } :
+ function (fn) {
+ loaded ? fn() : fns.push(fn);
+ };
+
+ }(this, document);
+
+
+ provide("domready", module.exports);
+
+ !function ($) {
+ $.ender({domReady: domReady});
+ $.ender({
+ ready: function (f) {
+ domReady(f);
+ return this;
+ }
+ }, true);
+ }(ender);
+
+}();
+
+!function () {
+
+ var module = { exports: {} }, exports = module.exports;
+
+ /*!
+ * Qwery - A Blazing Fast query selector engine
+ * https://github.com/ded/qwery
+ * copyright Dustin Diaz & Jacob Thornton 2011
+ * MIT License
+ */
+
+ !function (context, doc) {
+
+ var c, i, j, k, l, m, o, p, r, v,
+ el, node, len, found, classes, item, items, token,
+ html = doc.documentElement,
+ id = /#([\w\-]+)/,
+ clas = /\.[\w\-]+/g,
+ idOnly = /^#([\w\-]+$)/,
+ classOnly = /^\.([\w\-]+)$/,
+ tagOnly = /^([\w\-]+)$/,
+ tagAndOrClass = /^([\w]+)?\.([\w\-]+)$/,
+ normalizr = /\s*([\s\+\~>])\s*/g,
+ splitters = /[\s\>\+\~]/,
+ splittersMore = /(?![\s\w\-\/\?\&\=\:\.\(\)\!,@#%<>\{\}\$\*\^'"]*\])/,
+ dividers = new RegExp('(' + splitters.source + ')' + splittersMore.source, 'g'),
+ tokenizr = new RegExp(splitters.source + splittersMore.source),
+ specialChars = /([.*+?\^=!:${}()|\[\]\/\\])/g,
+ simple = /^([a-z0-9]+)?(?:([\.\#]+[\w\-\.#]+)?)/,
+ attr = /\[([\w\-]+)(?:([\|\^\$\*\~]?\=)['"]?([ \w\-\/\?\&\=\:\.\(\)\!,@#%<>\{\}\$\*\^]+)["']?)?\]/,
+ pseudo = /:([\w\-]+)(\(['"]?(\w+)['"]?\))?/,
+ chunker = new RegExp(simple.source + '(' + attr.source + ')?' + '(' + pseudo.source + ')?'),
+ walker = {
+ ' ': function (node) {
+ return node && node !== html && node.parentNode
+ },
+ '>': function (node, contestant) {
+ return node && node.parentNode == contestant.parentNode && node.parentNode;
+ },
+ '~': function (node) {
+ return node && node.previousSibling;
+ },
+ '+': function (node, contestant, p1, p2) {
+ if (!node) {
+ return false;
+ }
+ p1 = previous(node);
+ p2 = previous(contestant);
+ return p1 && p2 && p1 == p2 && p1;
+ }
+ };
+ function cache() {
+ this.c = {};
+ }
+ cache.prototype = {
+ g: function (k) {
+ return this.c[k] || undefined;
+ },
+ s: function (k, v) {
+ this.c[k] = v;
+ return v;
+ }
+ };
+
+ var classCache = new cache(),
+ cleanCache = new cache(),
+ attrCache = new cache(),
+ tokenCache = new cache();
+
+ function array(ar) {
+ r = [];
+ for (i = 0, len = ar.length; i < len; i++) {
+ r[i] = ar[i];
+ }
+ return r;
+ }
+
+ function previous(n) {
+ while (n = n.previousSibling) {
+ if (n.nodeType == 1) {
+ break;
+ }
+ }
+ return n
+ }
+
+ function q(query) {
+ return query.match(chunker);
+ }
+
+ // this next method expect at most these args
+ // given => div.hello[title="world"]:foo('bar')
+
+ // div.hello[title="world"]:foo('bar'), div, .hello, [title="world"], title, =, world, :foo('bar'), foo, ('bar'), bar]
+
+ function interpret(whole, tag, idsAndClasses, wholeAttribute, attribute, qualifier, value, wholePseudo, pseudo, wholePseudoVal, pseudoVal) {
+ var m, c, k;
+ if (tag && this.tagName.toLowerCase() !== tag) {
+ return false;
+ }
+ if (idsAndClasses && (m = idsAndClasses.match(id)) && m[1] !== this.id) {
+ return false;
+ }
+ if (idsAndClasses && (classes = idsAndClasses.match(clas))) {
+ for (i = classes.length; i--;) {
+ c = classes[i].slice(1);
+ if (!(classCache.g(c) || classCache.s(c, new RegExp('(^|\\s+)' + c + '(\\s+|$)'))).test(this.className)) {
+ return false;
+ }
+ }
+ }
+ if (pseudo && qwery.pseudos[pseudo] && !qwery.pseudos[pseudo](this, pseudoVal)) {
+ return false;
+ }
+ if (wholeAttribute && !value) {
+ o = this.attributes;
+ for (k in o) {
+ if (Object.prototype.hasOwnProperty.call(o, k) && (o[k].name || k) == attribute) {
+ return this;
+ }
+ }
+ }
+ if (wholeAttribute && !checkAttr(qualifier, this.getAttribute(attribute) || '', value)) {
+ return false;
+ }
+ return this;
+ }
+
+ function clean(s) {
+ return cleanCache.g(s) || cleanCache.s(s, s.replace(specialChars, '\\$1'));
+ }
+
+ function checkAttr(qualify, actual, val) {
+ switch (qualify) {
+ case '=':
+ return actual == val;
+ case '^=':
+ return actual.match(attrCache.g('^=' + val) || attrCache.s('^=' + val, new RegExp('^' + clean(val))));
+ case '$=':
+ return actual.match(attrCache.g('$=' + val) || attrCache.s('$=' + val, new RegExp(clean(val) + '$')));
+ case '*=':
+ return actual.match(attrCache.g(val) || attrCache.s(val, new RegExp(clean(val))));
+ case '~=':
+ return actual.match(attrCache.g('~=' + val) || attrCache.s('~=' + val, new RegExp('(?:^|\\s+)' + clean(val) + '(?:\\s+|$)')));
+ case '|=':
+ return actual.match(attrCache.g('|=' + val) || attrCache.s('|=' + val, new RegExp('^' + clean(val) + '(-|$)')));
+ }
+ return 0;
+ }
+
+ function _qwery(selector) {
+ var r = [], ret = [], i, j = 0, k, l, m, p, token, tag, els, root, intr, item, children,
+ tokens = tokenCache.g(selector) || tokenCache.s(selector, selector.split(tokenizr)),
+ dividedTokens = selector.match(dividers), dividedToken;
+ tokens = tokens.slice(0); // this makes a copy of the array so the cached original is not effected
+ if (!tokens.length) {
+ return r;
+ }
+
+ token = tokens.pop();
+ root = tokens.length && (m = tokens[tokens.length - 1].match(idOnly)) ? doc.getElementById(m[1]) : doc;
+ if (!root) {
+ return r;
+ }
+ intr = q(token);
+ els = dividedTokens && /^[+~]$/.test(dividedTokens[dividedTokens.length - 1]) ? function (r) {
+ while (root = root.nextSibling) {
+ root.nodeType == 1 && (intr[1] ? intr[1] == root.tagName.toLowerCase() : 1) && r.push(root)
+ }
+ return r
+ }([]) :
+ root.getElementsByTagName(intr[1] || '*');
+ for (i = 0, l = els.length; i < l; i++) {
+ if (item = interpret.apply(els[i], intr)) {
+ r[j++] = item;
+ }
+ }
+ if (!tokens.length) {
+ return r;
+ }
+
+ // loop through all descendent tokens
+ for (j = 0, l = r.length, k = 0; j < l; j++) {
+ p = r[j];
+ // loop through each token backwards crawling up tree
+ for (i = tokens.length; i--;) {
+ // loop through parent nodes
+ while (p = walker[dividedTokens[i]](p, r[j])) {
+ if (found = interpret.apply(p, q(tokens[i]))) {
+ break;
+ }
+ }
+ }
+ found && (ret[k++] = r[j]);
+ }
+ return ret;
+ }
+
+ function boilerPlate(selector, _root, fn) {
+ var root = (typeof _root == 'string') ? fn(_root)[0] : (_root || doc);
+ if (selector === window || isNode(selector)) {
+ return !_root || (selector !== window && isNode(root) && isAncestor(selector, root)) ? [selector] : [];
+ }
+ if (selector && typeof selector === 'object' && isFinite(selector.length)) {
+ return array(selector);
+ }
+ if (m = selector.match(idOnly)) {
+ return (el = doc.getElementById(m[1])) ? [el] : [];
+ }
+ if (m = selector.match(tagOnly)) {
+ return array(root.getElementsByTagName(m[1]));
+ }
+ return false;
+ }
+
+ function isNode(el) {
+ return (el && el.nodeType && (el.nodeType == 1 || el.nodeType == 9));
+ }
+
+ function uniq(ar) {
+ var a = [], i, j;
+ label:
+ for (i = 0; i < ar.length; i++) {
+ for (j = 0; j < a.length; j++) {
+ if (a[j] == ar[i]) {
+ continue label;
+ }
+ }
+ a[a.length] = ar[i];
+ }
+ return a;
+ }
+
+ function qwery(selector, _root) {
+ var root = (typeof _root == 'string') ? qwery(_root)[0] : (_root || doc);
+ if (!root || !selector) {
+ return [];
+ }
+ if (m = boilerPlate(selector, _root, qwery)) {
+ return m;
+ }
+ return select(selector, root);
+ }
+
+ var isAncestor = 'compareDocumentPosition' in html ?
+ function (element, container) {
+ return (container.compareDocumentPosition(element) & 16) == 16;
+ } : 'contains' in html ?
+ function (element, container) {
+ container = container == doc || container == window ? html : container;
+ return container !== element && container.contains(element);
+ } :
+ function (element, container) {
+ while (element = element.parentNode) {
+ if (element === container) {
+ return 1;
+ }
+ }
+ return 0;
+ },
+
+ select = (doc.querySelector && doc.querySelectorAll) ?
+ function (selector, root) {
+ if (doc.getElementsByClassName && (m = selector.match(classOnly))) {
+ return array((root).getElementsByClassName(m[1]));
+ }
+ return array((root).querySelectorAll(selector));
+ } :
+ function (selector, root) {
+ selector = selector.replace(normalizr, '$1');
+ var result = [], collection, collections = [], i;
+ if (m = selector.match(tagAndOrClass)) {
+ items = root.getElementsByTagName(m[1] || '*');
+ r = classCache.g(m[2]) || classCache.s(m[2], new RegExp('(^|\\s+)' + m[2] + '(\\s+|$)'));
+ for (i = 0, l = items.length, j = 0; i < l; i++) {
+ r.test(items[i].className) && (result[j++] = items[i]);
+ }
+ return result;
+ }
+ for (i = 0, items = selector.split(','), l = items.length; i < l; i++) {
+ collections[i] = _qwery(items[i]);
+ }
+ for (i = 0, l = collections.length; i < l && (collection = collections[i]); i++) {
+ var ret = collection;
+ if (root !== doc) {
+ ret = [];
+ for (j = 0, m = collection.length; j < m && (element = collection[j]); j++) {
+ // make sure element is a descendent of root
+ isAncestor(element, root) && ret.push(element);
+ }
+ }
+ result = result.concat(ret);
+ }
+ return uniq(result);
+ };
+
+ qwery.uniq = uniq;
+ qwery.pseudos = {};
+
+ var oldQwery = context.qwery;
+ qwery.noConflict = function () {
+ context.qwery = oldQwery;
+ return this;
+ };
+ context['qwery'] = qwery;
+
+ }(this, document);
+
+ provide("qwery", module.exports);
+
+ !function (doc) {
+ var q = qwery.noConflict();
+ var table = 'table',
+ nodeMap = {
+ thead: table,
+ tbody: table,
+ tfoot: table,
+ tr: 'tbody',
+ th: 'tr',
+ td: 'tr',
+ fieldset: 'form',
+ option: 'select'
+ }
+ function create(node, root) {
+ var tag = /^<([^\s>]+)/.exec(node)[1]
+ var el = (root || doc).createElement(nodeMap[tag] || 'div'), els = [];
+ el.innerHTML = node;
+ var nodes = el.childNodes;
+ el = el.firstChild;
+ els.push(el);
+ while (el = el.nextSibling) {
+ (el.nodeType == 1) && els.push(el);
+ }
+ return els;
+ }
+ $._select = function (s, r) {
+ return /^\s*</.test(s) ? create(s, r) : q(s, r);
+ };
+ $.pseudos = q.pseudos;
+ $.ender({
+ find: function (s) {
+ var r = [], i, l, j, k, els;
+ for (i = 0, l = this.length; i < l; i++) {
+ els = q(s, this[i]);
+ for (j = 0, k = els.length; j < k; j++) {
+ r.push(els[j]);
+ }
+ }
+ return $(q.uniq(r));
+ }
+ , and: function (s) {
+ var plus = $(s);
+ for (var i = this.length, j = 0, l = this.length + plus.length; i < l; i++, j++) {
+ this[i] = plus[j];
+ }
+ return this;
+ }
+ }, true);
+ }(document);
+
+}();
+
9,266 test/vendor/jquery-1.7.1.js
9,266 additions, 0 deletions not shown
649 test/vendor/jslitmus.js
@@ -0,0 +1,649 @@
+// JSLitmus.js
+//
+// Copyright (c) 2010, Robert Kieffer, http://broofa.com
+// Available under MIT license (http://en.wikipedia.org/wiki/MIT_License)
+
+(function() {
+ // Private methods and state
+
+ // Get platform info but don't go crazy trying to recognize everything
+ // that's out there. This is just for the major platforms and OSes.
+ var platform = 'unknown platform', ua = navigator.userAgent;
+
+ // Detect OS
+ var oses = ['Windows','iPhone OS','(Intel |PPC )?Mac OS X','Linux'].join('|');
+ var pOS = new RegExp('((' + oses + ') [^ \);]*)').test(ua) ? RegExp.$1 : null;
+ if (!pOS) pOS = new RegExp('((' + oses + ')[^ \);]*)').test(ua) ? RegExp.$1 : null;
+
+ // Detect browser
+ var pName = /(Chrome|MSIE|Safari|Opera|Firefox)/.test(ua) ? RegExp.$1 : null;
+
+ // Detect version
+ var vre = new RegExp('(Version|' + pName + ')[ \/]([^ ;]*)');
+ var pVersion = (pName && vre.test(ua)) ? RegExp.$2 : null;
+ var platform = (pOS && pName && pVersion) ? pName + ' ' + pVersion + ' on ' + pOS : 'unknown platform';
+